003 2026-04-02 concluded just-another-coding-agent

What compaction should preserve

agentsarchitecturememorysessionscompactioncodexclaude-code
Setup
long agent conversations, limited context windows, and one between-run question: what part of the old session should the next run actually carry forward
Found
between runs, compaction decides what past survives, but a long-running coding agent also needs a separate runtime-framing baseline so the next run starts under the right conditions
Result
JACA now stores an explicit replacement history plus a separate turn-context snapshot, Codex reinforced that split most clearly, and Claude Code reinforced that compaction needs budgeting, cleanup, and failure handling around it

What compaction is

Compaction is what happens when an agent session gets too long to replay in full.

This note is specifically about between-run compaction: the moment before an older session resumes.

JACA also has in-run compaction for a different problem inside a live turn, but that is not the focus here.

At that point the system has to make a choice. Either it keeps dragging the full past forward until the next run breaks, or it produces a shorter version of the past that still lets the next run continue honestly.

That is why this feature matters more than the name suggests. Compaction is not just a convenience summary. It decides what the next run will treat as the real history.

But that is only half of the problem.

A coding agent does not just need memory of what happened. It also needs the correct setup for what should happen next: which workspace it is in, which shell it should assume, what date and timezone it thinks it is, which model is active, and which execution settings are in force.

That second channel is not conversation memory. It is runtime framing.

The hard part

The difficult part is that several different problems get collapsed into one word:

  • deciding what survives as model-visible history
  • deciding what runtime framing must survive separately
  • deciding when compaction runs
  • deciding what runtime state must stay outside the compacted artifact
  • deciding how invalid compacted state gets rejected before it is written

Systems get into trouble when they blur those together. Then the summary becomes vague, hidden state leaks into it, and the resumed conversation quietly stops matching the original one.

sequenceDiagram
  autonumber
  actor User
  participant Session as Stored session
  participant Framing as Runtime baseline
  participant Budget as Budget check
  participant Compact as Compaction step
  participant NextRun as Next run

  User->>Session: Continue an older conversation
  Session->>Session: Rebuild the exact conversation history so far
  Session->>Framing: Recover latest run conditions
  Framing-->>Session: Workspace, shell, date, model, settings
  Session->>Budget: Check whether the rebuilt history still fits

  alt It still fits
    Budget-->>NextRun: Start the next run with history + runtime baseline
  else It no longer fits
    Budget->>Compact: Ask for a shorter continuation history
    Compact-->>Session: Return a compacted replacement history
    Session->>Session: Validate it and save it
    Session-->>NextRun: Start with compacted history + runtime baseline
  else The compacted result is invalid
    Session-->>User: Stop before the next run starts
  end
SystemConversation-memory artifactRuntime-framing artifactWhat that split buys
JACAexplicit replacement historyseparate turn-context snapshotresumed runs can recover both the story so far and the run conditions
Codexexplicit replacement historyTurnContextItem plus reference contexthistory and execution contract stay cleanly separated
Claude Codeseveral compaction paths, not one single artifactless explicit in the same shapecompaction still gets subsystem-level cleanup and control

JACA now

JACA keeps the raw session log append-only. It does not rewrite the past away. What it adds is a smaller replacement history for the part that no longer fits.

That replacement history is not just a note for humans. It is the exact compacted prefix a resumed run will replay. If there is no compaction entry, JACA replays the full persisted history. If there is one, JACA starts from that replacement history and then appends the newer messages that came after it.

That choice matters more than the wording of the summary itself. The durable artifact is the same thing the next run actually sees.

JACA is also intentionally narrow about timing. Automatic compaction happens before a resumed run begins. It does not fire in the middle of a turn. The system checks the replay budget first, compacts only when needed, and fails fast if the result is invalid.

JACA also persists a separate turn-context snapshot per run. That snapshot carries the runtime framing for the next resume boundary: workspace root, shell family, date, timezone, model, thinking setting, and the assembled runtime context text. That is important because compaction answers one question and the turn-context snapshot answers a different one.

Compaction answers: what work has happened?

The turn-context snapshot answers: under what conditions should the next run happen?

What Codex made clearer

Codex helped sharpen the boundary.

Its main idea is simple and strong: compaction produces an explicit replacement history, and later reconstruction uses that history directly. The compacted artifact is not advisory. It becomes the rebuilt base of the conversation.

Codex also keeps that base separate from turn context. That part matters just as much. Codex persists a TurnContextItem because a resumed coding turn needs more than memory. It needs the correct execution contract for the next step: current working directory, model, sandbox and approval policy, network policy, date, timezone, and other runtime framing.

That is the piece I think is easy to miss if you only talk about compaction. Some state belongs in the visible conversation history. Some state belongs beside it. Trying to force both into one compacted blob makes the system harder to trust and harder to evolve.

The other difference is timing. Codex supports compaction before a turn and, in some cases, during an ongoing turn. JACA does not. Seeing that difference clearly helped me understand what JACA should own and what it should not.

What Claude Code made clearer

Claude Code made a different point. Compaction is not one mechanism there. It is a family of mechanisms.

There are separate paths for automatic compaction, smaller targeted compaction, preserved session memory, and cleanup after compaction. That matters because it shows the surrounding discipline. Token thresholds, reserved output space, repeated-failure handling, and cache cleanup are treated as part of the feature, not as afterthoughts.

I did not copy that structure directly. The systems are different.

But the lesson stayed. Compaction needs budgeting, cleanup, and failure handling around it. If it lives as one hidden prompt helper buried in the runtime, it slowly turns into a source of silent drift.

What JACA borrowed

JACA borrowed two things and rejected a third.

From Codex, it borrowed the idea that compaction should end in an explicit, model-visible replacement history. That is why JACA stores a durable replacement history and replays it directly on resume.

It also borrowed the need for a separate runtime-framing baseline. In JACA that shows up as the persisted turn-context snapshot. The system can then recover the latest surviving run conditions separately from the compacted conversation history instead of pretending one artifact should do both jobs.

From Claude Code, it borrowed the idea that compaction deserves subsystem-level discipline. Token budgeting, bounded source building, strict validation, and failure counters are not optional polish. They are part of the feature.

What JACA rejected was turning compaction into a catch-all. The compacted artifact owns conversation continuity only. Backend working state, runtime framing, and hidden instructions have to live somewhere else or not exist at all.

Takeaway

Compaction looks like a memory feature. In a coding agent it is really two features living side by side: continuity of the conversation, and continuity of the runtime contract.

Once a session grows past what the model can carry raw, one system has to decide what the next run is allowed to believe about the past, and another has to decide under what conditions that next run should happen. Mix those together and the system gets muddy. Keep them separate and both become easier to reason about.