Skip to content

Compaction paths can wipe the entire chat history instead of compacting it #1166

Description

@nordicnode

While investigating a user report ("Model losing all chat history instead of compacting"), I found two verified mechanisms in the compaction paths that replace the model's entire context with nothing. Both reproduce against the current code; repro details below.

1. /compact with an empty model response wipes the history

packages/agent-runtime/src/run-agent-step.ts:562-574 replaces the whole message history with a single summary message built from fullResponse:

agentState.messageHistory = [
  userMessage(withSystemTags(`The following is a summary of the conversation between you and the user. ...:\n\n${fullResponse}`)),
]

There is no check that fullResponse is non-empty. A silent empty stop (finish part with a real reason and usage but no content) yields no recovery chunk — classifyStreamEndRecovery returns null for it (sdk/src/impl/stream-interruption.ts) — so nothing catches it. I reproduced the full shape with a mocked stream: fullResponse === "", zero recovery/error chunks, and the replacement message is 'The following is a summary of the conversation...:\n\n' — i.e. the model's next turn starts with an empty summary and no earlier turns. Near the context limit (exactly when a user reaches for /compact) empty completions are also the most likely, so the trigger lines up with the report.

2. A user message quoting the summary markers steals the summary's identity and erases the older memory

compactMessages (packages/agent-runtime/src/compact-history.ts) recognizes its own summary by CONTENT: a user-role message containing <conversation_summary> and the header sentence. Two consequences:

  • previousSummary = messages.findLast(isConversationSummary) — the LAST match wins.
  • isRealHistory excludes anything matching, so the matched message is never summarized.

So a later user message that merely quotes both markers (asking about this very mechanism, pasting a summary back) is taken for the real summary; extractSummaryContent on the quote returns '' (no closing tag) or the quoted text; the REAL summary is neither re-parsed nor kept as history. Every earlier turn vanishes from the model's context at that compaction. Reproduced with a 3-generation compactMessages chain: generation 3's summary retained none of generations 1-2 once the quoting message was present (previous_summary_entry_count: 0).

The inlined copy in agents/context-pruner.ts (still live for the base2 family — it is spawned before every step there) matches on the bare tag ALONE, so a user message that merely mentions <conversation_summary> triggers the same wipe there, and set_messages adopts it with no validation (packages/agent-runtime/src/tools/handlers/tool/set-messages.ts:20).

Suggested fix

  1. Guard the /compact replacement: only replace when fullResponse.trim() is non-empty; otherwise keep the history (the forced next step retries the summary anyway) and log.
  2. Identify summaries by provenance, not content: stamp the summary message with a CONVERSATION_SUMMARY tag in both buildSummaryMessage (compact-history.ts) and the pruner's copy, and have isConversationSummary require the tag, with a legacy fallback that requires the FULL envelope (open+close tag, header, <historical_memory>) so pre-tag summaries still fold in. A user message can then never gain summary identity by content alone. (The parity test's deliberate divergence — pruner matching the bare tag — closes too.)

Happy to open a PR with both, plus regression tests.

Related (same symptom family, separate cause, not covered by the fix above)

On desktop/CLI resume, an unreadable run-state.json falls back to a RunState with no sessionState (cli/src/utils/run-state-storage.ts:518-549), so the UI transcript survives but the model starts amnesiac next turn — worth checking in telemetry if reports continue after the compaction fixes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:cliThe Codebuff/Freebuff terminal clientbot:triagedClassified by the community triage bottype:bugA defect in the code with a reproducible failure

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions