Skip to content

Compaction summaries compound instead of replacing: context explodes and sessions 400 to death #1003

Open

Description

@ahrazzle

Summary

Compaction summaries compound instead of replacing. Each compaction entry's summary embeds the previous summaries in full, so the stack grows geometrically with every compaction. The stack dominates the resumed context, the context meter explodes past the model limit, compaction can no longer reduce it (each pass adds another layer), and every request is rejected with a 400. The session is dead, and restarting does not help because the transcript survives it.

Observed session

Session 3e33503f (hermes-agent issue #134107 work), 2026-10-07 11:44 → 2026-10-08 08:33 UTC. Seven compactions. The summary field of each, from ~/.commandcode/projects/.../3e33503f-...jsonl (3.69MB, 355 entries):

# Time (UTC) summary chars tokensBefore firstKeptEntryId
1 11:44 3,037 128,133 98e83e22
2 21:02 48,202 187,903 8c65e93a
3 22:57 88,308 90,398 8c65e93a
4 06:30 216,724 151,462 f350ffb1
5 08:21 345,202 140,818 e920fd9a
6 08:31 582,078 197,749 f8f26f11
7 08:33 1,144,246 321,348 f8f26f11

Totals: 2,427,797 chars of stacked summaries, 66% of the transcript file. The largest summary contains the "use clean-writing skill" text 30 times and the "Goal" header 10 times: prior summaries re-embedded verbatim. Compactions 2/3 and 6/7 share an identical firstKeptEntryId. The second of each pair re-summarized a range the first had already summarized.

Symptoms, in the order they were hit:

  • Compaction #6 reported "saved ~5.3k tokens" (meta.json lastTokensSaved: 5335). Almost nothing was left to save, because almost everything was already summary text.
  • Compaction #7 doubled the meter: ~648.8K / 256K used (100%).
  • Every request then failed: Error: 400 Bad Request, then Error: 400 The stealth model could not complete the request. The agent stopped with "Invalid request / Something went wrong".

The session is unrecoverable in place: compacting again adds another layer, and a restart reloads the same transcript.

Where the compounding happens

From the bundled harness (@commandcode/harness/dist/index.js), in the compaction mod and the session store:

  • entryToContextMessages() turns a stored compaction entry back into a user message: {role: "user", content: [{type: "text", text: COMPACTION_PREFIX + entry.summary}], meta: {isSummary: true, summaryEntryId}}. The full summary re-enters context as a message.
  • projectEntryForSummary() maps compaction/branch_summary entries through entryToContextMessages, so prior summaries are projected at full length into the summarizer's input.
  • prepareBranchEntries() walks entries newest→oldest and unshifts projected messages until a token budget is exceeded, with BOUNDARY_SQUEEZE_FRACTION = 0.9 letting compaction entries squeeze in even past the budget.
  • summarizePreservingSkills() → summarizeViaApi() sends that working set to the compaction model (deepseek-v4.1-flash, 1M window) and its digest becomes the new summary. Nothing caps the digest length.
  • The yield gate minCompactionYield() = max(1000, 1% of contextLimit) only checks that something was saved. On a 1M-window model that is ~10K tokens, so a compaction that leaves context near the limit still passes as a saving.

Net effect: new_summary ≈ old_summary + new material, every time. The summarizer reproduces the prior summary it was handed instead of compressing it. Compaction #7 is the extreme case: the 582KB summary from #6 plus two short user messages went in, and a 1,144KB summary came out.

Why the session then 400s: buildContextEntriesFromPath() keeps the last compaction entry, and projectContextMessages() projects its summary in full. The resumed context is dominated by the un-capped summary: 321,348 tokens before the seventh compaction by the harness's own accounting. The meter's 648.8K/256K is consistent with the estimator applied to the 1.14MB summary plus kept context.

What the harness could do

  1. Cap the digest: a fixed token/char budget for the summary, enforced after summarizeViaApi returns.
  2. Don't feed prior summaries back at full length: project them as a compact marker ("[prior summary, N tokens]") or exclude meta.isSummary messages from the summarizer input entirely. The digest is meant to replace them.
  3. Treat a compaction that leaves context >X% of the limit as failed and fall back to the hard-trim path (removeToolCallsExceptLast already exists for the non-summarized outcome).
  4. Skip redundant compactions: 2/3 and 6/7 share firstKeptEntryId. The second of each pair re-stacks the same range for no reason.
  5. Surface per-compaction summary size in the UI/meta so the bloat is visible before the meter pins.

Interim mitigation

A mod implementing detection and repair: aska-digital/cmd-compaction-guard (cmd mods add aska-digital/cmd-compaction-guard). It reads the transcript after each compaction, warns when stacked summaries pass 100KB, and /compact-repair rewrites the transcript with the stack truncated: older summaries blanked, the newest kept as head + tail, the entry chain (id / parentId / firstKeptEntryId) preserved so the session still loads and resumes, and a .bak backup always written. Verified against this session's transcript: 2.43MB → 6KB of summaries and 3.69MB → 1.22MB total. The chain stays intact, the repair is idempotent, and healthy transcripts are untouched. It also load-tested clean in the harness (cmd --mod … -p).

Automated posting by agentic team with human oversight.

Activity

  1. changed the title [-]Compaction summaries compound instead of replacing — context explodes, compaction can't reduce it, session 400s to death[/-] [+]Compaction summaries compound instead of replacing: context explodes and sessions 400 to death[/+] on Oct 8, 2026
  2. added a commit that references this issue on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions