Skip to content
Domain 6Task 6.1

Context Engineering

Learn this interactively

What You Need to Know

The context window is everything the model reads on one request: the system prompt, the conversation, the tool results and the response it writes. An agentic run is a job in which the model calls tools step after step. Prompt engineering asks what the instructions say. Context engineering asks what is in the window at all. Anthropic defines it as "the set of strategies for curating and maintaining the optimal set of tokens (information) during LLM inference". That definition covers everything that enters the window, not only the text you wrote. Agentic runs are where these choices count, because in those runs the window fills automatically.

This lesson uses one made-up example throughout: a company that sells expense management software. Its engineering team runs an agent each week to investigate failures in the nightly batch jobs and plan the fixes. Over hundreds of steps, it finds job records, reads run logs and checks retry histories. The team lead set the standing rules at step one. The rules are: amounts in euros, one ticket per failing job, and no re-run of a job that sends payments.

Bloat and Drift Are Not the Same Fault

A long run can fail in two ways. Each way has its own cause and its own fix.

Bloat is a problem of volume. An agentic run collects full API payloads, whole file contents and complete search results. The window fills with material whose useful content the agent extracted twenty steps ago. Cost and latency increase with each step, and in the end a request no longer fits in the window. At the expense software company, a job lookup from step ten became one line: settle-nightly failed at 03:12. The full record of that lookup is still in the window.

Drift is a problem of behaviour. The team lead stated the standing rules once. Now the rules are hundreds of turns back, behind everything that accumulated after them, so the model stops following them. Formats vary, the model skips a rule, and the model contradicts an established fact. The task stays the same while the character of the output changes. By step three hundred, tickets mix jobs and one plan re-runs a payment job.

Length makes both faults worse, not only more expensive, because attention is finite. Anthropic says models have "an 'attention budget' that they draw on when parsing large volumes of context. Every new token introduced depletes this budget by some amount". As the token count grows, "the model's ability to accurately recall information from that context decreases".

A bigger window is not a fix

Moving to a model with a larger context window postpones the point at which a request no longer fits in the window. It does nothing about recall. A run that degrades at 150k tokens degrades at 400k instead, and it spends more on each step. With a bigger window, the team's payment-job rule is still three hundred steps back. Pruning (discarding tool results the agent has already used) and compaction (replacing older turns with a summary) address bloat. Placement means moving the rules to where the model reads them, and placement addresses drift. Window size addresses neither.

The cheapest fix for bloat is to keep material out of the window until the task needs it. Anthropic's engineering guidance calls this "just in time" loading, and the guidance is current as of September 2026. Instead of pasting every record into the prompt at the start, the agent keeps "lightweight identifiers (file paths, stored queries, web links, etc.)". It loads the data through its tools when the task needs it. The team's agent would hold job names and open a run log only when it needs one. Lookups at run time are slower than data loaded in advance, so the same guidance describes a hybrid. A small set of essential data is loaded into the window at the start, in the way that Claude Code loads CLAUDE.md, its project instruction file. The agent fetches everything else as it needs it.

Pruning Spent Tool Output

Raw tool results are the largest single source of bloat. The fix keeps the fact the agent extracted and discards the payload of the tool result. The guide calls this tool output pruning. Current docs implement it as context editing. Context editing is a Messages API beta. A beta header is a request header that turns a beta feature on, and this one needs the header context-management-2025-06-27.

python
import anthropic

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    betas=["context-management-2025-06-27"],
    tools=TOOLS,
    messages=messages,
    context_management={
        "edits": [
            {
                "type": "clear_tool_uses_20250919",
                # Start clearing at 60k rather than the 100k default.
                "trigger": {"type": "input_tokens", "value": 60000},
                # Keep the five most recent tool use/result pairs intact.
                "keep": {"type": "tool_uses", "value": 5},
                # Clear enough to be worth the cache write it costs.
                "clear_at_least": {"type": "input_tokens", "value": 8000},
                # Results still cited downstream must survive.
                "exclude_tools": ["fetch_job_record"],
                "clear_tool_inputs": False,
            }
        ]
    },
)

