Trail content
8 minEnglish narration, Portuguese subtitlesPodcast (RSS)
Chapters
  1. 0:00Opening
  2. 1:01The idea
  3. 1:59How it works
  4. 2:28A real case
  5. 3:43A challenge
  6. 4:31When not to use it
  7. 6:05In production
  8. 7:45Wrap-up
Transcript

Opening

Renata's coding agent handles long tasks. Two or three hours. Migrating a module, upgrading dependencies, fixing a batch of tests.

At the start of a task, it's precise. After one hundred fifty turns, the context holds hundreds of tool results. Whole files, test output thousands of lines long, searches that led nowhere.

Near the end, the agent starts editing files it already fixed.

Reapplying changes that someone had already reverted, on purpose.

And forgetting the constraint agreed at the start. Don't touch the public API. On some tasks, the window overflows, and the run fails.

Starting a fresh session doesn't help. The agent would lose the plan, the decisions, and what's been done. In this lesson, you'll learn to keep an agent clear headed on a task longer than its context window can hold.

The idea

Treat the context as working memory, with ongoing maintenance. Three operations, in order of cost.

Prune. Delete the contents of old tool results that were already used, like a file read eighty turns ago. The call stays on record. The content goes.

Summarize. When the context passes a limit, replace the old part with a summary that keeps the goal, the decisions, the current state, and the next steps.

And externalize. Facts that must survive any summary, like the plan, the constraints, and the list of changed files, go to a file or state outside the context. The agent reads them when it needs to.

The trigger is a token limit, checked before each turn. The most recent turns always stay whole. And here's the division of labor. Code decides when to cut. The model only writes the summary.

How it works

The agent works as usual.

Before each turn, code measures the context.

The first measure is cheap. Delete the contents of old tool results.

If pruning was enough, the loop continues.

If not, a model summarizes everything before the latest turns.

And critical facts live outside the context, in a notes file or in state. The summary points to them.

A real case

Back to Renata. On turn three, she agreed: don't change the public API of the billing module. On turn eighty, the agent read the billing API file, about two thousand four hundred tokens, and ran the tests, about one thousand eight hundred tokens of log. On turn one hundred fifty, those results still ride along on every call, with dozens of others.

With illustrative numbers, by turn one hundred fifty, input already passes sixty thousand tokens per call. Without pruning, the next fifty turns resend that volume. About three million input tokens, just to finish the task.

Pruning swaps the old content for one line. The history still says the read happened. The two thousand four hundred tokens leave. And if the agent needs the file again, it reads it again. That's cheaper than carrying the file for fifty turns.

When pruning isn't enough, the summary comes in. Here's a schema that holds up in Renata's eval. The goal. Decisions. What's done. The current state. The next steps.

And this line. Constraints. Do not change the public API.

A challenge

Here's one for you. After a compaction, Renata's agent changes a public signature. The summary had the goal, what's done, and the next steps. But no constraints field. What's the fix?

Put constraints in the summary schema, and in a notes file outside the context. Anything that must survive every cut needs a place in the summary, and a copy outside it. A goal and next steps don't store a do not.

Raising the limit so compaction never runs only delays the rot. And a bigger model doesn't remember what you removed from the context. Without that field, the compactor helps you forget the one thing that must not be forgotten.

When not to use it

Short tasks. The compaction step costs more than it saves.

Heavy reading in a well bounded subtask. Isolate it in a subagent. Sometimes the two patterns go together. Isolation keeps heavy material out. Compaction cleans what already got in.

Content that must be read in full for the answer, like a contract reviewed clause by clause. Summarizing there loses what matters.

And a short support chat. Say it ends in eight turns, and a colleague wants to compact every four, to save money. Don't. The history fits, and a summary only risks losing the original request, like the order number in the first message. Keep those facts as explicit state instead.

Now the common traps. Summarizing too early. Compacting every few turns spends a call, and deletes detail that would still be used. Measure first. Cut when the token curve hurts.

A schema with no constraints. We just saw that one. Your retention eval needs a case like it.

Cutting in the middle of a tool call. Separate a tool request from its result, and the next API request is invalid. The cut starts on a user turn.

And throwing the raw history away. You'll need it to debug a summary that invented a decision. Compaction changes what the model sees, not what you keep.

In production

One question teams always ask. After pruning, the agent rereads a file it already read eighty turns ago. Is that a bug? No. Pruned content is gone on purpose. If the file still matters, the tool is called again. And the summary shouldn't copy whole files either. It stores decisions and state, not source code.

How do you evaluate it? Retention first. Ask questions about facts from early in the task, after compaction. That's the eval most people skip. Without it, compaction works on cost, and fails on the product.

Then the completion rate, and rework. Files edited twice, for the same reason.

Then tokens per turn, over the run. The curve should have teeth, not climb forever. And if the tokens before and after a compaction barely change, the old messages were never really replaced.

And the prompt cache. Changing the start of the context can reset it. Watch the cached tokens after each compaction.

Some providers already do part of this for you. Server side compaction, where the API summarizes old context on its own. Or context editing, where the API clears old tool results before the model sees them. Prefer those when they cover your case. And one privacy note. The summary and the notes file often end up in logs. If the conversation holds personal data, compaction creates one more copy. Restrict who can read those logs.

Wrap-up

So here's the idea to take with you. A long task needs a maintained memory, not a growing pile. Prune what's been used, summarize the rest into a schema that has room for every do not, and keep the critical facts in a file no summary can delete. Then hour three looks like minute three.

In the next lesson, a smell about the shape of the agent itself: one agent trying to do everything. The mega agent. See you there.

PatternContext and knowledge

Context Compaction

Compactação de Contexto

Context Compaction is an AI design pattern for LLM apps and agents, in the Context and knowledge group. Summarize or prune the context as it grows, keeping what matters.

Element
18/70
Level
Advanced
Read
8 min
Updated
On this page 18
  1. 01Intent
  2. 02Problem
  3. 03Solution
  4. 04Structure
  5. 05Pseudocode
  6. 06Real-world scenarios
  7. 07Applicability
  8. 08When not to use
  9. 09How to implement
  10. 10Variations
  11. 11Cost, latency, and failure modes
  12. 12How to evaluate
  13. 13In production
  14. 14Security
  15. 15Pros and cons
  16. 16Relations
  17. 17Code
  18. 18Check your understanding
  19. ··Quick answers

Intent

Summarize or prune the context as it grows, keeping what matters for the next steps. It is for long agents and conversations that would otherwise fill the window and get worse with every turn.

In one sentence
The minutes of a long meeting: nobody rewatches the recording, everybody reads the decisions.

Problem

Renata’s coding agent handles two- to three-hour tasks: migrating a module, upgrading dependencies, fixing a batch of tests. At the start of a task it is precise. After 150 turns, the context holds hundreds of tool results: whole files, test output thousands of lines long, searches that led nowhere.

Near the end, the agent starts editing files it already fixed, reapplying changes that were reverted, and forgetting the constraint agreed at the start (“do not touch the public API”). On some tasks the window overflows and the run fails.

That is the Context Rot smell: the history is still there, but it is no longer useful memory. The paper “Lost in the Middle” (Liu et al., 2023) helps explain the symptom. With a full window, the model uses the middle poorly, which is exactly where the turn-3 constraint sits.

Starting a new session does not help: the agent loses the plan, the decisions, and what has been done. Renata needs a way for the context to stay useful after hours.

The question this pattern answers: how do you keep an agent clear-headed on a task longer than its context window can hold?

Solution

Treat the context as working memory with ongoing maintenance. Three operations, in order of cost:

  • Prune: delete old tool results that have already been used (the contents of a file read 80 turns ago). The call stays on record; the content goes.
  • Summarize: when the context passes a limit, replace the old part with a summary that keeps the goal, decisions, current state, and next steps.
  • Externalize: facts that must survive any summary (plan, constraints, list of changed files) go to a file or state outside the context, and the agent reads them when needed.

The trigger is a token limit, checked before each turn. The most recent turns always stay whole.

Anthropic’s “Building effective agents” (December 2024) treats compaction as context maintenance, not a prompt trick. Code decides when to cut. The model only writes the summary.

Worked example

On turn 3, Renata agreed: “do not change the public API of the billing module”. On turn 80, the agent read billing/api.py (2,400 tokens) and ran the tests (1,800 tokens of log). On turn 150, those results still ride along on every call, with dozens of others.

For the account, suppose a 4,000-token system prompt and 400 tokens added per turn. By turn 150, input already passes 60,000 tokens. Without pruning, the next 50 turns resend that volume: about 3 million input tokens just to finish the task.

Pruning swaps the old content for one line:

tool_result read_file billing/api.py
[content removed; call the tool again if you need it]

History still says the read happened. The 2,400 tokens leave. If the agent needs the file again, it reads again. That is cheaper than carrying the file for 50 turns.

When pruning is not enough, the summary comes in. The schema that holds up in Renata’s eval looks like this:

{
  "goal": "Migrate billing to API v2",
  "constraints": ["do not change the public API"],
  "decisions": ["keep the endpoint names", "adapt only the internal client"],
  "done": ["internal client updated", "unit tests passing"],
  "current_state": "contract test fails in test_public_api.py:41",
  "next_steps": ["read the contract test", "fix the adapter, not the public signature"]
}

The turn-3 constraint survives only if the schema has a field for it, and if the same constraint lives in a notes file the summary cannot delete. Without that, the compactor “helps” you forget the one thing that must not be forgotten.

Check +20 XP on first tryAfter compaction, Renata's agent changes a public signature. The summary had goal, done, and next_steps, but no constraints field. What is the fix?

Structure

flowchart TD
  L(["1. Agent turn"]):::llm --> C{"2. Context over the limit?"}
  C -->|no| L
  C -->|yes| P["3. Prune old results"]:::tool
  P --> C2{"4. Still over?"}
  C2 -->|no| L
  C2 -->|yes| S(["5. Summarize the old part"]):::llm
  S --> N[("6. External notes and state")]:::store
  N --> L
  1. The agent works as usual.
  2. Before each turn, code measures the context.
  3. The first measure is cheap: delete the contents of old tool results.
  4. If pruning was enough, the loop continues.
  5. If not, a model summarizes everything before the latest turns.
  6. Critical facts live outside the context, in a notes file or state, and the summary points to them.

Pseudocode

schema Compaction { goal: string, decisions: list[string], done: list[string],
                    current_state: string, next_steps: list[string] }

function step(state):
    if tokens(state.messages) > SOFT_LIMIT:
        # 3. prune: keep the call, delete the old content
        for r in old_tool_results(state.messages, older_than=20):
            r.content = "[content removed; call the tool again if you need it]"

    if tokens(state.messages) > HARD_LIMIT:
        # 5. summarize the old part, keep the latest turns
        old, recent = split(state.messages, keep_last=10)
        summary = llm(COMPACT_PROMPT, old + read(state.notes_file)) -> Compaction
        write(state.notes_file, summary)                    # 6. externalize
        state.messages = [as_message(summary)] + recent
        trace.span("compaction", tokens_before, tokens(state.messages))

    return llm_with_tools(SYSTEM, TOOLS, state)              # 1

Real-world scenarios

  • Coding agent. Long tasks with lots of file reading. Pruning old results solves most of it; summarizing only kicks in on the longest tasks, and the plan lives in a notes file.
  • Back-office automation with durable execution. Processes that last days, with pauses for approval. Each resume starts from compacted state, not three days of history.
  • Educational tutor. Sessions that become weeks. What the student has mastered and the agreed rules (“hints only, no full solution”) stay as facts; the rest becomes a lesson summary.

Applicability

  • Tasks or conversations that often pass a few tens of thousands of tokens.
  • Agents that read a lot (files, pages, logs) and use little of it later.
  • Cost or latency per turn growing over the run.
  • Quality dropping at the end of long tasks while being fine at the start.

When not to use

  • Short tasks. The compaction step costs more than it saves.
  • The problem is heavy reading in a well-defined subtask. Isolate it in a subagent: see Context Isolation. Sometimes the two patterns go together.
  • Content that must be read in full for the answer (a contract reviewed clause by clause). Summarizing there loses what matters.
  • A short support chat where the problem is keeping two or three facts. Store those facts as explicit state, outside the raw transcript, instead of summarizing the chat.
Check +20 XP on first tryA support chat ends in 8 turns. A colleague wants to compact every 4 turns to 'save money'. What do you say?

How to implement

  1. Measure context size per turn in today’s traces and find where it grows.
  2. Start by pruning old tool results. Often that alone is enough.
  3. Define the summary schema: goal, decisions, done, current state, next steps, constraints.
  4. Give the agent a notes file (or memory tool) to record its plan and decisions along the way.
  5. Pick limits: a soft one for pruning and a hard one for summarizing.
  6. Check whether the provider offers server-side compaction or context editing, and prefer it when it covers your case.
  7. Build an eval with long tasks that depend on something from the start, and run it before and after.

Common traps

  • Summarizing too early. Compacting every few turns spends a call and deletes detail that would still be used. Measure first; cut when the token curve hurts.
  • A schema with no constraints. Goal and next steps do not store a “do not”. The retention eval needs a case like that.
  • Cutting in the middle of a tool call. Splitting a tool_use from its tool_result makes the next request invalid. The cut starts on a user turn.
  • Throwing the raw history away. You will need it to debug a summary that invented a decision. Compaction changes what the model sees, not what you keep.

Variations

  • Server-side compaction. The API summarizes old context on its own once it passes a limit. Less code; you must send back the compaction blocks it returns.
  • Context editing. The API clears old tool results before the model sees the context. It is pruning, with no code of yours.
  • Sliding window. Only the last N turns. Simple, but it loses everything outside the window; it fits when the past truly does not matter.
  • Notes written by the agent itself. The agent keeps a progress file that survives any cut. It combines with the other variations.

Cost, latency, and failure modes

Aspect What happens How it shows up in the trace
Calls One extra per compaction, none for pruning occasional compaction span
Cost The summary costs one call; the following turns get much cheaper input tokens dropping after each compaction
Latency The turn that compacts is slower duration spike on the compaction turn
Cache Changing the start of the context can reset the prompt cache cache_read_input_tokens dropping after compaction
Failure: lost fact The agent redoes work or breaks an old constraint repeated action after a compaction
Failure: invented summary The agent acts on something that did not happen decision in the summary with no source in the history

How to evaluate

  • Retention: eval questions about facts from early in the task, asked after compaction.
  • Completion rate and rework (files edited twice for the same reason) on long tasks.
  • Cost per task and tokens per turn at the end of the run.
{ "task": "Migrate the billing module to the new API", "constraint_turn": 3, "constraint": "do not change the public API", "check": "no public signature changed" }

The retention case is the eval most people skip. Without it, compaction “works” on cost and fails on the product.

Check +20 XP on first tryAfter pruning, the agent rereads billing/api.py, which it already read 80 turns ago. The trace shows the old result swapped for the placeholder. Is that a bug?

In production

  • A compaction span with tokens before and after and the generated summary.
  • A dashboard of tokens per turn over the run: the curve should have teeth, not climb forever.
  • An alert for runs that compact many times. Maybe the task should be split.
  • Keep the raw history outside the context to debug bad summaries.
  • If tokens_before and tokens_after barely change, the splice did not replace the old messages.

Security

The summary and the notes file often go to logs. If the conversation holds personal data (tax id, balance, an order number with a name), compaction creates another copy. Under Brazil’s LGPD, that is processing. Restrict who can read those logs, and do not send the raw history to a compaction model outside company policy.

Pros and cons

Pros Cons
Long tasks fit in the window Summaries lose details
Stable cost and latency per turn One extra call per compaction
The agent focuses on what is relevant now May invalidate the prompt cache
External notes survive any cut One more prompt to write, version, and evaluate

