Chapter 10: Context Engineering, Output Handling, and Debugging

Learning Objectives

Pre-Reading Check — Context Engineering

1. What does "context" include when practicing context engineering?

2. What is "context rot"?

3. Which technique clears re-fetchable tool output at no inference cost?

4. Which of the three context-management levers is the only one that solves cross-session persistence?

1. Context Engineering

Key Points

Context engineering is the discipline of curating the optimal set of tokens the model sees at inference time — "the smallest possible set of high-signal tokens that maximize the likelihood of some desired outcome." Context is not just the current user message; it is the whole state the model sees. Think of the window as a workbench, not a warehouse: keep it clear of clutter so the tools that matter stay within reach.

Two failure modes threaten a long-running context. Context bloat is the accumulation of low-value tokens; context drift is the subtler wandering of focus from the original task. The root cause of degradation is context rot: as tokens grow, recall drops — and it drops before the hard limit is reached. The transformer's n² pairwise relationships stretch the model's "attention budget," so "more tokens makes agents worse." The defense is progressive disclosure with just-in-time retrieval: keep lightweight identifiers (paths, IDs, URLs) and load content only when needed.

ProblemWhat it isSymptomPrimary remedy
Context bloatAccumulation of low-value tokensRising cost, slower turns, filled windowTool-output pruning / clearing
Context driftFocus wanders from the taskOff-topic answers, forgotten constraintsCompaction; progressive disclosure
Context rotRecall degrades as tokens growMissed facts even below the hard limitKeep the window small; isolate sub-tasks

Anthropic's cookbook documents three complementary levers, applied diagnosis-first. Compaction (compact_20260112) summarizes a conversation nearing the limit and reinitializes the window; it costs inference but preserves decisions, key facts, and task state. You must append the full response.content (including the compaction block) each turn or the state is silently lost. Tool-result clearing (clear_tool_uses_20250919) mechanically replaces old tool_result bodies with a placeholder at no inference cost (a ~67% reduction in Anthropic's example) but invalidates the prompt cache on each firing. The memory tool (memory_20250818) stores notes in external files outside the window — the only lever that persists across sessions.

Figure 10.1: Diagnosis-first decision flow for choosing a context-management lever

flowchart TD A["Window nearing the limit"] --> B{"What is the bottleneck?"} B -->|"Accumulated dialogue and reasoning"| C["Compaction: summarize + reinitialize window"] B -->|"Re-fetchable tool output"| D["Clearing: replace tool_result bodies with placeholder"] B -->|"Cross-session knowledge"| E["Memory tool: store notes in external files"] C --> F["Compose levers together on long-running agents"] D --> F E --> F
TechniqueMechanismInference costCross-session?Default triggerCache impact
Compaction (compact_20260112)Summarize + reinitialize windowYes (summarizer runs)No150K input tokensRewrites history
Clearing (clear_tool_uses_20250919)Replace old tool_result bodiesNoNo100K tokensInvalidates cache on firing
Memory (memory_20250818)Store notes in external filesReads onlyYesN/A (agent-driven)Neutral
[Animation slot: token accumulation across turns, showing how clearing collapses a 128K window to ~43K while keeping the most recent read.]
Post-Reading Check — Context Engineering

1. What does "context" include when practicing context engineering?

2. What is "context rot"?

3. Which technique clears re-fetchable tool output at no inference cost?

4. Which of the three context-management levers is the only one that solves cross-session persistence?

Pre-Reading Check — Context Isolation

1. In a subagent architecture, what does a subagent typically return to the orchestrator?

2. Why does the error reference recommend disabling unused MCP servers before spawning subagents?

3. How should mid-conversation operator instructions be delivered to keep trusted context separate from untrusted content?

4. Which of the following counts as "untrusted context"?

2. Context Isolation

Key Points

Context isolation gives separate parts of a workload separate context windows. The canonical mechanism is a multi-agent architecture: a lead (orchestrator) delegates sub-tasks to subagents, each with its own isolated window. The design property that makes it work is the return contract — a subagent reads many files or runs many tool calls in its own window but returns only a condensed summary, typically 1,000–2,000 tokens. The orchestrator accumulates distilled results, not raw exploration. Many isolated agents outperform one monolithic agent for three reasons: higher signal density, no single window holding everything (countering rot), and parallelism. One caution: subagents inherit every parent MCP tool definition, so disable unused MCP servers before spawning them.

Figure 10.2: Subagent context isolation — deep work in isolated windows, thin summaries returned