for edit in response.context_management.applied_edits:
    print(edit.type, edit.cleared_tool_uses, edit.cleared_input_tokens)

Three mechanics matter most. First, the clearing is server-side: the API does it. Your client keeps the full, unmodified history and does not need to track what was removed. Second, the API replaces each cleared result with placeholder text that tells Claude something was removed. So the model knows it once had that material. By step fifty, the settle-nightly record is a placeholder, and the agent can see that something was there.

Third, the response reports what happened in context_management.applied_edits, with cleared_tool_uses and cleared_input_tokens. Those are the numbers worth logging on each step. The token-counting endpoint previews the effect. It returns original_input_tokens alongside the input_tokens after clearing. A second strategy, clear_thinking_20251015, does the same job for extended-thinking blocks. When both strategies are present, clear_thinking_20251015 must be listed first in the edits array.

Clearing invalidates the cached prefix from the clearing point. That is why clear_at_least exists. It stops the run from paying for a cache write to recover a trivial number of tokens. Cache mechanics belong to 2.3, and cache economics belong to 5.4.

Compaction and the Preservation Contract

Compaction is for a long transcript that has to continue rather than end. It replaces the finished part of the transcript with a structured summary and continues from that summary. As of September 2026, the API supports it in two forms, and both forms are in beta. The docs say: "Use on-demand compaction wherever it is available".

  • Compaction on demand (beta header compact-2026-09-04). Your code decides when to compact. You send the conversation in its current state with the top-level compaction parameter. The response is a single compaction block with stop_reason of compaction and no reply. Your code then puts the block into messages: the block goes first, in place of the messages it summarises.
  • Compaction at a token threshold (beta header compact-2026-01-12). The API decides when to compact. A compact_20260112 edit in context_management summarises the conversation inside an ordinary request once input tokens reach the trigger. You append the response as usual.

You cannot send the compaction parameter with context_management. The threshold edit cannot run on a request that contains an on-demand block.

python
# On demand: your code asks for the summary, then swaps it in.
summary = client.beta.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    betas=["compact-2026-09-04"],
    system=SYSTEM,
    tools=TOOLS,  # the same system prompt and tools as the rest of the run
    messages=messages,
    compaction={"type": "summarize"},
)
if summary.stop_reason == "compaction":
    # The block replaces every message it summarised and goes first.
    messages = [{"role": "assistant", "content": summary.content}]
python
# At a token threshold: the API compacts inside an ordinary request.
response = client.beta.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    betas=["compact-2026-01-12"],
    messages=messages,
    context_management={
        "edits": [
            {
                "type": "compact_20260112",
                # Minimum accepted value is 50,000; the default is 150,000.
                "trigger": {"type": "input_tokens", "value": 120000},
                # WARNING: this REPLACES the default summarisation prompt.
                "instructions": (
                    "Preserve: decisions already taken and why, facts established "
                    "and their sources, constraints and prohibitions still in force, "
                    "open questions, and the state of unfinished work. Drop raw tool "
                    "payloads already reduced to facts, and superseded attempts."
                ),
                "pause_after_compaction": False,
            }
        ]
    },
)

# Compaction is billed as its own iteration and is NOT in the top-level counts.
total = sum(it.input_tokens + it.output_tokens for it in response.usage.iterations)

Two mechanics matter here. First, the summarised turns no longer inform the model, and nothing is archived anywhere that you can retrieve it. In the threshold form, the API ignores every content block before the compaction block on the next request. In the on-demand form, your code has already removed those messages. Documents, images and fetched URLs inside the summarised turns are lost with those turns. So re-send anything that a later turn still needs.

Second, in both forms a custom instructions string replaces the default summarisation prompt entirely. The string does not add to the default prompt. Anything that the default would have kept is no longer requested unless your string names it. A hand-written line that says "summarise the code changes" gives you the code changes and little else. If the team's string asks for "the tickets opened", the summary keeps the tickets and drops the payment-job rule.

So the preservation contract is an engineering artefact that you write in advance. It names what to preserve:

  • decisions and their reasons
  • established facts and their sources
  • constraints and prohibitions still binding
  • open questions
  • the state of partially finished work

The contract drops the raw material that those decisions came from, superseded attempts, and verbose payloads already reduced to facts. Anthropic states the risk plainly: "overly aggressive compaction can result in the loss of subtle but critical context whose importance only becomes apparent later". No crash follows. A contradiction appears fifteen steps later. If anyone traces it at all, the trace leads to a summary written long before.

The compaction pass is billed as its own iteration and appears in usage.iterations. The top-level input_tokens and output_tokens exclude it. On an on-demand compaction request, they show zero. A cost model that reads only the top-level fields undercounts every compacting run.

Context awareness can conflict with compaction. As of September 2026, Claude Sonnet 5, Claude Sonnet 4.6, Claude Sonnet 4.5 and Claude Haiku 4.5 have it. The API tells these models their total window and, after each tool call, how much of it is left. When the code that runs the job compacts the context, such a model "may sometimes naturally try to wrap up work as it approaches the context limit". The job stops before it is finished, and the team's fix plan is half written. The documented fix is a line in the prompt that says the context will be compacted automatically, so the model should not stop early.

Isolating the Sprawl

The guide's phrase is context isolation through subagents or multi-step agentic workflows. The saving comes from keeping the exploration out of the main window entirely, not from summarising it afterwards. The Agent SDK says each subagent "runs in its own conversation, which starts fresh unless the subagent is a fork". A fork is a subagent that inherits the whole conversation. And "either way, intermediate tool calls and results stay inside the subagent; only its final message returns to the parent". A subagent that reads forty files returns a paragraph, and the forty files cost the main conversation nothing. For the team's agent, those could be forty run logs.

python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

async def main():
    async for message in query(
        prompt=(
            "Use the log-triage agent to find the cause of the 03:12 batch failure. "
            "The run log is at /var/log/batch/2026-08-19.log, the failing job is "
            "settle-nightly, and retries are capped at three by policy. Return a "
            "cause, the log lines that support it, and nothing else."
        ),
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Grep", "Glob", "Agent"],
            agents={
                "log-triage": AgentDefinition(
                    description="Reads large log files and returns a single cause with evidence.",
                    prompt="You isolate failures in batch logs. Report one cause and the lines that support it.",
                    tools=["Read", "Grep", "Glob"],
                ),
            },
        ),
    ):
        if hasattr(message, "result"):
            print(message.result)

asyncio.run(main())

Unless the subagent is a fork, its context window "starts fresh, with no parent conversation, but isn't empty". It gets its own system prompt and the Agent tool's prompt string. It does not get the parent's conversation history, tool results or system prompt. So the parent puts every file path, error message and decision that the subagent needs into that prompt. That is why the example above states the log path, the job name and the retry cap that the main conversation already knew. In the other direction, the parent receives only the subagent's final message. Ask for that message in the format that the parent will use.

Isolation is not free. The documented rule for when to use a subagent depends on the kind of work rather than its size. Use a subagent when:

  • the task "produces verbose output you don't need in your main context"
  • you want to enforce specific tool restrictions or permissions
  • the work is self-contained and can return a summary

Stay in the main conversation when:

  • the task needs frequent back-and-forth between you and Claude or iterative refinement
  • multiple phases share significant context
  • you are making a quick, targeted change
  • latency matters, since a subagent that is not a fork starts fresh and may need time to gather context

The log triage fits the first list. Agreeing the week's fixes with the team lead fits the second. The same shape works without subagents as a multi-step workflow. Run the step that produces a long transcript as its own API call with its own message list. Keep the structured result. Start the next step from that result rather than from the transcript that produced it.

