Transcript Compaction Ledgers for Long-Running AI Coding Agents
24 Jun 2026 · 6 min read
- Context Engineering
- Agent Reliability
- Prompt Budgeting
- Developer Tools
Long-running coding agents usually fail in a boring way. They do not become spectacularly wrong all at once. They slowly accumulate old transcript state, stale assumptions, repeated logs, and too many half-relevant details until every next tool call gets slower and less trustworthy.
The fix is not a bigger context window. The fix is a better memory shape. A transcript compaction ledger keeps the durable facts, decisions, risks, and pinned evidence that matter for the next turn, then lets the rest age out.
In this post I will show a practical ledger design, when to compact, what to pin, what to throw away, and how to keep verifier failures from being buried under conversational sludge.
Why this matters
If an agent session lasts 30 to 90 minutes, the transcript starts mixing three very different things:
- durable project state
- temporary debugging chatter
- tool output that was useful once but is now just token ballast
That mixture causes three common problems:
- the model keeps seeing stale plans after the repo changed
- critical evidence gets lost inside raw logs
- the prompt budget goes to transcript replay instead of fresh code or verifier output
This gets more painful in shell-first coding systems, long bug hunts, and review loops where the agent needs to remember what was tried without re-reading every failed attempt.
Architecture or workflow overview
flowchart LR
A[Live transcript] --> B[Compactor]
B --> C[Ledger entries]
C --> D[Prompt packet]
B --> E[Refresh triggers]
E --> C
C --> F[Pinned evidence blocks]
F --> D
The key design choice is simple: treat the raw transcript as an event stream, not as the context packet itself.
A useful ledger usually contains four buckets:
- session facts: branch, changed files, current objective, constraints
- decision log: what was attempted, what was rejected, and why
- pinned evidence: exact logs, diffs, commands, or file excerpts worth preserving verbatim
- refresh metadata: TTLs, drift checks, verifier failures, and invalidation triggers
Implementation details
1. Define a ledger schema that separates facts from evidence
The compactor should not dump one giant summary blob. It should emit typed entries you can score, pin, expire, and rehydrate.
ledger:
sessionFacts:
branch: master
objective: "stabilize transcript compaction worker"
changedFiles:
- src/compactor.ts
- tests/ledger.test.ts
decisions:
- id: dec_014
claim: "Dropped raw npm install logs after extracting failing package name"
confidence: medium
sourceTurns: [81, 82]
expiresAfterTurns: 12
pinnedEvidence:
- id: ev_021
kind: verifier-output
whyPinned: "last failing assertion still unresolved"
contentRef: artifacts/jest-failure.txt
refreshTriggers:
repoFingerprint: 2f40d5f
refreshOn:
- verifier_failure
- changed_file_not_in_packet
- decision_ttl_expired
This layout makes one tradeoff explicit: only the evidence that must survive gets preserved verbatim. Everything else becomes a smaller typed fact.
2. Compact turns into scored entries
A compactor can run after every N turns or after high-noise tool events. The main job is to demote conversational noise and promote durable state.
type Turn = { id: number; role: 'user' | 'assistant' | 'tool'; text: string; tags?: string[] };
type LedgerEntry = {
id: string;
bucket: 'fact' | 'decision' | 'evidence';
summary: string;
score: number;
ttlTurns: number;
pinned?: boolean;
};
export function compactTurns(turns: Turn[]): LedgerEntry[] {
return turns.flatMap((turn) => {
if (turn.role === 'tool' && turn.text.length > 2000) {
return [{
id: `e_${turn.id}`,
bucket: 'evidence',
summary: extractVerifierSignal(turn.text),
score: 0.82,
ttlTurns: 8,
pinned: /FAIL|AssertionError|panic/i.test(turn.text)
}];
}
if (turn.tags?.includes('decision')) {
return [{
id: `d_${turn.id}`,
bucket: 'decision',
summary: turn.text,
score: 0.74,
ttlTurns: 12
}];
}
return [];
});
}
What I like about this pattern is that it stays inspectable. You can explain why something survived compaction instead of pretending the summary model just “knows”.
3. Rebuild the prompt packet from the ledger, not the full transcript
The next prompt should be assembled from the ledger with a budget. Facts first, pinned evidence second, recent raw turns last.
$ agent-context build --max-tokens 9000
[budget] facts=1400 decisions=1200 pinned_evidence=3400 recent_turns=2000 slack=1000
[packet] included 6 session facts
[packet] included 4 active decisions
[packet] included 2 pinned verifier blocks
[packet] included 3 recent turns
[packet] dropped 17 expired entries
This is usually where teams overcomplicate the system. You do not need a perfect memory layer. You need a deterministic packet builder that spends tokens on fresh and relevant state before replaying old dialogue.
4. Trigger refreshes when reality changes
| Trigger | Why it matters | What to do |
|---|---|---|
| Verifier failure changed | Old summary may hide the new failing edge | Re-pin latest failing output |
| Repo fingerprint changed | Code context is no longer aligned | Refresh file facts and changed paths |
| Tool output exceeds noise budget | Packet bloat risk | Summarize and demote raw output |
| Decision TTL expired | Old reasoning may no longer apply | Drop or revalidate the decision |
| Human override note added | High-trust instruction changed | Pin the note until task completion |
What went wrong and tradeoffs
Failure mode 1, summary drift
A compacted decision can become wrong after the repo changes. If the agent says “tests already passed” but a dependency or target file changed afterward, the summary is now poison.
That is why I would always store a repo fingerprint, changed-file set, or verifier hash alongside high-confidence entries.
Failure mode 2, over-pinning
Teams sometimes react to drift by pinning everything. That just recreates the raw transcript in a new format.
My rule is blunt: if a block is not needed to justify the next action or explain the last failure, it should age out.
Failure mode 3, hidden security leakage
Pinned evidence often includes secrets, auth headers, customer data, or stack traces with internal paths. A ledger is easier to reuse than a transcript, which means leaks can spread farther.
Before persisting ledger entries beyond one run, I would add:
- secret scrubbing for tokens, cookies, and private URLs
- path redaction for sensitive environments
- retention limits for pinned evidence artifacts
What I would not do
I would not build transcript compaction as a single giant LLM summary that gets rewritten every turn. It is too hard to diff, too easy to drift, and too tempting to trust.
I would also not tie compaction only to token count. Reality changes before the prompt hits the limit.
Practical checklist
- Keep separate buckets for facts, decisions, and pinned evidence
- Give decisions a TTL so stale reasoning dies on schedule
- Re-pin the latest verifier failure instead of keeping every old one
- Recompute repo facts after file drift or branch changes
- Prefer artifact references over embedding giant logs inline
- Redact secrets before storing persistent ledger entries
- Keep recent raw turns, but cap them aggressively
Conclusion
Long-running agents do better when they remember less, but remember the right things. Transcript compaction ledgers give you a durable memory layer that is smaller than a transcript and more trustworthy than a free-form summary.
If I were adding this to a real coding agent stack tomorrow, I would start with typed ledger buckets, a simple packet budget, and three invalidation triggers: verifier change, file drift, and decision TTL expiry.