Long-running agents do not usually fail because they run out of clever ideas. They fail because a context window fills, an automatic summary replaces the working history, and the next turn no longer knows which facts are commitments, which writes are uncertain, and which approval is still pending.
That is not a prompt-writing problem. It is a state-transfer problem. AI agent context compaction should act like a controlled handoff: preserve the task contract and durable evidence outside the conversation, shrink only recoverable detail, then prove that the next turn can resume safely. This guide shows a vendor-neutral pattern for coding and tool-using agents.
Why a summary is not enough
Compaction reduces a large live history to make room for future work. It can reduce repeated input, latency, and distraction. But a fluent paragraph is a poor database. It often blends confirmed facts, tentative plans, and assumptions into prose. That is dangerous when an agent can create a branch, send an email, update a record, or wait for a human decision.
Treat the conversation as a cache of reasoning and observations. Treat the following as durable state:
- The user goal, constraints, and definition of done.
- The current plan and the next safe action.
- Immutable input versions: commit, document revision, schema, and policy release.
- Operation IDs, idempotency keys, and the outcome of every side effect.
- Pending approvals and their exact proposed payloads.
- Evidence pointers: artifact hashes, test results, source URLs, and error receipts.
- Explicit uncertainty.
unknownis safer than a guessed success.
The summary can point to those records. It should not be their only copy. This distinction is the heart of a reliable compaction design.
Define a compaction contract before the session grows
Start by writing a small typed object owned by the application, not by a particular model response. A model may help propose its contents, but the runtime validates it and stores it durably.
type RunHandoff = {
runId: string;
task: { goal: string; doneWhen: string[]; constraints: string[] };
inputs: { repoCommit?: string; policyVersion: string; refs: string[] };
progress: { completed: string[]; nextAction: string; blockers: string[] };
operations: Array<{
id: string; kind: string; idempotencyKey: string;
state: "not_started" | "pending" | "succeeded" | "failed" | "unknown";
receiptRef?: string;
}>;
approvals: Array<{ id: string; action: string; payloadRef: string; state: "pending" | "resolved" }>;
invariants: string[];
compactedAt: string;
version: 1;
};
The important fields are not glamorous. policyVersion tells the resumed agent which rules still apply. An operation in unknown tells it to reconcile with the external system rather than retry. A pending approval uses a reference to a stored payload, so an approval cannot vanish because the transcript was compressed.
For a coding agent, useful invariants might be: “do not force-push,” “do not edit generated files,” “run the named test command before marking done,” and “a pull request is not created until its receipt exists.” For a customer-facing agent, include the tenant, actor, purpose, and allowed communication channel.
Keep the safety-critical facts outside the context window
There are three layers worth separating.
| Layer | Contents | Source of truth | What compaction may do |
|---|---|---|---|
| Durable run state | task contract, operations, approvals, receipts | database or workflow store | include compact references only |
| Artifacts | patches, files, test reports, fetched data | object store or repository | preserve by immutable ID and hash |
| Conversation context | observations, tool output, exploration | model session | summarize, index, or discard |
This design is useful even when a provider offers native compaction. Native support can make a session easier to continue, but application ownership still matters. The provider cannot know which external effect your product must reconcile or which approval your policy treats as binding.
Do not paste a whole database row into every request. Render a small resume packet: goal, current phase, protected constraints, next action, recent relevant observations, and IDs for anything else. Fetch details through a tool only when the agent needs them. That preserves context room and gives the runtime a chance to enforce access control.
Compact at stable boundaries, not during a risky action
The worst time to compress is halfway through a side effect. Create boundaries after a tool result is recorded, after a test suite finishes, after a patch is saved, or when the run pauses for approval. At each boundary, write the handoff first; only then ask for compaction.
async function checkpointThenCompact(runId: string) {
const state = await buildValidatedHandoff(runId);
await handoffs.put(runId, state); // durable write
await artifacts.flush(runId); // patch, logs, test reports
const prompt = renderResumePacket(state); // bounded, model-visible view
const compacted = await model.compact({ prompt });
await sessions.replaceHistory(runId, compacted);
await events.append(runId, { type: "compacted", version: state.version });
}
The order prevents a common failure: a summary says “tests passed,” but the test log was never saved. If model.compact fails, keep the old session or start a new one from the durable handoff. Compaction should fail open; it should not erase the only usable history.
Use a headroom trigger rather than waiting for the hard context limit. Reserve space for the next tool result, the model response, and a recovery message. A simple policy is to checkpoint at 65–75% estimated context use, then compact only at the next stable boundary. Measure actual behavior because provider token estimates, cached prefixes, and tool payload sizes differ.
Make uncertain side effects impossible to summarize as success
Tool timeouts are a classic trap. The request to create an issue may have reached the provider even if the agent receives no response. A text summary can quietly convert “timed out after dispatch” into “create the issue.” The resumed session then duplicates work.
Give each write an operation ID and idempotency key. Record pending before dispatch. When a response is missing, store unknown, not failed. On resume, call a narrow reconciliation tool that checks the external system by key, then transitions to succeeded, failed, or a human-review state.
await operations.insert({ id, idempotencyKey, state: "pending" });
try {
const result = await tools.createTicket({ idempotencyKey, body });
await operations.update(id, { state: "succeeded", receiptRef: result.id });
} catch (error) {
await operations.update(id, { state: "unknown" });
throw error; // resume path reconciles; it does not blindly retry
}
This is not just defensive coding. It gives compaction an honest, machine-readable answer about what remains to be done.
Protect instructions, approvals, and facts differently
Not every item deserves verbatim retention. Classify before you compact.
- Pin: user intent, policy constraints, authorization scope, pending approvals, and operation states. These are rendered on every resume.
- Reference: source files, patches, long tool output, and research. Keep a digest, location, and short description; retrieve on demand.
- Summarize: completed exploration, alternatives rejected, and diagnostics that explain the next choice.
- Drop: duplicated logs, superseded plans, and observations that have no future decision value.
Avoid letting the model decide that a pinned instruction is obsolete. An instruction may be replaced only through an explicit application event, such as a new user message or approved policy release. Likewise, an approval is a state transition in the workflow store, not a sentence like “the user was okay with it.”
Build a small, inspectable resume packet
The resume packet is the only state that must be easy for the next model turn to read. Keep it boring and bounded. It should name records rather than reproduce them, and it should make uncertainty visible. A packet for a code-change workflow can look like this:
Task: Add export validation; done when unit tests and integration test pass.
Constraints: no production write; do not modify generated files.
Inputs: repo 4c1f…, policy 12, issue 184.
Completed: reproduced bug; added validator; unit tests pass.
Next safe action: run integration test `pnpm test:exports`.
Operations: pull-request create = not_started.
Approvals: none pending.
Artifacts: patch sha256:…; unit report artifact://run_8a1/unit.xml.
Open uncertainty: integration suite has not run.
This gives an operator something useful to inspect too. If the next turn behaves oddly, compare its first proposed action with next safe action and the pinned constraints. Do not make the packet a new hidden prompt layer. It is an application-visible state view with a schema, audit trail, and size budget.
Version it. A later runtime may add an authorization reference or a new receipt type. The resume renderer should understand older records or reject them clearly, never silently omit an unknown safety field. In a multi-agent system, issue a child packet with only the child’s task, scoped inputs, allowed tools, and required return artifacts. The parent should receive evidence and status, not a sprawling copy of the child’s transcript.
Finally, include a compaction receipt: source session ID, handoff version, protected-field count, artifact count, token estimate before and after, and any omitted material. This makes it possible to diagnose whether a failure began with a bad plan, a missing durable record, or a lossy render.
Test compaction like a deployment boundary
Happy-path summaries are not a test strategy. Add compaction cases to your agent evaluation suite, including a forced compact after every major phase. The resumed agent should produce the same safe next action as an uninterrupted run.
Test at least these cases:
- A pending approval survives compaction and blocks the write.
- A timed-out external write remains
unknownand is reconciled, not repeated. - The resumed agent finds the correct patch and test artifact by hash.
- A changed policy version forces the agent to reload constraints.
- A malicious or irrelevant old tool result is absent from the resume packet.
- A compaction-service failure leaves the prior session and durable checkpoint usable.
Useful metrics are compaction count per successful task, resume success rate, invariant violations after compaction, artifact retrieval failures, and duplicate side effects. Also inspect the quality cost: compare acceptance tests and human corrections for compacted versus uninterrupted runs. Lower input tokens are not a win if completion quality falls.
A rollout plan that does not require a new framework
First, instrument context growth and identify runs that exceed a useful threshold. Next, add a durable run record for only two things: the task contract and operation state. Then store patches and tool artifacts with stable IDs. Once those exist, introduce a resume packet and force compaction in a non-production evaluation environment.
Only after the tests pass should you compact automatically in live workflows. Begin with read-only research or code-explanation tasks. Add write-capable actions after you have idempotency, reconciliation, and approval records. Keep a feature flag so a problematic compactor can be bypassed without losing the run state.
The practical goal is modest: a new model turn should be able to answer four questions without trusting a prose recap—what is the task, what constraints apply, what happened, and what is the next safe action? If the system can answer those from durable evidence, context compaction becomes a routine maintenance step instead of a hidden source of agent amnesia.
FAQ
What is AI agent context compaction?
It is the process of reducing an agent’s active conversation history so a long task can continue within a context window. Safe implementations retain durable task state and artifacts outside the conversation, then give the next turn a bounded resume packet.
Is context compaction the same as agent memory?
No. Compaction manages the current session’s working context. Memory is longer-lived information that may be retrieved across sessions. Both need provenance, access controls, and deletion rules, but they solve different lifecycle problems.
When should an agent compact context?
Checkpoint before the window is full and compact at a stable boundary: after a tool result, saved artifact, completed test, or approval pause. Leave room for the next action and recovery response.
How do I stop compaction from losing a pending approval?
Store the approval request and payload in a durable workflow record. Put only its ID, state, and short description in the resume packet. The runtime—not the model’s summary—must block the side effect until the record is resolved.
Can an agent safely retry after compaction?
Only after checking the operation record. For a dispatched write with no confirmed response, mark it unknown, reconcile by idempotency key or external receipt, and retry only when the system proves no prior effect occurred.
What should I measure after adding compaction?
Measure resumed-run success, invariant violations, duplicate side effects, artifact retrieval failures, context size, latency, and cost per accepted outcome. Compare these with a non-compacted baseline rather than optimizing token count alone.
Top comments (0)