Where a Fact Should Live

A transcript records the steps that led to the current state. Memory is the set of facts that must still be true in a later session. Confusing the two produces a common anti-pattern: replaying an entire prior conversation into a new session so the model "remembers". That replay is billed at full input cost for everything that ever happened, to recover a handful of facts. It puts everything into the most crowded possible window, in every session. Next Monday, the team's agent needs only the retry cap and the payment-job rule.

The memory tool is the documented alternative. It is generally available on the Messages API with no beta header. You declare it as a single tools entry, and it is available on all Claude 4 and later models:

python
message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=2048,
    tools=[{"type": "memory_20250818", "name": "memory"}],
    messages=[{"role": "user", "content": "Resume the weekly batch-failure triage."}],
)

The tool is client-side: your code runs it. Claude issues view, create, str_replace, insert, delete and rename commands against paths under a /memories prefix. Your handler maps that prefix onto whatever storage you run and returns the result in a tool_result block. When the tool is present, the API injects its own memory protocol into the system prompt. Its most important line states the design assumption. It reads: "ASSUME INTERRUPTION: Your context window might be reset at any moment, so you risk losing any progress that is not recorded in your memory directory".

The three techniques work together rather than compete. The docs say how: "compaction keeps the active context small without client-side bookkeeping, and memory preserves the information that must survive summarization". Context editing clears specific tool results on the client's behalf. The no-bookkeeping claim is true for threshold compaction. With compaction on demand, the swap is bookkeeping that your code does. For a long-running job, that is the full set of techniques. Prune the payloads that the agent has already used and compact the finished part of the transcript. Write anything that must survive the compaction into memory before the compaction runs. The team's payment-job rule is written into memory before the first compaction.

Ordering Is a Context Decision

When you have a long document and a question about it, put the document first. Put the question and the instructions after it. For inputs over about 20k tokens, the guidance is explicit. It says: "Put long-form data at the top: Place your long documents and inputs near the top of your prompt, above your query, instructions, and examples". It also reports a measurement: "Queries at the end can improve response quality by up to 30 percent in tests, especially with complex, multidocument inputs". The instruction the model acts on is the last thing it read. A question that is followed by 20k tokens of contract competes with everything after it. When the team asks which clauses in a payment provider contract cover failed transfers, the contract goes first.

Two structural refinements go together with the rule. Wrap each document in its own tags with metadata, so that each document in a multi-document input can be referred to on its own:

xml
<documents>
  <document index="1">
    <source>annual_report_2023.pdf</source>
    <document_content>{{ANNUAL_REPORT}}</document_content>
  </document>
  <document index="2">
    <source>competitor_analysis_q2.xlsx</source>
    <document_content>{{COMPETITOR_ANALYSIS}}</document_content>
  </document>
</documents>

Analyse both documents. Identify strategic advantages and recommend Q3 focus areas.

Then base the answer on quotes. Ask Claude to quote the relevant parts of the documents first, before it does the task. Quoting first narrows attention to the passages that matter. It also leaves a string that you can search for in the source. The team gets a quote of the failed-transfer clause before the answer.

In the exam

Questions in this sub-skill test curation decisions (choices about what the context holds and where each part sits) on agentic runs. Half of them depend on the difference between bloat and drift. The bigger-window option appears as a wrong option in every question about a run that degrades. The exam tests one anti-pattern in particular: replaying a prior conversation into a new session so the model "remembers".

Dated current state, 30 September 2026

The type strings, headers and defaults above are current as of this date and have changed form between releases. Remember the mechanism. The exact strings and figures are in the domain quick reference, where a rename is a one-line fix.

Exam Traps

Exam Trap

Answering a slower, pricier, less accurate long run with a bigger context window

A larger window lets more tokens fit, but recall still falls as the window fills. Prune the tool payloads that the agent has already used and compact the finished part of the transcript.

Exam Trap

Offering a pruning answer for wandering behaviour, or a placement answer for a window that will not fit

