Status: SHIPPED (CLI), 2026-06-10. Auto-compaction + manual /compact are
live. memcode used to send the FULL append-only ChatState.messages every turn;
now, at a safe turn boundary, when the estimated prompt exceeds the budget
(MEMCODE_COMPACT_BUDGET, default ~45K; off disables), the older turns are
summarized by Anthropic into a warm block and only the last ~8 turns stay raw.
cli/internal/agent/compaction/— PURE core:EstimateTokens,Plan(safe-boundary split — never divides a tool_use/tool_result pair),Render(transcript with aggressive tool-output clipping),CountTurns. Fully tested (compaction_test.go): facts-survive, adjacency-never-broken, boundary-only.cli/internal/agent/runtime/compact.go— orchestration:compactBudget,compactIfNeeded(auto, fired fromSubmitbefore the turn is assembled),Compact(manual /compact), the Anthropic-forced summarizer call, the synthetic summary turn, telemetry + episodic-log write.- Gateway:
compactmode +compactDoctrineinapi/internal/server/prompts.go(the compactor prompt is server-owned, like every other mode). - TUI:
/compactslash command (+compactSentinelintui.go). events.KindContextCompacted,sessionlog.KindCompaction.
Compaction NEVER touches the cold layer: .memcode/sessions/<id>/events.jsonl
keeps growing append-only, so the full history survives on disk. The synthetic
summary turn signposts this — it tells the agent that raw older turns are
retrievable via memcode{command:"session", target:"search", query:"…"} (the
same shape as reading a code file it doesn't have in context). Known gap
(deferred 2026-06-10): raw tool OUTPUT is not in the canonical log (the "not a
stdout landfill" doctrine), so exact older tool output isn't recoverable beyond
what the summary captured.
The decision (Tim, 2026-06-10): do not persist raw tool output — huge noisy logs, secret-leak surface, junk search hits, and it would make compaction less valuable (a stdout landfill). Instead, when dogfooding proves a real recall miss, add a fourth, curated layer:
hot = recent raw turns
warm = compacted session summary
cold = searchable durable memory (asks / answers / decisions / commits / $-output)
evidence = optional CURATED snippets (later)
evidence = compact records written ONLY when the agent used an output to make a
decision — decision-relevant snippets, capped + redacted + searchable, kept
SEPARATE from the canonical asks/answers/decisions log. Shape:
{ "kind": "evidence", "source": "test", "command": "go test ./...",
"summary": "provider tests failed due to undefined LaneRole",
"snippets": ["undefined: LaneRole", "internal/provider/lane.go:42"],
"paths": ["internal/provider/lane.go"] }Gate: build this only if dogfooding shows the warm summary + cold search actually fail to recall something load-bearing. Until then, ship as-is.
memcode sends the FULL append-only ChatState.messages every turn — no
compaction. Prompt caching makes resends cheap but the window still fills, so long
sessions drift off the cheap lane to Anthropic. It pays off on any backend (a long
Anthropic-only session also dies at its window limit without it).
hot recent raw turns + current task + active file/tool context (kept verbatim)
warm structured session summary (compacted older turns) (BUILD THIS)
cold episodic session log (.memcode/sessions) — durable recall (exists)
The model sees: doctrine + current objective + warm summary + retrieved memories
- recent raw turns + current context pack + user request. NOT the whole transcript.
Compact at a safe boundary (before accepting a new user turn) when the estimated prompt approaches the active model's window (leave output/tool headroom). The cheap lane (glm-5p1) is 202K and Anthropic is 1M, so spilling is rare — but a long session still compacts to stay cheap and fast. Coarse is fine.
- Keep the last ~6–10 exchanges raw.
- Summarize older turns into a structured block: objective · current plan · files inspected · files modified · key decisions · rejected approaches · constraints · failing tests/errors · open questions · session user-preferences.
- Tool outputs compact most aggressively — raw grep/test/build output bloats fast.
- Compactor model = Anthropic (v1). A bad summary becomes the session's truth. (Later: the cheap lane may summarize low-risk tool output.)
- Tool-use adjacency is sacred. Never split an assistant tool_use from its tool_result. Only compact at a completed boundary (no pending tool call).
- Store the warm summary in session state AND the episodic log (so /recap & recall see it, and a later turn can rebuild context).
- Compaction runs BEFORE routing — a smaller prompt keeps the turn within the
resolved model's window instead of being absorbed to Anthropic on
context_overflow.
- Manual
/compactcommand. - Telemetry:
raw_history_tokens,summary_tokens,compacted_turns,context_after_compaction.
- Old turns are removed from the assembled prompt, but their FACTS survive in the summary.
- No tool_use/tool_result adjacency is ever broken by compaction.
- Compaction only fires at safe boundaries.
Compaction is substrate-independent — it shrinks the prompt regardless of which backend serves the turn (Fireworks or Anthropic). It is a permanent part of the runtime, not gated on any inference rollout.