Relations

  • Smell it treats: Context Rot, when history grew and quality dropped.
  • Refactoring that leads here: Replace History with Summary.
  • Neighbors: Context Isolation keeps heavy material out of the history. Compaction cleans what already got in. If the subtask is well bounded, isolate first.

Code

The Python tab prunes and summarizes by hand. The TypeScript tab uses the API’s server-side compaction.

Python. Use this when you want to control what gets pruned and the shape of the summary.

import json

import anthropic

client = anthropic.Anthropic()
SOFT, HARD, KEEP_LAST = 60_000, 120_000, 10

COMPACT_PROMPT = """Summarize the agent's work so far as JSON with the keys:
goal, constraints, decisions, done, current_state, next_steps.
Keep every constraint agreed with the user. Do not invent anything."""


def count(messages: list) -> int:
    return client.messages.count_tokens(model="claude-opus-5-5", messages=messages).input_tokens


def prune(messages: list, older_than: int = 20) -> None:  # 3. prune
    for msg in messages[:-older_than]:
        if msg["role"] == "user" and isinstance(msg["content"], list):
            for block in msg["content"]:
                if block.get("type") == "tool_result":
                    block["content"] = "[removed; call the tool again if you need it]"


def compact(messages: list, notes_path: str) -> list:
    if count(messages) > SOFT:
        prune(messages)
    if count(messages) <= HARD:
        return messages
    old, recent = messages[:-KEEP_LAST], messages[-KEEP_LAST:]
    while recent and recent[0]["role"] != "user":  # the cut starts on a user turn
        old.append(recent.pop(0))
    reply = client.messages.create(  # 5. summary
        model="claude-opus-5-5", max_tokens=4000, system=COMPACT_PROMPT,
        messages=[{"role": "user", "content": json.dumps(old, default=str)[:400_000]}],
    )
    summary = reply.content[-1].text
    open(notes_path, "w", encoding="utf-8").write(summary)  # 6. externalize
    return [{"role": "user", "content": f"Summary of earlier work:\n{summary}"}] + recent

What you gained: you decide what goes, the summary’s shape, and where it lives. What you lost: cutting in the right place (never separating a tool_use from its tool_result) is on you.

TypeScript. Use this when you would rather the API summarize on its own and do not want to maintain compaction code.

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const messages: Anthropic.Beta.BetaMessageParam[] = [];

export async function turn(userMessage: string, tools: Anthropic.Beta.BetaTool[]) {
  messages.push({ role: "user", content: userMessage });

  const response = await client.beta.messages.create({
    betas: ["compact-2026-01-12"],
    model: "claude-opus-5-5",
    max_tokens: 16000,
    tools,
    messages,
    // 2 to 5. the API summarizes old context once it passes the limit
    context_management: { edits: [{ type: "compact_20260112" }] },
  });

  // send back the full content: compaction blocks must return on the next call
  messages.push({ role: "assistant", content: response.content });
  return response;
}

What you gained: no summary code to maintain. What you lost: control over the summary’s shape; keep critical notes in your own file anyway.

Check your understanding

Check +20 XP on first tryA 150-turn task goes bad at the end, with about 180,000 tokens in context. What is the first step?

Check +20 XP on first tryThe compaction span shows tokens_before=120000 and tokens_after=118000. What does that indicate?

Check +20 XP on first tryA back-office process paused two days for a human to approve. On resume, the system sends 48 hours of history to the model. What is the right design?

Quick answers

What is Context Compaction?

Context Compaction is an AI design pattern for LLM apps and agents, in the Context and knowledge group. Summarize or prune the context as it grows, keeping what matters.

Which smells does the Context Compaction pattern help cure?

Context Rot.

Where does Context Compaction show up in practice?

In the real-world scenarios Coding agent, Back-office automation with durable execution, and Educational tutor.

Go further

Relations

Smells it helps cure 1
Refactorings that lead here 1
Part of the tracks 1
Search