Wandering behaviour with flat token counts is drift: move the standing rules to a surface that is always present. Rising cost and latency are bloat: prune and compact. A run can show both, so match the fix to the dominant symptom.

Exam Trap

Believing compaction is lossless, so the default summary keeps everything important

Summarised turns stop reaching the model: the API ignores blocks before the compaction block, or your code removes them on demand. A custom instructions string replaces the default prompt, so name the full preservation contract in it, and store lasting constraints in memory.

Exam Trap

Assuming a subagent will receive the standing constraints from the main conversation

A subagent runs in its own conversation, which starts fresh unless it is a fork, and it does not inherit the parent's system prompt. State the constraint in the Agent tool prompt.

Practice Scenario

A food delivery app company runs a long agentic job that plans a week of server maintenance across sixty database clusters. At step forty the code that runs the job compacts the transcript at a token threshold and the run continues from the summary. Three steps later the agent schedules a shutdown of cluster 12. At step nine it had itself recorded cluster 12 as a protected cluster that holds live order payments and must never go offline. The compaction summary names the cluster but not the constraint. The company is staying on its current model. Its constraint list for the sixty clusters is far too long to repeat on every step. What happened, and what is the fix?

Build Exercise

Build Exercise

Measure the Window, Then Take Three Things Out of It

Difficulty
45 minutes

What you'll learn

  • How to measure where a context window actually fills up
  • How context editing clears tool results that the agent has already used and what it reports back
  • What a compaction summary keeps by default and what a custom instructions string silently drops
  • How an isolated context changes the cost and the work of passing it what it needs
  1. Build a small agentic loop in Python or TypeScript that calls one real tool at least fifteen times, and log per step: input_tokens, output_tokens, wall time (the elapsed real time), and the character count of the tool result you appended

    Why: You cannot manage the token use of a window you have never measured, and the way the numbers grow from step to step tells you which technique is worth applying.

    You should see: A per-step table where the tool-result column grows far faster than anything else.

  2. Plot the per-step input token count and mark where the tool payloads become larger than the rest of the window. That number is your baseline

    Why: A recorded baseline lets you measure each later change instead of judging it by opinion.

    You should see: A crossover point, usually much earlier than expected.

  3. Add a clear_tool_uses_20250919 strategy with the context-management-2025-06-27 beta header, set trigger and keep deliberately rather than by default, and read context_management.applied_edits back

    Why: Setting the values yourself makes you decide what the defaults otherwise decide for you, and applied_edits is the only reliable record of what was removed.

    You should see: cleared_tool_uses and cleared_input_tokens on the steps where clearing ran, and a flattened input-token curve after it.

  4. Write down the three facts the run must not lose. Then add a compaction strategy, let it run, read the compaction content block, and check whether the summary still contains all three

    Why: The rule is to write the contract before the summary exists; checking it afterwards is the only way to know the default prompt kept what you assumed.

    You should see: A summary that keeps some of your three facts and, very likely, quietly loses one.

  5. Re-run with an explicit instructions string and diff the two summaries, remembering that your string replaces the default prompt rather than adding to it

    Why: The replacement behaviour is the risk: a focused instruction can preserve your three facts while no longer asking for what the default used to keep.

    You should see: Your three facts present, and something the default summary had kept now missing.

  6. Move the half of the job that produces a long transcript into an isolated context, a subagent or a separate API call, and pass it deliberately only what it needs

    Why: You notice the limit on what an isolated context receives only when something that you assumed was shared is not shared.

    You should see: A first attempt that gets something wrong because you never told it, and a returned result the main context cannot use as-is.

  7. Write the facts that must stay true across sessions to a memory store instead of the transcript, restart from an empty window plus that memory, and compare total input tokens and correctness against your baseline

    Why: This is the whole sub-skill in one measurement: which intervention moved which number, and at what cost to correctness.

    You should see: A three-row comparison naming what pruning, compaction and memory each changed.

Sources