flowchart LR O["Orchestrator (lead agent)
accumulates distilled results"] S1["Subagent 1
isolated window"] S2["Subagent 2
isolated window"] S3["Subagent 3
isolated window"] M["Shared memory file"] O -->|"task assignment"| S1 O -->|"task assignment"| S2 O -->|"task assignment"| S3 S1 -->|"1K-2K token summary"| O S2 -->|"1K-2K token summary"| O S3 -->|"1K-2K token summary"| O S1 -.->|"read / write"| M S3 -.->|"read / write"| M

Isolation matters for security too. Trusted context is content you control — your system prompt, tool definitions, operator instructions. Untrusted context is anything from outside your control — a fetched page, an uploaded document, a third-party tool result — which may carry prompt-injection attempts. Deliver mid-conversation operator instructions as a {"role": "system", ...} message: that is a non-spoofable operator channel that injected text cannot forge. The practical rule: treat every tool result, fetched page, and uploaded document as data to reason about, never as instructions to obey — and route untrusted processing into a narrow-tool subagent to limit blast radius.

[Animation slot: orchestrator dispatching parallel subagents, each window filling then collapsing to a thin 1K–2K summary arrow back up.]
Post-Reading Check — Context Isolation

1. In a subagent architecture, what does a subagent typically return to the orchestrator?

2. Why does the error reference recommend disabling unused MCP servers before spawning subagents?

3. How should mid-conversation operator instructions be delivered to keep trusted context separate from untrusted content?

4. Which of the following counts as "untrusted context"?

Pre-Reading Check — Output Handling

1. What mechanism makes structured outputs guarantee schema-valid responses?

2. Which two requirements must every object in a strict-output schema satisfy?

3. Under defensive parsing, what should you always do before touching content?

4. What does a valid JSON schema guarantee about the answer?

5. Which technique most directly reduces fabricated information?

3. Output Handling

Key Points

Structured output constrains Claude's responses to a JSON schema through two features: JSON outputs (output_config.format) control what Claude says, and strict tool use (strict: true) validates how Claude calls your functions. Both rely on grammar-constrained sampling — the schema is compiled into a grammar that actively prevents emitting a schema-breaking token, unlike merely prompting "please return valid JSON." Every object must set "additionalProperties": false and list every property in required. Not supported: recursion, numeric constraints (minimum/maximum), string-length constraints, and complex regex. Note that strict: true is a sibling of name/description/input_schema on the tool — not on tool_choice.

Grammar-constrained sampling removes most parse failures — not all. Defensive parsing never assumes the schema held. Three cases matter: refusals (stop_reason: "refusal", content may be empty/partial), truncation (stop_reason: "max_tokens", output incomplete), and enum casing differences. The rule: always branch on stop_reason before touching content, and catch typed exceptions (e.g., anthropic.RateLimitError) rather than string-matching messages.

Figure 10.3: Defensive parsing — branch on stop_reason before touching content

flowchart TD A["Response received"] --> B{"stop_reason?"} B -->|"refusal"| C["handle_refusal(stop_details)
do not parse — content may be empty/partial"] B -->|"max_tokens"| D["handle_truncation()
incomplete — retry with more tokens or stream"] B -->|"other"| E["data = response.parsed_output
safe to consume"] E --> F["Validate parsed object
fallback path if schema did not hold"]

Structured, well-formed output can still be wrong. A schema guarantees the shape of an answer, never its truth — a beautifully typeset invoice is not proof the amounts are correct. Five techniques reduce and audit hallucination: (1) allow uncertainty (permit "I don't know," which alone drastically reduces fabrication); (2) ground in direct quotes for long documents; (3) verify with citations (the Citations API ties responses to source chunks); (4) chain-of-thought verification; and (5) best-of-N verification. Cross-check load-bearing numbers, dates, and citations against trusted sources.

[Animation slot: token-by-token generation with a grammar mask graying out invalid next-tokens, then a stop_reason branch splitting into refusal / max_tokens / safe-to-parse paths.]
Post-Reading Check — Output Handling

1. What mechanism makes structured outputs guarantee schema-valid responses?

2. Which two requirements must every object in a strict-output schema satisfy?

3. Under defensive parsing, what should you always do before touching content?

4. What does a valid JSON schema guarantee about the answer?

5. Which technique most directly reduces fabricated information?

Pre-Reading Check — Debugging and Error Handling

1. A 429 rate_limit_error and a 529 overloaded_error differ primarily in that:

2. Which artifact is most important for tracing a failed request end to end?

3. Which errors do the official SDKs automatically retry with exponential backoff?

4. Responses "seem off" but there is no error code. What is the most likely cause?

5. When a response goes wrong in Claude Code, why is rewinding preferred over correcting in-thread?

6. A request fails with a 404 not_found_error. What is the most likely cause and correct response?

4. Debugging and Error Handling

Key Points

When a request fails, first identify what kind of failure it is. The API returns a JSON error object with a type and message, plus a request_id, mapped onto HTTP status codes. Recovery follows the "retryable?" column: non-retryable 4xx errors are yours to fix; retryable errors (429, 5xx, 529) call for exponential backoff, which the official SDKs do automatically (twice by default, honoring retry-after).

CodeError typeRetryable?Common cause
400invalid_request_errorNoMalformed/invalid request
401authentication_errorNoAPI key problem
402billing_errorNoBilling/payment issue
403permission_errorNoKey lacks permission
404not_found_errorNoResource not found (often a typo'd model ID)
409conflict_errorYesConflicts with resource state — resolve and retry
413request_too_largeNoExceeds byte limits
429rate_limit_errorYesAccount hit a rate limit
500api_errorYesUnexpected internal error
504timeout_errorYesRequest timed out — use streaming
529overloaded_errorYesAPI temporarily overloaded across all users

Figure 10.4: Error recovery decision tree driven by the retryable column

flowchart TD A["Request fails with error type + status code"] --> B{"Retryable?"} B -->|"No — 400/401/402/403/404/413"| C["Fix in your code or configuration
(correct request, valid key, model ID)"] B -->|"Yes — 409"| D["Resolve resource-state conflict, then retry"] B -->|"Yes — 429/500/504/529"| E["Exponential backoff, honor retry-after"] E --> F{"Custom behavior needed?"} F -->|"No"| G["SDK auto-retries (twice by default)"] F -->|"Yes"| H["Configure max_retries / custom logic"]

Trace analysis reconstructs what happened from a request's artifacts. The most important is the request-id header (e.g., req_018EeWyXxfu5pfWkrYcMdjWG), mirrored as request_id in error bodies and exposed in Python/TypeScript as _request_id. Log it on every failure and include it in support tickets. Catch typed exceptions most-specific-first (e.g., NotFoundError before APIStatusError). Context-state diagnostics like /context tell you why a window is bloated so you can pick a remedy (/compact, /clear, /mcp disable).

The most important judgment is where a problem lives. The 429 vs. 529 distinction is the sharpest expression: a 429 is your responsibility (tier limit or a usage spike — honor retry-after, ramp traffic gradually); a 529 is not your fault (Anthropic-wide capacity, usually resolving in ~30–300 seconds — retry with jitter, a ~60s circuit breaker, and model fallback). A third origin — quality degradation with no error — is almost always conversation state; Claude Code does not silently change models.

SignalOriginWhose faultRecovery
429 rate_limit_errorIntegration layerYours (tier/spike)Honor retry-after; ramp traffic gradually
529 overloaded_errorProvider sideAnthropic capacityBounded retry + jitter, circuit breaker, model fallback
"Off but no error"Model output / stateUsually conversation state/context, /compact, /clear, /model, /effort, /rewind

Figure 10.5: Isolating the origin of a failure — integration-layer vs. provider-side vs. model-output

flowchart TD A["Something went wrong"] --> B{"Is there an error code?"} B -->|"4xx (except 409)"| C["Integration-layer — yours to fix
429: honor retry-after, ramp traffic"] B -->|"5xx or 529"| D["Provider-side — Anthropic's
retry w/ jitter, circuit breaker, /model fallback, check status page"] B -->|"No error, output off"| E["Model-output / conversation state"] E --> F["/context, /compact, /clear"] E --> G["/model, /effort"] E --> H["/rewind — remove bad turn instead of correcting in-thread"]

One recovery technique deserves emphasis: when a response goes wrong, rewinding (Esc twice or /rewind) beats correcting in-thread, because correcting keeps the wrong attempt in context and anchors later answers to it. Claude Code retries transient failures up to 10 times with exponential backoff, but deliberately does not retry TLS certificate-validation failures or server errors that arrive after visible output has streamed (a retry could double-execute tools).

[Animation slot: a failure flowing through the isolation checklist — error-code gate branching to integration-layer / provider-side / model-state, with 429 and 529 highlighted on opposite sides.]
Post-Reading Check — Debugging and Error Handling

1. A 429 rate_limit_error and a 529 overloaded_error differ primarily in that:

2. Which artifact is most important for tracing a failed request end to end?

3. Which errors do the official SDKs automatically retry with exponential backoff?

4. Responses "seem off" but there is no error code. What is the most likely cause?

5. When a response goes wrong in Claude Code, why is rewinding preferred over correcting in-thread?

6. A request fails with a 404 not_found_error. What is the most likely cause and correct response?

Your Progress

Answer Explanations