From 266c6a8d7919675199f47ac747684e8b551355d9 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Mon, 12 Oct 2026 23:05:21 -0400 Subject: [PATCH] Drop dropped-turn excerpts when the summary ledger is included The ledger supersedes the "Earlier conversation excerpts" message, as on main; sending both duplicated dropped-turn content. Removal is consistent whenever the ledger is included, so the cached prefix stays stable. Co-Authored-By: Claude Opus 5.5 --- blueprints/conversation/context.py | 9 +++++++++ tests/unit/test_context_trim.py | 12 ++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/blueprints/conversation/context.py b/blueprints/conversation/context.py index a3ad73b..7f10d7f 100644 --- a/blueprints/conversation/context.py +++ b/blueprints/conversation/context.py @@ -506,6 +506,15 @@ def trim_history( "Omitting optional conversation summary to preserve current request" ) return trimmed + # The ledger supersedes excerpts of dropped turns; whenever it is included + # they are consistently absent, so this doesn't disturb the cached prefix. + if ( + with_room + and isinstance(with_room[0], AIMessage) + and isinstance(with_room[0].content, str) + and with_room[0].content.startswith("Earlier conversation excerpts") + ): + with_room = with_room[1:] # The ledger changes every turn, so place it just before the current turn: # system, tools and retained history then stay byte-identical for the # prompt cache. Turns start at a HumanMessage, so the last one opens the diff --git a/tests/unit/test_context_trim.py b/tests/unit/test_context_trim.py index e30e2b8..762c2ae 100644 --- a/tests/unit/test_context_trim.py +++ b/tests/unit/test_context_trim.py @@ -501,8 +501,16 @@ def test_summary_placed_before_current_turn_when_turns_dropped(): assert cost(out) <= 6000 assert out[-4:] == [summary, question, messages[-2], messages[-1]] assert out.count(summary) == 1 - # Excerpts of dropped turns stay at the head; they only change with the cut. - assert out[0].content.startswith("Earlier conversation excerpts") + # The ledger supersedes excerpts of dropped turns; don't send both. + assert not any( + isinstance(m.content, str) + and m.content.startswith("Earlier conversation excerpts") + for m in out + ) + assert isinstance(out[0], HumanMessage) + # Without a ledger, the excerpts still cover the dropped turns. + plain = trim_history(messages, token_budget=6000) + assert plain[0].content.startswith("Earlier conversation excerpts") assert_valid_sequence(out)