Start Here

CCAR-F: 60 scenario items (multiple-choice and "select N"), 120 minutes, 4 scenarios drawn from 6, pass at 720 on a 100–1,000 scale. In this reader you attempt an item first, rate your confidence, fix what you got wrong, and read only after that.

What to do, in order

The sidebar is in the same order, and so is Next at the bottom of each page.

  1. Take the Pretest in Drill before you study anything (20 min). It is the first tab. Answer all 12 official samples and rate each answer H, M or L.
  2. Read the Exam Overview, especially the out-of-scope list, because out-of-scope options are free eliminations (30 min). Then read §6 of the official PDF once, straight through.
  3. Open the 3-Day Plan and follow the day you are on. Steps 4 to 6 are what it schedules.
  4. Work through the domains from D1 to D5. For each one, run Drill → Domain focus, read the domain page or book chapter only for what you missed, then fill that domain's rows in Trap Pairs. Run Relearn that evening and again the next morning.
  5. From the evening of Day 2, run Mixed set across the domains you have covered and use the Dashboard to pick what to fix. On the last day, sit a Timed mock or two Half mocks.
  6. The evening before the exam, read the Quick Reference once and go through Exam Day. Don't start new material in the last 12–24 hours.

With only one day, follow If you only have one day instead. If you are short on time, do only the ★ blocks in the plan.

The method in five rules

# Rule Why Evidence
1 Answer before you read. Attempt every item and topic before studying it, then check the key. Retrieval beats rereading, and it transfers to application questions. Yang et al. 2021, g = 0.50, 222 studies; Pan & Rickard 2018, d = 0.40
2 Rate confidence (H/M/L) before the reveal. Your sense of knowing an answer is unreliable. Recording it lets you check it against results. Bjork et al. 2013; Butterfield & Metcalfe 2001
3 Fix confident errors first. A high-confidence miss means you hold a false belief. Those errors are quick to correct and expensive to leave. Hypercorrection effect (Butterfield & Metcalfe 2001)
4 Space and interleave. Revisit each domain about a day later. Mix domains once each is familiar. A gap of about a day beats massed study, and mixing practises telling similar questions apart. Cepeda et al. 2006/2008; Mawson & Kang 2025, d = 0.54; Brunmair & Richter 2019, g = 0.42
5 Sleep 7–8 h, every night. Lost sleep costs more than the extra study time gains. Newbury et al. 2021

The daily-user trap

Using Claude Code every day makes the material feel familiar, and familiarity makes it easy to overrate how ready you are.

  • The exam tests what sits underneath the product: the agent loop, the Batch API, structured-output validation, MCP error semantics.
  • The exam also asks for tradeoffs: it gives you several workable fixes and asks which is the best first one.
  • Pick what to study from the Dashboard priority (weight × average danger), not from how ready you feel.
  • Practice feels harder than rereading, but that doesn't mean it works worse (Deslauriers et al. 2019).
  • Walkthroughs of things you already know are wasted time (expertise reversal).

Find a page by need

Need Go to
Find out where you stand, before studying Drill → Pretest
Facts, scenarios, out-of-scope list Exam Overview
Day-by-day schedule 3-Day Plan
Learn a domain or task statement for the first time Domain focus, then the matching domain page or book chapter for what you missed
Domain content D1 · D2 · D3 · D4 · D5
Tell look-alike decisions apart Trap Pairs
Re-answer everything you missed or guessed Relearn
Free-recall the 30 task statements and 6 scenarios Recall cards
Practise across domains, weighted to weak spots Mixed set (10/15/20)
See what to fix next, export the error log Dashboard
Rehearse pacing Timed mock (60/120 min) or Half mock (30/60)
Last-hour lookup Quick Reference
Exam logistics, pacing, tactics Exam Day
Use Claude as a tutor, without it doing the thinking Study with Claude
Extra items and outside resources Practice Questions · Resources

Don't bother

  • Rereading and highlighting have low utility (Dunlosky et al. 2013).
  • Learning styles have no supporting evidence.
  • Expressive writing before the exam has no effect on test anxiety (O'Meara & Lovett 2026).
  • Pomodoro as a learning technique has no controlled advantage.
  • A new Anki/FSRS deck this week is not worth it, because its benefit shows over long horizons.
  • The "AI rots your brain" EEG preprint (Kosmyna et al.) is small and not peer reviewed. The better-supported finding is narrower: unrestricted AI access harmed later unaided performance, a hint-only tutor did not (Bastani et al. 2025). Attempt first, then ask.

Drill

Answer first, then rate how sure you are, then read the rationale for every option, not only the key. Your confidence counts as much as the answer, and a confident miss gets the highest danger score. Misses come back in Relearn, and a topic only counts as secure after two confident, correct answers at least 8 hours apart. Progress is stored in this browser only; use Export JSON on the Dashboard to move it between laptop and phone.

Exam Overview — Claude Certified Architect – Foundations (CCAR-F)

This page comes from the official Exam Guide v1.0, effective July 2026 ("the exam guide" below), except logistics taken from the live Partner Academy FAQ, tagged (Partner Academy FAQ, checked 2026-10-21). Anything else that comes from elsewhere, or is interpretation, is marked (not in the exam guide).

Key facts

Credential Claude Certified Architect – Foundations
Exam code CCAR-F (community shorthand CCA-F; not in the exam guide)
Items 60
Item format Multiple-choice and multiple-response. Each item states how many responses to select
Structure 4 scenarios drawn from a bank of 6
Time 120 minutes of testing; ~135 minutes total seat time including check-in, instructions and survey (Partner Academy FAQ, checked 2026-10-21)
Delivery Proctored (online proctored and/or Pearson VUE test center)
Languages English, Japanese (Partner Academy FAQ, checked 2026-10-21)
Passing score 720 on a scaled range of 100–1,000
Fee $125 USD per attempt, up from $99 on 2026-06-30; partner-tier discounts apply at checkout (Partner Academy FAQ, checked 2026-10-21)
Validity 12 months from award
Result reporting Pass/fail with scaled score, plus percent-correct by domain (diagnostic only; the pass decision is on total scaled score). Score shown on screen at the end (Partner Academy FAQ, checked 2026-10-21)
Practice exam None official. The old one was retired in the move to Pearson; the only official sample questions are the 12 in the exam guide (Partner Academy FAQ, checked 2026-10-21)

Scoring is criterion-referenced: you are measured against a fixed standard set by a formal standard-setting study, not against other candidates. 720 is a scaled score, not 72% correct, so you can't work out how many questions you can afford to miss.

Version note

v1.0 is still the current guide as of 2026-10-21. The official PDF was uploaded 2026-07-08 and there is no v1.1 or v2. Guides are subject to change, so re-check the PDF before booking.

The v0.2 draft (June 2026) and v1.0 (July 2026) have the same domains, the same 30 task statements and the same 12 sample questions. Only the surrounding material changed. The differences that affect you are:

  • v0.2 said "All questions are multiple choice… one correct response and three incorrect." v1.0 says multiple-choice and multiple-response. Expect some "select two" items, and read the instruction line on every question.
  • v1.0 adds the policy sections (retakes, ID, NDA, appeals, recertification) that v0.2 lacked. It also introduces the exam code CCAR-F and adds percent-correct by domain to the score report; v0.2 reported pass/fail only.

Blueprint

# Domain Weight ≈ items of 60
1 Agentic Architecture & Orchestration 27% ~16
2 Tool Design & MCP Integration 18% ~11
3 Claude Code Configuration & Workflows 20% ~12
4 Prompt Engineering & Structured Output 20% ~12
5 Context Management & Reliability 15% ~9

Domains 1, 3 and 4 together are two-thirds of the exam.

The six scenarios (four appear on your form)

You can't choose which four you get, so study all six.

1. Customer Support Resolution Agent. A support agent built with the Claude Agent SDK handling high-ambiguity requests (returns, billing disputes, account issues) through custom MCP tools: get_customer, lookup_order, process_refund, escalate_to_human. Target is 80%+ first-contact resolution while knowing when to escalate. Primary domains: 1, 2, 5.

2. Code Generation with Claude Code. A team using Claude Code for generation, refactoring, debugging and documentation, integrating it with custom slash commands and CLAUDE.md, and deciding plan mode vs direct execution. Primary domains: 3, 5.

3. Multi-Agent Research System. A coordinator delegating to four specialized subagents (web search, document analysis, synthesis, report generation), producing comprehensive cited reports. Primary domains: 1, 2, 5.

4. Developer Productivity with Claude. An Agent SDK agent helping engineers explore unfamiliar codebases, understand legacy systems, generate boilerplate and automate repetitive tasks, using built-in tools (Read, Write, Bash, Grep, Glob) plus MCP servers. Primary domains: 2, 3, 1.

5. Claude Code for Continuous Integration. Claude Code in a CI/CD pipeline running automated code reviews, generating test cases and giving PR feedback, with a stated emphasis on actionable feedback and minimizing false positives. Primary domains: 3, 4.

6. Structured Data Extraction. Extracting information from unstructured documents, validating output with JSON schemas, maintaining high accuracy, handling edge cases gracefully, integrating downstream. Primary domains: 4, 5.

The scenarios are different settings for the same five domains.

Out of scope

The exam guide excludes the topics below. None of them is tested, even though Anthropic's docs and community study guides cover them:

Fine-tuning or training custom models · Claude API authentication, billing, or account management · language- and framework-specific implementation detail beyond what tool and schema configuration needs · deploying or hosting MCP servers (infrastructure, networking, containers) · Claude's internal architecture, training, or weights · Constitutional AI, RLHF, safety training · embedding models and vector database internals · computer use (browser/desktop automation) · vision and image analysis · streaming API implementation and server-sent events · rate limiting, quotas, pricing calculations · prompt caching implementation details beyond knowing it exists · token counting algorithms and tokenization · OAuth, API key rotation, authentication protocols · specific cloud provider configuration (AWS, GCP, Azure) · performance benchmarking and model comparison metrics.

Community prep material often drills prompt-cache TTLs, 429 handling, pause_turn and MCP transport specs. None of that is on this exam (inference; not in the exam guide).

In-scope topics (the exam guide's own summary)

Agentic loop implementation (control flow on stop_reason, tool result handling, termination) · multi-agent orchestration (coordinator-subagent, decomposition, parallel execution, iterative refinement) · subagent context management (explicit passing, structured state persistence, crash recovery via manifests) · tool interface design (descriptions, splitting vs consolidating, naming) · MCP tool and resource design · MCP server configuration (project vs user scope, env var expansion, multi-server access) · error handling and propagation (structured responses, transient vs business vs permission, local recovery before escalation) · escalation decision-making · CLAUDE.md configuration (hierarchy, @import, .claude/rules/ glob patterns) · custom commands and skills (scope, context: fork, allowed-tools, argument-hint) · plan mode vs direct execution · iterative refinement (I/O examples, test-driven iteration, interview pattern) · structured output via tool_use (schema design, tool_choice, nullable fields) · few-shot prompting · batch processing · context window optimization · human review workflows (confidence calibration, stratified sampling, accuracy segmentation) · information provenance.

Appendix: technologies named in the exam guide

The exam guide lists the specific technologies questions may refer to. Recognize each by name:

  • Claude Agent SDK: agent definitions, agentic loops, stop_reason handling, hooks (PostToolUse, tool call interception), subagent spawning via the Task tool, allowedTools configuration
  • MCP: servers, tools, resources, the isError flag, tool descriptions, tool distribution, .mcp.json, environment variable expansion
  • Claude Code: CLAUDE.md hierarchy (user/project/directory), .claude/rules/ with YAML frontmatter path scoping, .claude/commands/, .claude/skills/ with SKILL.md frontmatter (context: fork, allowed-tools, argument-hint), plan mode, direct execution, /memory, /compact, --resume, fork_session, the Explore subagent
  • Claude Code CLI: -p / --print, --output-format json, --json-schema
  • Claude API: tool_use with JSON schemas, tool_choice (auto, any, forced), stop_reason values (tool_use, end_turn), max_tokens, system prompts
  • Message Batches API: 50% cost savings, up to 24-hour window, custom_id, polling, no multi-turn tool calling
  • JSON Schema: required vs optional, enums, nullable, "other" + detail string, strict mode
  • Pydantic: schema validation, semantic validation errors, validation-retry loops
  • Built-in tools: Read, Write, Edit, Bash, Grep, Glob
  • Few-shot prompting, prompt chaining, context window management, session management, confidence scoring

Current product (not tested): this list reflects the exam guide, not the full current API and product. The domain pages mark where the product has moved on, e.g. the Task tool's current SDK name, extra stop_reason and tool_choice values, and a third MCP scope.

Intended audience

The exam guide describes the ideal candidate as a solution architect with 6+ months of hands-on experience with the Claude API, Agent SDK, Claude Code and MCP. No experience prerequisite is stated (inference), and eligibility is covered under Logistics.

Logistics and policies

Registration runs through the Anthropic Partner Academy; you then create a Pearson VUE account and schedule. The fee at checkout reflects any discount that applies to your partner tier.

Eligibility. Partner-only: you must belong to a Claude Partner Network organization, register with a company email on the partner domain (personal emails are rejected), and be 18+ with government ID. Non-partners can take Academy courses, which give a completion certificate, not the certification. (Partner Academy FAQ, checked 2026-10-21)

Cancel or reschedule at least 48 hours before your appointment (Partner Academy FAQ, checked 2026-10-21). The exam guide PDF says 24 hours; the FAQ is newer, so plan on 48. Inside the window you can no longer cancel or reschedule, and the exam guide says a late change or no-show forfeits the fee.

ID. Valid, unexpired, government-issued photo ID. The name on it must match your registration exactly. Name corrections go to [email protected] before scheduling.

Retakes. 14 days after a first fail, 30 days after a second, 90 days after a third. Maximum four attempts per rolling twelve months, per exam. You pay the full fee each time. (Confirmed by the Partner Academy FAQ, checked 2026-10-21.)

Conduct. Stay in view of the webcam if testing online; workspace clear of notes, books, phones, secondary monitors; no communication with anyone; no capturing exam content. Prohibited: phones, smart watches, headphones, study materials, recording devices. Scratch paper only if the proctor provides it.

NDA. You accept a confidentiality agreement before the exam starts. Decline and the session ends with no refund.

Recertification. On-time renewal is a free, non-proctored assessment on the Partner Academy. Let it lapse and you retake the full exam at full price. Validity is 12 months; certifications earned before 2026-06-30 under the old 6-month rule were extended to 12. (Partner Academy FAQ, checked 2026-10-21)

Appeals within 14 days, via Pearson VUE support. The standard-setting outcome and individual item content are not appealable.

Accommodations must be requested and approved by Pearson VUE before you schedule, 10 or more days ahead of the exam (Partner Academy FAQ, checked 2026-10-21).

Claims to ignore

Community material repeats these; none matches the exam guide or the FAQ.

Claim Correct value
77 questions 60 items
"4 of 8" or "13" scenarios 4 of 6
Single-answer only ("1 of 4") Multiple-choice and multiple-response
$99 fee $125 since 2026-06-30 (partner discounts apply)
ProctorFree delivery, one attempt only Pearson OnVUE or test center; up to 4 attempts per rolling 12 months (earlier platform)
6-month retake cool-off 14 / 30 / 90 days after the 1st / 2nd / 3rd fail
Results in 2 or 10 days Score on screen at the end

Other Claude certifications

Context only; none is a prerequisite of another, and this guide covers only CCAR-F (Anthropic blog, 2026-07-23; not in the exam guide).

  • Claude Certified Associate: Foundations, $99
  • Claude Certified Developer: Foundations, $125
  • Claude Certified Architect: Foundations (this exam), $125
  • Claude Certified Architect: Professional, $175

Three-Day Study Plan

Plan on 5–6 hours a day. With less, do only the ★ blocks. Modes are on the Drill page.

Standing rules:

  • Rate H/M/L before every reveal. Log every miss and every shaky answer.
  • Fixed bedtime for 7–8 h of sleep, all three nights.
  • Study a domain on its own first. Mix only after it is familiar.
  • Read a domain page or book chapter only for what Domain focus shows you missed.

Day 1: Baseline, map, Domain 1

Time Block Where Stop rule
20 min ★ Baseline. Answer all 12 official samples without studying. Drill → Pretest All 12 answered and rated. Don't peek at the explanations early.
30 min ★ Read the overview and the out-of-scope list. Out-of-scope topics are free eliminations. Exam Overview You can name the 5 domains with weights and the 6 scenarios.
30 min ★ Read §6 of the official PDF (Detailed Objectives) once, straight through. Official exam guide One pass. Don't stop to study.
2 h ★ Domain 1 (27%). Domain focus for D1, then read the page or book chapter only for the misses. Expect to be wrong on anti-patterns. Drill → Domain focus; D1 Last D1 set at 80% or more with no H-wrong answers, or the time is up.
45 min ★ Contrast drills for D1: prompt instruction vs hook, tool_choice vs gate, and the rest of the rows. Trap Pairs (D1 rows) Every D1 row filled from memory, then checked.
30 min ★ Evening: Relearn today's misses, then Recall cards for the D1 task statements (1.1–1.7). Drill → Relearn, Recall cards Every Relearn item correct once, then stop for the day.
10 min Optional: skim §8 Preparation Exercises 1 and 4 (they map to scenarios 1 and 3). Official exam guide

End of day, without looking: the five domains and weights, the six scenarios, what terminates an agent loop, why subagents start with no context, when a hook beats a prompt.

Day 2: Domains 2, 3, 4

Time Block Where Stop rule
15 min ★ First spaced pass, about a day after the first attempt: re-answer yesterday's misses and shaky items. Drill → Relearn Every item correct once.

Then run this loop for each of Domain 2, 3 and 4 (about 1.5 h each; ★):

Step Time Where Stop rule
1. Domain focus set 20 min Domain focus Set done, every answer rated.
2. Read only the weak spots 25 min Domain page or book chapter (D2, D3, D4) Only task statements you missed. Skip the rest.
3. Trap Pairs rows 20 min Trap Pairs Rows filled from memory, then checked.
4. Blank-page concept map, then explain one aloud 20 min Paper Map drawn from memory (self-generated maps: g = 0.72 vs 0.43 for studying a given map; Schroeder et al. 2018). Explain it aloud to a skeptical colleague. Stop when you find a gap, fix it, redo that branch.
5. Second Domain focus set 10 min Domain focus Ends the loop.

Domain-specific notes:

  • D2: descriptions are the primary selection mechanism and usually the correct first fix. Four error categories; only transient is retryable.
  • D3: mostly which file, which directory, which flag. Write the file map from the Quick Reference from memory.
  • D4: syntax vs semantics (a schema fixes malformed JSON, not wrong values). Batch API as one set: 50% · up to 24 h · no SLA · no multi-turn tool calling · custom_id. Batch is decided by how long the result can wait, not by cost.
Time Block Where Stop rule
40 min ★ Evening Mixed set across D1–D4 (20 items, weighted to weak spots). Drill → Mixed set All items answered, rated, explanations read.
15 min ★ Triage. Rank domains by priority = weight × average danger. Write tomorrow's repair order. Drill → Dashboard Ranked list written. Then bed on time.

Day 3: Domain 5, synthesis, then stop

Time Block Where Stop rule
30 min ★ Relearn to zero: every miss and shaky item. Drill → Relearn Each correct once, then stop.
1.5 h ★ Domain 5 (15%). Domain focus, weak spots only, Trap Pairs D5 rows. Focus: three valid escalation triggers, four-part error report, provenance checklist. Domain focus; D5 Last set at 80% or more, no H-wrong.
40 min ★ Scenario recall: free-recall each of the 6 scenarios (what it is, primary domains, three likeliest failures), self-graded. Drill → Recall cards (scenarios) All six graded. Re-read only the ones you failed.
2 h ★ Timed mock as two Half mocks (30 items / 60 min each), a few hours apart. Use the exam routine from Exam Day: instruction line, deciding phrase, rule-based elimination. Drill → Timed mock (Half mock) Hard stop at 60 min each. Log every miss and shaky item.
1.5 h ★ Weak-spot repair by Dashboard priority (weight × danger), not lowest percent. Confident-wrong items first. Dashboard, Domain focus, Trap Pairs Time boxed per domain; move on when it ends.
30 min ★ Evening: Relearn confident-wrong items only, then read the Quick Reference once. Relearn; Quick Reference No new material in the last 12–24 h. Then logistics from Exam Day and sleep.

If you have a spare 2 hours, do one full Timed mock (60 items / 120 min) before the evening instead of the two Half mocks.

If you only have one day

  1. Pretest before any study (20 min). Rate everything.
  2. Exam Overview, especially the out-of-scope list (25 min).
  3. Domain focus D1, then D3 (D1 and D3 are 47% of the blueprint), reading only the weak spots (about 2 h each with the Trap Pairs rows).
  4. Mixed set of 20 items, then Relearn to zero (1 h).
  5. Half mock (30 items / 60 min) if time allows.
  6. Quick Reference, then sleep.

Using Claude as a tutor

Attempt first, then have Claude grade you and ask you questions. Don't ask it for answers. Prompts and rules are in Study with Claude. Stop generating new items in the last 24 hours.

Domain 1 — Agentic Architecture & Orchestration (27%, ~16 items)

Most questions describe a production system that misbehaves and ask you to pick the fix. Two principles settle most of them.

  • When a mistake has real consequences (money, compliance, security), enforce the rule in code, not in the prompt. A prompt instruction is followed most of the time; code is followed every time.
  • Don't add machinery the failure doesn't need. If a small fix addresses the root cause, the bigger one is wrong.

In the book: Chapter 2 — The Agentic Loop (1.1) · Chapter 5 — Orchestration (1.2, 1.3) · Chapter 6 — Hooks & Gates (1.4, 1.5) · Chapter 1 — Agents & Workflows (1.6) · Chapter 7 — Sessions (1.7)


1.1 Design and implement agentic loops

An agentic loop is the code that sends a request to Claude, runs any tools Claude asks for, sends the results back, and repeats until Claude is done. Claude Code runs this loop every time you press enter; on the exam you are the one writing it.

The loop

1. POST /v1/messages  →  system, messages[], tools[]
2. Read response.stop_reason
3. If "tool_use":
     append the assistant message (with its tool_use blocks) to messages
     execute each requested tool
     append a USER message containing tool_result block(s)
     go to 1
4. If "end_turn": done — present the final response

The exam guide makes these points about the loop:

  • stop_reason controls the loop. "tool_use" means Claude wants a tool run, so continue. "end_turn" means Claude has finished, so stop.
  • Tool results are appended to conversation history. That history is the only place the model can see what its tools returned, so it is how the agent builds up knowledge across steps.
  • Model-driven decision-making: Claude reasons from context about which tool to call next, which is what makes the design agentic. A pre-configured decision tree or a fixed tool sequence is a different design.

The three anti-patterns

The exam guide names three wrong ways to decide the loop is finished. Expect them as distractors:

  1. Parsing natural language signals to determine loop termination: searching the response text for "done" or "I've finished."
  2. Setting arbitrary iteration caps as the primary stopping mechanism. A cap protects against a runaway loop. It does not tell you the task is complete.
  3. Checking for assistant text content as a completion indicator. A response with stop_reason: "tool_use" often contains text too, because Claude frequently says what it is about to do before calling a tool.

Any option that decides completion by something other than stop_reason is wrong.

Current product (not tested): the full current set is end_turn, max_tokens, stop_sequence, tool_use, pause_turn, refusal and model_context_window_exceeded. The exam guide names only tool_use and end_turn.


1.2 Orchestrate multi-agent systems with coordinator-subagent patterns

Hub and spoke

In a hub-and-spoke design, one coordinator agent sits in the middle and every subagent talks only to it, so the coordinator handles all communication and routing between them.

The exam guide gives three reasons for this: observability (one place to see what happened), consistent error handling, and controlled information flow (each subagent gets exactly what the coordinator chooses to give it).

The coordinator's job:

  • break the task into pieces (decomposition)
  • hand each piece to a subagent (delegation)
  • combine the results (aggregation)
  • deciding which subagents to invoke based on query complexity, rather than running the whole pipeline every time

Subagents have isolated context. A subagent does not see the coordinator's conversation history. It knows only what the coordinator puts in its prompt. Questions in Domains 1 and 5 depend on this fact.

Failure mode: decomposition that is too narrow

The exam guide warns that a coordinator can split a broad topic too narrowly, so the subagents between them cover only part of it. Sample question 7 shows this: every subagent succeeds, yet the report on "creative industries" misses most of the topic, because the coordinator split it into three visual-arts subtasks.

When every downstream agent worked correctly but the overall coverage is wrong, the cause is the coordinator's decomposition.

Skills tested

  • The coordinator should analyze query requirements and dynamically select subagents, instead of always running the full pipeline.
  • Partition the scope so subagents don't duplicate each other, for example by giving each one distinct subtopics or distinct source types.
  • Iterative refinement loops: the coordinator reviews the synthesis for gaps, sends targeted follow-up queries to the search and analysis subagents, runs synthesis again, and repeats until coverage is sufficient.

1.3 Configure subagent invocation, context passing, and spawning

Concept Detail
How subagents are spawned The coordinator calls the Task tool
Requirement The coordinator's allowedTools must include "Task", or it cannot spawn subagents
Context Must be explicitly provided in the prompt of the subagent. Subagents do not inherit the parent's context and do not share memory between invocations
Configuration An AgentDefinition per subagent type: its description, system prompt and allowed tools
Branching Fork-based session management lets you explore different approaches from the same starting analysis

Skills tested

  • Put the complete findings from earlier agents directly into the subagent's prompt. If the synthesis subagent needs the web search results and the document analysis, the coordinator pastes both in. A prompt that says "analyze the findings" without including them is a common wrong answer.
  • Pass context as structured data that keeps content separate from metadata (source URLs, document names, page numbers), so each claim can still be traced to its source after the handoff.
  • To run subagents in parallel, emit several Task tool calls in one coordinator response. One Task call per turn runs them one after another.
  • Give subagents goals and quality criteria, not step-by-step procedures, so they can adapt to what they find.

Quick diagnostics:

Question Answer
Why doesn't the coordinator spawn subagents? allowedTools is missing "Task"
Why is the synthesis subagent's output generic? The findings weren't in its prompt
How do I run subagents in parallel? Several Task calls in one response

Current product (not tested): the current Claude Agent SDK calls this tool Agent, and keeps Task as an alias. The exam guide says Task throughout, so answer with Task.

The SDK can also resume a specific subagent's own transcript (capture its session_id/agentId and pass resume: sessionId), instead of pasting prior findings into a fresh prompt. The exam guide's mechanism is still "provide the context explicitly in the prompt."


1.4 Multi-step workflows with enforcement and handoff

Programmatic enforcement vs prompt guidance

Programmatic enforcement means a rule is checked by code; prompt guidance means the model is asked to follow it. The exam guide puts it this way: "When deterministic compliance is required (e.g., identity verification before financial operations), prompt instructions alone have a non-zero failure rate." A system prompt rule can be skipped. If skipping it costs money or breaks compliance, it needs a check in code.

A prerequisite gate is such a check. Your code refuses to execute a tool call until an earlier step has completed. The exam guide's example is to block process_refund until get_customer has returned a verified customer ID. Sample question 1 tests the same idea; its correct option gates both lookup_order and process_refund on identity verification.

Sample question 1's explanation also says why the other options fail:

  • A stronger system prompt (B) and few-shot examples (C) both "rely on probabilistic LLM compliance, which is insufficient when errors have financial consequences."
  • A routing classifier (D) "addresses tool availability rather than tool ordering, which is not the actual problem."

Enforcement strength (community heuristic, not in the exam guide): tool-internal check or programmatic gate/hook > tool_choice for a single turn > prompt instruction. This applies to compliance stems (must-never rules, money, ordering guarantees); for quality or tool-routing stems the exam rewards better descriptions and prompts first (sample question 2 prefers improved tool descriptions over a routing layer).

Structured handoff to a human

When the agent escalates mid-task to a human who cannot see the conversation transcript, it should send a structured summary: customer ID, root cause, refund amount, recommended action. A raw transcript dump makes the human redo the agent's work.

Requests with several concerns

When a customer raises several issues in one message, split them into distinct items, investigate each in parallel using shared context, then combine the results into a unified resolution. Handling the first issue and asking the customer to open a second ticket is wrong.


1.5 Agent SDK hooks for interception and normalization

A hook is your code, registered with the Agent SDK, that runs automatically at a fixed point in the loop. The model cannot skip it. Claude Code's permission prompt is a similar idea: a check that runs before a tool executes, whatever the model intended.

The exam guide names two uses:

Purpose Hook Example from the exam guide
Change tool results before the model sees them PostToolUse (runs after the tool) Convert the mix of Unix timestamps, ISO 8601 strings and numeric status codes returned by different MCP tools into one format
Block a tool call that breaks policy Tool-call interception, implemented as PreToolUse (runs before the tool; can deny the call or rewrite its input) Block refunds over $500 and redirect to human escalation

The exam guide ties this back to 1.4: it contrasts "using hooks for deterministic guarantees versus relying on prompt instructions for probabilistic compliance." Use a hook when a business rule must always hold.

Distractors often mix up the two directions. PostToolUse sees results coming back from a tool. Interception sees calls going out. A PostToolUse hook cannot stop a refund, because by the time it runs the refund has already been processed.

The exam also tests the opposite mistake, using a hook for a soft preference such as tone.

Current product (not tested): in the SDK, a PreToolUse hook returns permissionDecision (allow, deny, ask or defer) inside hookSpecificOutput; matchers match tool names.


1.6 Task decomposition strategies

There are two ways to break a task into steps:

Pattern Use when Exam guide's example
Fixed sequential pipeline (prompt chaining): the same steps every time, each feeding the next The steps are known in advance Review each file on its own, then run a cross-file integration pass
Dynamic adaptive decomposition: the agent decides the next subtasks based on what it has found Open-ended investigation where you can't know the steps up front "Add comprehensive tests to a legacy codebase"

Skills tested

  • Use prompt chaining for predictable, multi-aspect reviews. Use dynamic decomposition for open-ended investigation.
  • Split a large code review into one pass per file plus a separate cross-file integration pass. The exam guide's reason is attention dilution: when one pass covers many files, the model gives each one shallower attention and can contradict itself across files. Sample question 12 (a 14-file review that comes back shallow and inconsistent) has this as its answer. Its explanation rejects two alternatives: a bigger context window "doesn't solve attention quality issues," and requiring 2 of 3 independent passes to agree "would actually suppress detection of real bugs."
  • For an open-ended task, first map the structure, then find the high-impact areas, then write a prioritized plan that changes as you discover dependencies.

1.7 Session state, resumption, and forking

Mechanism What it does When to use it
--resume <session-name> Continues a specific earlier named session, with its context The earlier context is still mostly valid
fork_session Creates an independent branch that starts from the same analysis You want to explore divergent approaches from one starting point, such as two testing strategies or two refactorings of the same codebase
Start a new session with a structured summary A fresh session seeded with the key facts The earlier tool results are stale (the files or data have changed)

The exam guide states two judgments outright:

  • "Starting a new session with a structured summary is more reliable than resuming with stale tool results."
  • When you do resume after code has changed, tell the agent which files changed so it re-analyzes those, rather than re-exploring everything.

Current product (not tested): named sessions exist: claude --name/-n <name> (or /rename mid-session) names one, and claude --resume takes an ID or a name. The SDK resume option still takes a session ID. continue / continue_conversation resumes the most recent session in the working directory, with no ID. Watch for options that confuse "most recent, no ID" with "specific, by ID or name".


Domain 1 decision table

Symptom in the question Answer
Loop won't stop, or stops too early Control the loop with stop_reason
Agent skips a mandatory earlier step Prerequisite gate in code, not a prompt instruction
A policy limit is sometimes exceeded Tool-call interception hook
Tool results arrive in inconsistent formats PostToolUse normalization hook
Subagent output is generic or has no sources Put the findings and their metadata in the subagent's prompt
Coordinator can't spawn subagents Add "Task" to allowedTools
Subagents run one after another instead of in parallel Several Task calls in one coordinator response
Report misses whole subtopics, though every subagent succeeded The coordinator's decomposition is too narrow
Large multi-file review is shallow and contradicts itself One pass per file plus a separate integration pass
Need to compare two approaches from one starting analysis fork_session
Resuming after files changed Resume and say what changed; start fresh with a summary if the old results are stale
Escalating to a human who can't see the transcript Structured handoff summary

Things to remember

  • An iteration cap is a safety net, and text in a response does not mean the turn is finished.
  • Subagents inherit nothing: no conversation history, no memory between invocations.

Domain 2 — Tool Design & MCP Integration (18%, ~11 items)

The domain has five task statements. It is about how you design tool interfaces and configure MCP servers, not about the MCP protocol itself. The exam guide's out-of-scope list removes "deploying or hosting MCP servers (infrastructure, networking, container orchestration)" and "OAuth, API key rotation, or authentication protocol details", so there are no questions on transports, OAuth, protocol internals or hosting.

In the book: Chapter 3 — Designing Tools (2.1, 2.3, 2.5) · Chapter 4 — Errors & Recovery (2.2) · Chapter 8 — MCP (2.4)


2.1 Design effective tool interfaces

Descriptions drive tool selection

The model chooses which tool to call mainly by reading the tool descriptions. The exam guide says tool descriptions are the primary mechanism LLMs use for tool selection, and minimal descriptions make selection between similar tools unreliable.

A good description covers:

  • what the tool does and what it returns
  • the input formats it accepts
  • example queries
  • edge cases
  • boundary explanations: when to use this tool rather than a similar one

Failure mode: misrouting

Misrouting is the model calling the wrong one of two similar tools. It happens when descriptions are vague or overlap. The exam guide's example is analyze_content and analyze_document with near-identical descriptions.

Sample question 2 has two tools, get_customer ("Retrieves customer information") and lookup_order ("Retrieves order details"). Both descriptions are minimal and both tools accept similar identifiers, so the agent confuses them. The correct answer is expand the descriptions. The explanation rejects the other options:

  • Few-shot examples "add token overhead without fixing the underlying issue."
  • A routing layer is "over-engineered and bypasses the LLM's natural language understanding."
  • Merging the two tools is "a valid architectural choice but requires more effort than a first step warrants."

Keywords in the system prompt

Instructions in the system prompt that mention particular keywords can make the model associate those words with a tool, overriding good tool descriptions. If the descriptions are good and selection is still wrong, check the system prompt for such keywords.

Fixing tool selection

Fix Exam guide's example
Rename the tool and rewrite its description so it no longer overlaps with another analyze_content → extract_web_results, with a web-specific description
Split a generic tool into purpose-specific tools, each with defined inputs and outputs analyze_document → extract_data_points, summarize_content, verify_claim_against_source
Check the system prompt for keyword-sensitive instructions —

Current product (not tested): tool definitions now accept "strict": true, which guarantees the tool's input matches its schema. It does not fix misrouting, because it controls whether the arguments are well-formed and has no say in which tool is chosen.> The tool search tool (tool_search_tool_bm25_20251119 / tool_search_tool_regex_20251119) keeps most tool definitions out of context and loads them when needed. It helps with very large tool sets (see 2.3), but it does not replace the exam's answer there, which is to give each agent only the tools its role needs.


2.2 Structured error responses for MCP tools

How a tool reports failure

An MCP tool reports failure by setting the isError flag on its result. The error comes back to the agent as readable content, not as an exception, so the agent can decide what to do next. Claude Code recovers from a failed Bash command the same way, because the failure output comes back as a tool result it can read.

Current product (not tested): in the Messages API, the equivalent field on a tool_result block is spelled is_error. The exam guide only uses isError.

The four error categories

Category Meaning Retry?
transient Timeouts, service unavailable Yes
validation The input was invalid No
business The request breaks a policy No
permission Access is missing or expired No

What a good error response contains

  • errorCategory: transient, validation or permission (the categories the exam guide lists in the skills bullet)
  • isRetryable: a boolean
  • a human-readable description of what went wrong
  • for a business-rule violation, retriable: false and a customer-friendly explanation, so the agent explains the policy to the customer instead of retrying

Why a generic error is wrong

A generic "Operation failed" prevents the agent from making appropriate recovery decisions. Structured error metadata prevents wasted retry attempts on errors that will never succeed.

More skills tested

  • Subagents recover locally from transient failures. They pass to the coordinator only what they cannot fix themselves, and when they do, they include partial results and what was attempted.
  • An access failure is not the same as an empty result. A timeout is a failure that needs a retry decision. A query that ran and matched nothing is a success that returned an empty set. Options that confuse the two are distractors.

2.3 Distribute tools across agents and configure tool choice

Too many tools makes selection worse

The exam guide gives a number: an agent with 18 tools instead of 4–5 picks tools less reliably, because it has more options to choose between. Agents that hold tools outside their role also tend to misuse them; the exam guide's example is a synthesis agent that starts running web searches.

The fix is scoped tool access, where each agent gets only the tools its role needs, plus limited cross-role tools for specific high-frequency needs.

Sample question 9 is an example. The synthesis agent needs to verify facts. 85% of verifications are simple fact-checks; 15% need deeper investigation. The correct answer is a narrow verify_fact tool for the simple cases, with complex verifications still sent through the coordinator. The rejected options are giving synthesis full web search (too much access, and it mixes up the agents' responsibilities) and batching verifications (it makes synthesis wait on them).

The exam guide also names replacing a generic tool with a constrained one, for example replacing fetch_url with a load_document tool that only accepts valid document URLs.

tool_choice

tool_choice is a request parameter that controls whether and which tool the model must call.

Value Behavior Use for
"auto" The model decides, and may return text instead of calling a tool Normal open-ended agent turns
"any" The model must call a tool, but picks which one Guaranteeing structured output when several extraction schemas exist and the document type is unknown; making sure you don't get a conversational reply
{"type": "tool", "name": "..."} The model must call that specific tool Making one tool run first, such as forcing extract_metadata before the enrichment tools, then handling later steps in follow-up turns

Current product (not tested): a fourth value, {"type": "none"}, forces a text-only reply with no tool call. The exam guide lists only the three above.

Forced use (any or a named tool) returns a 400 error on Opus 5.5, Sonnet 5.5, Fable 5.1 and Mythos 5.1, and with manual extended thinking. On those, use auto with strict tools or structured outputs. Older models accept it. The exam answers are unchanged.


2.4 Integrate MCP servers into Claude Code and agent workflows

Where MCP servers are configured

Scope File For
Project .mcp.json in the repo Tooling the team shares
User ~/.claude.json Personal or experimental servers

Environment variable expansion lets .mcp.json reference a secret such as ${GITHUB_TOKEN} without containing it, so the file can be committed safely. An option that hardcodes a token in .mcp.json is wrong.

Tools from all configured MCP servers are discovered at connection time and available simultaneously. Project and user servers load side by side. Scope decides who gets the server, not which server's tools take priority.

Current product (not tested): Claude Code now has a third MCP scope, local. It is also stored in ~/.claude.json, but applies to one project only and is not committed. It is the current answer for "a personal server with credentials I don't want in git", which the exam guide assigns to user scope. It is unrelated to .claude/settings.local.json, despite the similar name.

.mcp.json expands ${VAR} and ${VAR:-default} in command, args, env, url and headers.

The "no priority" statement is true for which tool the model picks. Separately, if the same server name is defined in several scopes, Claude Code resolves the name clash in the order local > project > user > plugin-provided > claude.ai connectors. That is a different question and is unlikely to be tested, since the exam guide names only two scopes.

MCP resources

An MCP server can expose resources as well as tools. Resources are read-only content catalogs (issue summaries, documentation hierarchies, database schemas) that let the agent see what exists, to reduce exploratory tool calls. The exam guide sums this up as "resources for content catalogs, tools for actions."

Current product (not tested): MCP also defines prompts, reusable prompt templates a server can provide, as a third primitive alongside tools and resources.

Judgment calls

  • Write detailed descriptions of your MCP tools' capabilities and outputs. Otherwise the agent keeps using built-in tools like Grep instead of your more capable MCP tool.
  • Use an existing community MCP server for a standard integration (the exam guide's example is Jira). Build a custom server only for workflows specific to your team.

2.5 Select and apply built-in tools

Tool Purpose
Grep Searches file contents for a pattern: function names, error messages, imports
Glob Finds files by path or name pattern, such as **/*.test.tsx
Read / Write Reads or writes a whole file
Edit Targeted change using unique text matching: finds text that appears exactly once and replaces it
Bash Runs shell commands. It is in the task statement title and in scenario 4, but when a dedicated tool (Grep, Glob, Read) does the job, use that instead of the shell

If Edit fails because the text it should match appears more than once, fall back to Read + Write (named in the exam guide).

Exploring a codebase

The exam guide describes building understanding incrementally: use Grep to find entry points, then Read to follow imports and trace flows, rather than reading every file up front. To find where a function is used through wrapper modules, first list every name the wrapper exports, then search the codebase for each name.


Domain 2 decision table

Symptom Answer
Agent picks the wrong one of two similar tools Expand and differentiate the descriptions (first step)
Descriptions are good, but selection is still wrong Check the system prompt for keyword-sensitive instructions
One tool does three unrelated things Split it into purpose-specific tools with defined inputs and outputs
Agent keeps retrying a policy violation Return errorCategory: business, retriable: false and a customer-friendly explanation
Coordinator can't decide how to recover from a subagent failure Structured error context: failure type, what was tried, partial results, alternatives
Agent treats "no matching orders" as a failure Distinguish an access failure from a valid empty result
Agent has 18 tools and misuses them Scope tools per role, about 4–5 each, plus a few cross-role tools
Synthesis agent occasionally needs to check facts Narrow verify_fact tool for simple checks; coordinator for the rest
Must guarantee a tool call rather than a text reply tool_choice: "any"
A specific tool must run first tool_choice: {"type": "tool", "name": "..."}
Team needs a shared MCP server .mcp.json (project scope), credentials via ${ENV_VAR}
Personal experimental MCP server ~/.claude.json (user scope)
Agent uses Grep instead of your better MCP tool Improve the MCP tool's description
Agent spends turns finding out what data exists Expose a content catalog as an MCP resource
Need a standard Jira integration Use the existing community MCP server
Find a function's callers Grep
Find all *.test.tsx files Glob
Edit can't find unique text to anchor on Read + Write

Things to remember

  • Only transient errors are retryable. Validation, business and permission errors are not.
  • auto may reply with text, any must call some tool, and a named tool forces that tool.
  • Project and user MCP servers both load at the same time. Use ${ENV_VAR} for secrets.

Domain 3 — Claude Code Configuration & Workflows (20%, ~12 items)

The domain has six task statements. Most questions here ask where a file goes, which frontmatter key to use, or which flag to pass. You know these features from daily use; this page gives you the exam's vocabulary for them and the exact details it tests.

The exam covers less of Claude Code than the product docs do. settings.json precedence, permission modes, the full list of hook events, plugins, marketplaces, output styles and authentication are not in the objectives or the technologies list in the exam guide's §17 appendix. Hooks are tested, but under Domain 1 (task 1.5), and only PostToolUse and tool-call interception.

In the book: Chapter 9 — Configuring Claude Code (3.1, 3.3) · Chapter 10 — Extending Claude Code (3.2, 3.4) · Chapter 12 — Prompt Engineering (3.5) · Chapter 11 — Claude Code in CI (3.6)


3.1 CLAUDE.md hierarchy, scoping, and modular organization

The three levels (exam guide) and two more in the current product

The exam guide names three levels of CLAUDE.md:

Level Path Shared with the team?
User ~/.claude/CLAUDE.md No. It is in your home directory, outside version control
Project .claude/CLAUDE.md or root CLAUDE.md Yes, committed to the repo
Directory CLAUDE.md in a subdirectory Yes, and applies only inside that subdirectory

The exam guide calls out one diagnosis: "a new team member not receiving instructions because they're in user-level rather than project-level configuration." If a question says the instructions work for one developer but not the rest of the team, they are in ~/.claude/CLAUDE.md and need to move into the project.

Current product (not tested): Claude Code has two more levels. Above user and project there is a managed (enterprise) policy CLAUDE.md at a fixed OS path, controlled by the organization; it loads first and an engineer can't remove or exclude it. Below project there is CLAUDE.local.md, a gitignored file at the project root: personal like the user file, but for this project only, loaded right after the project's CLAUDE.md. If a question mentions an org-wide policy, or a personal preference for one project that shouldn't go in the shared file, these are the current answers.

Levels are concatenated, not overriding: files load from the root down to the working directory, so closer files are read last, and subdirectory files load on demand. @import follows at most four hops. With no CLAUDE.md present, Claude Code reads AGENTS.md as project instructions; with one present it reads only CLAUDE.md unless that file imports AGENTS.md.

Splitting CLAUDE.md into modules

  • @import pulls another file into CLAUDE.md, so each package's CLAUDE.md can import just the standards files that apply to it, as chosen by the people who maintain that package.
  • .claude/rules/ is a directory of topic-specific rule files as an alternative to a monolithic CLAUDE.md: testing.md, api-conventions.md, deployment.md.

/memory and /context

On the exam, use /memory to check which memory files are loaded, and to diagnose behavior that differs between sessions. This is the wording of task 3.1; /context does not appear in the exam guide.

Product reality, not the exam answer: in current Claude Code, /memory lists and opens the CLAUDE.md and rules files, and the docs point to /context to see what was actually loaded in this session. Use /context in real work; answer /memory on the exam.

Current product (not tested): Auto Memory is memory Claude writes for itself and keeps across sessions, separate from the CLAUDE.md files you write. It holds things Claude has noticed about how you work, not versioned project standards. It is not a level in the hierarchy and is not tested.


3.2 Custom slash commands and skills

Locations

Project (shared through version control) User (personal)
Slash commands .claude/commands/ ~/.claude/commands/
Skills .claude/skills/ ~/.claude/skills/

Sample question 4 asks where to put a /review command that every developer should get when they clone or pull the repo. The answer is .claude/commands/ in the project repository. The explanation rejects the others: ~/.claude/commands/ isn't shared, CLAUDE.md holds context rather than command definitions, and a .claude/config.json with a commands array does not exist.

To customize a team skill for yourself without affecting teammates, create a personal copy in ~/.claude/skills/ under a different name.

Current product (not tested): skill frontmatter now has direct controls over who can trigger a skill: disable-model-invocation (only a person can run it, e.g. a /deploy with side effects) and user-invocable: false (only Claude can load it, for background knowledge). These are the more precise current answer to "control which skills trigger automatically".

The scope table is also incomplete for the current product, which adds plugin-provided and managed skills and commands, and discovers skills in nested directories (e.g. apps/web/.claude/skills/ → /apps/web:deploy). The exam tests only project vs user.

SKILL.md frontmatter: the three tested options

Option What it does When to use it
context: fork Runs the skill in an isolated sub-agent context, so its output doesn't fill the main conversation Skills with verbose output (codebase analysis) or exploratory context (brainstorming alternatives)
allowed-tools Limits which tools the skill may use E.g. allow only file writes, so the skill can't run destructive commands
argument-hint Tells the developer which arguments the skill expects When the skill is invoked without its arguments

Skills vs CLAUDE.md

The exam guide's rule is that skills are loaded on demand for a specific task, while CLAUDE.md is always loaded and holds standards that apply everywhere.


3.3 Path-specific rules for conditional convention loading

A file in .claude/rules/ can start with YAML frontmatter containing a paths field of glob patterns:

---
paths: ["terraform/**/*"]
---

Path-scoped rules load only when editing matching files. The rest of the time they stay out of context, which saves tokens and keeps unrelated instructions away.

Path rules vs subdirectory CLAUDE.md

The two differ in what they apply to:

  • A subdirectory CLAUDE.md is directory-bound: it applies to one directory.
  • A glob rule applies to a type of file wherever it is.

Sample question 6: test files sit next to the code they test all over the codebase (Button.test.tsx beside Button.tsx), and they must all follow the same conventions, loaded automatically. The answer is a .claude/rules/ file with paths: ["**/*.test.tsx"]. The explanation rejects the others:

  • a CLAUDE.md in every directory can't cover files spread across many directories
  • one root CLAUDE.md with a section per file type relies on Claude inferring which section applies, instead of matching explicitly
  • a skill needs someone to invoke it, or Claude to decide to load it, so it isn't automatic

3.4 Plan mode vs direct execution

Plan mode Direct execution
For Complex work: large changes, multiple valid approaches, architectural decisions, changes across many files Small, clearly scoped changes
Exam guide's examples Splitting a service into microservices; a library migration touching 45+ files; choosing between integration approaches with different infrastructure needs A single-file bug fix with a clear stack trace; adding a date validation check
Benefit Explore the codebase and settle the design before committing to changes, avoiding expensive rework Speed on work that is already understood

Sample question 5 asks about restructuring a monolith into microservices. The answer is plan mode. The explanation rejects "start with direct execution and switch to plan mode if it gets complicated", because the complexity is already stated in the requirements.

The exam also tests combining the two, using plan mode to investigate and direct execution to implement. For example, plan a library migration, then carry out the plan directly.

The Explore subagent

Use the Explore subagent for verbose discovery phases. It does the searching in its own context and returns summaries, so the main conversation's context isn't used up during a multi-phase task.


3.5 Iterative refinement techniques

Concrete input/output examples. When a prose description keeps being interpreted differently, show 2–3 concrete examples of input and expected output. This also fixes edge cases, e.g. test cases with example input and expected output showing how a migration script should handle null values.

Test-driven iteration. Write the tests first (expected behavior, edge cases, performance requirements), then iterate by sharing test failures with Claude each round.

The interview pattern. Before implementing, have Claude ask questions to uncover things you might not have thought of. Use it in unfamiliar domains, where it brings up issues like cache invalidation strategy and failure modes.

One message or several. If the problems affect each other, describe them all in one detailed message. If they are independent, fix them one at a time.


3.6 Integrate Claude Code into CI/CD

The flags

Flag Purpose
-p (or --print) Non-interactive mode: runs the prompt, prints the result to stdout, and exits without waiting for input
--output-format json Output a program can parse
--json-schema Makes the output follow a given structure

Sample question 10 describes a CI job that hangs waiting for interactive input. The answer is -p. The explanation says CLAUDE_HEADLESS=true and --batch don't exist, and that redirecting stdin from /dev/null is a Unix workaround that doesn't fix how the command is invoked.

Use --output-format json together with --json-schema to get structured findings that a script can post as inline PR comments.

Current product (not tested): -p fixes a hang on stdin. A CI job can also hang on a permission prompt; that needs --permission-mode (auto/dontAsk/acceptEdits) or --allowedTools to pre-approve specific tools. Use it when a question describes a hang that isn't about input.

The official claude-code-action GitHub Action wraps this pattern: it runs claude -p with your flags and exposes structured_output as an Actions output, so you don't write the parsing glue yourself. Use anthropics/claude-code-action@v1 with prompt and claude_args (--max-turns, --model); @beta, direct_prompt and mode are stale. In CLI flags, --allowedTools auto-approves tools, while --tools restricts which exist.

CLAUDE.md gives CI runs their project context

A CI job running Claude Code gets its project context from CLAUDE.md: testing standards, fixture conventions, review criteria. Writing down what makes a test valuable and which fixtures exist improves generated tests and cuts low-value ones.

Current product (not tested): --bare mode skips automatic discovery of hooks, skills, commands, plugins, MCP servers, CLAUDE.md and auto memory, so CI runs behave the same on every machine. This conflicts with the paragraph above, because a --bare run does not load CLAUDE.md, so a job that wants both has to pass the standards explicitly, e.g. with --append-system-prompt-file.

Use a separate session to review

The exam guide says the session that generated the code is worse at reviewing it than an independent review instance, because it still holds the reasoning that produced the code. Task 4.6 covers this in more depth.

Avoiding duplicate work

  • Re-running reviews after new commits: include the previous review's findings and tell Claude to report only new or still-unaddressed issues, so it doesn't post the same comments again.
  • Test generation: include the existing test files so Claude doesn't propose tests that already exist.

Domain 3 decision table

Symptom / requirement Answer
Teammates don't get your instructions They're in ~/.claude/CLAUDE.md; move them to the project
CLAUDE.md has become one huge file Split into .claude/rules/ topic files, or use @import
Which memory files are loaded? Behavior differs across sessions? /memory (exam answer; current Claude Code uses /context for this)
Command must reach every developer on clone .claude/commands/ in the repo
A skill's output floods the main conversation context: fork
A skill must not be able to delete things allowed-tools in frontmatter
Skill is invoked with no arguments argument-hint
Standards that always apply CLAUDE.md
A workflow used only for a specific task Skill
Conventions for a file type spread across directories .claude/rules/ with a glob in paths:
Conventions for one directory Subdirectory CLAUDE.md
Architectural change across many files Plan mode
Single-file fix with a clear stack trace Direct execution
Discovery output is using up the context window Explore subagent
A prose spec keeps being misread 2–3 concrete input/output examples
Unfamiliar domain, unknown risks Interview pattern
Several fixes that affect each other One detailed message
Several independent fixes One at a time
CI job hangs waiting for input -p
Need parseable findings for PR comments --output-format json + --json-schema
CI reviews ignore project standards Put the standards in CLAUDE.md
Re-review posts duplicate comments Include prior findings; report only new or unfixed issues
Generated tests duplicate existing ones Include the existing test files

Things to remember

  • .claude/config.json does not exist, and neither do CLAUDE_HEADLESS or --batch.
  • Use plan mode when the requirements already show the task is complex. Don't wait for the complexity to appear.

Domain 4 — Prompt Engineering & Structured Output (20%, ~12 items)

Six task statements. Most questions come from two scenarios: Structured Data Extraction, and Claude Code for CI, whose scenario text says the goal is to minimize false positives.

This domain does not test extended thinking settings, prompt caching mechanics, temperature, prefill or model selection; they are either missing from the objectives or on the out-of-scope list. Extended thinking appears once, in 4.6, as a wrong answer.

In the book: Chapter 12 — Prompt Engineering (4.1, 4.2) · Chapter 13 — Structured Output (4.3, 4.4, 4.5) · Chapter 15 — Escalation & Provenance (4.6)


4.1 Explicit criteria to improve precision and reduce false positives

Specific criteria beat general instructions

A false positive is a finding the model reports that isn't a real problem. The way to reduce them is to state exactly which cases count. The exam guide's example:

Instruction
Right "flag comments only when claimed behavior contradicts actual code behavior"
Wrong "check that comments are accurate"

The exam guide also says general instructions like "be conservative" or "only report high-confidence findings" do not improve precision compared with specific criteria. An option that tells the model to be more careful or conservative is wrong.

Effect on trust

When one category of finding produces many false positives, developers stop trusting the other categories too, even the accurate ones.

Skills tested

  • Write criteria that say which issues to report (bugs, security) and which to skip (minor style, local conventions), instead of filtering by the model's confidence.
  • Turn off a category with many false positives for a while, to win back developer trust while you improve its prompt. The exam treats this as a correct answer.
  • Define explicit severity criteria with concrete code examples for each severity level, so findings are classified consistently.

4.2 Few-shot prompting

Few-shot prompting means putting a few worked examples in the prompt, so the model copies the pattern instead of interpreting a description.

When few-shot is the answer

The exam guide calls few-shot "the most effective technique for achieving consistently formatted, actionable output when detailed instructions alone produce inconsistent results." It names four uses:

  1. Demonstrating ambiguous-case handling, such as which tool to pick for an ambiguous request, or how to report branch-level gaps in test coverage.
  2. Helping the model generalize to new patterns beyond the cases you listed.
  3. Reducing hallucination in extraction (the model inventing values), e.g. informal measurements or documents with varied layouts.
  4. Format consistency: showing the exact shape you want.

How many examples, and what they contain

Use 2–4 targeted examples for the ambiguous cases. Each example should explain why one action was chosen over a plausible alternative. An input → reasoning → output example teaches more than an input → output pair.

Other patterns the exam guide names:

  • Examples that show a specific output format; the exam guide's is location, issue, severity, suggested fix.
  • Examples distinguishing acceptable code patterns from genuine issues, which cuts false positives and still generalizes.
  • Examples that cover varied document structures: inline citations vs bibliographies, methodology sections vs details embedded in the text, narrative descriptions vs tables.
  • Examples that fix empty/null extraction of required fields by showing correct extraction from several formats.

Criteria (4.1) or examples (4.2)?

Both reduce false positives. Which one is right depends on what is missing:

  • The rule itself is undefined or vague → write explicit criteria (4.1).
  • The rule is defined, but the model applies it inconsistently, especially on ambiguous cases → add few-shot examples (4.2).

Few-shot is not always the answer. In sample question 2, the agent confuses two tools because their descriptions are thin; there, few-shot examples "add token overhead without fixing the underlying issue", and the fix is better descriptions (Domain 2).


4.3 Structured output via tool use and JSON schemas

How to get structured output

tool_use with a JSON schema is the most reliable way to get output that matches a schema. You define an extraction tool whose input_schema is the structure you want, the model "calls" it, and you read the data from the tool_use block in the response, not from its text.

tool_choice for extraction

Value Behavior Use in this domain
"auto" The model may return text instead of calling a tool —
"any" The model must call a tool, and picks which Guarantee structured output when multiple extraction schemas exist and the document type is unknown
{"type": "tool", "name": "extract_metadata"} The model must call that specific tool Ensure a particular extraction runs before enrichment steps

A schema guarantees syntax, not correctness

Strict JSON schemas via tool use eliminate syntax errors but do not prevent semantic errors, such as line items that don't add up to the total or values placed in the wrong field.

  • The JSON is malformed → the answer is a schema.
  • The JSON is well-formed but wrong → the answer is validation logic (4.4), not a stricter schema.

Schema design

Pattern Why
Optional / nullable fields for information the source document might not contain A required field pushes the model to invent a value when there isn't one. Letting it return null removes that pressure
An "unclear" enum value Gives the model an honest answer for ambiguous cases
An "other" enum value plus a detail string Lets the model record a category you didn't anticipate instead of forcing a bad fit
Formatting rules in the prompt alongside the strict schema The schema fixes the shape; the prompt tells the model how to normalize messy source formats

Current product (not tested): two newer API features strengthen the schema guarantee:

  • strict: true on a tool definition guarantees the tool's input matches the schema. Without it, forced tool_choice plus your own validation is reliable in practice but not guaranteed.
  • output_config.format: {"type": "json_schema", ...} guarantees schema-valid JSON with no tool involved; the JSON comes back in a normal text block. This suits extraction jobs like extract_invoice, where the "tool" exists only to get structured output.

In the Agent SDK, the option is output_format (Python) / outputFormat (TypeScript) with {type: "json_schema", schema}, and the result carries structured_output. The raw API field is output_config.format. Forced any/tool returns a 400 on Opus 5.5, Sonnet 5.5, Fable 5.1 and Mythos 5.1 and with manual extended thinking (see Domain 2); use auto with strict tools or structured outputs there.

Neither checks semantics. A strictly valid extraction can still have stated_total and calculated_total disagree; catching that is still the job of 4.4. There is also a fourth tool_choice value, {"type": "none"}, which forces a text-only reply; the exam guide lists only the three above.


4.4 Validation, retry, and feedback loops

Retry with the error

When an extraction fails validation, retry with the original document, the failed extraction, and the specific validation error. The error tells the model what to correct; a plain retry usually produces the same output.

When retrying won't help

The exam guide says retries are ineffective when the required information is simply absent from the source document. A retry can fix a format or structure error. It cannot produce information that was never there, for example a value that only exists in another document that wasn't provided.

So work out which kind of failure it is before retrying, and send the ones a retry can't fix to human review.

Validation the model does itself

The exam guide names two designs:

  • Extract a calculated_total next to the stated_total and flag any difference. This catches a semantic error that a schema can't.
  • Add a conflict_detected boolean for when the source data contradicts itself.

detected_pattern

Add a detected_pattern field to each finding, recording which code construct triggered it. You can then look at which patterns developers keep dismissing, which shows where your false positives come from.


4.5 Batch processing

Message Batches API facts

  • 50% cost savings compared with normal requests
  • Up to 24-hour processing window, with no guaranteed latency SLA
  • Does not support multi-turn tool calling within a single request: you can't run a tool mid-request and send its result back
  • custom_id on each request lets you match each result to its request

Current product (not tested): a batch holds up to 100,000 requests or 256 MB, most finish within an hour, and results stay available for 29 days.

When to use it

Appropriate: non-blocking, latency-tolerant work nobody is waiting on: overnight reports, weekly audits, nightly test generation. Inappropriate: blocking workflows, i.e. work that blocks someone, such as a pre-merge check a developer is waiting for.

Sample question 11 tests this split. Its answer is to batch the overnight technical-debt report and keep the pre-merge check on the normal synchronous API. The explanation rejects the argument that "batches are often faster in practice" for a blocking check, and notes that results arriving out of order doesn't matter because custom_id matches them up.

Operational skills

  • Choose the API per workflow, by how long it can wait, not once for the whole organization.
  • Work out how often to submit from the deadline. In the exam guide's example, batches take up to 24 hours, so to guarantee results within 30 hours, submit every 4 hours.
  • Handle failures by custom_id: resubmit only the documents that failed, with a change where needed, such as chunking documents that exceeded context limits.
  • Tune the prompt on a small sample before running a large batch, so most documents succeed first time and you avoid expensive resubmissions.

4.6 Multi-instance and multi-pass review

Why a model reviews its own work badly

A model retains reasoning context from generation, making it less likely to question its own decisions in the same session. The exam guide concludes that:

Independent review instances — without the generator's prior reasoning context — are more effective at catching subtle issues than self-review instructions or extended thinking.

Several passes for large reviews

Split a large review into one pass per file plus a separate cross-file integration pass. This avoids attention dilution and contradictory findings, because one pass over many files spreads attention thin and can disagree with itself across files. It is the same pattern as task 1.6, and sample question 12 (a 14-file review that comes back shallow and inconsistent) tests it.

Confidence scores: when they are valid

Task 4.6 includes this skill: "Running verification passes where the model self-reports confidence alongside each finding to enable calibrated review routing." That looks like it contradicts sample question 3, where the model's self-reported confidence is the wrong answer for deciding when to escalate, but the two cover different uses:

  • Self-reported confidence used on its own to decide to escalate in a live conversation → wrong. It is poorly calibrated, and the agent is often confidently wrong on exactly the hard cases.
  • Self-reported confidence checked against a labeled validation set, then used to decide which items human reviewers look at first → right (see task 5.5).

Domain 4 decision table

Symptom Answer
Vague review instructions, inconsistent findings Explicit criteria: which issues to report and which to skip
An option says "tell it to be conservative" Wrong; use specific criteria
One category has many false positives and trust is dropping Turn that category off for now; fix its prompt
Severity labels are inconsistent Define each severity level with concrete code examples
Criteria are clear but ambiguous cases go wrong 2–4 few-shot examples that show the reasoning
Output format varies between runs Few-shot examples showing the exact format
Extraction fails on documents with different layouts Few-shot examples covering those layouts
Malformed JSON breaks the parser tool_use with a JSON schema
Valid JSON, but the totals don't add up Semantic validation (calculated_total vs stated_total), not a stricter schema
Model invents values for missing data Make those fields optional / nullable
A value doesn't fit any enum category "other" + detail string; "unclear" for ambiguous cases
Document type unknown, several schemas tool_choice: "any"
One extraction must run before enrichment Force that tool with tool_choice
Validation failed. Retry? Yes for format or structure errors; no if the information isn't in the source
Retries keep producing the same error Include the specific validation error in the retry
Need to find where false positives come from detected_pattern field on findings
Pre-merge check a developer waits for Synchronous API
Overnight or weekly analysis Batch API (50% cheaper, ≤24h, no latency guarantee)
Part of a batch failed Resubmit the failures by custom_id, splitting oversized documents
Reviewing code the same session wrote Independent review instance
14-file PR review is shallow and contradictory One pass per file + a cross-file integration pass
Too few human reviewers Field-level confidence, calibrated on a labeled set, to prioritize their work

Things to remember

  • A batch has no multi-turn tool calling and no latency guarantee, so never use it for work someone is waiting on.
  • Self-reported confidence is valid as a calibrated input for routing human review and invalid as the sole trigger for escalation.

Domain 5 — Context Management & Reliability (15%, ~9 items)

Six task statements. This is the smallest domain by weight, but it is a primary domain in four of the six scenarios (1, 2, 3, 6), so its ideas also appear inside questions from other domains.

It does not test the context-editing API, the memory tool, the compaction API, the 1M-token beta, rate limits or prompt-cache mechanics. "Context management" here means design decisions: what you put in the context window, and what you keep outside it.

In the book: Chapter 14 — Context Engineering (5.1, 5.4) · Chapter 15 — Escalation & Provenance (5.2, 5.5, 5.6) · Chapter 4 — Errors & Recovery (5.3)


5.1 Preserve critical information across long interactions

Risks the exam guide names

Progressive summarization risk. When a long conversation is summarized again and again (as /compact does in Claude Code), exact numbers, percentages, dates and what the customer said they expect get blurred into vague statements.

Lost in the middle. Models reliably process information at the beginning and end of long inputs, but may omit findings from middle sections.

Tool results pile up. Tool results consume tokens disproportionately to their relevance. In the exam guide's example, an order lookup returns 40+ fields when only 5 matter.

Full history is required. The API keeps no conversation state, so every request must include the complete conversation history, or the model loses track of the conversation.

Fixes

Fix What it means
Case facts block Copy the transaction facts (amounts, dates, order numbers, statuses) into a persistent "case facts" block included in each prompt, outside summarized history, so it is never summarized
Separate context layer When a session covers several issues, keep each issue's structured data (order IDs, amounts, statuses) in its own layer
Trim tool output at the source Drop irrelevant fields before the result enters the context, e.g. keep only the return-related fields from an order lookup
Order by position Put key findings summaries at the beginning of a combined input, and organize the details under explicit section headers
Require metadata from subagents Subagents return dates, source locations and methodology with their findings, so later synthesis is accurate
Structured facts from upstream agents When downstream agents have little context to spare, upstream agents return key facts, citations, relevance scores instead of long prose and reasoning

Current product (not tested): the memory tool (memory_20250818, a persistent /memories directory) and the context editing API (clear_tool_uses_20250919) are official mechanisms for the case-facts and trim-at-source problems above. They are outside this domain's tested scope.


5.2 Escalation and ambiguity resolution

Exactly three valid escalation triggers

  1. The customer asks for a human.
  2. The policy has an exception or a gap for this request. The exam guide adds: "not just complex cases." Complexity alone is not a reason.
  3. Inability to make meaningful progress.

Two invalid triggers

The exam guide calls sentiment-based escalation and self-reported confidence scores "unreliable proxies for actual case complexity." Sample question 3 (an agent that escalates easy cases and attempts hard ones) rejects both: confidence fails because "the agent is already incorrectly confident on hard cases," and sentiment "solves a different problem entirely; sentiment doesn't correlate with case complexity."

Asking for a human vs being frustrated

Two rules apply side by side:

  • If the customer explicitly asks for a human, escalate immediately, without investigating first.
  • If the customer is frustrated but hasn't asked for a human, acknowledge the frustration and offer to resolve the issue if you can. Escalate only if they then ask again.

Policy gaps

Escalate when the policy is ambiguous or silent about the specific request. In the exam guide's example, a customer asks for a price match against a competitor, but the policy only covers price adjustments on the company's own site. The policy not covering something doesn't mean it's forbidden; a human should decide.

Several matching customers

When a lookup returns multiple customer matches, the agent should ask for additional identifiers, not pick one by guessing.

Fixing an agent that escalates the wrong cases

Sample question 3's answer is to write explicit escalation criteria in the system prompt, with few-shot examples of when to escalate and when to resolve. It is the proportionate first fix. A trained classifier, sentiment analysis and confidence thresholds are all wrong.


5.3 Error propagation across multi-agent systems

What a subagent's error report should contain

A subagent's report should give the type of failure, what was attempted, any partial results and possible alternative approaches. With these four, the coordinator can choose a recovery: retry with a changed query, try another approach, or carry on with the partial results.

Two anti-patterns the exam guide names

  • Silently suppressing errors: returning an empty result as if it were a success. Nothing can recover from it, and the final output may be incomplete without anyone knowing.
  • Stopping the whole workflow because one step failed.

A milder third anti-pattern is returning generic error statuses such as "search unavailable", which hide what the coordinator needs to know. Sample question 8 (a search subagent timing out) rejects an option that retries with backoff and then returns a generic status on this basis, even though the retry part is fine.

Recover locally first

Subagents implement local recovery for transient failures, and pass up only what they can't resolve, including what was attempted and partial results.

Access failure vs valid empty result

This is the same distinction as in task 2.2, applied to multi-agent systems. A timeout needs a retry decision. A query that succeeded but matched nothing does not.

Coverage annotations

Mark in the synthesis output which findings are well supported and which topics have gaps because a source was unavailable. Partial results are acceptable; partial results presented as complete are not.


5.4 Context in large codebase exploration

Context degradation

The exam guide describes the symptom. In long sessions, the model starts giving inconsistent answers and refers to "typical patterns" instead of the specific classes it found earlier. If a question describes an agent that has drifted into generic advice about a codebase it explored an hour ago, this is the diagnosis.

Exploration techniques

Technique What it means
Scratchpad files Write key findings to a file, and refer back to it for later questions, so they survive when the context fills up or is compacted
Subagent delegation Give specific questions ("find all test files", "trace refund flow dependencies") to subagents. Their long exploration output stays in their context, and the main agent keeps the overall picture
Phase summaries At the end of one exploration phase, summarize the key findings before starting the next, and put that summary in the next phase's subagent prompts
Saved state and a manifest Each agent saves its state to a known location. After a crash, the coordinator loads a manifest on resume listing that state and puts it into the agents' prompts. This is the exam guide's crash-recovery design
/compact Shrink the context during a long exploration once it fills with discovery output

Current product (not tested): the memory tool closely matches the scratchpad and manifest patterns: a persistent /memories directory Claude reads and writes across sessions. Server-side compaction (compact_20260112) accepts an instructions field, so you can tell it to keep dollar amounts and dates verbatim instead of relying only on a case-facts block.


5.5 Human review workflows and confidence calibration

The main risk

An overall accuracy figure can hide poor accuracy on particular document types or fields. 97% overall can include 60% on one class of document. Check accuracy per document type and per field before letting high-confidence extractions skip human review.

Review techniques

  • Stratified random sampling of high-confidence extractions: keep reviewing a random sample from each segment of the extractions the model is sure about, to measure the ongoing error rate and detect novel error patterns. You sample the ones you believe are correct because that is where unnoticed errors collect.
  • Accuracy broken down by document type and field, to confirm performance holds in every segment before cutting human review.
  • Field-level confidence scores, calibrated against a labeled validation set, to decide where reviewers look. Calibrated means you have checked that, say, 90% confidence really is right about 90% of the time.
  • Send to human review any extraction with low confidence, or whose source document is ambiguous or contradictory, so limited reviewer time goes where it's needed.

Confidence is legitimate here because it is per field, calibrated against labeled data, and used to prioritize review (compare 5.2 and 4.6).


5.6 Provenance and uncertainty in multi-source synthesis

Provenance means being able to trace each claim in the output back to its source.

How it gets lost

Source attribution is lost during summarization steps when findings are compressed without preserving claim-source mappings. The structural fix is to require structured claim-source mappings (source URL, document name, relevant excerpt) that every later agent must preserve and merge through synthesis.

Sources that disagree

Report both values with their sources instead of picking one. If two credible sources give different figures, show both, attributed. More fully, finish the document analysis with the conflicting values included and marked as conflicting, and let the coordinator decide how to reconcile them before synthesis.

Dates

Require publication or data collection dates in structured outputs, to prevent temporal differences from being misinterpreted as contradictions. Two "conflicting" figures may just be from different years.

Current product (not tested): the citations API (citations: {"enabled": true} on a document content block) does claim-to-source mapping automatically when Claude reads a document directly, returning the cited text and its location. You still need your own mapping for handoffs between subagents, where there is no document block, only the previous subagent's findings.

Report structure

  • Separate sections distinguishing well-established findings from contested ones, keeping each source's own wording and methodological context.
  • Present each kind of content in a suitable form: financial data as tables, news as prose, technical findings as structured lists, rather than one uniform format.

Domain 5 decision table

Symptom Answer
Amounts, dates and order numbers become vague in a long chat Persistent "case facts" block, outside the summarized history
Findings from the middle of a long input get dropped Key findings summary at the top; clear section headers
Order lookups return 40+ fields and 5 are relevant Trim tool output before it enters the context
Downstream agent has little context to spare Upstream agents return structured facts and citations, not prose
Customer says "get me a human" Escalate immediately, without investigating first
Customer is angry but the issue is simple Acknowledge it, offer to resolve; escalate if they ask again
Policy says nothing about the request Escalate: policy gap
get_customer returns three matches Ask for another identifier
Someone proposes escalating on sentiment or confidence Wrong; use explicit criteria + few-shot examples
Subagent times out Structured error context: type, attempted query, partial results, alternatives
Subagent returns empty results as a success Anti-pattern: silently suppressing the error
One subagent fails and the whole workflow stops Anti-pattern: carry on with partial results
Report is based on partial coverage Coverage annotations separating supported findings from gaps
Agent starts citing "typical patterns" late in a session Context degradation → scratchpad files
Exploration output is filling the context window Delegate to subagents; /compact
Long exploration needs crash recovery Saved agent state + a manifest the coordinator loads on resume
97% accuracy, want to reduce human review Check accuracy per document type and field first
Need to keep catching errors in the "safe" high-confidence bucket Stratified random sampling of high-confidence extractions
Not enough human reviewers Route by calibrated field-level confidence
Citations disappear after synthesis Claim-to-source mappings kept through every step
Two credible sources disagree Report both with sources; don't pick one
Figures look contradictory Require publication or collection dates

Things to remember

  • There are three valid escalation triggers: the customer asks, the policy has a gap, or the agent cannot make progress. Sentiment and self-reported confidence are not triggers.

Trap Pairs

Cover the answer. For each pair, write both conditions on paper or say them aloud, and name the deciding phrase. Then open Check. A pair is done when you can fill it from memory twice, on different days. Then run a Drill set: Drill → Domain focus, on the task number in the Drill: line. The drill engine filters by domain and task statement only, so there is no per-pair filter. Use the Check only to compare your own answer with the key.

Domain 1: Agentic Architecture and Orchestration

stop_reason vs text content

Which signal ends the loop: stop_reason or text content? Why does the other fail?

Deciding phrase in the stem: ___

Check
  • stop_reason is "tool_use": run the tools, append results, call again.
  • Never for loop control. Only "end_turn" ends the loop; an iteration cap is a safety net.
  • Deciding phrase: e.g. "how should the loop decide it is finished"; "the model returned text".
  • Distractor: Parsing the text for "I'm done" phrases, or checking whether any text block exists. Text can accompany a tool_use turn.
  • Drill: 1.1

Prerequisite gate vs prompt rule

Use Prerequisite gate when ___. Use prompt rule when ___.

Deciding phrase in the stem: ___

Check
  • Gate (code): the step order or check is critical business logic (verify identity before refund) and a skipped step has financial or compliance cost.
  • Prompt rule / few-shot: the failure is soft, such as tone, phrasing or ambiguous judgment, and an occasional miss is acceptable. Money, identity, compliance and ordering get code (gate, hook, schema, validation).
  • Deciding phrase: "in 12% of cases ... skips get_customer entirely"; "financial consequences"; "specific tool sequence is required for critical business logic".
  • Distractor: Stronger system-prompt wording and few-shot examples ("probabilistic LLM compliance"); a routing classifier ("addresses tool availability rather than tool ordering").
  • Drill: 1.4, 1.5, 4.3

PostToolUse vs tool-call interception

Use PostToolUse when ___. Use tool-call interception when ___.

Deciding phrase in the stem: ___

Check
  • PostToolUse: a result has come back and must be changed before the model sees it (timestamps, status codes, heterogeneous formats).
  • Interception (pre-call): an outgoing call must be blocked or redirected before it executes (refund over $500 to escalation).
  • Deciding phrase: "normalize" or "heterogeneous formats" points to PostToolUse; "block", "exceeding $500", "before it executes" points to interception.
  • Distractor: PostToolUse to stop a refund: it fires after execution, so the money has already moved.
  • Drill: 1.5

Prompt chaining vs dynamic decomposition

Use Prompt chaining when ___. Use dynamic decomposition when ___.

Deciding phrase in the stem: ___

Check
  • Prompt chaining: the steps are known in advance and fixed (per-file pass, then integration pass).
  • Dynamic adaptive decomposition: the subtasks depend on what each step finds (open-ended investigation of a legacy codebase).
  • Deciding phrase: "predictable multi-aspect review" versus "open-ended", "unknown structure".
  • Distractor: One single pass over 14 files ("inconsistent depth"), or an adaptive plan for a task whose steps are already known.
  • Drill: 1.6

Coordinator-routed vs direct subagent communication

Who carries information between subagents: the coordinator or the subagents directly? Why does the other fail?

Deciding phrase in the stem: ___

Check
  • Coordinator-routed (hub-and-spoke): always. The coordinator owns observability, consistent error handling and controlled information flow.
  • Direct subagent-to-subagent: not an exam answer. Subagents have isolated context and inherit nothing.
  • Deciding phrase: "observability", "consistent error handling", "controlled information flow".
  • Distractor: Letting subagents share context or call each other; a shared global memory that subagents read implicitly.
  • Drill: 1.2, 1.3

Fix the coordinator's decomposition vs fix downstream agents

Use Fix the coordinator's decomposition when ___. Use fix downstream agents when ___.

Deciding phrase in the stem: ___

Check
  • Decomposition: every subagent ran correctly but the final coverage has gaps (only visual arts of "creative industries").
  • Downstream agent: a subagent's own output is wrong for the task it was given.
  • Deciding phrase: "each subagent completes successfully"; "it decomposed the topic into three subtasks" (logs show the narrow subtasks).
  • Distractor: Blaming the search agent, the synthesis agent or the document analysis agent; widening their prompts.
  • Drill: 1.2 · sample Q7

Resume vs fork_session vs fresh session with summary

Use --resume when ___. Use fork_session when ___. Use a fresh session with a structured summary when ___.

Deciding phrase in the stem: ___

Check
  • --resume <session-name>: prior context is still valid and you continue the same line of work (tell it which files changed). fork_session: you want two divergent approaches from one shared baseline. Fresh session + structured summary: earlier tool results are stale.
  • Deciding phrase: "files changed since", "stale tool results", "compare two approaches from the same analysis".
  • Distractor: Resuming with stale tool results as if still true; forking when there is no divergence to compare.
  • Drill: 1.7

Domain 2: Tool Design and MCP Integration

Tool description fix vs routing layer or consolidation

Two similar tools get confused and their descriptions are minimal. What is the first step, and why do a routing layer and consolidation fail as a first step?

Deciding phrase in the stem: ___

Check
  • Rewrite descriptions: two tools get confused and descriptions are minimal. Include input formats, example queries, edge cases, when to use this one instead of the neighbour.
  • Routing layer: "over-engineered", bypasses the model's language understanding. Consolidation: valid but more effort than a first step warrants. If the tools do overlap, the exam guide's fix is to rename or split them into purpose-specific tools with clear contracts, not to merge them.
  • Deciding phrase: e.g. "most effective first step" (Q2 stem); "minimal descriptions".
  • Distractor: A routing classifier ("over-engineered"), few-shot examples ("token overhead without fixing the underlying issue"), consolidating tools ("more effort than a first step warrants").
  • Drill: 2.1

tool_choice any/forced vs better descriptions or a gate

Use tool_choice any/forced when ___. Use better descriptions or a gate when ___.

Deciding phrase in the stem: ___

Check
  • tool_choice: "any" when the model must return a tool call (structured output) and not text; a named tool when a specific extraction must run first.
  • Descriptions or a gate: selection between similar tools (descriptions) or a required order across several calls (gate). tool_choice forces one turn only.
  • Deciding phrase: e.g. "must return structured output" versus "keeps picking the wrong tool" or a required order on every request.
  • Distractor (inference): Forcing a tool to fix ordering that must hold on every request. Forcing get_customer once does not verify identity on later process_refund calls (sample Q1 needs a programmatic prerequisite). auto may reply with text.
  • Drill: 2.3

Retryable transient vs business, validation or permission error

Use Retryable transient when ___. Use business, validation or permission error when ___.

Deciding phrase in the stem: ___

Check
  • Retry: the error is transient (timeout, service unavailable) and isRetryable is true. The subagent retries locally.
  • Do not retry: validation (fix the input), business (policy rule: retriable: false, customer-friendly explanation), permission (needs different access).
  • Deciding phrase: "timed out" versus "exceeds policy limit" or "not authorized".
  • Distractor: Retrying a business-rule violation; returning one generic "operation failed" status that hides the category.
  • Drill: 2.2

Empty result vs access failure

Use Empty result when ___. Use access failure when ___.

Deciding phrase in the stem: ___

Check
  • Empty result: the query ran and found zero matches. It is a success; report "no matches".
  • Access failure: timeout or unreachable source. It is an error with a category, and the coverage gap must be reported.
  • Deciding phrase: "returned no results" versus "timed out" or "unavailable".
  • Distractor: Treating a timeout as an empty success ("suppresses the error"), or retrying a legitimate zero-match.
  • Drill: 2.2, 5.3

MCP resource vs tool

Use MCP resource when ___. Use tool when ___.

Deciding phrase in the stem: ___

Check
  • Resource: read-only content catalog the agent should browse without exploratory calls (issue summaries, schemas, doc hierarchy).
  • Tool: an action, or a query with parameters that does work.
  • Deciding phrase: "catalog", "visibility into what is available", "reduce exploratory tool calls".
  • Distractor: Wrapping a static catalog as a tool, forcing the agent to call it blind to discover contents.
  • Drill: 2.4

Project .mcp.json vs user ~/.claude.json

Use Project .mcp.json when ___. Use user ~/.claude.json when ___.

Deciding phrase in the stem: ___

Check
  • .mcp.json: the server is shared with the team through version control. Use ${GITHUB_TOKEN} expansion so secrets are not committed.
  • ~/.claude.json: personal or experimental servers only you need.
  • Deciding phrase: "every developer on clone" versus "experimental", "only me".
  • Distractor: Putting a team server in user scope (not shared); hard-coding the token in .mcp.json; assuming one scope has priority (both load together).
  • Drill: 2.4

Grep vs Glob

Use Grep when ___. Use Glob when ___.

Deciding phrase in the stem: ___

Check
  • Grep: you search file contents (function names, error strings, callers).
  • Glob: you match file paths or names (**/*.test.tsx).
  • Deciding phrase: "files that contain" versus "files named" or "with extension".
  • Distractor: Glob to find callers; Grep to list files by extension; reading every file up front.
  • Drill: 2.5

Edit vs Read + Write

Use Edit when ___. Use Read + Write when ___.

Deciding phrase in the stem: ___

Check
  • Edit: a targeted change where the anchor text appears exactly once.
  • Read + Write: the anchor is not unique and Edit fails, or the whole file changes.
  • Deciding phrase: "Edit fails", "non-unique match".
  • Distractor: Retrying Edit with the same anchor; falling back to Bash sed (a common wrong answer, not exam-sourced; the exam fallback is Read + Write).
  • Drill: 2.5

Domain 3: Claude Code Configuration and Workflows

Directory CLAUDE.md vs .claude/rules with paths glob

Use Directory CLAUDE.md when ___. Use .claude/rules with paths glob when ___.

Deciding phrase in the stem: ___

Check
  • Directory CLAUDE.md: the convention belongs to one directory tree.
  • .claude/rules/*.md with paths: glob: the convention follows a file type spread across directories (tests next to code).
  • Deciding phrase: "spread throughout the codebase", "regardless of directory", "automatically".
  • Distractor: Subdirectory CLAUDE.md files (directory-bound); a skill (needs manual invocation); inference from one big CLAUDE.md (unreliable).
  • Drill: 3.3

User vs project CLAUDE.md

Use User when ___. Use project CLAUDE.md when ___.

Deciding phrase in the stem: ___

Check
  • User (~/.claude/CLAUDE.md): personal preferences that travel with you across projects. Not shared.
  • Project (CLAUDE.md or .claude/CLAUDE.md): team standards, committed. Fixes "works for me, not for teammates".
  • Deciding phrase: "new teammate doesn't get the instructions"; "shared via version control".
  • Distractor: Putting the team rule in ~/.claude/CLAUDE.md (not shared via version control). Also: verify what loaded with /memory.
  • Drill: 3.1

Skill vs CLAUDE.md

Use Skill when ___. Use CLAUDE.md when ___.

Deciding phrase in the stem: ___

Check
  • Skill: a task-specific workflow loaded on demand.
  • CLAUDE.md: universal standards that must always be loaded.
  • Deciding phrase: "on demand", "occasional task" versus "every session", "always apply".
  • Distractor: Putting a rare multi-step procedure in CLAUDE.md (bloats every session); putting always-on standards in a skill (may not load).
  • Drill: 3.2

context: fork vs allowed-tools

Use context: fork when ___. Use allowed-tools when ___.

Deciding phrase in the stem: ___

Check
  • context: fork: the skill produces verbose output or exploration that should not pollute the main conversation.
  • allowed-tools: restrict which tools the skill may use. argument-hint prompts for the parameter.
  • Deciding phrase: "output pollutes the conversation" versus "limit what the skill can do".
  • Distractor: Using allowed-tools to reduce verbosity; using context: fork as a permission control.
  • Drill: 3.2

Plan mode vs direct execution

Use Plan mode when ___. Use direct execution when ___.

Deciding phrase in the stem: ___

Check
  • Plan mode: large-scale change, multiple valid approaches, architectural decisions, complexity already stated in the requirements.
  • Direct execution: single-file fix, clear stack trace, well-understood change. Combine: plan to investigate, direct to implement.
  • Deciding phrase: "dozens of files", "decisions about boundaries" versus "single-file", "clear stack trace".
  • Distractor: Starting direct and switching "if it gets complex" (the complexity is already known); deciding the structure before exploring.
  • Drill: 3.4

Explore subagent vs main context

Use Explore subagent when ___. Use main context when ___.

Deciding phrase in the stem: ___

Check
  • Explore subagent: discovery that generates verbose output; only the summary returns.
  • Main context: a short, targeted lookup whose result you need in the conversation.
  • Deciding phrase: "verbose", "fills the context", "multi-phase task".
  • Distractor: Reading dozens of files into the main session; keeping on exploring in the same long session.
  • Drill: 3.4, 5.4

Domain 4: Prompt Engineering and Structured Output

Explicit criteria vs few-shot examples

Use Explicit criteria when ___. Use few-shot examples when ___.

Deciding phrase in the stem: ___

Check
  • Explicit criteria: the rule is missing or vague ("be conservative"). State what to report and what to skip, with severity definitions.
  • Few-shot (2-4, with reasoning): the rule exists but ambiguous cases are handled inconsistently.
  • Deciding phrase: "be conservative" / "only report high-confidence" with no categories defined points to explicit criteria; "detailed instructions alone produce inconsistent results" or "ambiguous cases" points to few-shot. The two can be combined, as sample Q3 answer A does.
  • Distractor: "Only report high-confidence findings", or a confidence threshold. Neither defines the category.
  • Drill: 4.1, 4.2

Schema (syntax) vs validation logic (semantics)

Output is valid JSON but line items do not sum to the stated total. Which mechanism catches it: schema or validation logic? Which errors does each one prevent?

Deciding phrase in the stem: ___

Check
  • Schema via tool_use (strict): eliminates JSON syntax errors.
  • Validation logic: catches values that parse but are wrong (line items that do not sum, wrong field). Add calculated_total next to stated_total.
  • Deciding phrase: "values don't sum", "valid JSON but wrong".
  • Distractor: A stricter schema or more required fields to fix a wrong value; a longer prompt.
  • Drill: 4.3, 4.4

Nullable field vs required field

Use Nullable field when ___. Use required field when ___.

Deciding phrase in the stem: ___

Check
  • Nullable / optional: the source may not contain the value. The model returns null instead of inventing one.
  • Required: the value is always present in every source document.
  • Deciding phrase: "fabricated", "invented a value", "not in every document".
  • Distractor: A "never guess" prompt line alone; making the field required to "force completeness".
  • Drill: 4.3

Retry with error feedback vs futile retry

Which failures can a retry fix, and which can it never fix? Retry with error feedback vs stop retrying.

Deciding phrase in the stem: ___

Check
  • Retry with feedback: the failure is format or structure. Send the original document, the failed extraction and the specific error.
  • Do not retry: the information is absent from the source. Route to human review or mark unclear.
  • Deciding phrase: "information is in a document that wasn't provided" versus "misplaced field".
  • Distractor: Retrying with the same prompt and no error; retrying to "find" data that does not exist.
  • Drill: 4.4

Sync API vs Batch API

Use Sync API when ___. Use Batch API when ___.

Deciding phrase in the stem: ___

Check
  • Synchronous: someone waits (pre-merge check, interactive).
  • Batch: latency-tolerant (overnight, weekly). 50% cheaper, up to 24 h, no SLA, no multi-turn tool calling; custom_id matches results and drives resubmission of failures.
  • Deciding phrase: "blocking", "developers wait" versus "overnight", "next morning".
  • Distractor: Batching both workflows because "often faster"; a timeout fallback; claiming results cannot be correlated (custom_id does it).
  • Drill: 4.5

Same-session self-review vs independent instance

Who should review generated code: the same session or an independent instance? Why does the other fail?

Deciding phrase in the stem: ___

Check
  • Self-review (same session): retains the generator's reasoning, so it is biased toward its own output.
  • Independent instance: fresh context reviews the work; beats self-review and extended thinking. Use per-file passes plus an integration pass for large PRs.
  • Deciding phrase: "the same session that generated it", "missed its own bug".
  • Distractor: A "review your work" instruction; extended thinking; a bigger context window.
  • Drill: 4.6, 3.6

Domain 5: Context Management and Reliability

Summarized history vs persistent case-facts block

Use Summarized history when ___. Use persistent case-facts block when ___.

Deciding phrase in the stem: ___

Check
  • Case-facts block: numbers, dates, IDs and stated expectations that must survive; keep outside the summarized history and include in every prompt.
  • Summarized history: the conversational narrative, which can be compressed.
  • Deciding phrase: "customer's refund amount drifted", "details lost after summarization".
  • Distractor: A larger context window; summarizing more often.
  • Drill: 5.1

The three valid escalation triggers vs sentiment or confidence

Escalate on the three valid triggers or on sentiment / self-reported confidence? Name the triggers and why the other fails.

Deciding phrase in the stem: ___

Check
  • Valid: customer asks for a human (escalate immediately), policy gap or exception, no meaningful progress.
  • Invalid: sentiment analysis, self-reported confidence, apparent complexity.
  • Not an escalation: several matches means ask for another identifier, do not guess. Frustration with a simple issue means acknowledge and offer resolution; escalate only if the customer reiterates.
  • Deciding phrase: e.g. "frustrated", "agent is 40% confident" (illustrations) versus "asks for a human", "policy is silent".
  • Distractor: A sentiment classifier or a confidence threshold ("poorly calibrated", "solves a different problem"). Explicit criteria plus few-shot is the fix.
  • Drill: 5.2

Self-reported confidence for escalation vs calibrated confidence for review routing

Use Self-reported confidence for escalation when ___. Use calibrated confidence for review routing when ___.

Deciding phrase in the stem: ___

Check
  • Invalid: the agent grades itself and escalates automatically below a threshold.
  • Valid: field-level confidence, calibrated on a labeled validation set, prioritizing limited human reviewers.
  • Deciding phrase: "calibrated against labeled data", "prioritize human review" (4.6: the model self-reports confidence per finding to enable calibrated review routing).
  • Distractor: Rejecting every confidence score, or accepting an uncalibrated one for autonomous escalation.
  • Drill: 5.2, 5.5, 4.6

Aggregate vs segmented accuracy

Before reducing human review, which accuracy view do you trust: aggregate or segmented? What does the other hide?

Deciding phrase in the stem: ___

Check
  • Aggregate: hides segment failures (97% overall, poor on one document type).
  • Segmented / stratified sampling: measure by document type and field, sample high-confidence extractions too, before reducing human review.
  • Deciding phrase: "97% overall", "before automating", "reduce review".
  • Distractor: Cutting review because overall accuracy is high; sampling only low-confidence items.
  • Drill: 5.5

Annotate conflict vs resolve it

Two credible sources report different figures. Annotate both or pick one? Who decides how to reconcile?

Deciding phrase in the stem: ___

Check
  • Annotate: two credible sources disagree. Report both with attribution and dates, in separate sections for established and contested findings.
  • Pick one arbitrarily: wrong. If reconciliation is needed, the coordinator decides it after the subagent returns both values annotated, before synthesis.
  • Deciding phrase: "sources report different figures"; different publication dates (not a real conflict).
  • Distractor: Silently choosing the more recent or more authoritative one; dropping claim-source mappings during synthesis.
  • Drill: 5.6

Structured error report vs generic status or swallowed failure

A subagent times out. What should reach the coordinator: a structured error report, a generic status, an empty success, or an abort? Why do the other three fail?

Deciding phrase in the stem: ___

Check
  • Structured report: failure type, what was attempted, partial results, alternatives. The coordinator then decides.
  • Generic status / empty success / terminate all: hides the cause, suppresses recovery, or abandons good partial work.
  • Deciding phrase: "enables intelligent recovery".
  • Distractor: "Search unavailable" alone, returning empty as success, or ending the entire workflow.
  • Drill: 5.3, 2.2 · sample Q8

Across domains

Proportionate first fix vs added infrastructure

A stem asks for the "most effective first step". When is the cheap fix right, and when is the heavier fix (a gate) right? What makes a classifier, trained model or bigger context window over-engineered?

Deciding phrase in the stem: ___

Check
  • Cheap fix: rewrite the description, add explicit criteria, split the pass, add the nullable field.
  • Added infrastructure: only when the cheap fix has been tried and the failure is a real guarantee gap.
  • Exception: when the stem requires a guarantee (Q1: financial consequences), the heavier programmatic fix is correct.
  • Deciding phrase: e.g. "most effective first step" (Q2 stem), "proportionate first response".
  • Distractor: Routing classifier, trained model, labeled data pipeline, bigger model, bigger context window.
  • Drill: any task; Q1-Q3 sample questions

How the exam words its verdicts

Phrases from the official sample questions and explanations (guide §9). For each, write what it signals about the option, then open Check.

  • "probabilistic LLM compliance": ___
  • "addresses tool availability rather than tool ordering": ___
  • "over-engineered": ___
  • "most effective first step": ___
  • "is a valid architectural choice but requires more effort than a first step warrants": ___
  • "poorly calibrated": ___
  • "solves a different problem entirely": ___
  • "proportionate first response": ___
  • "relies on inference rather than explicit matching": ___
  • "directory-bound": ___
  • "doesn't exist" / "non-existent features": ___
  • "incorrectly blame downstream agents that are working correctly": ___
  • "hides valuable context" / "suppresses the error" / "terminates the entire workflow unnecessarily": ___
  • "least privilege" / "over-provisions" / "separation of concerns": ___
  • "attention dilution": ___
  • "shifts burden to developers without improving the system": ___
  • "adds unnecessary complexity": ___
  • "complexity is already stated in the requirements": ___
  • "misconception": ___
Check
  • "probabilistic LLM compliance": A prompt or few-shot fix offered where a guarantee is needed. Wrong when the cost is financial or compliance.
  • "addresses tool availability rather than tool ordering": The option fixes a different problem than the one stated. Wrong. Check which failure the stem describes.
  • "over-engineered": Classifier, trained model or labeled-data pipeline before the cheap fix was tried. Wrong.
  • "most effective first step" (Q2 stem): Pick the lowest-effort fix at the root cause (descriptions, criteria), not the larger valid design.
  • "is a valid architectural choice but requires more effort than a first step warrants": A valid design, but not the answer to a "first step" question.
  • "poorly calibrated": Self-reported confidence as an autonomous trigger. Wrong.
  • "solves a different problem entirely": Sentiment (or similar) does not track the underlying issue. Wrong.
  • "proportionate first response": Prompt-level fix before infrastructure. Right.
  • "relies on inference rather than explicit matching": Unreliable. Deterministic path-glob matching is the answer where files match by type.
  • "directory-bound": Subdirectory CLAUDE.md cannot cover files spread across directories.
  • "doesn't exist" / "non-existent features": Fictional flag or file (CLAUDE_HEADLESS, --batch, .claude/config.json). Wrong.
  • "incorrectly blame downstream agents that are working correctly": The fault is the coordinator's decomposition.
  • "hides valuable context" / "suppresses the error" / "terminates the entire workflow unnecessarily": Generic status, empty success, abort-all. Wrong; structured error context is right.
  • "least privilege" / "over-provisions" / "separation of concerns": Give an agent only the tools its role needs, not all tools.
  • "attention dilution": Split into per-file passes plus an integration pass; a bigger window does not fix it.
  • "shifts burden to developers without improving the system": Pushing the work onto humans is wrong.
  • "adds unnecessary complexity": Match each workload to its API instead of building a hybrid.
  • "complexity is already stated in the requirements": Plan mode now, not later.
  • "misconception": The option states something false (batch results cannot be correlated: custom_id does it).

Quick Reference — final-hour review

One page to read on the morning of the exam.

Exam mechanics

60 items · 120 min (2 min/item) · 4 of 6 scenarios · pass 720/1000 scaled · multiple-choice and multiple-response. Read how many to select.

D1 27% · D2 18% · D3 20% · D4 20% · D5 15%.

Five rules that answer most questions

  1. When a mistake has real consequences, enforce the rule in code. Money, compliance, security, identity checks, required order of steps → prerequisite gate or hook. Not a system prompt instruction, not few-shot examples.
  2. Otherwise, pick the smallest fix that addresses the root cause. The exam guide's answers reject "over-engineered" options (routing classifiers, trained models, extra infrastructure) when rewriting a description or writing explicit criteria would work.
  3. Fix the root cause, not the symptom. If every downstream component works, the problem is upstream. If tool descriptions are thin, fix the descriptions rather than adding examples.
  4. Anything that must survive a handoff goes in structured fields. Sources, error details, case facts and escalation summaries should be fields, not prose the next step has to interpret.
  5. A bigger model or a bigger context window is almost never the answer.

Phrases in the question that decide the answer

Phrase Points to
"must never", "always before", "guaranteed" Programmatic enforcement (gate / hook)
"most effective first step" Lowest-effort root-cause fix
"developers wait for the result", "blocking", "pre-merge" Synchronous API, not batch
"overnight", "weekly", "nightly" Batch API
"spread throughout the codebase", "regardless of location" .claude/rules/ glob
"available to every developer on clone" .claude/commands/ (project)
"multiple valid approaches", "architectural" Plan mode
"single-file", "clear stack trace" Direct execution
"the same session that generated it" Independent review instance
"inconsistent depth", "contradictory across files" Per-file passes + integration pass
"customer asks for a human" Escalate immediately
"policy is silent on" Escalate: policy gap
"multiple matches returned" Ask for another identifier
"values don't sum", "wrong field" Semantic validation, not a stricter schema
"model fabricated a value" Make the field nullable/optional
"information isn't in the document" Retry won't help; send to human review
"job hangs waiting for input" -p
"verbose output pollutes the conversation" context: fork / Explore subagent

Fixed facts

stop_reason: "tool_use" → continue the loop · "end_turn" → stop. On the exam, nothing else ends a loop. (Current API also has max_tokens, stop_sequence, pause_turn, refusal and model_context_window_exceeded; not tested.)

Spawning subagents: the Task tool; allowedTools must include "Task"; parallel = multiple Task calls in one response; subagents inherit nothing. (The current SDK calls it the Agent tool, with Task kept as an alias; the exam says Task.)

tool_choice: "auto" may return text · "any" must call some tool · {"type":"tool","name":"x"} forces one. (Current API also has {"type":"none"}, forcing a text-only reply; not tested. Forced any/tool returns 400 on the newest models and with manual extended thinking.)

Error categories: transient · validation · business · permission. Transient is the retryable one; business violations carry retriable: false plus a customer-friendly explanation. Payload: errorCategory, isRetryable, human-readable description. MCP flag is isError.

Empty result ≠ error. Timeout = error. Zero matches = success.

Tool count: ~4–5 per agent; 18 degrades selection.

Batch API: 50% off · ≤24h · no SLA · no multi-turn tool calling · custom_id correlates and drives selective resubmission.

CI flags: -p / --print · --output-format json · --json-schema. (CLAUDE_HEADLESS and --batch don't exist.)

File and directory map

~/.claude/CLAUDE.md          user-level, NOT shared
~/.claude/commands/          personal slash commands
~/.claude/skills/            personal skills (rename to avoid clashing)
~/.claude.json               user-scope MCP servers (personal/experimental)

CLAUDE.md  or  .claude/CLAUDE.md    project-level, committed
<subdir>/CLAUDE.md                  directory-scoped conventions
.claude/rules/*.md                  topic files; YAML `paths:` globs for conditional loading
.claude/commands/*.md               team slash commands, version-controlled
.claude/skills/<name>/SKILL.md      team skills
.mcp.json                           project-scope MCP servers, ${ENV_VAR} for secrets

SKILL.md frontmatter tested: context: fork · allowed-tools · argument-hint. Commands used: /memory (check which memory files are loaded; exam answer. Current product uses /context for this) · /compact · --resume <session-name> (named sessions exist now via --name/-n or /rename; SDK resume takes an ID) · fork_session.

Current product (not tested): there is also a managed (enterprise) policy CLAUDE.md, a gitignored project-root CLAUDE.local.md (personal, one project only), and a third MCP scope, local (one project, private, not in git).

Built-in tools

Read · Write · Edit · Bash · Grep · Glob. Task 2.5 names all six.

Grep = search file contents. Glob = match file paths/names. Read/Write = whole file. Edit = targeted, needs unique anchor text → on failure fall back to Read + Write. Bash exists, but don't shell out where a dedicated tool does the job. Exploration order: Grep for entry points → Read to follow imports. Don't read everything up front.

Confidence scores: when they are valid

Use Verdict
Agent reports its own confidence and escalates automatically below a threshold Wrong (5.2): poorly calibrated, often confidently wrong on hard cases
Field-level confidence, calibrated against a labeled validation set, used to prioritize limited human reviewers Right (5.5, 4.6)

Few-shot vs explicit criteria vs enforcement

The problem is… The fix is…
The rule doesn't exist or is vague Explicit criteria
The rule exists but ambiguous cases go wrong 2–4 few-shot examples with reasoning
The rule exists, is clear, and must never be violated Programmatic gate or hook

Escalation triggers

Valid: customer asks for a human (escalate immediately, no investigation first) · policy exception or gap · no meaningful progress

Invalid: sentiment analysis · self-reported confidence · "the case seems complex" · turn count

Provenance checklist for multi-source synthesis

Structured claim-to-source mappings (URL, document name, excerpt) kept at every step · publication or collection dates required · conflicts reported with both sources, never silently resolved · separate report sections for well-established and contested findings · coverage annotations for gaps · each content type in a suitable form (tables for financial data, prose for news, lists for technical findings).

Answer-selection procedure (community-derived heuristic)

  1. Find the qualifier in the stem (first, most, must never, least effort).
  2. Label the failure mode (ambiguity, missing rule, ordering, stale context, silent failure).
  3. Fix the earliest controllable layer: description, example, schema, validation, context, boundary.
  4. Compliance, money or ordering: a deterministic gate beats a prompt.
  5. Prefer the option that preserves information and removes the least context.
  6. Eliminate options that rely on the Off the table list below or on fabricated flags and features.

Off the table

Fine-tuning and training custom models · Claude API auth, billing, account management · language- and framework-specific implementation detail · deploying or hosting MCP servers (infrastructure, networking, containers) · Claude's internal architecture and weights · Constitutional AI / RLHF · embedding models and vector DBs · computer use · vision · streaming / SSE · rate limits, quotas, pricing calculations · OAuth and key rotation · cloud provider config · performance benchmarking · prompt caching beyond knowing it exists · tokenization.

An option that depends on one of these is very likely a distractor.

Exam Day

Night before

  • No new material. Skim your error log, the confident-wrong list and the Quick Reference, then stop by early evening.
  • Eat a normal dinner, skip alcohol and keep your caffeine routine unchanged.
  • Bed on time for 7–8 h of sleep. If you can't sleep, rest anyway: one short night hurts less than a night of worry about it (Newbury et al. 2021).
  • Set out ID, water, and (online) clear the room.

Morning

  • Usual breakfast and usual caffeine only. Light movement.
  • 2 minutes of slow breathing before you start.
  • Arrive or log in early (30 min of buffer).

Logistics checklist:

  • [ ] ID name matches your registration exactly.
  • [ ] Pearson VUE system test done (online delivery).
  • [ ] Room and desk clear; route or login planned.
  • [ ] Cancel or reschedule needs 48 h notice (the PDF says 24 h; the live FAQ says 48 h).
  • [ ] Plan for ~135 min in the chair: 120 testing plus check-in, instructions and survey. The score shows on screen at the end.

Pacing

Measure Target
Per item ~2 min
Per 15-item scenario block ~23 min (about 2 min reading the scenario, then ~1.4 min per item)
Soft cap per item 2.5 min, then pick your best answer and move on
Review ~20 min at the end, if navigation allows

Neither exam guide says whether you can go back to earlier items or flag them. Read the tutorial's navigation instructions before item 1.

  • Can go back: four blocks of ~23 min, then ~20 min review (flagged items first, then select-N items), then confirm every item has an answer.
  • Can't go back: fold the review time into each block. Spend it on the close calls as you go.

The platform requiring an answer before moving on (a v0.2 draft note, see the exam overview) only rules out blank answers; it says nothing about going back.

Reading routine

For every item:

  1. Read the instruction line: how many to select.
  2. Read the stem and find the deciding phrase before looking at the options.
  3. Predict the answer, then match it to an option.

Deciding phrases: must never · before any · every time · available to every developer · developers wait for · regardless of directory · the same session · first step · most effective.

Elimination rules

Eliminate by rule, not by how plausible an option sounds.

  • Remove any option that meets a must always requirement with a prompt instruction.
  • Remove any option that adds infrastructure (classifier, trained model, routing layer, bigger model, bigger context window) when a description, criterion or schema field would do.
  • Remove any option that pushes work onto a human or the customer when the system should handle it.

When two options both look right, pick the one that fixes the root cause described in the question. The exam guide's explanations often reject fixes that help but address a different problem.

Select-N items

  • No research exists on strategy for these. Treat each as all-or-nothing.
  • Pick exactly N. Fewer or more loses the item.
  • Judge each option on its own against the stem. Don't pick by pairing.

Changing answers

  • Change an answer only for a reason you can state (a missed phrase, a recalled fact).
  • Don't change on unease alone. Changes are more often wrong-to-right than the reverse (Kruger et al. 2005; USMLE Step 2 CK, N = 27,830).
  • Answer every item. There is no stated penalty for guessing.

Nerves

  • Reappraise arousal as readiness. Tell yourself that a racing heart means your body is preparing.
  • 30–60 s of slow breathing (about 5–6 breaths per minute) if panic hits.
  • The effects are small, but both cost nothing.
  • The scale is criterion-referenced, so a hard item tells you nothing about your score and you can't count misses.

From candidate reports

Community reports, not official.

  • Stems are dense, with a key qualifier buried inside. One word often decides the answer.
  • Pacing is tight at ~2 min per item. A first pass at ~90 seconds, flag and return, works if navigation allows.
  • CI/git with Claude Code (3.6) was heavier than many expected.
  • High practice scores did not guarantee a pass.

Preface — from using Claude Code to building with Claude

You already use Claude Code daily: CLAUDE.md, plan mode, slash commands, MCP servers. Many of the exam's judgement questions can be answered from that experience.

What you haven't done is build the thing. You haven't sent a Messages API request, written the while loop that turns a stateless call into an agent, authored an MCP server, or designed a schema that stops a model fabricating values. The exam assumes that work and asks about stop_reason, tool_result, AgentDefinition, and tool_choice, the machinery the product hides from you.

You have already operated most of the architectures the exam tests, from the outside, so the work is attaching each exam term to behaviour you have seen.

What you already know, and what it is called here

What you do in Claude Code What the exam calls it Chapter
Press enter, watch it grep, read, edit, run tests The agentic loop, driven by stop_reason 2
The tool output indented in your transcript tool_result blocks in a user message 2
A Bash command fails and Claude adapts is_error: true on a tool result (the failure comes back as readable content, not an exception) 4
Your CLAUDE.md The system parameter, and a three-level configuration hierarchy 2, 9
Approving or denying a permission prompt A human-in-the-loop gate between the model asking and your code executing; PreToolUse interception 2, 6
Installing an MCP server from a config file Tool definitions crossing the API boundary; .mcp.json project scope vs ~/.claude.json user scope 3, 8
Claude reaching for the wrong one of two similar tools Tool descriptions as the primary selection mechanism 3
Claude spawning subagents on its own Orchestrator-workers; the Task tool and AgentDefinition; isolated subagent context 1, 5
A subagent that cannot see your main conversation Subagents inherit nothing not written into their prompt 5, 14
Running /compact and watching context shrink The messages array being rewritten; what survives summarization 2, 14
Plan mode A programmatic gate between steps of a chain 1, 10
.claude/commands/ and .claude/skills/ Project vs user scope; SKILL.md frontmatter 10
--resume on yesterday's session Session state, and tool results that were true when fetched 7
The Explore subagent Context isolation for verbose discovery 10, 14
claude -p in a shell script Headless invocation for CI 11

The four things you have never done

  1. Written the loop. Start with Chapter 2, and do its exercises as well as reading it. Writing the loop yourself, about forty lines, turns half of Domain 1 from reasoning into recognition.
  2. Authored a tool. A tool description is a prompt competing for the model's attention, not documentation (Chapter 3). In Chapter 8 you write an MCP server as well as use one.
  3. Designed for structured output. tool_use with a JSON schema eliminates malformed JSON and does nothing about line items that don't sum (Chapter 13).
  4. Made behaviour reliable at volume. Chapter 12 covers explicit categorical criteria, few-shot examples that show reasoning, and why "be more conservative" never fixes precision.

How the book is arranged

The book has fifteen chapters, and each of the 30 task statements has exactly one home chapter.

  • 1–8 build the agent: patterns, loop, tools, errors, orchestration, hooks, sessions, MCP.
  • 9–11 cover Claude Code configuration. You'll move fastest here, because you already have the concepts and only need the vocabulary and exact paths.
  • 12–15 cover prompting, structured output, context, and Domain 5's reliability judgements.

If you are short on time, read Chapter 2 in full, read Chapter 6 for the rule behind tasks 1.4 and 1.5, and skim Chapters 9–11 for the configuration details.

Each chapter ends with What the exam tests (task statements and judgement calls) and Exercises.

Where the exam guide and the product disagree

The guide dates from July 2026, and Claude Code has moved since. Seven points no longer match current docs:

  • Wrong semantics (3): allowedTools gating subagent delegation; allowed-tools restricting a skill's tools; Read + Write as the fallback when Edit hits a non-unique match.
  • Overtaken (3): Task renamed to Agent; /memory reporting what's loaded; MCP having two scopes rather than three.
  • Cosmetic (1).

On these points your instincts will be right about the product and wrong for the exam, so answer the guide's version. Chapters keep the guide's answer in the main text and add a marked note on current reality. The full list with sources is in REVIEW-FINDINGS.md at the repo root. Read it once before the exam.

Six of the seven concern Claude Code and Agent SDK configuration, and Domains 4 and 5 are almost unaffected.

A note on the code

Substantive examples come in Python then TypeScript, using the exam scenarios' tool names (get_customer, lookup_order, process_refund, escalate_to_human, extract_invoice). Four of the six scenarios appear on your form, and the same mechanism recurs across them in different settings. A deterministic gate on a refund is the same idea as a gate in a CI pipeline, and the exam can test it in whichever setting you know less well.

Examples use claude-sonnet-5, a still-available model that accepts forced tool_choice. The newest models (Sonnet 5.5, Opus 5.5, Fable 5.1) reject forced tool_choice with a 400, which the exam does not test.

Chapter 1 — Agents, workflows, and knowing which one you need

A support agent refunds the wrong account because identity verification was requested in a prompt rather than enforced in code. A 14-file review contradicts itself because one call was asked to do 14 jobs. The central question is how much of the control flow belongs in your code and how much is left to the model's judgement. Too little structure gives you non-determinism where you needed guarantees. Too much gives you a brittle, expensive state machine.

Claude Code is itself an agent built from the primitives this chapter covers: a loop, tools, a system prompt, and gates. The vocabulary comes from Anthropic's "Building effective agents" (workflow vs agent, the pattern catalogue) and the Agent SDK work (the loop).

The augmented LLM

The building block is the augmented LLM: a model plus tools, retrieval, and memory.

Underneath it is the Messages API, a single stateless call. It takes a system prompt, alternating user and assistant messages, and optional tool definitions, and returns one assistant message. It has no memory, cannot run anything, and cannot read files. Everything Claude Code does beyond that (reading the repo, editing, running tests, remembering earlier steps) is scaffolding built around this call.

Each augmentation is a design decision:

  • Tools set what the model can do, and their descriptions steer what it will do. A description is a prompt (Chapter 3). When Claude Code picks the wrong MCP tool, the description is at fault.
  • Retrieval sets what the model knows when it acts: pre-fetched (CLAUDE.md, injected up front) or fetched on demand (the file Claude opens on turn six).
  • Memory sets what survives between turns and sessions. Subagents inherit nothing except their prompt, which is why a Claude Code subagent can't see your main thread.

For each exam scenario, ask which augmentation matters most. The support agent's refund depends on memory of a verified customer ID. The research system's hard problem is what the coordinator carries between subagents. Extraction barely needs tools, and its success depends on schema design.

Workflows and agents

Exam options are written against Anthropic's exact definitions:

  • Workflow: LLMs and tools orchestrated through predefined code paths. You write the sequence, and the model fills in the steps.
  • Agent: the LLM dynamically directs its own process and tool usage. You give it a goal and tools, and it decides each next step from what it just learned.
  • Agentic systems: the umbrella term for both. It does not imply autonomy.

In Claude Code, a bare request is an agent, since nobody told it to grep, read three files, edit, then test. A slash command whose body lists the steps in order is a workflow. The model and tools are the same in both, and only the location of the control flow differs.

The distinction is architectural. Task statement 1.1 tests it as model-driven decision-making versus pre-configured decision trees or fixed tool sequences. Neither is the default answer.

The agentic loop

Anthropic describes the loop as gather context → take action → verify work → repeat.

  • Gather: fetch what's needed rather than being handed everything, e.g. grep/tail on a log instead of reading all of it.
  • Act: call a tool that changes something.
  • Verify: check the result against the goal.

Verification is skipped because in most domains it is expensive or impossible to automate. Nothing cheaply tells you whether a summary is good or a report has gaps. The loop decays into gather-act-gather-act, and errors compound unnoticed.

Coding became the first strong agent domain because it has free, fast, deterministic verifiers: compiler, type checker, tests. Outside coding, ask what is my test suite? It might be a JSON Schema validator (Chapter 13), or a second model call with a rubric (evaluator-optimizer, below). If no verifier exists, keep the design a workflow with human review rather than an autonomous agent.

Mechanically, the loop is a while you write. The API is stateless, so you own the message list, resending it every iteration. Each response's stop_reason says whether Claude is done or wants a tool run. Termination is a protocol signal, not a judgement call. Chapter 2 builds the loop.

The five workflow patterns

Pattern Shape Choose it when
Prompt chaining Fixed sequence of calls with programmatic gates between steps Subtasks are fixed and known in advance; you trade latency for per-step accuracy
Routing Classify input, send to a specialised prompt/model/toolset Inputs fall into distinct categories and misrouting is rare and recoverable
Parallelization Concurrent calls, aggregated Subtasks are independent (sectioning), or you want confidence from repeats (voting)
Orchestrator-workers A central model decomposes at runtime, delegates, synthesises Subtasks depend on what the input contains
Evaluator-optimizer Generator + critic in a loop until a bar is met Clear evaluation criteria exist and iteration measurably improves output
Autonomous agent Goal + tools + loop Step count is unpredictable, the flow can't be drawn in advance, and the environment gives actionable feedback

Details by pattern:

  • Chaining: the gates matter most. Without them you have a long prompt split across calls, and with them a malformed extraction is caught at step 2 instead of step 5. Plan mode is a gate, because nothing executes until the plan clears a check. The blueprint's example is per-file review passes plus a separate cross-file integration pass.
  • Parallelization has two forms. Sectioning runs independent subtasks at once, e.g. one instance answers while another screens for policy violations. Voting runs the same task several times to raise recall. A common mistake is requiring 2-of-3 agreement, which suppresses real findings rather than filtering false ones.
  • Parallelization vs orchestrator-workers: ask whether you can write the subtask list before seeing the input. If you can, it's parallelization and the split belongs in code. If you can't, it's orchestrator-workers and a model must decompose. When Claude Code decides for itself to spawn subagents, that is orchestrator-workers.
  • Orchestrator-workers fail at the decomposition step, not in the workers. Chapter 5 covers it.
  • Evaluator-optimizer loops uselessly when no reliable criterion exists, because the critic can't tell good from bad.
  • Autonomous agents trade cost and latency for adaptability, so they need guardrails: sandboxing, checkpoints, human review at consequential boundaries.

Choosing

Ask in order:

  1. Can you draw the decision tree? If it's finite on a whiteboard, build the workflow. Readable code is cheaper to debug than a reasoning trace and doesn't vary between runs.
  2. Are the subtasks predictable? That is, can you enumerate them before seeing the input? This settles parallelization vs orchestrator-workers, and fixed pipeline vs dynamic decomposition.
  3. Is the task worth the tokens? An agent resends its growing history every iteration, so cost grows super-linearly with steps, and subagents multiply it. The exam tests no exact multiplier, only that more steps and more agents cost more.
  4. What does an error cost, and would you notice? Cost and discoverability are separate axes. A wrong refund is expensive and visible, while a silent coverage gap is cheap per incident and invisible. Low discoverability argues for structure and explicit verification even when errors look cheap.

The overriding rule is to pick the simplest solution that works and add complexity only when it demonstrably helps. Often a single well-specified call with good examples beats any agentic design.

Task statement 1.6

Task 1.6 (design task decomposition strategies) comes down to one choice, between a fixed sequential pipeline (prompt chaining) and dynamic adaptive decomposition based on intermediate findings.

Prompt chaining Dynamic decomposition
Subtasks known before you start Yes No
Who decomposes Your code The model, at runtime
Blueprint's example Per-file review passes plus a cross-file integration pass "Add comprehensive tests to a legacy codebase"
Cost of getting it wrong Rigid: unanticipated input falls through Coverage gaps from a narrow initial split
Failure to look for Missing gates between steps Orchestrator's decomposition, not the workers

The legacy-tests example shows what "adaptive" means. You map the structure, identify high-impact areas from that map, then build a prioritised plan that keeps adapting as dependencies come to light. The plan can't be written up front because it depends on the codebase.

The same task, two ways

The example task is triaging a support ticket. The workflow fixes the path: classify, fetch the records the class implies, draft. Each helper is one Messages API call, and the control flow between them is your code.

Python

async def triage_workflow(ticket: str) -> Resolution:
    # Step 1 — classify. Forced tool call guarantees structured output.
    category = await classify_ticket(ticket)  # "billing" | "shipping" | "account"

    # Programmatic gate: unroutable tickets never reach the drafting step.
    if category.confidence < 0.7:
        return escalate_to_human(ticket, reason="low_confidence_classification")

    # Step 2 — predetermined context fetch per category.
    if category.name == "billing":
        context = await get_customer(category.customer_id)
    elif category.name == "shipping":
        context = await lookup_order(category.order_id)
    else:
        context = await get_customer(category.customer_id)

    # Step 3 — draft against fetched context only.
    return await draft_resolution(ticket, context)

TypeScript

async function triageWorkflow(ticket: string): Promise<Resolution> {
  const category = await classifyTicket(ticket);

  if (category.confidence < 0.7) {
    return escalateToHuman(ticket, "low_confidence_classification");
  }

  const context =
    category.name === "shipping"
      ? await lookupOrder(category.orderId)
      : await getCustomer(category.customerId);

  return draftResolution(ticket, context);
}

The agent version gives a goal and constraints, then loops on stop_reason. The message list is the conversation, SYSTEM is the system prompt, and TRIAGE_TOOLS stands for tool definitions (name, description, input_schema), which is what a .mcp.json entry becomes at the API boundary. Chapter 2 shows their real shape.

Python

TRIAGE_TOOLS = [get_customer, lookup_order, process_refund, escalate_to_human]

SYSTEM = """Resolve the customer's ticket. Verify the customer's identity with
get_customer before any financial operation. Escalate when the request falls
outside documented policy or the customer asks for a human."""

async def triage_agent(ticket: str) -> Resolution:
    messages = [{"role": "user", "content": ticket}]

    while True:
        response = await client.messages.create(
            model="claude-sonnet-5",
            system=SYSTEM,
            tools=TRIAGE_TOOLS,
            messages=messages,
            max_tokens=2048,
        )
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason == "end_turn":
            return Resolution.from_content(response.content)

        results = [await execute_tool(block) for block in response.content
                   if block.type == "tool_use"]
        messages.append({"role": "user", "content": results})

TypeScript

async function triageAgent(ticket: string): Promise<Resolution> {
  const messages: MessageParam[] = [{ role: "user", content: ticket }];

  for (;;) {
    const response = await client.messages.create({
      model: "claude-sonnet-5",
      system: SYSTEM,
      tools: TRIAGE_TOOLS,
      messages,
      max_tokens: 2048,
    });
    messages.push({ role: "assistant", content: response.content });

    if (response.stop_reason === "end_turn") {
      return resolutionFrom(response.content);
    }

    const toolResults = await Promise.all(
      response.content
        .filter((b): b is ToolUseBlock => b.type === "tool_use")
        .map(executeTool),
    );
    messages.push({ role: "user", content: toolResults });
  }
}

The tools and model are unchanged, and only the decomposition has moved from if statements into the model. This version handles a ticket that is both a billing dispute and a shipping complaint, which the workflow can't express without a new branch. The cost is non-deterministic ordering, so the identity-verification rule in the prompt is not sufficient. Prompts have a non-zero failure rate, just as CLAUDE.md instructions are usually but not always followed. A rule with financial consequences needs a programmatic prerequisite gate or an interception hook (Chapter 6).

In production the answer is usually a composite: an agent loop for the investigative middle, with hard gates around operations that must not go wrong. Patterns compose (routing feeds a chain; an orchestrator wraps evaluator-optimizer workers).

What the exam tests

  • Task 1.6: choose fixed pipeline or dynamic decomposition and justify it from the scenario's predictability. The two blueprint cases are per-file plus cross-file passes for a large PR (the stated cause is attention dilution), and map-then-prioritise for legacy-code tests (the plan can't exist until the structure is known).
  • Task 1.1: model-driven decisions versus pre-configured tool sequences.
  • Task 1.2: a coverage gap with healthy subagents is fixed in the decomposition (Chapter 5).
  • Distractors are competent engineering at the wrong layer: a bigger context window for an attention problem, a consensus rule that suppresses real findings, an extra agent where a gate would do.
  • Scoring rule: proportionality. Name the described failure and pick the least complex fix. When two options both work, the simpler one is correct.

Exercises

  1. Build both triage versions against four mock MCP tools. Run 20 tickets (10 single-concern, 10 with two concerns), logging tokens and tool calls per ticket, and find the ticket the workflow mishandles. Do this one first, because writing 40 lines of tool-use loop by hand teaches more than reading.
  2. For the 14-file review, implement one call over all files, then per-file passes plus an integration pass. Feed both a PR with one planted cross-file bug and three single-file bugs, and record what each catches.
  3. Write an orchestrator's decomposition step for "research the impact of AI on creative industries" and inspect the subtasks before any worker runs. If they're all visual arts, rewrite the coordinator prompt to specify coverage criteria rather than procedure. Claude Code hides this step from you.
  4. For one exam scenario, write its verifier in one sentence. If you can't, write what would have to be true for one to exist, and decide whether it should be an agent at all.

Chapter 2 — The agentic loop in code

The loop that drives an agent like Claude Code fits in about forty lines. The backtracking and recovery you see in Claude Code come from Claude deciding each next step by reasoning over the conversation history. The loop only ferries messages and checks one field. This chapter builds that loop by hand (task statement 1.1) against the customer-support scenario: get_customer, lookup_order, process_refund, escalate_to_human.

The Messages API, from zero

One endpoint, POST /v1/messages. The request fields that matter:

Field What it is
model Model identifier.
max_tokens Ceiling on reply length.
system Persistent instructions (role, policy, tone). Separate from messages because it is not part of the conversation.
messages The conversation, oldest first. Each entry has role ("user" / "assistant") and content.
tools Tool definitions: name, natural-language description, input_schema (JSON Schema).

content can be a plain string, but its real form is a list of typed blocks:

  • text: prose.
  • image: an image.
  • tool_use (assistant messages only): Claude asking you to run something.
  • tool_result (user messages only): you handing back what happened.

One message can mix several block types, so a loop has to handle them all.

The response is an assistant message: role: "assistant", a content list of blocks, and a stop_reason saying why generation ended. Because it is already shaped like a message, you append it straight onto messages.

tools declares tools without installing anything. Nothing runs on Anthropic's side: Claude emits a tool_use block and stops, you run the function, and you send the result back. Execution is local, so your code decides whether a tool runs at all.

The API is stateless. There is no session and no server-side memory. Every request re-sends the whole messages array, and the model knows only what is in it. /compact rewrites that array.

One turn of the cycle

  1. Send system, the full messages array, and tools.
  2. Claude replies with an assistant message: text blocks, possibly tool_use blocks, and a stop_reason. The stop_reason, not the content, decides what happens next.
  3. If stop_reason == "tool_use", append the assistant message in full, run every requested tool, append one user message holding all the tool_result blocks, and loop.
  4. If stop_reason == "end_turn", stop. The text blocks are the answer.

The tool output you see indented in a Claude Code transcript is how it renders tool_result blocks. Memorise both shapes:

Field tool_use (assistant) tool_result (user)
type "tool_use" "tool_result"
identifier id, server-generated, e.g. toolu_01A… tool_use_id, which must match the id character for character
payload name (tool name) and input (object matching the schema) content, either a string or a list of text/image blocks
failure signal — is_error: true, optional; the message goes in content

The API enforces three pairing rules and rejects any violation with a 400:

  • Every tool_use must be answered by a tool_result in the immediately following message. Return two results for three calls and the request is rejected.
  • Every tool_result needs a preceding tool_use with that id. This is what breaks when you truncate history to save context and the conversation ends up opening on a tool_result.
  • All results go in one user message, and the tool_result blocks must come first in its content. Any extra text goes after them.

Human-in-the-loop review sits between tool_use and execution, when Claude has asked but nothing has run yet. Approving runs the tool, and denying sends back a tool_result saying so.

The loop, written out

Python

import json
import time
import anthropic

client = anthropic.Anthropic()

TOOLS = [
    {
        "name": "get_customer",
        "description": (
            "Retrieve a customer record by email or account ID. Returns the "
            "verified customer_id, tier, and account status. Call this before "
            "any order lookup or refund."
        ),
        "input_schema": {
            "type": "object",
            "properties": {"identifier": {"type": "string"}},
            "required": ["identifier"],
        },
    },
    {
        "name": "lookup_order",
        "description": "Fetch an order by order_id, including line items, "
                       "ship date, and current refund eligibility.",
        "input_schema": {
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    },
    {
        "name": "process_refund",
        "description": "Issue a refund against a verified customer and order. "
                       "Amount is in USD cents.",
        "input_schema": {
            "type": "object",
            "properties": {
                "customer_id": {"type": "string"},
                "order_id": {"type": "string"},
                "amount_cents": {"type": "integer"},
                "reason": {"type": "string"},
            },
            "required": ["customer_id", "order_id", "amount_cents", "reason"],
        },
    },
    {
        "name": "escalate_to_human",
        "description": "Hand the case to a human agent with a structured "
                       "summary. Use when policy blocks resolution or the "
                       "customer asks for a person.",
        "input_schema": {
            "type": "object",
            "properties": {
                "customer_id": {"type": "string"},
                "root_cause": {"type": "string"},
                "recommended_action": {"type": "string"},
            },
            "required": ["customer_id", "root_cause", "recommended_action"],
        },
    },
]

TOOL_IMPLS = {
    "get_customer": get_customer,
    "lookup_order": lookup_order,
    "process_refund": process_refund,
    "escalate_to_human": escalate_to_human,
}


def execute_tool(block):
    """Return a tool_result block for one tool_use block."""
    try:
        result = TOOL_IMPLS[block.name](**block.input)
        return {
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": json.dumps(result),
        }
    except Exception as exc:
        return {
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": f"{type(exc).__name__}: {exc}",
            "is_error": True,
        }


def run_agent(user_message, max_iterations=25, budget_seconds=120):
    messages = [{"role": "user", "content": user_message}]
    deadline = time.monotonic() + budget_seconds

    for iteration in range(max_iterations):
        if time.monotonic() > deadline:
            raise TimeoutError("wall-clock budget exhausted")

        response = client.messages.create(
            model="claude-sonnet-5",
            max_tokens=2048,
            system=SUPPORT_SYSTEM_PROMPT,
            tools=TOOLS,
            messages=messages,
        )
        log.info("iteration=%d stop_reason=%s", iteration, response.stop_reason)

        # The assistant message goes back verbatim, text blocks and all.
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason == "tool_use":
            tool_uses = [b for b in response.content if b.type == "tool_use"]
            results = [execute_tool(b) for b in tool_uses]
            messages.append({"role": "user", "content": results})
            continue

        if response.stop_reason == "end_turn":
            return response

        if response.stop_reason == "max_tokens":
            raise RuntimeError("response truncated; raise max_tokens")

        raise RuntimeError(f"unhandled stop_reason: {response.stop_reason}")

    raise RuntimeError("iteration cap hit — investigate, do not just raise it")

TypeScript

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

const client = new Anthropic();

type Impl = (input: any) => Promise<unknown>;
const toolImpls: Record<string, Impl> = {
  get_customer: getCustomer,
  lookup_order: lookupOrder,
  process_refund: processRefund,
  escalate_to_human: escalateToHuman,
};

async function executeTool(
  block: Anthropic.ToolUseBlock,
): Promise<Anthropic.ToolResultBlockParam> {
  try {
    const result = await toolImpls[block.name](block.input);
    return {
      type: "tool_result",
      tool_use_id: block.id,
      content: JSON.stringify(result),
    };
  } catch (err) {
    return {
      type: "tool_result",
      tool_use_id: block.id,
      content: err instanceof Error ? err.message : String(err),
      is_error: true,
    };
  }
}

export async function runAgent(
  userMessage: string,
  { maxIterations = 25, budgetMs = 120_000 } = {},
): Promise<Anthropic.Message> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: userMessage },
  ];
  const deadline = Date.now() + budgetMs;

  for (let iteration = 0; iteration < maxIterations; iteration++) {
    if (Date.now() > deadline) throw new Error("wall-clock budget exhausted");

    const response = await client.messages.create({
      model: "claude-sonnet-5",
      max_tokens: 2048,
      system: SUPPORT_SYSTEM_PROMPT,
      tools: TOOLS,
      messages,
    });
    log.info({ iteration, stop_reason: response.stop_reason });

    messages.push({ role: "assistant", content: response.content });

    if (response.stop_reason === "tool_use") {
      const toolUses = response.content.filter(
        (b): b is Anthropic.ToolUseBlock => b.type === "tool_use",
      );
      const results = await Promise.all(toolUses.map(executeTool));
      messages.push({ role: "user", content: results });
      continue;
    }

    if (response.stop_reason === "end_turn") return response;
    if (response.stop_reason === "max_tokens") {
      throw new Error("response truncated; raise max_tokens");
    }
    throw new Error(`unhandled stop_reason: ${response.stop_reason}`);
  }

  throw new Error("iteration cap hit — investigate, do not just raise it");
}

The only branch is on stop_reason. The loop never inspects Claude's text, keeps no whitelist of which tool may follow which, and has no workflow state machine. Swap in Read/Bash/Edit/Grep, put a permission gate before execute_tool, and you have the skeleton of Claude Code's loop.

stop_reason is the control signal

Value Meaning What the loop does
"tool_use" The turn ended because Claude wants tools run. At least one tool_use block is present. Execute all of them, append results, iterate.
"end_turn" Claude finished naturally. No tools pending. Terminate; the text blocks are the answer.
"max_tokens" The response hit the max_tokens ceiling mid-generation and was truncated. Treat as an error condition. Any partial tool_use block is unusable, so do not execute it.
"stop_sequence" Hit one of your stop_sequences. A requested stop. Treat like "end_turn".
"model_context_window_exceeded" Claude 4.5+: the context window filled mid-generation. Older models return a validation error instead. The conversation is too big. Trim or compact history; raising max_tokens won't help.

Continue on "tool_use", terminate on "end_turn", and handle unknown values in an explicit else that raises. A loop that falls through treats a new stop reason as completion.

Strict tool use and thinking blocks

  • "strict": true next to a tool's input_schema makes schema-valid arguments guaranteed rather than merely very likely. The only cost is that one field. Chapter 3 covers the JSON Schema subset it supports.
  • Extended thinking: when a tool_use block sits alongside thinking or redacted_thinking blocks, send those thinking blocks back unmodified and in the same position on the next turn. Editing, reordering, or dropping one breaks its signature, and the request fails.

Why history is the agent's memory

Tool results are appended to history so the model can reason about the next action, and this is how the agent accumulates knowledge. In the support case:

  1. get_customer("[email protected]") → history now holds customer_id: "cus_8812", tier: "enterprise".
  2. Claude reads that and calls lookup_order → shipped 19 days ago, inside a 30-day window.
  3. Claude calls process_refund with arguments it derived from history. You did not route them.

Ways to break it:

  • Drop the assistant message, keep the results. 400: a tool_result with no tool_use.
  • Keep the assistant message, drop the results. 400. If it somehow got through, Claude would re-request the same tool forever.
  • Summarise the result into your own prose. You have become the decision-maker without meaning to, filtering what the model can reason over.

All three treat history as a log when it is state. The same applies to /compact: whatever it drops, the agent no longer knows. That is why a long session "forgets" a decision from an hour ago.

Model-driven decisions versus decision trees

The exam contrasts Claude choosing the next tool from context with a pre-configured decision tree or fixed sequence. A fixed sequence (always get_customer → lookup_order → branch on amount) assumes every case has the same shape. Real cases don't:

  • Customer gives an order number → no email lookup needed.
  • Three disputed charges → three order lookups.
  • "Just let me talk to someone" → escalate_to_human, nothing else.

The loop above handles all of these with no routing code. You control the envelope: which tools exist, what their schemas allow, and which calls a hook may block (Chapter 6). Claude chooses within it. Plan mode is an example, since it narrows the envelope to read-only tools and Claude still decides everything inside it.

When ordering must be guaranteed, use a programmatic prerequisite gate (task 1.4, e.g. block process_refund until get_customer returns a verified id). Don't hard-code the call order in the loop.

Three anti-patterns the guide names

These three come from task 1.1's skills list.

Parsing natural language for termination

Python

# WRONG
text = "".join(b.text for b in response.content if b.type == "text")
if "let me know if" in text.lower() or "anything else" in text.lower():
    return response

TypeScript

// WRONG
const text = response.content
  .filter((b) => b.type === "text")
  .map((b) => (b as Anthropic.TextBlock).text)
  .join("");
if (/let me know if|anything else/i.test(text)) return response;

This fails both ways. "Let me know if you need anything else" can arrive alongside a pending tool_use, so the loop exits early and the refund never happens. "Your refund of $84.99 has been submitted." matches no phrase, so a finished conversation keeps looping.

An iteration cap as the stopping mechanism

Python

# WRONG
for _ in range(5):
    response = client.messages.create(...)
    messages.append({"role": "assistant", "content": response.content})
    tool_uses = [b for b in response.content if b.type == "tool_use"]
    if not tool_uses:
        break
    messages.append({"role": "user", "content": [execute_tool(b) for b in tool_uses]})
return response   # after 5 turns, whatever we have

TypeScript

// WRONG
for (let i = 0; i < 5; i++) {
  response = await client.messages.create({ /* ... */ });
  messages.push({ role: "assistant", content: response.content });
  const toolUses = response.content.filter((b) => b.type === "tool_use");
  if (toolUses.length === 0) break;
  messages.push({
    role: "user",
    content: await Promise.all(toolUses.map(executeTool)),
  });
}
return response; // after 5 turns, whatever we have

A 5-turn finish and a runaway both leave through the same return, and nothing tells them apart. A three-issue billing dispute that needs 8–9 turns is silently cut off. Keep a cap, but exceeding it must raise an error, not return.

Assistant text as a completion indicator

Python

# WRONG
if any(b.type == "text" for b in response.content):
    return response

TypeScript

// WRONG
if (response.content.some((b) => b.type === "text")) return response;

This sounds structural, but a tool_use response often carries text too. It is the sentence of narration you see above every tool call in Claude Code:

{
  "stop_reason": "tool_use",
  "content": [
    { "type": "text",
      "text": "I've confirmed Dana's account. Let me pull up order SO-44120." },
    { "type": "tool_use", "id": "toolu_01Xy…", "name": "lookup_order",
      "input": { "order_id": "SO-44120" } }
  ]
}

tool_choice and the loop

Value Behaviour Loop impact
{"type": "auto"} (default) Claude decides whether to call a tool. Either stop reason is possible. This is the agentic setting.
{"type": "any"} Claude must call some tool, its choice which. Guarantees "tool_use", so the loop cannot terminate on this turn. Use it to stop the model answering conversationally when an action is required.
{"type": "tool", "name": "get_customer"} Claude must call that named tool. Also guarantees "tool_use", with the tool fixed.
{"type": "none"} Claude must not call a tool. Forces a text-only "end_turn".

Forcing applies per turn, not to the whole loop. Force get_customer on iteration one, then go back to auto. If you leave it forced, the loop can never reach "end_turn".

Current product (not tested): on the newest models (Sonnet 5.5, Opus 5.5, Fable 5.1, Mythos 5.1), any and tool return a 400; only auto and none work. Use auto plus strict: true tool use, or output_config.format. Manual extended thinking also rejects any/tool, and a forced call skips thinking. Older models (e.g. Sonnet 5, used in these examples) accept forced use.

Parallel tool calls

One assistant turn can hold several tool_use blocks, for example two lookup_order calls for two disputed orders. It is the same as Claude Code firing three Reads at once. Run them concurrently (Promise.all, or asyncio.gather for slow I/O; the Python listing runs them sequentially for clarity) and return every result in one user message.

Claude parallelises only calls it believes are independent, and it can be wrong when one call's input should come from another's output. Safeguards:

  • State prerequisites in descriptions, e.g. "process_refund needs a verified customer_id from get_customer". This nudges the model to sequence the calls itself.
  • Gate the dependent tool programmatically when correctness can't rely on the model. A premature call then gets a structured error it can recover from.
  • disable_parallel_tool_use alongside tool_choice caps a response at one tool call. The cost is latency.

tool_use_id binds each result to its call. Keeping results in order is good hygiene but not required for validity.

Guardrails that are not termination logic

Only stop_reason decides that the task is complete. Separate operational guardrails decide that the loop is no longer allowed to run:

  • Max iterations bounds cost and catches cycles, such as retrying a tool that fails the same way every time. Exceeding it should raise, alert, and keep the transcript. Never return a partial answer as final.
  • Wall-clock budget covers what an iteration count can't. One slow MCP call can take 90 s inside a single iteration.
  • Structured logs record iteration, stop_reason, tool names, and durations, so an incident has a trace to read. Chapter 5 makes the same observability argument for routing subagent traffic through a coordinator.

The same loop, at a higher level

The Claude Agent SDK's query() runs this loop for you and yields typed messages. It is the engine Claude Code runs on.

MCP tool names are namespaced mcp__<server>__<tool>. <server> is the key in your config, not the server's internal name. Server key support plus tool get_customer gives mcp__support__get_customer. The namespacing exists because two servers can both expose search. Rename the config key and every allowlist entry for it breaks, which is the usual cause of a tool that "exists but Claude never calls".

Python

from claude_agent_sdk import query, ClaudeAgentOptions

options = ClaudeAgentOptions(
    system_prompt=SUPPORT_SYSTEM_PROMPT,
    mcp_servers={"support": {"command": "node", "args": ["./support-mcp.js"]}},
    allowed_tools=[
        "mcp__support__get_customer",
        "mcp__support__lookup_order",
        "mcp__support__process_refund",
        "mcp__support__escalate_to_human",
    ],
)

async for message in query(
    prompt="Dana ([email protected]) says order SO-44120 arrived damaged.",
    options=options,
):
    log.info("%s", message)

TypeScript

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Dana ([email protected]) says order SO-44120 arrived damaged.",
  options: {
    systemPrompt: SUPPORT_SYSTEM_PROMPT,
    mcpServers: {
      support: { command: "node", args: ["./support-mcp.js"] },
    },
    allowedTools: [
      "mcp__support__get_customer",
      "mcp__support__lookup_order",
      "mcp__support__process_refund",
      "mcp__support__escalate_to_human",
    ],
  },
})) {
  log.info(message);
}

The SDK builds tools from the MCP servers, branches on stop_reason, dispatches each tool_use, and batches the results, all as you did by hand. It also adds permission modes, hooks, and subagents. For your own code:

  • A custom handler that throws kills the loop, and Claude never sees the error. Returning an error result (isError) puts the failure into history, where the agent can react. It is is_error one level up.
  • maxTurns/max_turns defaults to unlimited, so the iteration cap and wall-clock budget are still your job. Retrying failed HTTP requests belongs to the client, not the loop.

What the exam tests

  • Task 1.1, mostly diagnostic: "this loop misbehaves; why?" It has three knowledge bullets: the stop_reason lifecycle, results appended to history, and model-driven decisions versus decision trees.
  • Completion determined by scanning text, hitting an iteration cap, or the presence of prose is wrong. Continue on "tool_use", terminate on "end_turn".
  • Expect an item that turns on a tool_use response that also contains narration text.
  • Task 2.3 tool_choice: "any" guarantees an action instead of chat. A forced named tool pins the first step, then you return to auto.
  • Task 1.4: enforcing order with a gate is a different design from hard-coding the call order in the loop.
  • Streaming, token counting, and rate limits are out of scope.

Exercises

  1. Send one request with no tools and print the raw response: find role, content, the block types, and stop_reason. Add one tool and a prompt that needs it, then diff the two responses.
  2. Implement run_agent from scratch, in your less-used language, against stub tools. Don't look at the listing until it runs, then diff and account for every difference.
  3. Break it four ways and record what happens: omit the assistant message; return one result for two tool_use blocks; change one character of a tool_use_id; set max_tokens to 64. Which give a 400, which give a silent behaviour change, which loop forever?
  4. Add a stdin approval prompt before process_refund. On refusal, return a tool_result saying a human denied it, and watch what Claude does. That is the same mechanism as Claude Code's permission prompt.
  5. Send a message disputing three orders, and log the tool_use blocks per turn. Then set disable_parallel_tool_use and compare iterations and wall-clock time.
  6. Write the smallest conversation that makes the iteration-cap anti-pattern silently return an incomplete resolution. Keep the transcript.

Chapter 3 — Designing tools Claude can choose between

Claude Code's built-in tools are described in detail, which is probably a large part of why it picks between Grep, Glob, and Edit reliably. Write two tools you think are obviously distinct and you may see coin-flip selection between them. The exam's position is that tool descriptions are the primary mechanism for tool selection, and minimal descriptions produce unreliable selection among similar tools.

What a tool definition is

A tool definition has three fields: name, description, input_schema (a JSON Schema object). The model sees nothing else. Anything you know about the tool (which database it hits, what ID it needs, overlapping fields) is invisible unless it's in the description.

The description is prompt text. It is injected into context at selection time and read like any other instruction, by a reader who can't see the source. If you treat it as an API contract, you'll write two lines where you needed twenty.

Python

extract_data_points = {
    "name": "extract_data_points",
    "description": (
        "Extract structured numeric and categorical data points from a research "
        "document that has already been loaded. Returns a list of records, each with "
        "a field name, a value, a unit where applicable, and the page and paragraph "
        "the value was read from.\n\n"
        "Input: document_id from load_document, plus the field names to extract.\n"
        "Use this when you need specific values out of a document — revenue figures, "
        "sample sizes, publication dates, dosage tables.\n"
        "Do NOT use this to understand what a document argues; use summarize_content. "
        "Do NOT use this to check whether an external claim is supported; use "
        "verify_claim_against_source.\n"
        "Edge case: if a requested field is absent, the record is returned with a null "
        "value and a reason, rather than being omitted."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "document_id": {
                "type": "string",
                "description": "Identifier returned by load_document.",
            },
            "field_names": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Field labels to extract, e.g. ['sample_size', 'p_value'].",
            },
        },
        "required": ["document_id", "field_names"],
    },
}

TypeScript

const extractDataPoints: Anthropic.Tool = {
  name: "extract_data_points",
  description: [
    "Extract structured numeric and categorical data points from a research document",
    "that has already been loaded. Returns a list of records, each with a field name, a",
    "value, a unit where applicable, and the page and paragraph the value was read from.",
    "",
    "Input: document_id from load_document, plus the field names to extract.",
    "Use this when you need specific values out of a document — revenue figures, sample",
    "sizes, publication dates, dosage tables.",
    "Do NOT use this to understand what a document argues; use summarize_content.",
    "Do NOT use this to check whether an external claim is supported; use",
    "verify_claim_against_source.",
    "Edge case: if a requested field is absent, the record is returned with a null value",
    "and a reason, rather than being omitted.",
  ].join("\n"),
  input_schema: {
    type: "object",
    properties: {
      document_id: { type: "string", description: "Identifier returned by load_document." },
      field_names: {
        type: "array",
        items: { type: "string" },
        description: "Field labels to extract, e.g. ['sample_size', 'p_value'].",
      },
    },
    required: ["document_id", "field_names"],
  },
};
  • The field is input_schema, and tools are a flat list. OpenAI's shape (type: "function" wrapper, nested function.parameters) is not accepted, and parameters appears in distractors.
  • Per-property description strings are prompt text too. Put format constraints there that the schema can't express, e.g. "#-prefixed, eight digits" or "must point at a document, not an arbitrary page".

What a good description contains

Audit any description against five elements:

  1. What it does and returns: what fields come back, not how the tool is implemented.
  2. Accepted input formats, including ones that look valid but aren't.
  3. Example queries in the user's language.
  4. Edge cases, especially "failed" versus "succeeded and found nothing".
  5. Boundaries against sibling tools, by name.

With near-neighbour tools, #5 outweighs the other four, because it's the only information the model can't infer from the tool in isolation.

Bad:

name: lookup_order
description: Retrieves order details.

Good:

name: lookup_order
description: >
  Look up a single order by its order number and return status, line items,
  fulfilment and delivery dates, carrier tracking, and the refund state of each
  line. Accepts an order number with or without the leading '#' (e.g. "#48812",
  "48812"). Does NOT accept email addresses, names, or account IDs.

  Use for queries like "where is my order 48812", "was #48812 delivered",
  "which items shipped in my last order".

  Use get_customer instead when the request identifies a person rather than an
  order ("look up my account", "what's on file for [email protected]") or when
  you need the customer's order history rather than one order.

  If the order number is well-formed but not found, returns an empty result with
  found: false — this is a successful call, not an error, and usually means a
  typo or an order belonging to a different account.

What each part of the good version prevents:

  • Return description: the model no longer calls get_customer just to find out whether tracking data exists.
  • "Does NOT accept" clause: no email passed in, no turn burned on a validation error.
  • Example queries: they do the job of system-prompt few-shot examples, for fewer tokens, and they're maintained with the tool.
  • Boundary sentence naming get_customer: draws the line between the two tools.
  • Empty-result note: stops the agent treating "not found" as a failure and retrying or escalating, when it should ask the customer to re-check the number.

A description of 150 to 250 tokens costs less than a misrouted call plus the recovery turn.

strict: true and input schemas

Descriptions fix which tool is called. A perfect description still leaves a separate failure, the right tool called with a malformed argument, such as a string for a number or a missing required field. "strict": true beside input_schema constrains generation so the input matches the schema exactly, much as strict JSON output does in Chapter 13. Add it wherever a malformed input is costly (e.g. process_refund.amount_cents). It doesn't help selection, so a strict tool still needs a good description.

input_examples lets you attach example input objects as structured data. It is an alternative to writing example queries in prose, but prose still carries when to use the tool, which bare inputs don't.

Diagnosing misrouting

Misrouting shows up as consistently picking the wrong member of a specific pair on a consistent class of query. For example, get_customer gets called on order queries, or analyze_content and analyze_document (near-identical descriptions) are picked interchangeably regardless of input type.

Diagnose in order of cost:

  1. Compare the two descriptions. Could a new engineer, given only those strings, pick correctly for every failing query? If not, the descriptions are the cause.
  2. Check the schemas. If both take a bare string query, the schema offers no signal either.
  3. Only if the descriptions already discriminate, read the system prompt for keyword-sensitive instructions. "Always begin by understanding the customer" pulls calls toward get_customer whatever its description says. It overrides good descriptions and is invisible unless you look.

These don't fix it:

  • A deterministic routing layer that pre-selects tools from the input. It discards the model's language understanding and fails on every phrasing you didn't anticipate.
  • 5–8 few-shot examples in the system prompt. They add tokens to every request and leave the cause untouched. The model memorises surface patterns but still can't tell the tools apart.

Fixing misrouting

  1. Rename and rewrite to remove overlap. analyze_content becomes extract_web_results: it takes search snippets and URLs, returns passages with source attribution, and explicitly routes loaded documents to the document tools. The rename matters as much as the rewrite, because the name is what the model sees first.
  2. Split a generic tool into purpose-specific tools. If one tool does unrelated jobs, no description can fix it, because the ambiguity is in the tool. An analyze_document that summarises, extracts, or fact-checks depending on phrasing becomes extract_data_points, summarize_content, and verify_claim_against_source, each with defined I/O contracts and each naming the other two in its boundaries.
  3. Review the system prompt. It's the cheapest fix. Remove keywords that collide with tool names, and move real sequencing out of prose and into tool_choice or a programmatic prerequisite.

Use one naming convention across the inventory, snake_case verb plus resource, namespaced by service when there are several backends. billing_get_invoice and fulfilment_get_shipment are easy to tell apart; two get_records are not.

Distributing tools across agents

Selection reliability is a property of the tool set, not the individual tool. In the exam's numbers, an agent with 18 tools selects less reliably than one with 4–5.

Agents also misuse tools outside their specialisation. Give a synthesis agent web search and it will search, because the tool is there and a gap seems to fit it. You get mediocre research instead of good synthesis, and blurred responsibility.

The default is scoped tool access, where each agent holds only what its role needs. In the research system:

  • Search agent: search and fetch tools.
  • Document agent: load_document plus the three extraction tools.
  • Synthesis agent: none of those.
  • Report agent: formatting and citation tools.
  • Coordinator: delegation tools only.

The verify_fact case. Strict scoping means synthesis must go through the coordinator to reach search, which adds 2–3 round trips and about 40% latency. Measurement shows 85% of verifications are simple (dates, names, statistics) and 15% need real investigation. The fix is to give synthesis a limited, scoped verify_fact tool for the 85% and keep routing the 15% through the coordinator. The wrong answers are:

  • The full search toolkit for synthesis: over-provisioned, and it dissolves the separation of concerns.
  • Batching verifications to the end: later steps depend on earlier facts, so it creates blocking dependencies.

Apply the same instinct to individual tools by replacing generic ones with constrained ones. fetch_url (fetches anything) becomes load_document (validates that the URL is a document and rejects the rest).

The tool search tool, as an alternative to manual scoping

For an agent that legitimately needs many tools of one kind, the tool search tool (tool_search_tool_bm25_20251119 for keyword search, tool_search_tool_regex_20251119 for patterns) defers most definitions and lets Claude search for the relevant one on demand. It solves the context and selection-load problem, but not separation of concerns: a synthesis agent that shouldn't research still shouldn't have search. Scoping answers "who should be able to do this". Tool search answers "how do I keep a large legitimate inventory usable".

tool_choice as a sequencing instrument

Setting Behaviour Use when
"auto" The model decides whether to call a tool at all and which one. May return conversational text. Ordinary agent turns where a text answer is a valid outcome.
"any" The model must call some tool, but chooses which. You need structured output and a prose reply would be a failure.
{"type": "tool", "name": "..."} A specific named tool is called. A particular tool must run first, before the model has discretion.
{"type": "none"} No tool may be called. You want plain text and must rule out a premature call.

When enrichment depends on extract_metadata's output, don't write "always call extract_metadata first" in the prompt. Force it on turn one, then return to "auto".

Python

first = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=2048,
    tools=[extract_metadata, enrich_company, enrich_filing],
    tool_choice={"type": "tool", "name": "extract_metadata"},
    messages=[{"role": "user", "content": "Process the Q3 filing at doc_8814."}],
)
# ... append the tool_use block and its tool_result, then continue:
followup = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=2048,
    tools=[extract_metadata, enrich_company, enrich_filing],
    tool_choice={"type": "auto"},
    messages=conversation,
)

TypeScript

const first = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 2048,
  tools: [extractMetadata, enrichCompany, enrichFiling],
  tool_choice: { type: "tool", name: "extract_metadata" },
  messages: [{ role: "user", content: "Process the Q3 filing at doc_8814." }],
});

const followup = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 2048,
  tools: [extractMetadata, enrichCompany, enrichFiling],
  tool_choice: { type: "auto" },
  messages: conversation,
});

tool_choice applies to one request. It can't enforce a prerequisite across an open-ended multi-turn loop. For business-critical sequencing (verify the customer before refunding), block the call in your own code. In order of strength: prompt < tool_choice < programmatic gate. Sample question 1 turns on this. Chapter 6 covers gates.

Current product (not tested): forced tool_choice (any/tool) returns a 400 on Sonnet 5.5, Opus 5.5 and Fable 5.1; see Chapter 2. The examples use claude-sonnet-5, which accepts it.

The built-in Claude Code tools

You know these tools already. The table lists the distinctions the exam draws.

Tool Use for
Grep File contents: callers of reconcileInvoice, error strings, imports
Glob File paths/names: **/*.test.tsx, src/**/handler.py
Read / Write Whole files
Edit Surgical change at a unique anchor string; fails if the anchor repeats
Bash Builds and tests. Not for grep/cat where a dedicated tool exists, because you lose structured results, the permission model, and guardrails
  • Edit fallback (the guide's answer): when Edit can't find unique text, use Read to load the whole file, then Write to save the reconstructed version. It's slower and costs more tokens, but it's deterministic, and it is the exam's answer. Current Claude Code documents a different first move: widen old_string with context, or set replace_all: true.
  • Incremental exploration: Grep for entry points, then Read to follow imports. Don't read everything up front. Forty files for one code path wastes context and pushes the relevant part into the weak-attention middle.
  • Tracing through wrapper modules: re-exports rename things, so one Grep for the original name misses callers. First identify the exported names (Grep the barrel file or export statements), then search for each.

What the exam tests

  • Task 2.1: descriptions are the primary selection mechanism. Given a misrouting symptom (get_customer/lookup_order, analyze_content/analyze_document), the best first step is expanding the descriptions: formats, examples, edge cases, sibling boundaries. Few-shot examples, a routing layer, and consolidating tools are the wrong first steps.
  • Match each repair to its example: rename + rewrite for overlap; split with defined I/O contracts for a generic tool; system prompt review when good descriptions are overridden by keywords.
  • Task 2.3: 18 tools vs 4–5; out-of-specialisation misuse; scoped access by default; a limited verify_fact when a measured majority of cases are simple, with the rest via the coordinator; fetch_url → load_document. tool_choice: forced to run extract_metadata first, "any" to guarantee a call rather than text.
  • Task 2.5: Grep = contents, Glob = paths, Read/Write = whole files, Edit = unique anchor with Read + Write fallback, Bash only when no dedicated tool exists. Two strategies: incremental exploration from Grep, and exported-names-first tracing through wrappers.
  • MCP deployment, auth, and transports are out of scope.

Exercises

  1. Give get_customer and lookup_order one-line descriptions. Run 20 queries (10 each) with tool_choice: "auto" and record accuracy. Rewrite both with all five elements and re-run. For queries still failing, decide whether the description or the schema is at fault.
  2. Write a generic analyze_document with a mode parameter, then split it into extract_data_points, summarize_content, and verify_claim_against_source with distinct schemas, in Python and TypeScript. Check that each description names the other two.
  3. Build a two-turn harness: force extract_metadata, append the tool_use/tool_result, then follow up with "auto". Change the follow-up to "any" and see what happens when nothing useful is left to call. That shows where "any" is a trap.
  4. Audit a Claude Code built-in tool's description against the five elements, then write one of your own to that standard.

Chapter 4 — Errors, recovery, and telling the agent the truth

When a Bash command fails in Claude Code, Claude reads stderr, adjusts, and tries something else. No handler knows what command not found: uv means. Recovery works because the failure arrives as content the model can read, not as an exception that ends the turn.

In an agentic system, an error is a message to the model, and the model is its primary consumer. The operator and the log come second. If you raise an exception out of the loop, you remove the one participant that could narrow the query, try another source or carry on with what it has. Given "the pricing service did not respond within 30 seconds; this is usually transient", the model can plan recovery from the task as a whole, which hand-written branches cannot see. Backend reflexes like "fail fast" and "let it crash" are right for failures people read. Here they do harm, because failing fast kills the reasoning that might have solved the problem.

Two task statements share this material:

  • 2.2: structured error responses from MCP tools. The recipient is the calling agent.
  • 5.3: error propagation in multi-agent systems. The recipient is a coordinator handling a subagent that couldn't finish.

Both use the same taxonomy, the same access-failure versus empty-result distinction, and the same rule that partial results travel with the error.

Scope. Rate limits, quotas, and pricing are out of scope. Retry strategy is in scope as design judgement (attempt budgets, idempotency).

Don't conflate these layers:

  • isError/is_error and the categories below cover your tool failing while the API is healthy.
  • overloaded_error (HTTP 529) and rate_limit_error (HTTP 429) mean the API call itself failed. They never reach the model as a tool_result. The SDK retries them beneath your loop with a small default budget, exponential backoff, and retry-after. Don't build a competing retry policy for this layer.
  • stop_reason: "model_context_window_exceeded" (Claude 4.5+) means the conversation ran out of room. Fix it by trimming or compacting history, not by retrying.

The two spellings of the same flag

A tool result is content plus one boolean saying "this is a failure":

  • MCP: isError on the tool result.
  • Messages API: is_error on a tool_result block.

isError is not a thrown exception. The tool returns normally with the flag set and a description. The model reads it as the outcome of its call, and the turn continues.

Protocol error Tool error
What broke The MCP server itself (malformed request, transport failure) The operation. The server worked fine.
Model sees it? Generally no Yes, via isError
Examples — Upstream payment failure, missing document, customer ID fails validation

Four categories, one retryable

Category Typical cause Retryable What the agent should do
transient Upstream timeout, service unavailable, connection reset Yes Retry within a bounded budget, ideally inside the subagent; escalate only if the budget is exhausted
validation Malformed or out-of-range input, missing required field No Correct the arguments from the error description and call again with a materially different input
business Policy violation (refund window elapsed, item non-returnable) No Stop, and explain the policy to the user in the customer-friendly terms the error supplies
permission Missing scope, expired credential, record outside the caller's tenant No Stop and escalate to a human or to a differently-scoped path; never loop

Only transient errors are retryable. Retrying the others fails the same way and wastes tokens and latency. "Not retryable" doesn't mean hopeless. After a validation error the agent should call again with corrected input, which is a different act from retrying.

The guide is inconsistent here. The knowledge statements name four categories, but task 2.2's skills bullet lists errorCategory as only transient/validation/permission. Business-rule violations are instead described as the case needing retriable: false plus a customer-friendly message. Default to four categories, recognise the three-value list, and remember that retriable: false is attached to business violations by name.

Generic error text

Generic, uniform error text is a named anti-pattern. If every failure reads Operation failed, the model can't tell a network blip from a policy refusal. Its only strategy is to retry, so it retries a refund that is out of policy on attempt one and still out of policy on attempt four. Structured metadata prevents those wasted retries. The payload carries:

  • errorCategory
  • isRetryable boolean
  • A description written for a reader who must decide what to do next
  • For business violations: retriable: false plus a customer-friendly explanation the agent can relay

Python

import json
from dataclasses import dataclass, asdict

@dataclass
class ToolFailure:
    errorCategory: str          # transient | validation | business | permission
    isRetryable: bool
    description: str            # for the model: what happened, what to try
    customerMessage: str | None = None   # for business errors: safe to relay verbatim

def process_refund(order_id: str, amount_cents: int) -> dict:
    order = orders.get(order_id)
    if order is None:
        return error_result(ToolFailure(
            errorCategory="validation",
            isRetryable=False,
            description=(
                f"No order matches id '{order_id}'. Order ids look like 'ORD-8842911'. "
                "Confirm the id with the customer before calling again."
            ),
        ))
    if order.days_since_delivery > 30:
        return error_result(ToolFailure(
            errorCategory="business",
            isRetryable=False,
            description="Refund window closed; policy REFUND-30D. Do not retry.",
            customerMessage=(
                "This order was delivered 47 days ago and our refund window is 30 days, "
                "so I can't process a refund automatically — but I can raise it with a specialist."
            ),
        ))
    ...

def error_result(failure: ToolFailure) -> dict:
    return {
        "isError": True,
        "content": [{"type": "text", "text": json.dumps(asdict(failure))}],
    }

TypeScript

type ErrorCategory = "transient" | "validation" | "business" | "permission";

interface ToolFailure {
  errorCategory: ErrorCategory;
  isRetryable: boolean;
  description: string;
  customerMessage?: string;
}

const errorResult = (failure: ToolFailure) => ({
  isError: true,
  content: [{ type: "text" as const, text: JSON.stringify(failure) }],
});

export async function processRefund(orderId: string, amountCents: number) {
  const order = await orders.get(orderId);
  if (!order) {
    return errorResult({
      errorCategory: "validation",
      isRetryable: false,
      description:
        `No order matches id '${orderId}'. Order ids look like 'ORD-8842911'. ` +
        "Confirm the id with the customer before calling again.",
    });
  }
  if (order.daysSinceDelivery > 30) {
    return errorResult({
      errorCategory: "business",
      isRetryable: false,
      description: "Refund window closed; policy REFUND-30D. Do not retry.",
      customerMessage:
        "This order was delivered 47 days ago and our refund window is 30 days, so I can't " +
        "process a refund automatically — but I can raise it with a specialist.",
    });
  }
  // ...
}

The description is a prompt fragment, not a log line. The model weighs it like CLAUDE.md text, so write it as an instruction.

Access failure is not an empty result

This appears in both Domain 2 and Domain 5.

  • Access failure: couldn't reach the data. It failed and needs a retry decision.
  • Valid empty result: the query reached the data and nothing matched. That is a success, and the answer is "none".

Collapse them and you lose information both ways. A timeout marked as an empty success produces a confident "no records" with no basis. An empty match marked as an error burns the retry budget re-asking an answered question. Make them differ in structure as well as wording.

Python

def search_incident_reports(query: str, since: str) -> dict:
    try:
        rows = incident_index.search(query, since=since, timeout=20)
    except SearchTimeout:
        return error_result(ToolFailure(
            errorCategory="transient",
            isRetryable=True,
            description=f"Incident index timed out after 20s for query {query!r}.",
        ))
    payload = {
        "isError": False,          # explicit, not the absence of the field
        "resultCount": len(rows),  # 0 is a real, trustworthy answer
        "results": rows,
        "queryExecuted": query,
    }
    return {"isError": False, "content": [{"type": "text", "text": json.dumps(payload)}]}

TypeScript

export async function searchIncidentReports(query: string, since: string) {
  try {
    const rows = await incidentIndex.search(query, { since, timeoutMs: 20_000 });
    const payload = { isError: false, resultCount: rows.length, results: rows, queryExecuted: query };
    return { isError: false, content: [{ type: "text" as const, text: JSON.stringify(payload) }] };
  } catch (err) {
    if (err instanceof SearchTimeout) {
      return errorResult({
        errorCategory: "transient",
        isRetryable: true,
        description: `Incident index timed out after 20s for query "${query}".`,
      });
    }
    throw err;
  }
}

resultCount: 0 with no error flag tells the model the search ran and found nothing.

Local recovery, then escalation

The subagent is the right place to absorb transient failure. It is closest to the tool, knows expected latency, and retrying there costs the coordinator nothing. Handle transients locally and propagate only what you couldn't resolve, always with partial results and what was attempted.

Task 5.3's structured error context has four parts, each of which feeds a coordinator decision:

Part Lets the coordinator…
Failure type decide whether retrying is coherent
What was attempted avoid reissuing the identical query
Partial results decide whether it already has enough
Alternative approaches go somewhere other than "give up"

Python

async def market_research_subagent(topic: str) -> dict:
    attempts, partial = [], []
    for query in expand_topic(topic):           # e.g. three narrower sub-queries
        attempts.append(query)
        try:
            partial.extend(await web_search(query, timeout=25, retries=2))
        except SearchTimeout:
            continue                             # local recovery: keep going
    if not partial:
        return {
            "status": "failed",
            "failureType": "transient",          # timeout, not "no such topic"
            "attempted": attempts,
            "partialResults": [],
            "alternatives": [
                "Retry with a single narrowed query such as 'EU battery recycling mandates 2026'",
                "Route to the internal-archive subagent, which has cached analyst notes",
            ],
        }
    if len(partial) < expected_minimum(topic):
        return {
            "status": "partial",
            "failureType": "transient",
            "attempted": attempts,
            "partialResults": partial,
            "coverageGaps": ["regulatory outlook — all three source queries timed out"],
            "alternatives": ["Re-run only the regulatory sub-query"],
        }
    return {"status": "complete", "results": partial}

TypeScript

export async function marketResearchSubagent(topic: string) {
  const attempted: string[] = [];
  const partial: Finding[] = [];

  for (const query of expandTopic(topic)) {
    attempted.push(query);
    try {
      partial.push(...(await webSearch(query, { timeoutMs: 25_000, retries: 2 })));
    } catch (err) {
      if (!(err instanceof SearchTimeout)) throw err;   // local recovery only for transient
    }
  }

  if (partial.length === 0) {
    return {
      status: "failed",
      failureType: "transient",
      attempted,
      partialResults: [],
      alternatives: [
        "Retry with a single narrowed query such as 'EU battery recycling mandates 2026'",
        "Route to the internal-archive subagent, which has cached analyst notes",
      ],
    };
  }
  if (partial.length < expectedMinimum(topic)) {
    return {
      status: "partial",
      failureType: "transient",
      attempted,
      partialResults: partial,
      coverageGaps: ["regulatory outlook — all three source queries timed out"],
      alternatives: ["Re-run only the regulatory sub-query"],
    };
  }
  return { status: "complete", results: partial };
}

Walking sample question 8

A web-search subagent times out mid-research. The correct answer is the four-part structured payload above. The distractors are:

  • Retry with backoff, then return generic "search unavailable". The retry is fine and belongs there. The defect is the generic status, which throws away the queries tried and the data collected.
  • Catch the timeout, return an empty result marked successful. This suppresses the error, so no recovery is possible, and synthesis treats absence of findings as evidence of absence.
  • Terminate the workflow from a top-level handler. This reports the failure but is out of proportion, because it kills the whole run when a narrowed retry or another source would likely have worked.

The last two are task 5.3's named anti-patterns: suppressing errors as empty success, and terminating an entire workflow on one subtask failure.

Coverage annotations

Synthesis output must mark which findings are well supported and which topic areas have gaps because sources were unavailable. "Competitor pricing: three independent sources; regulatory outlook: no sources retrieved, all queries timed out" is useful. If the same report silently drops the regulatory section, the reader can't tell "investigated, found nothing" from "never reached". Partial results are acceptable as long as they are disclosed.

Idempotency

process_refund posts the refund, then the response times out. The tool correctly reports a retryable transient error, the agent retries, and the customer is refunded twice. The category was right, but the operation wasn't safe to retry.

Retryability is a property of the operation as well as the error. Make mutating tools idempotent: the client supplies a key derived from the request's intent, not the attempt, and the server returns the original outcome for a repeated key.

Python

def refund_result(refund_id: str, replayed: bool) -> dict:
    payload = {"isError": False, "refundId": refund_id, "replayed": replayed}
    return {"isError": False, "content": [{"type": "text", "text": json.dumps(payload)}]}

def process_refund(order_id: str, amount_cents: int, idempotency_key: str) -> dict:
    prior = refund_ledger.get(idempotency_key)
    if prior is not None:
        return refund_result(prior.id, replayed=True)
    receipt = payments.refund(order_id, amount_cents, key=idempotency_key)
    refund_ledger.put(idempotency_key, receipt)
    return refund_result(receipt.id, replayed=False)

TypeScript

const refundResult = (refundId: string, replayed: boolean) => {
  const payload = { isError: false, refundId, replayed };
  return { isError: false, content: [{ type: "text" as const, text: JSON.stringify(payload) }] };
};

export async function processRefund(
  orderId: string, amountCents: number, idempotencyKey: string,
) {
  const prior = await refundLedger.get(idempotencyKey);
  if (prior) return refundResult(prior.id, true);

  const receipt = await payments.refund(orderId, amountCents, { key: idempotencyKey });
  await refundLedger.put(idempotencyKey, receipt);
  return refundResult(receipt.id, false);
}

Make the key a required schema parameter and describe it as identifying the refund attempt, so the model reuses it on retry. The caller is a model choosing arguments from prose, so the description is the only thing that prevents a duplicate payment. If the model generates a new key on each call, the key protects nothing.

Retry strategy as a design decision

  • Exponential backoff with jitter for transients. Backoff gives the dependency room to recover. Jitter stops parallel subagents retrying in lockstep (thundering herd).
  • Bound attempts: 2–3 when a human is waiting, more for batch.
  • Retries the model sees cost tokens. Each failed attempt stays in context for the rest of the session, crowding out useful material. Absorb retries inside the subagent and report one summarised outcome.
  • Set a per-turn budget alongside the per-call one. When it's exhausted, escalate with the four-part payload.

What the exam tests

  • The four categories, and that only transient is retryable.
  • isError (MCP) and is_error (Messages API) by name. Their purpose is to make failure visible to the model.
  • Access failure versus valid empty result (Domains 2 and 5). A timeout reported as empty success, or an empty match reported as an error, is wrong on sight.
  • The four-part payload for multi-agent failures. A generic "search unavailable" is always the trap, even with sound retry logic around it.
  • The two named anti-patterns: suppressing as empty success, and terminating the whole workflow on one failure.
  • Local recovery first. Escalation carries partial results rather than replacing them.
  • Coverage annotations (task 5.3): partial is fine, undisclosed is not.

Exercises

  1. Write an MCP tool wrapping a service you have credentials for. Give every exit path an errorCategory, an isRetryable, and a model-oriented description. In Claude Code, trigger each failure: it should retry the timeout and stop on the policy refusal. Then change every description to Operation failed and compare.
  2. Build a search tool with structurally distinct empty and timeout results. With the source unreachable, confirm the agent retries. With the source reachable and the query unmatchable, confirm it reports "no matches" with zero retries.
  3. Implement the market-research subagent and force one sub-query to time out permanently. Check that the coordinator gets partial results plus coverageGaps, and that the synthesis names the gap. Then make the subagent return {"status": "complete", "results": []} and record what the synthesis claims.
  4. Add a required idempotency key to a mutating tool and simulate a timeout after commit. Confirm the retry replays the receipt. Then reword the key's description to sound per-call and watch the model mint a fresh one.

Chapter 5 — Orchestration

When Claude Code fans a broad request out to subagents, it is running a small distributed system. A coordinator decides to delegate, isolated workers each see only what they are told, and a synthesis step reconciles results that never talked to each other. This chapter covers building that system yourself with the Agent SDK (task statements 1.2 and 1.3), so you can diagnose it and use it outside Claude Code.

The running example is the exam's Multi-Agent Research System. A coordinator delegates to search and document-analysis subagents, and a synthesis subagent writes the report. Customer support is the secondary example.

Why orchestration is a separate problem from the loop

Chapter 2's loop is one agent with one growing messages array. Orchestration applies when one context can't hold the whole problem well:

  • Tool load: 18 tools select worse than 4–5, and out-of-specialisation tools get misused (Chapter 3).
  • Context load: a single conversation accumulates every document, result, and draft, so by synthesis time the signal is buried. Separate bounded contexts are a context-management technique as well as a division of labour (Chapter 14).

Hub-and-spoke

The default architecture puts one coordinator at the centre. Subagents talk only to the coordinator, never to each other. The coordinator decomposes the task, delegates the parts and aggregates the results. A finished search subagent returns to the coordinator, which decides what synthesis sees.

The exam names three benefits of giving up direct subagent-to-subagent communication:

  1. Observability: everything passes through one place, so one log tells the whole story.
  2. Consistent error handling: one place decides whether to retry, substitute, or pass on partial results.
  3. Controlled information flow: the coordinator can filter, summarise, or redact before passing data on, e.g. keep sensitive document content away from the report agent.

In support, a case that needs order history and a fraud check before process_refund routes both through a coordinator, not a fraud agent reading the order agent's output directly. The coordinator sees the whole case and decides what an inconclusive fraud check means.

Subagent context isolation

A subagent's context is isolated. It does not inherit the coordinator's history, and separate invocations of "the same" subagent share nothing. The Task tool starts a subagent with only the prompt you write. Anything the coordinator or an earlier subagent learned exists for it only if it is in that prompt.

In the exam's example, a synthesis subagent is prompted with just "Synthesize the research findings into a report." The findings are in the coordinator's history, so the subagent has none, and its output is generic and uncited. The fix is to put the findings in the prompt, not to use a bigger model or give synthesis a WebSearch tool. A search tool would move research into the wrong agent and break separation of concerns.

Python

# WRONG — the coordinator assumes shared memory that doesn't exist
synthesis_prompt = "Synthesize the research findings into a report."

# RIGHT — the coordinator passes what it actually collected
synthesis_prompt = f"""
Write a research report answering: {research_question}

You have no access to the search or document tools. Work only from the
findings below. Cite every claim using the bracketed source ids.

## Web search findings
{format_search_findings(search_results)}

## Document analysis findings
{format_document_findings(document_results)}

Quality criteria: every factual claim must carry a citation id. Flag any
subtopic in the original question that the findings below do not cover.
"""

TypeScript

// WRONG — the coordinator assumes shared memory that doesn't exist
const synthesisPrompt = "Synthesize the research findings into a report.";

// RIGHT — the coordinator passes what it actually collected
const synthesisPrompt = `
Write a research report answering: ${researchQuestion}

You have no access to the search or document tools. Work only from the
findings below. Cite every claim using the bracketed source ids.

## Web search findings
${formatSearchFindings(searchResults)}

## Document analysis findings
${formatDocumentFindings(documentResults)}

Quality criteria: every factual claim must carry a citation id. Flag any
subtopic in the original question that the findings below do not cover.
`;

The corrected prompt ends with a goal and quality bar (cite every claim, flag coverage gaps) and leaves the subagent free to structure the report to fit the material. It does not give a numbered procedure.

Structured findings and attribution

If a subagent returns prose that mixes claims with URLs, synthesis has to re-parse attribution from running text. Sometimes it drops or garbles the sources, and the report makes confident claims nobody can trace. Separate content from metadata at return time: every finding carries its source URL, or document name and page, as a field alongside the content.

Python

search_findings = [
    {
        "claim": "The library dropped support for callback-style APIs in v5.",
        "source_url": "https://example.com/library/changelog/v5",
        "retrieved_at": "2026-08-30",
    },
    {
        "claim": "Migration guides recommend the async adapter for v4 codebases.",
        "source_url": "https://example.com/library/migration-guide",
        "retrieved_at": "2026-08-30",
    },
]

document_findings = [
    {
        "claim": "Internal usage of the callback API appears in 14 call sites.",
        "document_name": "internal-audit-q3.pdf",
        "page": 6,
    },
]

TypeScript

const searchFindings = [
  {
    claim: "The library dropped support for callback-style APIs in v5.",
    sourceUrl: "https://example.com/library/changelog/v5",
    retrievedAt: "2026-08-30",
  },
  {
    claim: "Migration guides recommend the async adapter for v4 codebases.",
    sourceUrl: "https://example.com/library/migration-guide",
    retrievedAt: "2026-08-30",
  },
];

const documentFindings = [
  {
    claim: "Internal usage of the callback API appears in 14 call sites.",
    documentName: "internal-audit-q3.pdf",
    page: 6,
  },
];

format_search_findings / format_document_findings render these with metadata still attached to each claim.

The Task tool and the AgentDefinition

The Task tool looks like any tool (name, description, schema), but calling it starts a new agentic loop with its own messages array and returns that loop's final result as the tool result. Claude Code spawns its subagents this way.

Two configuration facts matter here:

  1. allowedTools must include "Task" for the coordinator to delegate. When a coordinator never spawns subagents despite its prompt, the cause is structural rather than a reasoning failure, so check this first. (Claude Code renamed Task to Agent in v2.1.63 and keeps Task as an alias: tool_use blocks name the tool "Agent", while system:init still lists "Task", so match both in code. The guide and exam say Task, so answer Task.)
  2. Each spawnable role is an AgentDefinition: name, description (when the coordinator should use it), prompt (scopes behaviour), and tools (restriction). It is the SDK equivalent of a Claude Code subagent definition.

Python

from claude_agent_sdk import ClaudeAgentOptions, AgentDefinition

options = ClaudeAgentOptions(
    system_prompt=COORDINATOR_SYSTEM_PROMPT,
    allowed_tools=["Task"],
    agents={
        "search_agent": AgentDefinition(
            description="Runs web searches for a specific subtopic and "
                        "returns findings with source URLs attached.",
            prompt=SEARCH_AGENT_SYSTEM_PROMPT,
            tools=["WebSearch", "WebFetch"],
        ),
        "document_agent": AgentDefinition(
            description="Loads and extracts claims from a specific set of "
                        "documents, returning findings with document name "
                        "and page number attached.",
            prompt=DOCUMENT_AGENT_SYSTEM_PROMPT,
            tools=["load_document", "extract_data_points"],
        ),
        "synthesis_agent": AgentDefinition(
            description="Writes a cited research report from findings "
                        "supplied in its prompt. Has no research tools.",
            prompt=SYNTHESIS_AGENT_SYSTEM_PROMPT,
            tools=[],
        ),
    },
)

TypeScript

import { AgentDefinition } from "@anthropic-ai/claude-agent-sdk";

const options = {
  systemPrompt: COORDINATOR_SYSTEM_PROMPT,
  allowedTools: ["Task"],
  agents: {
    search_agent: {
      description: "Runs web searches for a specific subtopic and returns " +
        "findings with source URLs attached.",
      prompt: SEARCH_AGENT_SYSTEM_PROMPT,
      tools: ["WebSearch", "WebFetch"],
    } satisfies AgentDefinition,
    document_agent: {
      description: "Loads and extracts claims from a specific set of " +
        "documents, returning findings with document name and page " +
        "number attached.",
      prompt: DOCUMENT_AGENT_SYSTEM_PROMPT,
      tools: ["load_document", "extract_data_points"],
    } satisfies AgentDefinition,
    synthesis_agent: {
      description: "Writes a cited research report from findings supplied " +
        "in its prompt. Has no research tools.",
      prompt: SYNTHESIS_AGENT_SYSTEM_PROMPT,
      tools: [],
    } satisfies AgentDefinition,
  },
};
  • Caveat on allowedTools: the SDK documents it as an auto-approve list that "does not restrict Claude to only these tools". Omitting "Task" won't reliably block delegation in real code, but it is still the exam's answer to "why won't my coordinator spawn subagents?". AgentDefinition.tools does restrict the subagent, since an omitted tool "isn't in the subagent's session at all."
  • description is what the coordinator routes on. A vague one misroutes between subagent types, just as a vague tool description does between tools.
  • synthesis_agent has no tools by design, which enforces the Chapter 3 scoping argument in configuration.

Other AgentDefinition fields (not tested field by field):

Field Purpose
disallowedTools Remove specific tools (incl. mcp__server, mcp__*) from an otherwise permissive role
model Full ID, alias, or 'inherit': cheaper model for search, stronger for synthesis
skills, memory ('user'/'project'/'local'), mcpServers, initialPrompt Subagent's own skills, memory scope, MCP connections, seed message
maxTurns Caps the subagent's loop. At the cap it returns a resumable partial result rather than failing
background Run without blocking the coordinator's turn
effort How much the subagent reasons
permissionMode Subagent's own permission mode

Resuming a subagent

A Task/Agent result carries a session_id (shown as agentId in current tooling) for that subagent's transcript. Pass it back as resume: sessionId on a later Task call to continue that subagent's history instead of starting from nothing. This is useful when a document subagent has built up expensive context on a document set. Resume when the prior context is still valid and worth its cost; otherwise spawn fresh (the same judgement as Chapter 7).

Spawning in parallel

Parallel delegation = multiple Task calls in a single coordinator response. Four Task calls in one turn run four subagents side by side. One call per turn over four turns runs them in sequence, which takes far longer because each is its own multi-step loop.

There is no parallelism flag. Distractors offer real parameters that govern something else, such as disable_parallel_tool_use, which controls batching of ordinary tool calls in a turn.

With query(), the SDK harness sees the four tool_use blocks and runs the four loops concurrently, while your code supplies options and reads typed messages.

Python

from claude_agent_sdk import query

async for message in query(
    prompt="Research the market impact of generative AI on creative "
           "industries: visual arts, music, writing, and film/video, "
           "each as a distinct workstream.",
    options=options,  # the ClaudeAgentOptions defined above
):
    log.info("%s", message)
# Internally, the coordinator's turn carries four Task tool_use blocks
# naming "search_agent", each with a distinct, non-overlapping subtopic
# in its input — the SDK runs the four resulting subagent loops concurrently.

TypeScript

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Research the market impact of generative AI on creative " +
    "industries: visual arts, music, writing, and film/video, each as " +
    "a distinct workstream.",
  options, // the ClaudeAgentOptions defined above
})) {
  log.info(message);
}
// Internally, the coordinator's turn carries four Task tool_use blocks
// naming "search_agent", each with a distinct, non-overlapping subtopic
// in its input — the SDK runs the four resulting subagent loops concurrently.

Fan-out ceilings exist because anything that can spawn more of itself needs a hard cap:

  • CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH: nesting depth (default 3).
  • CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS: concurrent loops (default 20).
  • maxBudgetUsd / max_budget_usd: total spend across the delegation tree. Exceeding it stops with result subtype error_max_budget_usd.

Partitioning scope without narrowing the topic

Splitting "creative industries" into visual arts, music, writing, and film/video partitions scope correctly, with full coverage and no duplication. The exam calls this partitioning research scope to minimise duplication: assign each subagent distinct subtopics or source types (e.g. one agent on regulatory filings, one on news, one on vendor pages).

Now split it into gallery representation, digital art marketplaces, and museum curation, which are all visual arts. Every subagent succeeds with well-cited findings, and synthesis produces a confident report covering about a quarter of the topic.

When every subagent succeeds and aggregate coverage is still wrong, the defect is in the coordinator's decomposition. Error handling can't catch it, so you have to compare the decomposition against the original question's breadth, before delegating and again after synthesis.

Iterative refinement

A one-pass pipeline (decompose → delegate → synthesise) can't catch narrow decomposition after the fact. Make it a loop: evaluate synthesis for gaps against the original question, re-delegate targeted queries for the gaps, re-synthesise, and repeat until coverage is sufficient.

Python

def run_research_coordinator(question, max_refinement_rounds=3):
    findings = delegate_initial_research(question)   # parallel Task calls
    report = invoke_synthesis(question, findings)

    for round_num in range(max_refinement_rounds):
        gaps = evaluate_coverage(question, report)    # coordinator's own check
        if not gaps:
            return report
        log.info("refinement round=%d gaps=%s", round_num, gaps)
        targeted_findings = delegate_targeted_research(gaps)  # more Task calls
        findings = findings + targeted_findings
        report = invoke_synthesis(question, findings)

    report["coverage_warning"] = (
        "Refinement rounds exhausted; known gaps may remain unresolved."
    )
    return report

TypeScript

async function runResearchCoordinator(
  question: string,
  maxRefinementRounds = 3,
) {
  let findings = await delegateInitialResearch(question); // parallel Task calls
  let report = await invokeSynthesis(question, findings);

  for (let round = 0; round < maxRefinementRounds; round++) {
    const gaps = await evaluateCoverage(question, report); // coordinator's own check
    if (gaps.length === 0) return report;
    log.info({ round, gaps });
    const targetedFindings = await delegateTargetedResearch(gaps); // more Task calls
    findings = [...findings, ...targetedFindings];
    report = await invokeSynthesis(question, findings);
  }

  report.coverageWarning =
    "Refinement rounds exhausted; known gaps may remain unresolved.";
  return report;
}

evaluate_coverage must look for named subtopics that never got a subagent, not just check for non-empty output. The three visual-arts subagents all returned good, non-empty results. The loop is bounded, and when it runs out of rounds it attaches a warning instead of returning incomplete work as complete.

Query complexity

The opposite failure is over-processing. "What year was [a well-known data regulation] enacted?" doesn't need search, then document analysis, then synthesis. The coordinator's prompt should have it assess complexity first (simple lookup, comparison, or open investigation) and invoke only the subagents needed. A coordinator told to always run the full team spends four invocations on a one-agent question. In support, a plain order-status request doesn't need a fraud check.

Fork-based sessions, briefly

Forking branches a session into independent copies from a shared baseline, so you can explore two divergent approaches (e.g. two report framings) without re-deriving the shared analysis. It is a session mechanism, covered in Chapter 7.

Scaling past hub-and-spoke, and the default subagent

  • Workflow tool (separate from Task/Agent) orchestrates dozens to hundreds of agents outside the coordinator's conversation. Hub-and-spoke keeps everything in the coordinator's context, which is what buys observability and controlled flow, and that context would overflow at large scale. The two suit different problem sizes, and hub-and-spoke still applies at small to moderate scale.
  • general-purpose is a built-in subagent always available even with no agents defined, unless disabled with CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS=1. A coordinator with an empty agents map can still delegate.

What the exam tests

Tasks 1.2 and 1.3 are diagnostic: name the cause of a symptom.

  • Coordinator never spawns subagents: check allowedTools for "Task".
  • Generic or uncited subagent output: put the findings in its prompt. Don't add tools or restructure turns.
  • Parallel delegation: multiple Task calls in one coordinator response. Distractors are real parameters that govern adjacent things.
  • Narrow decomposition: clean logs, wrong coverage. The defect is upstream in decomposition, found by comparing the question's breadth with what was delegated.
  • Complexity assessment versus routing everything through the full pipeline.
  • Hub-and-spoke benefits (observability, consistent error handling, controlled information flow) versus direct subagent communication.
  • Structured findings with separated content and metadata; coordinator prompts stating goals and quality criteria, not procedure.

Exercises

  1. Build the research coordinator with search_agent, document_agent, and synthesis_agent (tools scoped per Chapter 3), with the synthesis prompt as just "Synthesize the research findings into a report." Observe the generic output, then pass structured, attributed findings and compare.
  2. Have a coordinator decompose "the effects of remote work on urban commercial real estate" twice: once narrowly (three subtopics, same angle) and once broadly. Compare reports and write one sentence on what a reader of the narrow one would wrongly believe is covered.
  3. Add the refinement loop to exercise 2's narrow decomposition. Confirm it finds the gap, re-delegates, and closes it. Then remove the cap and make evaluate_coverage impossible to satisfy.
  4. Have the coordinator assess complexity first. Run a single-fact lookup, a two-way comparison, and an open investigation, and log which subagents run for each.

Chapter 6 — Hooks and gates

Claude Code's permission prompt is one instance of a general pattern. The model emits a tool_use block, and code outside the model inspects the call before it runs and can refuse it. When the prompt stops an rm -rf, the model's instructions play no part in that, because the check runs outside the model.

The rule behind tasks 1.4 and 1.5 is that prompt instructions have a non-zero failure rate. However carefully you write "always verify the customer before refunding", some runs will refund anyway once there are enough conversations, long contexts and adversarial customers. The model weighs an instruction against everything else in its context. A gate is code that runs on every call and gives the same answer each time.

In the support scenario the model can make most decisions itself, such as which order, which policy and what to say. Two conditions have to hold on every run:

  1. A refund is never issued against an unverified identity.
  2. A refund never exceeds the policy threshold without a human.

Claude Code's hook system gives you two places to run code: PreToolUse (before a call executes) and PostToolUse (after a result returns). The permission prompt is a PreToolUse hook with a UI. The sections below implement both in code. Chapters 9–10 cover the configuration files.

The enforcement ladder

Mechanism What it constrains Scope Guarantee
System prompt instruction What the model is told to prefer Every turn, informally None. The model can be wrong or overridden by competing context
tool_choice forcing Which tool the next response must call One request Strong, but only for a single turn
Programmatic gate or hook Whether a call is allowed to execute, or what a result looks like once it returns The whole session, or however long your code holds the state Absolute. The check runs in your code, outside the model

A common wrong answer forces get_customer with tool_choice. That guarantees only that the next response calls it. Three turns later tool_choice is back on "auto" and nothing stops an unverified process_refund. A prerequisite that has to hold for a whole conversation needs code that keeps state and checks every call, which is what a gate is.

Use deterministic enforcement when a miss would have financial, compliance or security consequences, or when a step must never be skipped, such as verifying identity before payment. For anything less serious a good prompt is enough, and a hook is the wrong tool (see "When a prompt is enough" below).

Prerequisite gates

A prerequisite gate sits between the model's request and execution and refuses the call until an earlier condition holds. The exam's example: block process_refund until get_customer has returned a verified customer ID. The code is only a dict lookup and an if, but three details are easy to get wrong:

  1. Keep the state per conversation. With one process-wide boolean, session B can refund on session A's verification. Nothing reports an error, so the bug looks like a working gate, which is worse than having no gate. Key the state by conversation ID.
  2. Return the block as a result instead of raising. An exception kills the loop like any uncaught exception (Chapter 4). Return a normal tool_result with is_error and a description of what's missing, and the model recovers: it calls get_customer, then retries.
  3. Gate every call that can act on the wrong account. In sample question 1 the agent sometimes skips get_customer and calls lookup_order with only the customer's stated name, which leads to misidentified accounts and incorrect refunds. The correct option blocks both lookup_order and process_refund until get_customer has returned a verified customer ID. The code below shows the process_refund half; lookup_order gets the same check.

Python

verified_customers: dict[str, str] = {}  # conversation_id -> customer_id

def gated_process_refund(conversation_id: str, order_id: str,
                          amount_cents: int, reason: str) -> dict:
    customer_id = verified_customers.get(conversation_id)
    if customer_id is None:
        return {
            "is_error": True,
            "content": (
                "process_refund blocked: no verified customer on this "
                "conversation. Call get_customer first, then retry with "
                "the returned customer_id."
            ),
        }
    return process_refund(customer_id, order_id, amount_cents, reason)

def gated_get_customer(conversation_id: str, identifier: str) -> dict:
    result = get_customer(identifier)
    if result.get("verified"):
        verified_customers[conversation_id] = result["customer_id"]
    return result

TypeScript

const verifiedCustomers = new Map<string, string>(); // conversationId -> customerId

async function gatedProcessRefund(
  conversationId: string, orderId: string, amountCents: number, reason: string,
) {
  const customerId = verifiedCustomers.get(conversationId);
  if (!customerId) {
    return {
      is_error: true,
      content:
        "process_refund blocked: no verified customer on this conversation. " +
        "Call get_customer first, then retry with the returned customer_id.",
    };
  }
  return processRefund(customerId, orderId, amountCents, reason);
}

async function gatedGetCustomer(conversationId: string, identifier: string) {
  const result = await getCustomer(identifier);
  if (result.verified) verifiedCustomers.set(conversationId, result.customerId);
  return result;
}

Interception versus PostToolUse

Both hooks sit in the loop, but they differ in timing:

  • Interception (PreToolUse, pre-call) inspects a call before dispatch and can block it.
  • PostToolUse inspects a result after it returns. The action has already happened.

Once process_refund returns, the money has moved. A PostToolUse hook can log the refund, flag it or page someone, but it can't reverse it. A rule like "refunds over $500 never go out without a human" has to be enforced before the call, by interception.

Python

REFUND_THRESHOLD_CENTS = 50_000

def intercept_process_refund(conversation_id: str, order_id: str,
                              amount_cents: int, reason: str) -> dict | None:
    """Return a blocking result if the call must not proceed, else None."""
    if amount_cents > REFUND_THRESHOLD_CENTS:
        return {
            "is_error": True,
            "content": (
                f"process_refund blocked: ${amount_cents/100:.2f} exceeds the "
                "$500 auto-approval threshold. Call escalate_to_human with a "
                "root cause and recommended action instead."
            ),
        }
    return None

def dispatch_process_refund(conversation_id, order_id, amount_cents, reason):
    blocked = intercept_process_refund(conversation_id, order_id, amount_cents, reason)
    if blocked is not None:
        return blocked
    return gated_process_refund(conversation_id, order_id, amount_cents, reason)

TypeScript

const REFUND_THRESHOLD_CENTS = 50_000;

function interceptProcessRefund(
  conversationId: string, orderId: string, amountCents: number, reason: string,
) {
  if (amountCents > REFUND_THRESHOLD_CENTS) {
    return {
      is_error: true,
      content:
        `process_refund blocked: $${(amountCents / 100).toFixed(2)} exceeds the ` +
        "$500 auto-approval threshold. Call escalate_to_human with a root cause " +
        "and recommended action instead.",
    };
  }
  return null;
}

async function dispatchProcessRefund(
  conversationId: string, orderId: string, amountCents: number, reason: string,
) {
  const blocked = interceptProcessRefund(conversationId, orderId, amountCents, reason);
  if (blocked) return blocked;
  return gatedProcessRefund(conversationId, orderId, amountCents, reason);
}

The blocking result redirects the agent by naming escalate_to_human, so it has a next step and doesn't retry or give up.

How the options in a typical refund-threshold question score:

Option Verdict
System prompt: escalate refunds over $500 Probabilistic. Insufficient for money
Few-shot example escalating a $600 refund Probabilistic
Interception blocking > $500, redirecting to escalation Correct
PostToolUse flagging > $500 for audit Right family, wrong direction. Every refund still goes out

PostToolUse: normalizing what comes back

PostToolUse solves data quality, not compliance. Real support tooling spans systems: lookup_order returns Unix epoch timestamps, get_customer returns ISO 8601, a third returns status codes 0/1/2 with the meaning in an unattached lookup table. If the model has to translate between formats on top of its real work, it will misread one now and then.

A PostToolUse hook rewrites every result into one shape (field names, date format, status vocabulary) before the model sees it.

Python

from datetime import datetime, timezone

STATUS_CODES = {0: "active", 1: "suspended", 2: "closed"}

def normalize_tool_result(tool_name: str, raw: dict) -> dict:
    result = dict(raw)
    if tool_name == "lookup_order" and isinstance(result.get("ship_date"), int):
        result["ship_date"] = datetime.fromtimestamp(
            result["ship_date"], tz=timezone.utc
        ).date().isoformat()
    if tool_name == "get_customer" and isinstance(result.get("status"), int):
        result["status"] = STATUS_CODES.get(result["status"], "unknown")
    return result

TypeScript

const STATUS_CODES: Record<number, string> = { 0: "active", 1: "suspended", 2: "closed" };

function normalizeToolResult(toolName: string, raw: Record<string, unknown>) {
  const result = { ...raw };
  if (toolName === "lookup_order" && typeof result.ship_date === "number") {
    result.ship_date = new Date(result.ship_date * 1000).toISOString().slice(0, 10);
  }
  if (toolName === "get_customer" && typeof result.status === "number") {
    result.status = STATUS_CODES[result.status as number] ?? "unknown";
  }
  return result;
}

Wire it into execute_tool from Chapter 2: call the tool, normalise, then serialise into content.

Agent SDK names (task 1.5 is phrased in SDK terms):

  • PreToolUse callback returns hookSpecificOutput with permissionDecision ("allow", "deny", "ask", "defer") and permissionDecisionReason. It can rewrite arguments via updatedInput instead of blocking.
  • PostToolUse callback returns additionalContext to append, or updatedToolOutput to replace the output before Claude sees it. This is how you normalise results in the SDK.

A prompt line such as "dates may be Unix or ISO; interpret carefully" usually works, but it costs tokens on every turn and the model still has to apply it correctly each time. If lookup_order is a third-party MCP server, you can't change what it returns, only what the model sees. A hook converts each result once, so every turn gets clean data.

When a prompt is enough

Hooks are code, so they can have bugs and need maintenance. For soft preferences, such as polite phrasing, offering store credit first or mentioning the loyalty programme, a prompt is enough because a miss costs little.

Ask whether an occasional miss would justify code that can never be skipped. For money, identity checks and compliance it would. For tone, phrasing or a choice between two acceptable orderings it wouldn't. Over-gating brings back the rigidity of the decision trees criticised in Chapter 2.

Multi-concern decomposition

"SO-44120 arrived damaged, and I was also charged twice for last month's subscription" is two concerns in one message. Two ways to handle it badly:

  • Resolving one and telling the customer to open a second ticket.
  • Handling independent concerns serially.

Both hurt first-contact resolution, the scenario's headline metric.

The correct pattern is to decompose the message into distinct items, investigate in parallel on shared context, and synthesise one unified resolution. Shared context means fetching the customer once and passing the same verified customer_id to both branches. If each branch calls get_customer itself, each one re-verifies through the gate. The two lookups are independent once the customer is known, so they can be parallel tool_use blocks.

Python

async def resolve_multi_concern(conversation_id: str, customer_identifier: str,
                                 concerns: list[dict]) -> dict:
    customer = gated_get_customer(conversation_id, customer_identifier)
    findings = await asyncio.gather(*(
        investigate(concern, customer_id=customer["customer_id"])
        for concern in concerns
    ))
    return synthesize_resolution(customer, concerns, findings)

TypeScript

async function resolveMultiConcern(
  conversationId: string, customerIdentifier: string, concerns: Concern[],
) {
  const customer = await gatedGetCustomer(conversationId, customerIdentifier);
  const findings = await Promise.all(
    concerns.map((concern) => investigate(concern, customer.customerId)),
  );
  return synthesizeResolution(customer, concerns, findings);
}

Synthesis produces one reply that resolves both concerns.

Structured handoff

escalate_to_human fires when interception blocks a refund, policy is silent, or the customer asks for a person. The person who receives it has no access to the transcript and starts with nothing. Sending them the raw transcript leaves them to research the case under time pressure.

A structured handoff carries four fields:

Field Answers
Customer ID Who is this?
Root cause What happened?
Refund amount How much money is at stake?
Recommended action What should happen next?

Python

def escalate_to_human(customer_id: str, root_cause: str, amount_cents: int,
                       recommended_action: str) -> dict:
    return {
        "is_error": False,
        "content": json.dumps({
            "customer_id": customer_id,
            "root_cause": root_cause,
            "refund_amount_cents": amount_cents,
            "recommended_action": recommended_action,
        }),
    }

TypeScript

function escalateToHuman(
  customerId: string, rootCause: string, amountCents: number, recommendedAction: string,
) {
  return {
    is_error: false,
    content: JSON.stringify({
      customerId,
      rootCause,
      refundAmountCents: amountCents,
      recommendedAction,
    }),
  };
}

Wrong answers in the select-two version:

  • Raw transcript: what the structured handoff replaces.
  • Agent's self-reported confidence: the same unreliable signal rejected for autonomous escalation (Chapter 15), and just as unreliable here.
  • Tool calls with timings: debugging telemetry the human can't act on.

What the exam tests

  • Tasks 1.4 and 1.5: prompts have a non-zero failure rate, so enforcement for financial, compliance, or security misses belongs in code. The usual example is identity verification before a refund.
  • Agent occasionally skips a mandatory step: add a programmatic prerequisite gate. Not a stronger instruction, more few-shot examples, or an iteration-cap workaround.
  • Direction (a common 1.5 distractor): PostToolUse can't stop what already happened. Interception blocks before dispatch. PostToolUse for a refund threshold is the right family, wrong direction.
  • Interception redirects to escalate_to_human rather than issuing a bare denial.
  • Heterogeneous formats (Unix, ISO 8601, numeric codes): PostToolUse normalisation, not "interpret carefully".
  • Multi-concern: decompose, parallel on shared context, one resolution. Not serial, not escalated for being compound.
  • Handoff fields: customer ID, root cause, refund amount, recommended action. Reject transcript, confidence score, and telemetry.
  • When not to hook: hooks are for guarantees, not preferences such as tone or phrasing.

Exercises

  1. Add verified_customers to Chapter 2's run_agent, wire in gated_get_customer/gated_process_refund, and push the model to refund before verifying. Confirm the block arrives as a normal tool_result and the model recovers.
  2. Put the $500 interception in front of gated_process_refund. Run a $200 and a $650 request, and confirm only the second is blocked and redirected to escalate_to_human.
  3. Build two lookup_order stubs (Unix and ISO ship_date) and make normalize_tool_result produce identical output. Then replace it with "interpret dates carefully" in the prompt, run 20 conversations, and compare delivery-window reasoning accuracy.
  4. Write resolve_multi_concern with two stub investigations sharing one customer fetch. Then make each call get_customer itself and observe the extra round trip the gate forces.

Chapter 7 — Sessions

You resume a two-day-old Claude Code session and Claude confidently describes a file you rewrote yesterday. Claude is reading its history correctly, but the history predates your change. Task statement 1.7 covers when to resume, when to fork, and when resuming is worse than starting over.

What a session is

A session is the running list of blocks: user turns, the model's text and tool calls, and the tool_result blocks that came back. It is replayed to the model every turn. Resuming reloads that list without revalidating any of it. Every tool result in it is treated as still describing the world.

That is why a resumed refactor remembers the plan and naming decisions. The list is also a cache, and it is correct only while the world still matches what it recorded.

Resuming a named session

The tested skill is using --resume <session-name> to continue a specific investigation, not just the latest one, because you usually have several lines of work going. Name a session at launch with claude -n <name> (or /rename mid-session), then resume it with claude --resume <name>. The SDK's resume option takes a session ID instead.

Say that on Monday Claude explored a legacy billing module, designed a split of process_refund out of a monolith, and started the new module. If nothing changed overnight, resuming on Tuesday is right, and re-deriving the work would waste time.

The rule is to resume when prior context is mostly still valid.

resume versus continue

resume continue
SDK form Python resume=<session_id> TS continue: true; Python continue_conversation=True
Takes an id? Yes No
Picks up Exactly the named session Whatever was most recent in the current directory
Scenario wording "a specific investigation from earlier" "the most recent session"

With several lines of work in one project, continue may pick the wrong one.

Resuming after files change

Overnight, a teammate rewrote six files in the auth module your session analysed. When you resume, the transcript still contains Monday's contents and analysis verbatim, and nothing marks them as stale. If you say nothing, Claude reasons from Monday's files and every downstream decision inherits the error. Chapter 4 showed an unlabelled failure being read as an empty result. Here an unlabelled stale result is read as current.

When you resume, tell the agent specifically what changed. Naming the six files by path makes the problem explicit and bounded. Claude re-reads exactly those files, keeps Monday's reasoning and discards only the facts that are out of date. This is the guide's skill of "informing a resumed session about specific file changes for targeted re-analysis rather than requiring full re-exploration."

Python

resume_prompt = """
Resuming the refund-module refactor session.
Since we last worked on this, the following files were rewritten
upstream and your prior analysis of them is stale — re-read and
re-analyze these before continuing, everything else in your prior
context still holds:

  - src/billing/auth_context.py
  - src/billing/session_guard.py
  - src/billing/token_refresh.py
  - src/billing/scopes.py
  - src/billing/middleware.py
  - src/billing/audit_hooks.py

Do not rely on your previous reading of these six files.
"""

TypeScript

const resumePrompt = `
Resuming the refund-module refactor session.
Since we last worked on this, the following files were rewritten
upstream and your prior analysis of them is stale — re-read and
re-analyze these before continuing, everything else in your prior
context still holds:

  - src/billing/auth-context.ts
  - src/billing/session-guard.ts
  - src/billing/token-refresh.ts
  - src/billing/scopes.ts
  - src/billing/middleware.ts
  - src/billing/audit-hooks.ts

Do not rely on your previous reading of these six files.
`;

A vague "Some files changed, please double-check" triggers the unbounded re-exploration you resumed to avoid.

When to start a fresh session

A research session crawled competitor pricing, a regulatory filing, and news. By the time you return, prices have updated, the filing was amended, and three links 404. Most of what the session knows is suspect, and a "here's what changed" note can't fix that.

The guide says that starting a new session with a structured summary is more reliable than resuming with stale tool results. A resumed history is tool_result blocks that were true when fetched, with no signal of decay. A fresh session seeded with a summary of only facts you've just re-confirmed (the questions, the conclusions that don't depend on today's prices, the sources worth revisiting) reasons from facts you have checked. Current Agent SDK guidance agrees. Once tool results have plausibly gone stale, it says to capture what you've learned as application state and pass it into a fresh session.

Python

structured_summary = """
Prior research session (competitor pricing analysis) concluded with
these durable findings — re-verify nothing below unless noted:

  - Research questions: (1) how does competitor X price the enterprise
    tier, (2) what regulatory constraints apply in the EU market.
  - Methodology that worked: querying the vendor's public pricing page
    plus the EU filing register directly outperformed general web search.
  - Durable conclusion: competitor X's enterprise tier is seat-based,
    not usage-based — this is a structural fact unlikely to have moved.

Treat all specific prices, filing statuses, and news article contents
from the prior session as UNVERIFIED. Re-fetch before citing any figure.
"""

TypeScript

const structuredSummary = `
Prior research session (competitor pricing analysis) concluded with
these durable findings — re-verify nothing below unless noted:

  - Research questions: (1) how does competitor X price the enterprise
    tier, (2) what regulatory constraints apply in the EU market.
  - Methodology that worked: querying the vendor's public pricing page
    plus the EU filing register directly outperformed general web search.
  - Durable conclusion: competitor X's enterprise tier is seat-based,
    not usage-based — this is a structural fact unlikely to have moved.

Treat all specific prices, filing statuses, and news article contents
from the prior session as UNVERIFIED. Re-fetch before citing any figure.
`;

No threshold number decides this, so place the scenario in one of two cases:

  • A known, enumerable subset changed: resume and inform.
  • Staleness is broad or unenumerable (a wide crawl aged out, the data source rotated, too much time passed): start fresh with a summary. A patch note would be as long and uncertain as redoing the work.

Forking

fork_session is for a different situation. You have paid for a shared baseline and want to explore several paths from it without them polluting each other, and without paying for the baseline twice.

For example, Claude has mapped a service's test setup (fixtures, mocks, flaky spots). Two strategies, "migrate to contract tests" and "add property-based tests", both need that map and then diverge.

  • Both in one session: strategy A's dead ends stay in context and warp reasoning about B.
  • Two fresh sessions: no pollution, but the careful exploration is repeated or reduced to a summary.
  • fork_session: each branch inherits the full baseline once, then diverges in isolation.

The research scenario is the same: fork after the shared literature review into a regulatory branch and a pricing branch.

In code, a fork is a resume with a flag that makes it branch from the baseline id instead of continuing it.

Python

from claude_agent_sdk import ClaudeAgentOptions, query

contract_tests_branch = query(
    prompt="Propose a migration to contract tests for this service.",
    options=ClaudeAgentOptions(resume=baseline_session_id, fork_session=True),
)

incremental_branch = query(
    prompt="Propose adding property-based tests to the existing suite.",
    options=ClaudeAgentOptions(resume=baseline_session_id, fork_session=True),
)

TypeScript

import { query } from "@anthropic-ai/claude-agent-sdk";

const contractTestsBranch = query({
  prompt: "Propose a migration to contract tests for this service.",
  options: { resume: baselineSessionId, forkSession: true },
});

const incrementalBranch = query({
  prompt: "Propose adding property-based tests to the existing suite.",
  options: { resume: baselineSessionId, forkSession: true },
});

Plain --resume twice would put both prompts in one growing transcript. (Python's ClaudeSDKClient wraps resume/fork in a session-holding object, so real apps rarely pass ids by hand.)

Situation Right mechanism Why
Continuing one investigation, prior context still holds --resume <session-name> Reuses valid context at no cost
Continuing one investigation, a known subset of files changed --resume, plus an explicit list of what changed Targets re-analysis instead of trusting stale results or re-exploring everything
Continuing one investigation, most tool results are stale or unenumerable Fresh session seeded with a structured, hand-verified summary Avoids re-presenting decayed tool output as current fact
Comparing two or more approaches from one shared baseline fork_session Keeps the full baseline once, isolates each branch's exploration from the others

What the exam tests

Task 1.7 stems ask you to pick one of the four rows above.

  • A handful of files changed: resume and name the files. Traps: resume silently (stale results read as current), or discard everything (throws away valid context to fix a narrow problem).
  • Broad staleness (long gap, wide crawl, external data): fresh session with an injected summary. A targeted note isn't enough because there's no small list to give. Know the reason: resumed tool_results carry no marker of decay.
  • "Compare" / "divergent approaches" from "the same starting point": fork_session. Traps: repeated --resume into one transcript, or duplicated fresh sessions that discard the baseline.

Exercises

  1. Start a multi-file exploration with claude -n billing-audit, exit, then edit two or three of the files it read. Resume twice with claude --resume billing-audit, once saying nothing and once naming the changed files, and compare how each reasons about them.
  2. Fork that exploration into an aggressive-refactor branch and a minimal one. Confirm neither sees the other, and note what you'd lose running both as follow-ups in the original session.
  3. After a wide web-research session, write a summary of only what you'd trust unchecked. Compare a fresh session seeded with it against resuming the original. Find where the resumed one cites a fetched detail as current.

Chapter 8 — MCP

As a consumer, MCP looks like a plugin system where you add a config entry and get new tools. The exam asks about what goes in the config, who else sees it, and whether a tool should exist at all.

MCP standardises the interface between an agent and an external capability. One GitHub server works from Claude Code, an Agent SDK app or another host.

Scope of task 2.4: configuration, scoping, discovery, descriptions, build versus buy. Out of scope: transports (stdio vs network), hosting, containers, load balancing, OAuth. An answer option that relies on one of these is a distractor.

What MCP standardises

Primitive What it is Example
Tools Actions that do something or fetch a specific known thing Jira search_issues, create_issue
Resources Read-only content to browse A docs table of contents, a label taxonomy
Prompts Reusable prompt templates shipped by the server Rarely used in Claude Code workflows. Just know it exists

Resources are for catalogs; tools are for actions. A resource fixes a specific reliability problem, where an agent that doesn't know what exists guesses and calls tools speculatively.

  • Issue tracker: without a resource, the agent calls search_issues 2–3 times with guessed keywords just to learn which components and labels exist. A resource exposing the label taxonomy, component list, or open-epic summary lets it read once, then make one targeted call.
  • Document store: a resource exposing folder hierarchy and titles stops repeated list_documents calls at different prefixes.

The exam won't ask you to implement resources by hand.

Scoping: local, project, and user

Scope decides who shares the configuration. It has no effect on how the model picks tools.

Scope Where Shared? Use for
Project .mcp.json at repo root, committed Everyone who clones Team workflow tooling a new hire should have on day one
User ~/.claude.json Just you, across all projects Personal integrations (calendar, notes)
Local ~/.claude.json, keyed to this project Just you, this project only, not in git Experiments, servers you're authoring, project-specific creds you won't commit
  • The exam guide uses the two-scope model (project vs user). Local is real in current Claude Code, but assume project-vs-user unless a question names local.
  • Don't confuse local MCP scope with .claude/settings.local.json, an unrelated use of "local".
  • Set scope with claude mcp add <name> --scope local|project|user ... rather than hand-editing ~/.claude.json.
  • The my-experimental-notes-server below fits local scope better than user scope today.
// .mcp.json — project scope, committed, shared with the team
{
  "mcpServers": {
    "jira": {
      "command": "npx",
      "args": ["-y", "@some-vendor/jira-mcp-server"],
      "env": {
        "JIRA_API_TOKEN": "${JIRA_API_TOKEN}",
        "JIRA_BASE_URL": "https://acme.atlassian.net"
      }
    }
  }
}
// ~/.claude.json — user scope, personal, not committed anywhere
{
  "mcpServers": {
    "my-experimental-notes-server": {
      "command": "node",
      "args": ["/Users/paulo/dev/notes-mcp/index.js"]
    }
  }
}

Environment variable expansion. ${JIRA_API_TOKEN} in .mcp.json is a placeholder resolved from the runner's environment at connection time. The team shares the server config while each engineer's token stays in their own shell or .env. Hardcoding a token in .mcp.json is always wrong, because committing a secret leaks it.

  • Fallbacks: ${JIRA_BASE_URL:-https://acme.atlassian.net}.
  • Expansion works in env, in command/args (stdio), and in url/headers (HTTP).

Scope is not priority. Tools from every configured server, project and user alike, are discovered at connection time and available simultaneously, in one flat inventory with the built-ins. The model picks among them per turn, just as it picks between Grep and Glob. A question where a project server "takes priority" over a user server, or hides it, describes something that doesn't happen.

There is one narrow exception, which the exam doesn't test. When two scopes define a server with the same name, Claude Code resolves the collision with a fixed precedence (local, project, user, plugin, claude.ai connector, in that order) and connects once. That is scope resolution, and tool selection still has no priority.

Why your MCP tool loses to Grep

You install a server with a better, structured search, and Claude still uses Grep or WebSearch. The cause is the Chapter 3 principle that the description is prompt text read at selection time. Grep comes with a careful paragraph and your tool has "Search Jira issues", so the model has more evidence for Grep every time.

The fix is to enhance the MCP tool's description.

Python

search_issues = {
    "name": "search_issues",
    "description": (
        "Search Jira issues by text, status, assignee, or label within the "
        "acme-platform project. Returns issue key, summary, status, assignee, "
        "labels, and the last three comments for each match — structured data "
        "pulled live from Jira, not a text search over a local checkout.\n\n"
        "Use this for any question about tickets, bugs, or work items: "
        "'what's blocking the payments epic', 'find open bugs tagged "
        "checkout', 'who is assigned OPS-4471'.\n"
        "Do NOT use Grep or a general web search for these queries — issue "
        "text lives in Jira, not in the repository, and this tool returns "
        "current status and assignment that a code search cannot see.\n"
        "If no issues match the filters, returns an empty list; this is a "
        "successful call, not an error, and usually means the filters are "
        "too narrow rather than that Jira is unreachable."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "Free-text search, e.g. 'refund timeout'."},
            "status": {"type": "string", "description": "Optional status filter, e.g. 'In Progress'."},
        },
        "required": ["query"],
    },
}

TypeScript

const searchIssues: Anthropic.Tool = {
  name: "search_issues",
  description: [
    "Search Jira issues by text, status, assignee, or label within the",
    "acme-platform project. Returns issue key, summary, status, assignee,",
    "labels, and the last three comments for each match — structured data",
    "pulled live from Jira, not a text search over a local checkout.",
    "",
    "Use this for any question about tickets, bugs, or work items: 'what's",
    "blocking the payments epic', 'find open bugs tagged checkout', 'who is",
    "assigned OPS-4471'.",
    "Do NOT use Grep or a general web search for these queries — issue text",
    "lives in Jira, not in the repository, and this tool returns current",
    "status and assignment that a code search cannot see.",
    "If no issues match the filters, returns an empty list; this is a",
    "successful call, not an error, and usually means the filters are too",
    "narrow rather than that Jira is unreachable.",
  ].join("\n"),
  input_schema: {
    type: "object",
    properties: {
      query: { type: "string", description: "Free-text search, e.g. 'refund timeout'." },
      status: { type: "string", description: "Optional status filter, e.g. 'In Progress'." },
    },
    required: ["query"],
  },
};

The "Do NOT use Grep" line names the competing built-in, as boundary sentences name sibling tools (Chapter 3). Community servers often ship thin descriptions, so when configuring one for a team, check them and wrap or override where needed.

Build versus buy

  • Standard integrations (Jira, GitHub, Slack, Drive, common DBs): use an existing community or vendor server, configured at project scope with env-expanded tokens. Someone has already handled pagination, custom-field mapping, rate limiting, and the API edge cases that only show up after months across many installations. A home-grown server handles the cases you tested and can fail without warning on the rest.
  • Team-specific workflows: build custom. Examples: one tool wrapping three internal systems, a resource exposing your own document taxonomy, business rules such as which refund reasons need a supervisor or which labels auto-escalate. A generic server can't know your rules exist. You also then own description quality from scratch, which is one more reason not to build by default.

What the exam tests

  • Scope recall: project .mcp.json (shared) versus user ~/.claude.json (personal/experimental), plus env var expansion. The wrong answer hardcodes a token in a committed file.
  • Scope ≠ priority: with both scopes configured, every server's tools are discovered at connection time and sit together in one inventory, with no scope-based priority.
  • Resources vs tools: an agent burning calls on exploratory discovery of a content catalog (issue summaries, doc hierarchy, schema) needs a resource, not a new tool or a bigger prompt.
  • MCP tool loses to Grep: enhance its description. Don't change scope, restrict the built-in, or add a routing rule.
  • Build vs buy: standard integration means a community server. Custom is only for team-specific needs.
  • Transports, hosting, and OAuth are out of scope.

Exercises

  1. In a real repo, write a project .mcp.json entry for a community server with ${VAR} for every credential, and a personal entry in ~/.claude.json. Justify each placement in one sentence.
  2. Inspect a tool description from an MCP server you use and audit it against Chapter 3's five elements. If it's thin, rewrite it, adding a sentence naming the built-in (Grep, WebSearch) it should beat.
  3. For an internal issue tracker, split capabilities into tools (create, transition, assign) and resources (label taxonomy, components, sprint summaries), justifying each as an action or a catalog.
  4. In 2–3 sentences, argue why a "file ticket + tag per internal escalation rules + notify on-call" tool should be custom rather than assembled from Jira and Slack servers, and name the business logic a generic server couldn't know.

Chapter 9 — Configuring Claude Code

You already use CLAUDE.md. This chapter gives you the exam's vocabulary for it (task statements 3.1 and 3.3): which file wins, what "shared" means at each level, and how rules are scoped. A rule can load because of where a file sits (directory-bound) or because of what kind of file it is (type-bound, via glob). Test files interleaved across 40 packages have no directory whose boundary is "every test file", so no directory layout can scope a rule to them, but a glob in .claude/rules/ can.

Slash commands, skills, and plan mode are in Chapter 10.

The CLAUDE.md hierarchy

Level Path Shared? Scope
Managed/enterprise policy macOS /Library/Application Support/ClaudeCode/CLAUDE.md; Linux/WSL /etc/claude-code/CLAUDE.md; Windows C:\Program Files\ClaudeCode\CLAUDE.md Org-enforced. Can't be deleted or excluded Loads first
User ~/.claude/CLAUDE.md No. Your home dir, never in git Every project you touch
Project .claude/CLAUDE.md or root CLAUDE.md (both valid) Yes. Committed, reaches every clone and CI job This repo
CLAUDE.local.md Project root, gitignored No This repo, just you. Loads right after project CLAUDE.md
Directory CLAUDE.md in a subdirectory Yes (committed) Only work touching that subtree

User, project, and directory are the three the exam leans on. CLAUDE.local.md suits personal-but-repo-specific preferences, like "I run this repo's tests with -k slow".

Project-level is for anything the team must follow. User-level is for your own preferences (commit verbosity, explain-before-code) and is never right for team conventions.

A common diagnostic question has a new engineer clone the repo and find that half the team's conventions don't apply for them. The conventions are in the tech lead's ~/.claude/CLAUDE.md, written as personal habit and never promoted. The fix is always to move the content from ~/.claude/CLAUDE.md to the project file. More documentation or an onboarding checklist doesn't fix it.

Directory-level handles conventions for one subtree. A monorepo keeps org-wide conventions at root, DB access patterns in services/billing/CLAUDE.md, and component conventions in apps/web/CLAUDE.md. Each loads only when work touches its subtree, so it activates on location.

Current product (not tested): levels don't override one another. Claude Code concatenates every loaded CLAUDE.md from root down to the working directory, so closer files are read last, and subdirectory files load on demand. @import follows up to four hops, with relative paths resolved from the importing file.

Modular organization: @import and .claude/rules/

Two separate tools break up a monolithic CLAUDE.md.

@import pulls an external file into a CLAUDE.md by path. The package maintainer picks which standards apply:

# services/billing/CLAUDE.md

This service handles payment processing and refund issuance.

@docs/standards/python-style.md
@docs/standards/api-design.md

Refunds above $500 require the process_refund tool's manual-review path;
see the runbook in docs/refunds.md before touching that code path.

A Terraform-only package imports terraform-conventions.md instead and never loads the Python guide.

  • AGENTS.md interop: Claude Code reads AGENTS.md only when no CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md exists in the working directory or above. Once a CLAUDE.md exists it reads that alone, unless it imports AGENTS.md. Keep one source with @AGENTS.md inside a CLAUDE.md (or a symlink) rather than two drifting files.
  • claudeMdExcludes (settings.json, any layer, arrays merge) excludes CLAUDE.md or rules files by glob on absolute paths. Use it to stop a subpackage inheriting a rule without moving the rule file.

.claude/rules/ splits one file doing too many jobs into topic files (testing.md, api-conventions.md, deployment.md), each small enough to own and review.

.claude/
  CLAUDE.md              # thin: project overview, links to rules
  rules/
    testing.md           # pytest conventions, fixture policy
    api-conventions.md   # REST error shapes, versioning policy
    deployment.md         # rollout steps, rollback procedure

Without frontmatter these files always load, so the split is only organisational. With paths frontmatter (below), they load conditionally.

Diagnosing with /memory and /context

Instructions come from several levels, imports, and rule files, so answering "why did Claude do that?" needs tooling.

On the exam, /memory verifies which memory files are loaded and diagnoses inconsistent behaviour across sessions. That is the guide's wording for task 3.1, and /context appears nowhere in the guide.

Current Claude Code differs. /memory now lists and opens CLAUDE.md and rules locations (including ones not yet created) and toggles auto memory. The docs point to /context for what actually loaded this session. In your own work, check /context first, then use /memory to open the file and find out why (wrong level, stale @import path, non-matching glob). On the exam, answer /memory.

Path-specific rules

A .claude/rules/ file with YAML frontmatter paths: (a list of globs) loads only when the file being edited matches.

---
paths: ["terraform/**/*"]
---

# Terraform conventions

All resources must be tagged with `team` and `cost-center`. Modules under
terraform/modules/ are versioned by git tag, not by branch — never reference
a module by branch name in a `source` argument. Run `terraform fmt` before
every commit; CI will reject unformatted plans.

Mechanics:

  • Rules are discovered recursively (.claude/rules/frontend/testing.md works) and can be symlinked in, so one rule can be shared across repos.
  • ~/.claude/rules/ applies to all your projects and loads before the project's .claude/rules/. Both load, and the project rule is read later, so it takes priority on conflict.
  • Brace expansion works (**/*.{ts,tsx}), capped at 1000 expanded patterns and 4 MiB.

That Terraform example is directory-shaped, so a directory CLAUDE.md could also express it. The case only a glob can express is file type scattered across locations, which is the exam's sample question 6:

Button.tsx sits next to Button.test.tsx, and useAuth.ts next to useAuth.test.ts, across 40 component directories. You want one test rule: use the custom render wrapper, follow arrange-act-assert comments, mock the API client uniformly.

  • Root CLAUDE.md: the rule applies to components and tests alike, written as an "if this is a test file" aside. The model has to infer scope from prose.
  • CLAUDE.md in each directory: 40 copies, no single source of truth, and the rule is still keyed to address rather than suffix.
  • Glob rule: scopes on the filename pattern (*.test.ts).
---
paths: ["**/*.test.tsx", "**/*.test.ts"]
---

# Frontend test conventions

Use `renderWithProviders` from src/test-utils, never the raw `render` export
from the testing library — our components depend on theme and auth context
that raw render does not supply. Structure test bodies as arrange, act,
assert, with a blank line between each section. Mock the API client via
`mockApiClient()` from src/test-utils; do not construct ad hoc fetch mocks.

Editing any *.test.tsx anywhere loads it.

Scoping question Right mechanism Why
"Everyone on the team needs this instruction." Project-level CLAUDE.md (.claude/CLAUDE.md or root CLAUDE.md) Committed to version control, reaches every clone.
"Only I want this instruction, on every project I touch." User-level CLAUDE.md (~/.claude/CLAUDE.md) Personal, never shared, not version-controlled.
"This convention applies to one subtree and nowhere else." Directory-level CLAUDE.md Directory-bound: loads based on where the edited file lives.
"This convention applies to a file type, wherever it appears in the tree." .claude/rules/*.md with a paths glob Type-bound: loads based on what the edited file is, regardless of location.
"This file is too big; I want to pull in only the standards relevant to this package." @import inside that package's CLAUDE.md Selective inclusion driven by maintainer judgment.
"This file is too big; I want to split it by topic without changing when anything loads." Plain files under .claude/rules/ (no paths frontmatter) Organizational split; always loaded, just not monolithic.

Token economy (the second half of 3.3): conditional loading keeps Terraform rules out of context while you edit React, and the reverse. A large root CLAUDE.md carries everything on every turn, so every turn pays for rules that do not apply.

Distractors for the scattered-tests scenario:

  • "Consolidate into root CLAUDE.md with a testing section." Relies on the model inferring scope from prose. There is no mechanical match, so the advice can leak into non-test files or be missed in a long file.
  • "Put it in a skill." Skills are invoked on demand, by command or by the model's judgement. A convention that must apply automatically every time can't depend on invocation.

Choosing between subdirectory CLAUDE.md and glob rules

If the boundary is a place in the tree, use a subdirectory CLAUDE.md. If it is a kind of file that appears in many places, use a glob rule.

In practice you combine them, with a thin root CLAUDE.md for orientation and org policy, directory files for the few location-bound conventions, and glob rules for the type-bound ones (tests, migrations, generated-code markers, IaC).

Auto memory

Auto memory is written by Claude, not you. It is stored under ~/.claude/projects/<project>/memory/, is indexed by MEMORY.md, and loads at every session start in that project. Entries are typed (user, feedback, project, reference), capped around 200 lines / 25 KB.

  • CLAUDE.md: conventions you guarantee, reviewed and versioned.
  • Auto memory: what Claude learned about how you work (terse commits, a refactor already rejected, who owns deploys).

If Claude "remembers" something never written in any CLAUDE.md, that's auto memory, not a hierarchy level you missed.

What the exam tests

  • 3.1 levels and paths: user, project, directory (plus managed above, CLAUDE.local.md below project).
  • 3.1 diagnostic: new team member missing instructions because they sit in ~/.claude/CLAUDE.md. Fix: promote to the project file.
  • @import ≠ .claude/rules/: @import selectively pulls standards files in, chosen by the maintainer. rules/ splits a monolith into topic files.
  • /memory verifies which memory files are loaded (the guide's answer, even though current Claude Code uses /context for this).
  • 3.3 discriminator: subdirectory CLAUDE.md is directory-bound (where the file lives). paths glob rules are type-bound (what the file is). For scattered conventions (interleaved tests, migrations across services), pick the glob and reject root consolidation (inference, not matching) and skills (invocation, not automatic).
  • Secondary benefit: path scoping keeps irrelevant rules out of context and off the token budget.

Exercises

  1. Run /context in a repo you maintain. Trace each loaded file to its level and to any @import or glob rule, and explain every surprise.
  2. Split a repo's conventions (testing, API errors, deployment) into .claude/rules/ files, and give at least one a realistic paths glob (**/*.test.ts, **/*.tf, **/migrations/*.py).
  3. Write one scenario where a subdirectory CLAUDE.md is right and one where it isn't, using the location-vs-type test. Write the glob rule for the second.
  4. Put a team convention in ~/.claude/CLAUDE.md, confirm with /context in a fresh clone or scratch home that it's absent, then promote it to the project file and confirm it appears.

Chapter 10 — Extending Claude Code

Slash commands and skills turn a prompt into a version-controlled file the team can invoke by name (task 3.2). Plan mode decides whether a task needs a design phase before any edit (task 3.4). The exam wants the exact mechanism (file, directory, frontmatter field) and the ability to pick the right one from a task description.

Slash commands

A custom slash command is a markdown file whose body becomes the prompt, and its filename is the invocation.

Location Scope Reaches
.claude/commands/ in the repo Project Every teammate on clone or pull, via version control
~/.claude/commands/ User Only you, across all projects

If /review must run the same way for every developer on clone, the answer is .claude/commands/. Distractors:

  • Put it in CLAUDE.md. CLAUDE.md carries context and standards, not invocable actions.
  • A registration file listing commands. None is needed, because the file's presence registers the command.

Personal shorthand that shouldn't reach teammates goes in ~/.claude/commands/.

.claude/commands/review.md          # project-scoped — every teammate gets /review
~/.claude/commands/my-triage.md     # user-scoped — only you get /my-triage

Skills

A skill is a command plus frontmatter that Claude Code enforces around the invocation. Current Claude Code treats them as one mechanism: .claude/commands/foo.md and .claude/skills/foo/SKILL.md both give /foo, and a skill is the superset.

Locations and precedence:

  • Enterprise/managed: org-controlled, applies to everyone.
  • Personal: ~/.claude/skills/<name>/SKILL.md.
  • Project: .claude/skills/<name>/SKILL.md, shared via git.
  • Plugin: <plugin>/skills/, invoked as plugin:skill to avoid name collisions.
  • Nested project dirs: apps/web/.claude/skills/deploy/ is invoked as /apps/web:deploy, so frontend and backend deploy skills can coexist in a monorepo.

Frontmatter fields the exam tests:

Field Does Use when
argument-hint Prompts the developer for a missing argument when invoked bare The skill can't work without, e.g., a service name or ticket. Otherwise it guesses or fails silently
allowed-tools Restricts tools during skill execution (the guide's framing) The skill must not be able to delete or run arbitrary shell. E.g. release notes: read plus file-write only
context: fork Runs the skill in an isolated sub-agent context. Only the result returns to your conversation The skill is verbose (reads dozens of files for a 3-paragraph summary) or exploratory (brainstorming whose discarded options would bias later turns)
  • In current docs, outside the exam, allowed-tools lists tools usable without asking permission. That pre-approves them but doesn't block other tools. The field that removes tools is disallowed-tools. On the exam, allowed-tools restricts tools. In your repos, use disallowed-tools for the guarantee.
---
name: analyze-refund-patterns
description: Scans recent process_refund calls for anomalies and reports a summary.
context: fork
allowed-tools: Read, Grep, Glob
argument-hint: "[date-range, e.g. 2026-08-01..2026-08-31]"
---

The body after the frontmatter is plain markdown.

Who may invoke a skill:

  • disable-model-invocation: only a human can trigger it, never the model on its own judgement. Use it for side effects like /deploy.
  • user-invocable: false: hidden from the command list, so the model invokes it automatically. Use it for background-knowledge skills.
  • paths frontmatter: same glob scoping as .claude/rules/. The skill is considered for auto-invocation only when the current file matches.

Other frontmatter exists and isn't tested: when_to_use, arguments/$name, model, effort, background, agent, hooks, shell, metadata, license, compatibility.

Bundled skills (/doctor, /code-review, /run, /verify, /loop, /debug, …) ship out of the box. disableBundledSkills and skillOverrides turn them off or replace them.

Personal variants of a shared skill

To get different behaviour from a shared skill, create a second skill under a different name in ~/.claude/skills/. The project has .claude/skills/extract-invoice/SKILL.md, and you want currency normalisation too, so create ~/.claude/skills/extract-invoice-mine/SKILL.md. The shared skill is untouched and teammates are unaffected.

Skills versus CLAUDE.md

Choose by whether the content applies to every interaction or only to one nameable task, regardless of how important it is:

  • CLAUDE.md: universal standards loaded every turn, e.g. the commit message convention.
  • Skill: an on-demand, task-specific workflow, e.g. auditing process_refund call sites for compliance. Loading it every turn would waste context.

Plan mode versus direct execution

Decide from the task description, before any exploration, whether plan mode is needed.

Plan mode gives safe exploration plus a human-reviewed design before any file changes. It prevents rework from committing to the wrong design. Use it when there are several legitimate approaches and a wide blast radius. Typical cases:

  • Monolith to microservices.
  • Migrating 45 files to a new library version.
  • Choosing between integration approaches with different infrastructure requirements.

Direct execution fits tasks with one reasonable shape: a single-file fix where the stack trace names the line, or adding one validation conditional. Planning those produces a plan that says "add the null check", which adds friction with no safety gain.

People often get the timing backwards. The complexity is stated in the request, and "Restructure into microservices" announces it in the first sentence. A common distractor starts in direct execution "to see how far you get" and switches to plan mode once things get messy. By then you've made hard-to-undo choices across many files, so choose plan mode at the outset.

Plan mode Direct execution
Number of legitimate approaches Several, actively competing One, or near enough that debate is wasted motion
What you're protecting against Costly rework from committing to the wrong design early Nothing unusual, only normal execution risk
Cost of using it anyway on the wrong case Ceremony and delay with no safety payoff Irreversible missteps across many files before anyone notices

Combine them. Plan the migration once (target API, order, special-case call sites), then directly execute the 45 edits. The plan is a durable spec the execution phase consumes. Treating plan mode and direct execution as one either/or choice for the whole task is a common wrong answer.

The Explore subagent

Investigation can consume the context budget the rest of the task needs: mapping 45 call sites, surveying service boundaries, dead ends. The Explore subagent does the verbose discovery in its own isolated context and returns a summary. It works the same way as context: fork (Chapter 5 covers subagent mechanics). Use it in the investigation phase of a multi-phase task so exploration doesn't exhaust context before you act. A plan that had to read all 45 files in-session leaves no room to discuss it.

What the exam tests

  • 3.2 placement: command or skill for every developer on clone goes in .claude/commands/ or .claude/skills/ (in git). A personal variant goes in ~/ under a new name, never as an edit to the shared file.
  • Frontmatter by scenario: noisy analysis or brainstorming means context: fork. Must not destroy anything means scoped allowed-tools. Invoked with no parameters means argument-hint.
  • Skill vs CLAUDE.md: always-loaded standards go in CLAUDE.md, on-demand workflows in a skill, regardless of importance.
  • 3.4: complexity stated up front (microservices, 45-file migration, integration choice) means plan mode at the outset, not midway. Single-file fix or one conditional means direct execution.
  • Combined: plan for investigation, direct execution for implementation. The Explore subagent keeps verbose discovery from exhausting context.

Exercises

  1. Write .claude/skills/audit-refund-flow/SKILL.md (checks process_refund call sites for missing identity verification) with context: fork and read-only allowed-tools. Then make a date-range version with argument-hint. Confirm the forked exploration never appears in your transcript.
  2. Before starting a real refactor, write one sentence on whether its complexity is declared up front. If it is, plan first and compare the plan with what direct execution would have done for the first few files.
  3. Copy a team skill under a new name into ~/.claude/skills/ and modify it. Confirm the original still behaves as teammates expect.
  4. On a small multi-call-site change, plan across all call sites, then execute. Note where the plan was wrong and whether an Explore pass first would have caught it.

Chapter 11 — Claude Code in CI

Interactive Claude Code waits for you when it's stuck or needs permission. In a pipeline nobody answers, so the job hangs until the runner times out. This chapter (task statement 3.6) covers what makes Claude Code a CI participant: a flag that never waits for input, flags for machine-parseable output, and CLAUDE.md as the way to tell an unattended instance what "good" looks like.

Running example: a PR review bot that checks a diff against team standards and posts inline comments. Secondary example: test generation for changed code.

The -p flag

-p / --print runs Claude Code non-interactively in a single shot. It takes the prompt, does the work, writes to stdout, and exits, without asking follow-up questions or blocking on stdin. Sample question 10 asks what fixes a CI job that hangs waiting for interactive input.

claude -p "Review the diff between origin/main and HEAD for correctness issues, \
security concerns, and violations of the standards in CLAUDE.md" \
  --output-format json \
  --json-schema "$(cat ./ci/review-schema.json)"

Wrong answers to reject by name:

  • CLAUDE_HEADLESS: doesn't exist.
  • --batch: doesn't exist.
  • < /dev/null: a generic Unix trick. It doesn't use Claude Code's non-interactive contract (write stdout, exit).

For GitHub Actions specifically, the official anthropics/claude-code-action wraps headless claude -p. It takes claude_args (e.g. --json-schema) and exposes structured_output as an Actions output. It is more idiomatic than hand-rolled subprocess calls. The code below does the same thing by hand.

Structured findings: --output-format json and --json-schema

The consumer is a script that posts inline comments at file and line, or decides pass/fail. Parsing prose breaks as soon as a word changes.

  • --output-format json: output is JSON.
  • --json-schema: constrains that JSON to your schema (fields, types, required), so the bot can deserialise into a known type.

Use them together. An option naming --output-format json alone, or regex-parsing prose, is incomplete.

Two mechanical gotchas the CLI enforces (the guide doesn't mention them):

  1. --json-schema takes the schema inline as a JSON string, not a file path. --json-schema ./ci/review-schema.json fails with --json-schema is not valid JSON. Keep the file in git, but pass its contents: "$(cat ./ci/review-schema.json)".
  2. --output-format json returns a single JSON object (result, session_id, usage, total_cost_usd, and structured_output when a schema is passed). Read json.loads(stdout)["structured_output"]. The array of events (system/assistant/user/result) belongs to --output-format stream-json, a newline-delimited stream for live progress (token-level needs --verbose --include-partial-messages). Treating the json output as an array gives a TypeError.

Finding schema:

{
  "type": "object",
  "properties": {
    "findings": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "file": { "type": "string" },
          "line": { "type": "integer" },
          "severity": { "type": "string", "enum": ["blocking", "warning", "nit"] },
          "message": { "type": "string" },
          "issueId": { "type": "string" }
        },
        "required": ["file", "line", "severity", "message", "issueId"]
      }
    }
  },
  "required": ["findings"]
}

Build in issueId from the start, because it's what makes duplicate-free re-review possible (below). Chapter 13 covers schema design in general.

Python

import json
import subprocess

def claude_structured(prompt: str, schema_path: str) -> dict:
    """Run Claude Code non-interactively and return the structured payload."""
    schema = open(schema_path).read()
    completed = subprocess.run(
        ["claude", "-p", prompt, "--output-format", "json", "--json-schema", schema],
        capture_output=True, text=True, check=True,
    )
    return json.loads(completed.stdout)["structured_output"]

def run_review(schema_path: str, base_ref: str = "origin/main") -> dict:
    return claude_structured(
        f"Review the diff between {base_ref} and HEAD against the standards "
        "in CLAUDE.md. Report only issues you are confident about.",
        schema_path,
    )

def post_inline_comments(findings: dict, pr_number: int) -> None:
    for finding in findings["findings"]:
        github_client.create_review_comment(
            pr_number,
            path=finding["file"],
            line=finding["line"],
            body=f"**{finding['severity']}**: {finding['message']}",
        )

TypeScript

import { execFile } from "node:child_process";
import { promises as fs } from "node:fs";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);

async function claudeStructured<T>(prompt: string, schemaPath: string): Promise<T> {
  const schema = await fs.readFile(schemaPath, "utf-8");
  const { stdout } = await execFileAsync("claude", [
    "-p", prompt, "--output-format", "json", "--json-schema", schema,
  ]);
  const response = JSON.parse(stdout) as { structured_output: T };
  return response.structured_output;
}

interface Finding {
  file: string;
  line: number;
  severity: "blocking" | "warning" | "nit";
  message: string;
  issueId: string;
}

async function runReview(schemaPath: string, baseRef = "origin/main"): Promise<{ findings: Finding[] }> {
  return claudeStructured(
    `Review the diff between ${baseRef} and HEAD against the standards in CLAUDE.md. ` +
      "Report only issues you are confident about.",
    schemaPath,
  );
}

async function postInlineComments(findings: { findings: Finding[] }, prNumber: number) {
  for (const finding of findings.findings) {
    await githubClient.createReviewComment(prNumber, {
      path: finding.file,
      line: finding.line,
      body: `**${finding.severity}**: ${finding.message}`,
    });
  }
}

The prompt line about confidence states the goal of minimising false positives, which matters because a bot that comments on every quibble gets muted within a week. It is a weak way to meet that goal, though. Chapter 12 explains why a vague appeal to confidence fails and why explicit report/skip categories in CLAUDE.md work better. The schema constrains the shape; the prompt and CLAUDE.md constrain which findings get posted.

Non-interactive execution

  • --bare is recommended for CI and scripting. It skips auto-discovery of hooks, skills, commands, subagents, plugins, MCP servers, CLAUDE.md, and auto memory, for determinism across runners. The cost is that CLAUDE.md doesn't load either. If you need both reproducibility and team standards, supply them explicitly, e.g. --append-system-prompt-file.
  • Permission prompts stall a pipeline like unanswered stdin. Settle them up front:

    • --allowedTools pre-approves specific tools without restricting the rest. --tools restricts which tools exist, and --disallowedTools removes one.
    • --permission-mode (auto, dontAsk, acceptEdits) sets the default for everything else.

    A read-only review bot needs little. A test-generation pass must have the write tool pre-approved.

CLAUDE.md as the CI context mechanism

A CI instance has no memory of your interactive sessions. It doesn't know "no magic numbers" or "use Fixtures.customer()". CLAUDE.md is the mechanism for supplying testing standards, fixture conventions, and review criteria, loaded the same as in interactive sessions (without --bare).

Write specific rules. "Follow best practices" adds nothing, while "Flag any process_refund call without an idempotency key" encodes something you've been burned by.

Test generation: documenting what a valuable test looks like

Asking for "tests for this" produces lots of low-value tests: restating the type checker, testing getters. The fix in the Code Generation scenario is to document testing standards, valuable-test criteria, and available fixtures in CLAUDE.md. For example:

  • One test per distinct business rule, not per code path.
  • Constructor and trivial-getter tests aren't valuable.
  • Use the Fixtures module, not inline literals.
  • Refund-window and partial-refund edge cases are always worth covering. Description-field formatting variants are not.

The second lever is to include existing test files in context as well as the changed source. Otherwise the pass can't know test_refund_after_window_closed exists and will propose a near-duplicate.

Python

def generate_tests_for_pr(changed_file: str, existing_test_file: str, schema_path: str) -> dict:
    existing_tests = open(existing_test_file).read()
    return claude_structured(
        f"Generate tests for the changes in {changed_file}. "
        "Testing standards and valuable-test criteria are in CLAUDE.md. "
        "Here are the existing tests for this module — do not propose scenarios "
        f"already covered:\n\n{existing_tests}",
        schema_path,
    )

TypeScript

async function generateTestsForPr(
  changedFile: string, existingTestFile: string, schemaPath: string,
): Promise<{ tests: unknown[] }> {
  const existingTests = await fs.readFile(existingTestFile, "utf-8");
  return claudeStructured(
    `Generate tests for the changes in ${changedFile}. ` +
      "Testing standards and valuable-test criteria are in CLAUDE.md. " +
      "Here are the existing tests for this module — do not propose scenarios " +
      `already covered:\n\n${existingTests}`,
    schemaPath,
  );
}

Why the session that wrote the code is a poor judge of it

Having the session that just wrote the code review it seems efficient, but it works worse than an independent instance, for these reasons:

  • While writing, the session made many small calls (skip this unlikely edge case, this name is close enough, this shortcut is fine), each reasonable in context.
  • Reviewing its own work, it grades decisions it just rationalised. Its context carries momentum toward "this is fine", and self-consistency makes it stick with earlier judgements rather than reopen them.
  • A fresh claude -p reads the diff against CLAUDE.md with no prior context and no investment in those choices, so it is better placed to catch the skipped null check or the misused fixture.

The CI pipeline should run a separate review step.

Avoiding duplicate comments across re-reviews

A bot that re-runs on every push reposts the same comments, because a fresh claude -p has no memory of the last run. The fix is to pass the prior findings in the prompt and instruct it to report only new or still-unaddressed issues. The bot's own state (a file, the comment thread, a DB row) holds the last run's issueIds.

Python

def run_incremental_review(schema_path: str, prior_findings: list[dict], base_ref: str) -> dict:
    prior_summary = json.dumps(prior_findings)
    return claude_structured(
        f"Review the diff between {base_ref} and HEAD against CLAUDE.md standards. "
        f"Here are findings from the previous review of this PR: {prior_summary}. "
        "Report only issues that are new since that review, or that were previously "
        "flagged and remain unaddressed in the current diff. Reuse the same issueId "
        "for a still-unaddressed issue rather than minting a new one.",
        schema_path,
    )

TypeScript

async function runIncrementalReview(
  schemaPath: string, priorFindings: Finding[], baseRef: string,
): Promise<{ findings: Finding[] }> {
  return claudeStructured(
    `Review the diff between ${baseRef} and HEAD against CLAUDE.md standards. ` +
      `Here are findings from the previous review of this PR: ${JSON.stringify(priorFindings)}. ` +
      "Report only issues that are new since that review, or that were previously " +
      "flagged and remain unaddressed in the current diff. Reuse the same issueId " +
      "for a still-unaddressed issue rather than minting a new one.",
    schemaPath,
  );
}

Rejected alternatives:

  • Don't re-run after new commits. You lose the safety net exactly when a fix introduces a new problem.
  • Diff raw comment text to suppress repeats. This is fragile, because slight rewording breaks the match.

What the exam tests

  • -p / --print is the only fix for a CI hang on interactive input. CLAUDE_HEADLESS and --batch are invented. /dev/null doesn't address the command contract. This is sample question 10.
  • --output-format json + --json-schema together give structured findings for inline PR comments. The payload is one object with structured_output. The event array is stream-json.
  • CLAUDE.md supplies testing standards, fixture conventions, and review criteria to CI runs. --bare skips its auto-loading, so "reproducible and must follow CLAUDE.md" means supplying it explicitly.
  • --permission-mode / --allowedTools pre-clear actions so the pipeline doesn't stall on a permission prompt.
  • Independent review instance beats self-review. Be able to explain why, in terms of momentum and self-consistency.
  • Avoid duplicate work, as a pair: prior findings plus "report only new or unaddressed" for re-review; existing test files in context for generation.

Exercises

  1. Add a claude -p review step to a GitHub Actions workflow on every PR, with --output-format json and your own --json-schema, posting each finding inline via the GitHub API. Confirm no hang on a large diff.
  2. Write a specific CLAUDE.md review and testing section (real fixture names, real "not worth flagging" examples). Run the same diff with and without it and compare, including false positives that disappeared.
  3. Run the bot, capture issueIds, fix one issue and leave one, push, and re-run with prior findings. The fixed one should drop off and the other persist with the same id.
  4. Have a session review its own nontrivial change to process_refund or extract_invoice, then run a fresh claude -p review of the same diff. Compare.

Chapter 12 — Prompt engineering

Every CLAUDE.md line you tightened because Claude kept getting it wrong was prompt engineering. The exam asks you to diagnose a prompt's failure and pick the matching fix.

Tasks 4.1 and 4.2 turn on one distinction. Explicit criteria fix a rule that was never written down. Few-shot examples fix a rule that is written down but applied inconsistently.

Task 3.5 (iterative refinement) is separate. It covers the process you run with Claude Code to converge on a working prompt or implementation, not what the prompt contains.

Running example

A review bot (the Code Generation scenario) has run on every PR for a month. About one comment in three is noise: style nits labelled "medium", or false alarms on patterns standard in this codebase. Developers stop reading it, including the comment that would have caught a real bug.

False positives in one category erode trust in every category, because readers don't track trust per category.

The instinctive fix is "be conservative" or "only report high-confidence findings", which is a named trap. General appeals to caution don't improve precision compared with specific categorical criteria. The model was already guessing at an undefined boundary. "Be conservative" makes it guess less often, in both directions, in a ratio you didn't choose.

4.1 When the rule does not exist

Write the rule down, as in this pair:

  • Vague: "Check that comments are accurate." This is a fuzzy standard that gets judged differently on Monday and Friday.
  • Explicit: "Flag comments only when claimed behavior contradicts actual code behavior." This is an operational test. You read the claim, read the code, and check whether they contradict.

The general skill is to state which categories to report and which to skip, not to filter by confidence. Confidence is the model's self-assessment of an undefined question, and it is no more reliable than the question. Put bugs and security in scope, minor style and locally idiomatic patterns out, stated as rules about the code.

Python

REVIEW_SYSTEM_PROMPT = """
You are reviewing a pull request diff for get_customer, lookup_order, and
process_refund. Report an issue only if it falls into one of these categories:

- BUG: the code will produce an incorrect result or crash on some input that
  the diff's tests do not cover. Do not report a bug for behavior that matches
  the existing tests, even if you would have designed it differently.
- SECURITY: the change introduces an injection risk, a missing authorization
  check on a path that mutates customer data, or logs a secret or PII field.
- COMMENT_MISMATCH: a comment or docstring makes a factual claim about
  behavior that contradicts what the code actually does. Do not report a
  comment merely because it is vague, outdated in tone, or could be phrased
  better — only contradiction counts.

Do NOT report: formatting, naming conventions, import order, or any pattern
that already appears elsewhere in this file. If a pattern is already in the
codebase, it is a local convention, not a bug, even if you would write it
differently.
"""

TypeScript

const REVIEW_SYSTEM_PROMPT = `
You are reviewing a pull request diff for getCustomer, lookupOrder, and
processRefund. Report an issue only if it falls into one of these categories:

- BUG: the code will produce an incorrect result or crash on some input that
  the diff's tests do not cover. Do not report a bug for behavior that matches
  the existing tests, even if you would have designed it differently.
- SECURITY: the change introduces an injection risk, a missing authorization
  check on a path that mutates customer data, or logs a secret or PII field.
- COMMENT_MISMATCH: a comment or docstring makes a factual claim about
  behavior that contradicts what the code actually does. Do not report a
  comment merely because it is vague, outdated in tone, or could be phrased
  better -- only contradiction counts.

Do NOT report: formatting, naming conventions, import order, or any pattern
that already appears elsewhere in this file. If a pattern is already in the
codebase, it is a local convention, not a bug, even if you would write it
differently.
`;

Severity: if labels vary with no anchor, define each level with a concrete code example. "High: an unauthenticated endpoint returning another customer's order history, like lookup_order(order_id) with no ownership check" can be pattern-matched. "High: something important" can't.

Disable a collapsing category. When one category's false positives are destroying trust and you haven't found the right criteria yet, turn it off. Ship BUG + SECURITY only, disable COMMENT_MISMATCH, fix its prompt offline against a sample set, and re-enable it once its precision is defensible.

4.2 When the rule exists but bends on hard cases

The criteria are in place and good, and the bot gets the obvious cases right, but it is inconsistent near the boundary. process_refund catches a broad exception and logs without re-raising, which could be a bug or deliberate degradation. It's flagged on some runs and not others. The rule is defined but applied inconsistently where judgement is needed, which calls for few-shot examples.

Few-shot means 2–4 fully worked examples in the prompt: input, reasoning, output. It works best for consistent, actionable output when prose keeps producing variation, because an example demonstrates the reasoning behind an ambiguous call, and the model can generalise that reasoning to novel cases. The examples don't need to cover every pattern, because they show how to weigh competing considerations.

Include the reasoning with each verdict.

  • "diff X → no finding" teaches one answer.
  • "diff X → broad catch swallows the exception without logging the customer-facing error path, no upstream handler visible, so a bug, not deliberate degradation → BUG, medium" teaches the test to run next time.

Python

FEW_SHOT_EXAMPLES = """
Example 1 — ambiguous exception handling
Diff: process_refund catches `PaymentGatewayError` broadly, logs the message
at INFO level, and returns a generic success response to the caller.
Reasoning: swallowing a payment failure and reporting success to the caller
means a failed refund looks successful to the customer-facing code. This is
not a deliberate degrade-gracefully pattern (there's no fallback path here,
just silence), and the customer-facing consequence is a real correctness
issue, not a style choice.
Output: {"location": "process_refund:47", "issue": "Payment failure is
caught and logged but reported to the caller as success", "severity": "high",
"suggested_fix": "Return an error result when the gateway call fails instead
of a generic success payload"}

Example 2 — a broad catch that IS acceptable
Diff: lookup_order catches `Exception` around a metrics-emission call, logs
at DEBUG, and continues; the function's real return value is computed before
the try block and is unaffected either way.
Reasoning: the broad catch only protects a non-critical side effect
(metrics), and its failure cannot alter the function's actual return value.
This matches the existing pattern used around every other metrics call in
this file, so it's a local convention, not a bug.
Output: no finding.
"""

TypeScript

const FEW_SHOT_EXAMPLES = `
Example 1 -- ambiguous exception handling
Diff: processRefund catches PaymentGatewayError broadly, logs the message at
INFO level, and returns a generic success response to the caller.
Reasoning: swallowing a payment failure and reporting success to the caller
means a failed refund looks successful to the customer-facing code. This is
not a deliberate degrade-gracefully pattern (there's no fallback path here,
just silence), and the customer-facing consequence is a real correctness
issue, not a style choice.
Output: {"location": "processRefund:47", "issue": "Payment failure is caught
and logged but reported to the caller as success", "severity": "high",
"suggestedFix": "Return an error result when the gateway call fails instead
of a generic success payload"}

Example 2 -- a broad catch that IS acceptable
Diff: lookupOrder catches a generic error around a metrics-emission call,
logs at DEBUG, and continues; the function's real return value is computed
before the try block and is unaffected either way.
Reasoning: the broad catch only protects a non-critical side effect
(metrics), and its failure cannot alter the function's actual return value.
This matches the existing pattern used around every other metrics call in
this file, so it's a local convention, not a bug.
Output: no finding.
`;

Named uses of few-shot:

  1. Ambiguous cases, shown with reasoning (above). The guide's examples: tool selection for ambiguous requests (below) and branch-level test coverage gaps (is an untested else branch worth flagging?).
  2. Distinguishing acceptable patterns from real issues. The pair above deliberately shows one real issue and one lookalike that's fine. Positives alone teach surface matching ("broad catch = flag").
  3. Output format. Location, issue, severity, fix, in order. The model copies a shown structure far more reliably than it follows a described one.
  4. Reducing hallucination in extraction from informal measurements and varied document structures. extract_invoice will fabricate total_amount when the total appears only as "call it forty-five hundred, even" in a footnote, unless it has seen that handled. Give 2–3 worked extractions covering real variants (line-item table with a totals block, total only in prose, implied discount), including absent field returns null, don't invent. The same applies to inline citations vs a bibliography, or methodology up front vs embedded in results.

Tool selection works the same way. "Where's my package" is clearly lookup_order. "Third time my order's been late, I want someone to look at my account" splits between lookup_order and escalate_to_human. An example reasoning that the repeated history and the word "someone" point to escalation, and that a lookup first only delays a human, teaches the weighing so it transfers to different wording.

Telling 4.1 and 4.2 apart

Few-shot is the wrong fix when no one has written down what counts, because there is no correct behaviour to demonstrate yet. Rewording the criteria is the wrong fix when they are agreed and the trouble is on judgement calls.

Symptom Diagnosis Fix
No stated rule for what counts as a bug vs. a nit Undefined decision boundary Explicit categorical criteria (4.1)
Severity labels vary reviewer to reviewer, no anchor Undefined severity scale Severity criteria with concrete examples per level (4.1)
One category has terrible precision, trust is collapsing Undefined or poorly-tuned rule for that category Disable the category temporarily, fix its prompt offline (4.1)
Rule is written down, edge cases still go wrong Defined boundary, inconsistent application 2 to 4 few-shot examples with reasoning (4.2)
Output format drifts run to run despite instructions Defined intent, inconsistent formatting Examples showing the literal output shape (4.2)
Extraction returns null or fabricates values on unusual documents Defined schema, inconsistent handling of varied structure Examples spanning the structural variants (4.2)

"Be conservative" is neither a criterion nor an example, so it fixes neither 4.1 nor 4.2.

3.5 Iterative refinement

Task 3.5 covers how you converge on a result with Claude Code, turn by turn, using four techniques:

  1. Concrete input/output examples when prose fails. "Normalise dates, handling the three legacy formats" gets read inconsistently. 2–3 exact input/output pairs pin it down. It's the same mechanism as few-shot, used to communicate a spec during development. The guide's named case is data-migration edge cases. Instead of "handle nulls sensibly", give customer_id: null, order_total: 45.00 → route to the quarantine table, don't insert with a fabricated ID.
  2. Test-driven iteration. Write tests first (expected behaviour, edge cases, performance requirements), then iterate by feeding Claude Code the test failures. A failing test is a precise, falsifiable statement of the gap, and every red test replaces a round of "no, I meant…".
  3. The interview pattern. Have Claude Code ask you questions first, as a competent engineer would before coding. It is most valuable in unfamiliar domains, where it brings up things you wouldn't think to specify: cache invalidation strategy for a caching layer, failure modes of a job queue you've never run. It's the clarifying question in plan mode, invoked deliberately and up front.
  4. Single message vs sequential, depending on whether the problems interact:
    • Independent (a typo here, an unrelated off-by-one there): fix sequentially. Bundling adds nothing and makes each fix harder to verify.
    • Interacting (a caching bug plus an invalidation-ordering bug that only makes sense given the first): one detailed message. Fixed separately, the first fix can invalidate your description of the second or make it worse. For example, a migration's null-handling fix depends on where the transaction boundary is placed.

Context awareness

On Sonnet 5, Sonnet 4.6, Sonnet 4.5, and Haiku 4.5, Claude automatically receives <budget:token_budget> and <system_warning>Token usage: X/Y remaining</system_warning> as a conversation runs. So "as you approach your budget, stop exploring and write up what you have" now works without you computing the number.

It does not replace Chapter 14's case-facts block or manifest. Budget awareness paces work against a shrinking window. Those patterns decide which exact numbers and conclusions must survive compaction. A model that wraps up gracefully at 90% can still lose a dollar amount to summarisation.

Models older than Opus 4.7 don't get this automatically. They need an explicit task budget via a beta flag, stated in the prompt.

What the exam tests

  • 4.1/4.2 as a diagnostic pair. A frequent trap is "be more conservative" or "raise the confidence threshold". Treat any general appeal to caution as wrong.
  • Missing boundary (4.1): write criteria, define severity with concrete examples, or temporarily disable a collapsing category.
  • Shaky on ambiguous cases (4.2): 2–4 few-shot examples showing reasoning, distinguishing acceptable from real issues, demonstrating output format.
  • Extraction under 4.2: varied document structure and null-field fabrication are few-shot problems once the schema is correct. Schema problems are Chapter 13.
  • 3.5: concrete I/O examples after prose has failed; test-first with failures as feedback; interview pattern for unfamiliar domains; single message vs sequential depends on interaction.

Exercises

  1. Write a vague review prompt ("check for bugs and be careful about false positives"). Run it on a diff with one real bug and two borderline patterns. Rewrite it with explicit report/skip categories and confirm the borderline verdicts moved (the 4.1 effect).
  2. Add two reasoning-bearing few-shot examples (one flag, one not). Test on a third, unseen borderline pattern and check that the model generalises the reasoning (the 4.2 effect).
  3. Run an ambiguous transformation (real-world dates, or mapping requests to lookup_order/escalate_to_human) three times from prose alone, then three times with 2–3 I/O examples. Compare the variance.
  4. Build interacting problems: a rate limiter with a counter race plus a window off-by-one, where fixing the boundary moves the race. Report both in one message, then sequentially in a fresh session. If the sequential first fix has to be partly undone, you've reproduced the 3.5 failure.

Chapter 13 — Structured output

When output goes to a program rather than a person, a careful prompt that asks for JSON isn't enough. The running example is extract_invoice, which returns vendor, line items, taxes, and total for an accounts-payable pipeline.

It fails in two different ways, with different fixes:

  • Syntax: malformed JSON (dangling comma, unescaped quote in "Smith & Sons"). Fixed by schema and tool use.
  • Semantics: well-formed JSON that is wrong (line items sum to $412.50, total says $391.00). Fixed by validation.

Using a stricter schema for a semantic error, or validation for a syntax error, is a misdiagnosis.

Tool use as the extraction mechanism

A tool_use block's input conforms to the tool's input_schema. For extraction, the "tool" does nothing and extract_invoice touches no database. Its schema is the contract, and tool_use.input arrives as a parsed object.

Tool use is a reliable way to get schema-compliant output. Asking for JSON in prose means validating a string after the fact, and a dropped brace shows up at json.loads, far from the cause. With tool use there's no string to parse, so malformed JSON is structurally impossible, which is a guarantee and not only a lower error rate.

Python

extract_invoice = {
    "name": "extract_invoice",
    "description": (
        "Extract structured data from an invoice document: vendor identity, "
        "line items, tax, and totals. Use this on any invoice, receipt, or "
        "billing statement provided as text or an attached document. Do not "
        "use this on purchase orders or shipping manifests, which have no "
        "total to reconcile."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "vendor_name": {"type": "string"},
            "invoice_number": {"type": ["string", "null"]},
            "invoice_date": {
                "type": ["string", "null"],
                "description": "ISO 8601 date, e.g. 2026-03-14. Null if unreadable.",
            },
            "line_items": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "description": {"type": "string"},
                        "quantity": {"type": "number"},
                        "unit_price": {"type": "number"},
                        "line_total": {"type": "number"},
                    },
                    "required": ["description", "quantity", "unit_price", "line_total"],
                },
            },
            "tax_amount": {"type": ["number", "null"]},
            "stated_total": {
                "type": "number",
                "description": "The total as printed on the invoice.",
            },
            "calculated_total": {
                "type": "number",
                "description": (
                    "Sum of line_total across line_items, plus tax_amount. "
                    "Compute this yourself; do not copy stated_total."
                ),
            },
            "conflict_detected": {
                "type": "boolean",
                "description": "True if calculated_total and stated_total differ by more than $0.01.",
            },
            "category": {
                "type": "string",
                "enum": ["goods", "services", "subscription", "other"],
            },
            "category_detail": {
                "type": ["string", "null"],
                "description": "Required when category is 'other'; free-text description.",
            },
        },
        "required": [
            "vendor_name", "line_items", "stated_total",
            "calculated_total", "conflict_detected", "category",
        ],
    },
}

TypeScript

const extractInvoice: Anthropic.Tool = {
  name: "extract_invoice",
  description: [
    "Extract structured data from an invoice document: vendor identity, line",
    "items, tax, and totals. Use this on any invoice, receipt, or billing",
    "statement provided as text or an attached document. Do not use this on",
    "purchase orders or shipping manifests, which have no total to reconcile.",
  ].join("\n"),
  input_schema: {
    type: "object",
    properties: {
      vendor_name: { type: "string" },
      invoice_number: { type: ["string", "null"] },
      invoice_date: {
        type: ["string", "null"],
        description: "ISO 8601 date, e.g. 2026-03-14. Null if unreadable.",
      },
      line_items: {
        type: "array",
        items: {
          type: "object",
          properties: {
            description: { type: "string" },
            quantity: { type: "number" },
            unit_price: { type: "number" },
            line_total: { type: "number" },
          },
          required: ["description", "quantity", "unit_price", "line_total"],
        },
      },
      tax_amount: { type: ["number", "null"] },
      stated_total: {
        type: "number",
        description: "The total as printed on the invoice.",
      },
      calculated_total: {
        type: "number",
        description:
          "Sum of line_total across line_items, plus tax_amount. Compute this yourself; do not copy stated_total.",
      },
      conflict_detected: {
        type: "boolean",
        description: "True if calculated_total and stated_total differ by more than $0.01.",
      },
      category: {
        type: "string",
        enum: ["goods", "services", "subscription", "other"],
      },
      category_detail: {
        type: ["string", "null"],
        description: "Required when category is 'other'; free-text description.",
      },
    },
    required: [
      "vendor_name", "line_items", "stated_total",
      "calculated_total", "conflict_detected", "category",
    ],
  },
};

Reading the result:

Python

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    tools=[extract_invoice],
    tool_choice={"type": "tool", "name": "extract_invoice"},
    messages=[{"role": "user", "content": invoice_text}],
)
invoice_block = next(b for b in response.content if b.type == "tool_use")
invoice_data = invoice_block.input  # already a parsed dict, schema-shaped

TypeScript

const response = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  tools: [extractInvoice],
  tool_choice: { type: "tool", name: "extract_invoice" },
  messages: [{ role: "user", content: invoiceText }],
});
const invoiceBlock = response.content.find(
  (b): b is Anthropic.ToolUseBlock => b.type === "tool_use",
);
const invoiceData = invoiceBlock!.input; // already parsed, schema-shaped

output_config.format and strict tool use

  • output_config.format with {"type": "json_schema", "schema": {...}} is dedicated structured output with no tools involved. Constrained decoding guarantees a match, and the JSON arrives in a plain text block. It is the more direct fit when there's no natural "tool", e.g. classifying a ticket into fixed fields.
  • strict: true on a tool definition. tool_choice forcing guarantees that a call happens, but without strict the arguments are only reliable, not guaranteed. strict applies constrained decoding to the arguments, so extract_invoice should carry it.

The two are independent. output_config.format shapes the answer and strict shapes tool calls, so one request can use both (e.g. lookup-then-report).

Python

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": invoice_text}],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "vendor_name": {"type": "string"},
                    "stated_total": {"type": "number"},
                },
                "required": ["vendor_name", "stated_total"],
                "additionalProperties": False,
            },
        }
    },
)
invoice_data = json.loads(response.content[0].text)

TypeScript

const response = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: invoiceText }],
  output_config: {
    format: {
      type: "json_schema",
      schema: {
        type: "object",
        properties: {
          vendor_name: { type: "string" },
          stated_total: { type: "number" },
        },
        required: ["vendor_name", "stated_total"],
        additionalProperties: false,
      },
    },
  },
});
const invoiceData = JSON.parse(response.content[0].text);

Supported JSON Schema subset (both mechanisms):

  • No numeric bounds (minimum, maximum, multipleOf).
  • No string length bounds.
  • additionalProperties must be false.
  • No recursive schemas.
  • Only a limited regex dialect for pattern.

Nullable ["string", "null"] and enum-plus-detail (below) still work. Move range and length checks into your own code.

The first call with a schema pays grammar compilation latency. The grammar is cached for 24 h from last use. Changing the schema invalidates both the cached grammar and the prompt cache, so keep the schema fixed and put per-request variation in the prompt text.

Choosing how the tool gets called

Setting Guarantees Invoice-pipeline use
{"type": "auto"} Nothing. Claude may answer in text Wrong default for extraction
{"type": "any"} Some tool is called Mixed document stream, schema unknown until Claude looks
{"type": "tool", "name": "extract_invoice"} That specific tool is called Document type known, or extraction must precede enrichment
  • "any": a mixed stream (invoices, POs, manifests). Claude must pick some extraction tool, classifying implicitly.
  • Forced: force extract_invoice first when enrich_vendor_record needs vendor_name, or flag_for_review needs conflict_detected. Then return to "auto".

Current product (not tested): forced tool_choice returns a 400 on Sonnet 5.5, Opus 5.5 and Fable 5.1; see Chapter 2. On those models use strict: true or output_config.format.

Schema design decisions

  • Required fields invite fabrication. Require invoice_number and a receipt that lacks one will sometimes get an invented value. Make it nullable (["string", "null"]). An honest null trips your validation, but a fabricated value passes it.
  • Enum with an escape hatch. category ∈ "goods", "services", "subscription", "other", plus category_detail, required when "other". You get clean aggregation on the common cases and an accurate description of the rest.
  • "unclear" for judgement enums (paid/unpaid/disputed), so an ambiguous case has a legitimate answer and the model isn't forced into a wrong guess.
  • Format normalisation goes in the prompt, not the schema. A string field accepts 03/14/2026, 14 Mar 2026, and 2026-03-14 alike. "Return dates as ISO 8601; if ambiguous, infer the most likely reading" does what no pattern can.

Syntax versus semantics

Schema compliance tells you the output parses, not that it's correct. The $412.50 vs $391.00 invoice is fully schema-valid. Structure says nothing about arithmetic, provenance, or internal consistency.

The worked example has the model do the arithmetic in a checkable field.

  • stated_total: the number printed on the document.
  • calculated_total: "sum of line_total across line_items, plus tax_amount. Compute this yourself; do not copy stated_total."

The model has to add up the lines instead of copying the printed total, and your code diffs the two. A difference of more than a cent goes to a human before AP.

conflict_detected is a boolean the model sets when the document is internally inconsistent (totals that disagree, two dates for one event, a name spelled two ways). Because it is a field, the pipeline can check it on every document.

Failure Fixed by
Malformed / unparseable JSON Schema + tool use (eliminated structurally)
Required field omitted Tool use catches and refuses it. Whether retry helps is the next section
Valid JSON, wrong values (don't reconcile, wrong field, fabrication) Validation logic, not a stricter schema

Validation, retry, and feedback loops

Retry only fixes some failures:

  • Retriable: the information is in the document and the first pass got it wrong (an omitted field, a dropped line item, a miscalculated total).
  • Not retriable: the information isn't in the source (e.g. a PO in a system you never provided). Attempt five has no more access than attempt one, and an unguarded loop burns requests forever. Route to a human, or to a step that fetches the missing document.

Classify the failure before retrying. When you retry, send the original document + the failed extraction + the specific validation error, so the model fixes a named mistake.

Python

def retry_extraction(client, document_text, failed_extraction, validation_error):
    retry_prompt = (
        f"The previous extraction of this invoice failed validation:\n\n"
        f"{validation_error}\n\n"
        f"Previous extraction:\n{json.dumps(failed_extraction, indent=2)}\n\n"
        f"Original invoice:\n{document_text}\n\n"
        f"Re-extract, correcting the specific issue above. Recompute "
        f"calculated_total from line_items rather than reusing the prior value."
    )
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=1024,
        tools=[extract_invoice],
        tool_choice={"type": "tool", "name": "extract_invoice"},
        messages=[{"role": "user", "content": retry_prompt}],
    )
    return next(b for b in response.content if b.type == "tool_use").input

TypeScript

async function retryExtraction(
  client: Anthropic,
  documentText: string,
  failedExtraction: unknown,
  validationError: string,
): Promise<unknown> {
  const retryPrompt = [
    `The previous extraction of this invoice failed validation:`,
    ``,
    validationError,
    ``,
    `Previous extraction:`,
    JSON.stringify(failedExtraction, null, 2),
    ``,
    `Original invoice:`,
    documentText,
    ``,
    `Re-extract, correcting the specific issue above. Recompute`,
    `calculated_total from line_items rather than reusing the prior value.`,
  ].join("\n");

  const response = await client.messages.create({
    model: "claude-sonnet-5",
    max_tokens: 1024,
    tools: [extractInvoice],
    tool_choice: { type: "tool", name: "extract_invoice" },
    messages: [{ role: "user", content: retryPrompt }],
  });
  const block = response.content.find(
    (b): b is Anthropic.ToolUseBlock => b.type === "tool_use",
  );
  return block!.input;
}

For code-scanning agents, detected_pattern is a field naming the construct that triggered each finding. When developers dismiss findings, group the dismissals by detected_pattern to see which construct over-triggers. Like calculated_total, it turns part of the model's reasoning into a field you can query.

Designing for batch volume

Message Batches API (task 4.5):

  • 50% cheaper, flat, not volume-tiered.
  • Processed within up to 24 hours, with no SLA for anything faster. Design for the slow case.
  • Not for multi-turn tool calling (see below).
  • Every request carries a custom_id.

The deciding question is latency tolerance, not cost, meaning whether anyone is blocked waiting for the result.

  • Fits: overnight tech-debt report, weekly compliance audit, nightly test generation, nightly invoice reconciliation. The consumer checks a dashboard tomorrow.
  • Doesn't fit: a pre-merge check. A developer can't wait an unknown 20 minutes to 24 hours.

The SLA arithmetic

Suppose the commitment is a 30-hour SLA and you batch on a fixed schedule.

  • Worst case = wait until the next submission + up to 24 h of processing.
  • 30 − 24 = 6 h of budget for the wait.
  • Every 6 h gives a 30 h worst case, exactly at the ceiling with no margin.
  • Every 4 h gives a 28 h worst case and a 2 h buffer, which makes it the correct answer.

In general, subtract the window from the SLA, then choose an interval comfortably inside the remainder, not at the boundary.

Batch requests

A batch is a list of ordinary Messages requests, each with your custom_id. You submit it, poll, and read the results. They come back in any order, so match on custom_id, never on position.

Python

from anthropic.types.message_create_params import MessageCreateParamsNonStreaming
from anthropic.types.messages.batch_create_params import Request

batch_requests = [
    Request(
        custom_id=f"invoice-{invoice['id']}",
        params=MessageCreateParamsNonStreaming(
            model="claude-sonnet-5",
            max_tokens=1024,
            tools=[extract_invoice],
            tool_choice={"type": "tool", "name": "extract_invoice"},
            messages=[{"role": "user", "content": invoice["text"]}],
        ),
    )
    for invoice in todays_invoices
]

batch = client.messages.batches.create(requests=batch_requests)

# Later, after polling batch.processing_status until "ended":
for result in client.messages.batches.results(batch.id):
    if result.result.type == "succeeded":
        block = next(b for b in result.result.message.content if b.type == "tool_use")
        store_invoice(custom_id=result.custom_id, data=block.input)
    else:
        queue_for_resubmission(custom_id=result.custom_id, failure=result.result)

TypeScript

const batchRequests = todaysInvoices.map((invoice) => ({
  custom_id: `invoice-${invoice.id}`,
  params: {
    model: "claude-sonnet-5",
    max_tokens: 1024,
    tools: [extractInvoice],
    tool_choice: { type: "tool" as const, name: "extract_invoice" },
    messages: [{ role: "user" as const, content: invoice.text }],
  },
}));

const batch = await client.messages.batches.create({ requests: batchRequests });

// Later, after polling batch.processing_status until "ended":
for await (const result of client.messages.batches.results(batch.id)) {
  if (result.result.type === "succeeded") {
    const block = result.result.message.content.find(
      (b): b is Anthropic.ToolUseBlock => b.type === "tool_use",
    );
    storeInvoice(result.custom_id, block!.input);
  } else {
    queueForResubmission(result.custom_id, result.result);
  }
}

A batch request does not support multi-turn tool calling. Each entry is one call. It can return tool_use, but nothing executes the tool and feeds the result back, and no error tells you so.

  • Fits: invoice extraction, because extract_invoice is never executed. The tool block is just the vehicle for structured output.
  • Doesn't fit: look up a vendor mid-extraction, decide whether to flag, then extract. That loop needs multi-turn execution. Run it synchronously, or split it into a batch-friendly first pass plus a later step.

Handling batch failures

  • Results come back errored, canceled, or expired for some fraction. Resubmit only the failed custom_ids, never the whole batch.
  • If failures share a cause, such as oversized scans exceeding context, fix the cause before resending, for example by chunking those documents.
  • Before batching the full volume, run 50–100 invoices synchronously and iterate until first-pass success is high. Discovering a date-format gap after a day-long batch costs the day plus the redo.

What the exam tests

  • 4.3: guaranteed-parseable output needs tool_use + JSON schema, which eliminates syntax errors as a category. tool_choice: "any" when the doc type is unknown, forced when it's known or must precede enrichment.
  • 4.4: syntax vs semantics. Malformed is a schema problem. Well-formed but inconsistent is a validation problem. Be able to reproduce calculated_total vs stated_total + conflict_detected from memory.
  • 4.4 retry classification: format and arithmetic errors are retriable. Information absent from the source is not, so route to a human.
  • 4.5 arithmetic: SLA minus 24 h window, then an interval comfortably inside the remainder (30 h gives every 4 h).
  • 4.5: "call a tool, see the result, decide next" can't run in a batch request. Resubmit only failed custom_ids.

Exercises

  1. Build extract_invoice. Run five invoices with inconsistent totals and confirm conflict_detected flags all five. Then simulate raw-text JSON corruption and note that tool use eliminates it.
  2. Write a retry harness sending document + failed extraction + specific error. Test an arithmetic slip (fixable) and a field that is absent from the document (not fixable, whatever the attempt count).
  3. Redo the SLA arithmetic for a 20-hour SLA with a 24 h window. Can any cadence satisfy it? Explain why not once the SLA is shorter than the window.
  4. Batch ten invoices including one malformed document. Poll, iterate by custom_id, and resubmit only the failure.

Chapter 14 — Context engineering

Two hours into a session, answers soften ("typically follows the pattern used elsewhere") and after /compact a decision from 20 minutes ago has to be repeated. Both have the same cause. The messages array is finite, it holds everything the agent knows, and neither growth nor compression is free.

Task 5.1 Task 5.4
Problem Keep critical facts alive in one long conversation Coordinate context across an exploration too big for one conversation
Example A two-hour, three-issue support case where a dollar amount must survive exactly A multi-phase codebase investigation across subagents
Key fixes Case-facts block, trim tool output Subagents, scratchpads, phase summaries, manifests

5.1 Preserving facts inside one long conversation

  1. Progressive summarisation destroys specifics. Paraphrase keeps the gist and loses numbers. "Customer disputed a charge and we discussed a refund" is a faithful summary of "disputed a $284.17 charge on SO-88213, promised a refund by the 15th, second contact". The amount, order ID, and date are gone. Summarisation is working as designed when it does this.
  2. Lost in the middle. Models attend reliably to the start and end of long input and worse to the middle. A fact stated once mid-conversation is at higher risk of being ignored. It comes from how models read long input, so a model upgrade won't remove it.
  3. Tool results cost tokens by size, not relevance. A realistic lookup_order returns 40+ fields (SKUs, routing codes, tax flags, three address blocks). A return needs five: order id, item, amount, ship date, status. Nothing removes an appended result, so three lookups bury the signal under a hundred-plus irrelevant fields, reprocessed every turn.

The API is stateless, so you must pass the complete history with every request. If you drop earlier turns to save tokens, the model knows nothing about them.

On Claude 4.5+, running out of room mid-generation gives stop_reason: "model_context_window_exceeded" (older models reject the request outright). Handle it separately from max_tokens, which looks similar but has a different cause.

The case-facts block

The fix is to extract transactional facts (amounts, dates, order numbers, statuses, customer-stated expectations) into a structured block that you include in full in every prompt, outside summarised history. It is the per-conversation equivalent of CLAUDE.md sitting in system rather than messages.

Python

from dataclasses import dataclass, field
from typing import Optional

@dataclass
class CaseFacts:
    """Durable, non-summarizable facts for one support case."""
    customer_id: Optional[str] = None
    tier: Optional[str] = None
    issues: list[dict] = field(default_factory=list)
    # each issue: {order_id, amount_cents, status, promised_date, stated_by_customer}

    def render(self) -> str:
        lines = [f"CASE FACTS (verified, do not paraphrase):",
                  f"customer_id={self.customer_id} tier={self.tier}"]
        for i, issue in enumerate(self.issues, start=1):
            lines.append(
                f"  issue {i}: order={issue['order_id']} "
                f"amount=${issue['amount_cents']/100:.2f} "
                f"status={issue['status']} "
                f"promised_by={issue.get('promised_date', 'n/a')}"
            )
        return "\n".join(lines)


def build_request(case_facts: CaseFacts, messages: list, user_turn: str) -> dict:
    # The facts block rides in `system`, alongside the persistent instructions,
    # so /compact and any summarization pass over `messages` never touch it.
    return {
        "model": "claude-sonnet-5",
        "system": f"{SUPPORT_SYSTEM_PROMPT}\n\n{case_facts.render()}",
        "messages": messages + [{"role": "user", "content": user_turn}],
    }

TypeScript

interface Issue {
  orderId: string;
  amountCents: number;
  status: string;
  promisedDate?: string;
}

interface CaseFacts {
  customerId?: string;
  tier?: string;
  issues: Issue[];
}

function renderCaseFacts(facts: CaseFacts): string {
  const lines = [
    "CASE FACTS (verified, do not paraphrase):",
    `customer_id=${facts.customerId} tier=${facts.tier}`,
  ];
  facts.issues.forEach((issue, i) => {
    lines.push(
      `  issue ${i + 1}: order=${issue.orderId} ` +
        `amount=$${(issue.amountCents / 100).toFixed(2)} ` +
        `status=${issue.status} promised_by=${issue.promisedDate ?? "n/a"}`,
    );
  });
  return lines.join("\n");
}

function buildRequest(facts: CaseFacts, messages: Anthropic.MessageParam[], userTurn: string) {
  return {
    model: "claude-sonnet-5",
    system: `${SUPPORT_SYSTEM_PROMPT}\n\n${renderCaseFacts(facts)}`,
    messages: [...messages, { role: "user" as const, content: userTurn }],
  };
}
  • Rebuilt each request from your application state, not produced by asking the model to summarise itself, so it never passes through lossy compression.
  • A separate layer per issue in multi-issue sessions. A billing dispute plus a later shipping complaint each keep their own order id, amount, and status. If both are written in one paragraph, the second issue can end up with the first one's amount. Structured issue data lets the agent answer "status of the shipping issue?" without wading through the billing discussion.

Trimming tool output at the source

The case-facts block protects facts after extraction. Tool bloat happens earlier, when lookup_order returns. Trim before appending. You design the tool boundary, so make the function the agent calls project the full record down to what the task needs.

Python

RETURN_RELEVANT_FIELDS = {"order_id", "item", "amount_cents", "ship_date", "status"}

def lookup_order(order_id: str) -> dict:
    full_record = fulfillment_backend.get_order(order_id)  # 40+ fields
    return {k: v for k, v in full_record.items() if k in RETURN_RELEVANT_FIELDS}

TypeScript

const RETURN_RELEVANT_FIELDS = new Set([
  "orderId", "item", "amountCents", "shipDate", "status",
]);

async function lookupOrder(orderId: string): Promise<Record<string, unknown>> {
  const fullRecord = await fulfillmentBackend.getOrder(orderId); // 40+ fields
  return Object.fromEntries(
    Object.entries(fullRecord).filter(([key]) => RETURN_RELEVANT_FIELDS.has(key)),
  );
}

Once fields are in several tool_results, only a destructive rewrite removes them, and that rewrite is the summarisation that damages numbers.

When you don't own the tool (a third-party MCP server), use the context editing API (beta context-management-2025-06-27) as a fallback:

  • context_management: {"edits": [{"type": "clear_tool_uses_20250919", ...}]} clears old tool results by rule.
  • Knobs: trigger (when), keep (recent results to leave), clear_at_least (minimum to reclaim), exclude_tools (never clear), clear_tool_inputs (also strip the call arguments).
  • clear_thinking_20251015 does the same for old thinking blocks.

Ordering bundled results

When bundling several tool results plus a question, put the key findings summary first, then supporting detail under explicit section headers, so the model has anchors in the middle of the input. It costs nothing, and it matters most when results are long and you're tempted to concatenate them in call order.

KEY FINDINGS
- Order SO-88213: shipped 19 days ago, within 30-day return window.
- Customer has one prior return on this account (SO-77410, approved).
- Policy: standard return, no manager approval required.

## Order Detail
...

## Account History
...

## Applicable Policy Section
...

Requiring metadata and structure from subagents

A subagent returning verbose prose with no provenance leaves the consumer unable to tell which claim came from where, when, or how confident, and it wastes context re-deriving structure. Put a contract on the output shape:

  • Every result carries dates, source locations, and methodological context.
  • When the consumer's budget is limited (normal in multi-agent pipelines), return key facts, citations, and relevance scores instead of verbose content and reasoning chains.

Python

@dataclass
class SubagentFinding:
    claim: str
    source: str          # file path, ticket id, or document name
    as_of_date: str       # when this fact was true / last verified
    relevance: float      # 0-1, how directly this bears on the question asked
    # deliberately no "reasoning" field — the coordinator needs the conclusion,
    # not the chain that produced it

TypeScript

interface SubagentFinding {
  claim: string;
  source: string;      // file path, ticket id, or document name
  asOfDate: string;     // when this fact was true / last verified
  relevance: number;    // 0-1, how directly this bears on the question asked
  // deliberately no `reasoning` field — the coordinator needs the conclusion,
  // not the chain that produced it
}

The reasoning chain was necessary for the subagent and is nearly worthless to the coordinator. Its token cost grows with thoroughness, which is the wrong way round for a limited budget. Five paragraphs compress to one line that can sit beside four other subagents' findings.

5.4 Context across a large codebase exploration

Here the task never fit in one window, so it's split across phases and subagents from the start. The named symptom is an agent that, asked about retries an hour in, says "this typically follows a standard retry pattern with exponential backoff" instead of naming RetryableOrderProcessor at services/orders/retry.py:142, which it read 40 tool calls ago. It is reconstructing a generic answer because the specific one has degraded out of reach. The fact may still be in context, but the signal-to-noise ratio has collapsed. This is context degradation, not a reasoning failure a better prompt fixes.

Subagent delegation for verbose exploration

For "find every file touching the refund flow" or "trace what calls process_refund", spawn a subagent. Its context absorbs the dead ends and the coordinator's doesn't. It returns a short structured list, ideally in the metadata shape above.

The coordinator keeps to its Chapter 5 role of deciding what to investigate, dispatching and reconciling. Reading files directly "just this once" pollutes the context that delegation is meant to protect.

Scratchpad files

Delegation protects the coordinator, but a long subagent trace can degrade too. A scratchpad file records key findings on disk as they're found, and the agent re-reads it rather than re-deriving. Write "refund cancellation → RefundCanceller.revert at services/orders/cancel.py:88, called from services/webhooks/stripe.py:210" the moment it's found, and read it back when needed.

# scratchpad: refund-flow-trace.md

## Confirmed call chain
- entry: `services/webhooks/stripe.py:210` (webhook handler)
- calls: `RefundCanceller.revert` at `services/orders/cancel.py:88`
- calls: `LedgerAdjustment.apply` at `services/ledger/adjust.py:41`

## Open questions
- Does `LedgerAdjustment.apply` retry on ledger-service timeout? Not yet checked.

## Files ruled out
- `services/orders/refund.py` — handles issuance, not cancellation. Not relevant here.

It's a curated record of conclusions, not a log (the transcript is already the log).

The memory tool ({"type": "memory_20250818", "name": "memory"}) is the supported version. It is client-executed and gives a persistent /memories directory across sessions with view, create, str_replace, insert, delete, and rename. Anthropic's "multisession software development" pattern (a progress log plus a feature checklist, read back on resume) is this chapter's scratchpad-plus-manifest. Build it yourself when you need a bespoke format or location.

Phase summaries before the next round of subagents

In a multi-phase exploration (map refund code paths, then audit each for a bug pattern), summarise phase one and inject that summary into each phase-two subagent's initial prompt. Phase two then starts with what phase one learned and doesn't re-explore it.

Manifests for crash recovery

Sessions get interrupted when the process dies or when context fills and needs a fresh start. Each agent exports its state to a known location as it works (conclusions, open items, scratchpad references). On resume, the coordinator loads a manifest (an index of what state exists where) and injects the relevant parts into fresh agents' prompts.

{
  "exploration_id": "refund-flow-audit-2026-09-03",
  "phase": 2,
  "phase_1_summary": "scratchpad/refund-flow-trace.md",
  "subagents_completed": ["trace-webhook-path", "trace-ledger-path"],
  "subagents_pending": ["audit-retry-behavior", "audit-idempotency"],
  "open_questions": [
    "Does LedgerAdjustment.apply retry on ledger-service timeout?"
  ]
}

A crash then costs minutes, not a rerun of phase one.

/compact inside an exploration

/compact is a legitimate way to reclaim room filled with exploratory noise, as long as you get the sequence right. Compact after the phase summary is written and the scratchpad is current, so durable findings are already safe and only noise gets compressed. Compact before and you may lose the fact the summary was meant to capture.

Server-side compaction (beta compact-2026-01-12, context_management: {"edits": [{"type": "compact_20260112", "instructions": "..."}]}):

  • instructions replaces the default summarisation prompt, e.g. "preserve all dollar amounts, order numbers, and dates verbatim". It complements the case-facts block and doesn't replace it, and it's only as good as your knowledge of what's at risk.
  • pause_after_compaction stops after compacting so you can check what survived before the agent continues.

What the exam tests

  • 5.1 stem: an amount, date, or order number goes vague after several turns. Diagnosis: progressive summarisation; the fix is a persistent case-facts block. Distractors: "summarise more carefully", "ask the model to double-check".
  • 40-field tool result: trim at the source, before it enters context.
  • Where the block goes: the guide says "included in each prompt, outside summarized history". Any answer that keeps the facts inside summarised history is wrong.
  • 5.4 symptom: "typical patterns" instead of the specific class means context degradation, not something a better prompt fixes.
  • Subagent for an isolated question, rather than the coordinator exploring directly.
  • Manifest is for crash recovery. Scratchpad is for degradation within an ongoing session.
  • /compact is legitimate. The tested judgement is compacting after findings are externalised.

Exercises

  1. In a long Claude Code session, ask about something found 30+ tool calls ago (function name, path, config value). Note any hedged answer and its phrasing (look for "typically").
  2. Design a case-facts block for one customer with two disputed charges and one shipping complaint. Decide what's persistent versus conversational, and write the block after all three are raised.
  3. Take a tool returning a large record and write the trimmed projection for one task. Compare field counts and estimate the token difference over 20 turns.
  4. Design a manifest for a three-phase audit (dependencies, dead code, coverage): what each phase writes, what the coordinator reads on resume, and what a phase-three subagent gets if the crash hit mid-phase-two.

Chapter 15 — Escalation, review, and provenance

This chapter covers four tasks: who checks a result (4.6), when the system should hand a case to a human (5.2), how humans review at scale (5.5), and how citations survive synthesis (5.6). A final section reconciles two rules about confidence scores that appear to contradict each other.

Review architecture

"Review the diff you just wrote" sometimes catches a typo but rarely questions a decision, because a model reviewing its own output in the same session keeps the reasoning context from generation. It re-reads the code alongside its own justification and stays biased toward it.

Extended thinking doesn't fix this. More reasoning helps generation, but the bias comes from reasoning that already happened and now anchors the review, so more effort doesn't remove it.

The fix is an independent instance whose context holds only the artifact (diff, extraction, plan) and none of the generator's reasoning. It catches subtle issues that both self-review instructions and extended thinking miss.

Python

async def generate_and_review(task_description: str) -> dict:
    generation = await claude.messages.create(
        model="claude-sonnet-5",
        messages=[{"role": "user", "content": task_description}],
    )
    code = extract_code(generation)

    # A fresh instance: no system prompt overlap with generation, no shared
    # conversation history, nothing but the artifact and a review brief.
    review = await claude.messages.create(
        model="claude-sonnet-5",
        messages=[{
            "role": "user",
            "content": (
                "Review the following code for correctness bugs, edge cases, "
                "and unstated assumptions. You have no knowledge of why any "
                "decision was made; judge only what is written.\n\n" + code
            ),
        }],
    )
    return {"code": code, "review": extract_text(review)}

TypeScript

async function generateAndReview(taskDescription: string) {
  const generation = await claude.messages.create({
    model: "claude-sonnet-5",
    messages: [{ role: "user", content: taskDescription }],
  });
  const code = extractCode(generation);

  const review = await claude.messages.create({
    model: "claude-sonnet-5",
    messages: [{
      role: "user",
      content:
        "Review the following code for correctness bugs, edge cases, and " +
        "unstated assumptions. You have no knowledge of why any decision " +
        "was made; judge only what is written.\n\n" + code,
    }],
  });
  return { code, review: extractText(review) };
}

Two further techniques help with large artifacts:

  • Multi-pass review against attention dilution. One pass over 10 files spreads attention thin, and cross-file and single-file issues tangle into contradictory findings. Instead, do a per-file local pass for internal correctness plus a separate cross-file integration pass for data flow (does a return value match what callers assume, is a schema change honoured everywhere).
  • Per-finding self-reported confidence as a routing signal. High confidence ("missing null check, throws on documented empty list") goes to the human first. Low ("might be a race depending on scheduling") goes to a follow-up pass or lower priority. The score prioritises human attention but does not decide correctness or block merges on its own (see the final section).

Escalation and ambiguity resolution

The exam's answer is a closed list of three valid triggers:

  1. The customer explicitly asks for a human.
  2. Policy has a gap the agent lacks authority to resolve.
  3. The agent can't make meaningful progress after a real attempt.

Anything else ("felt hard", "seemed upset", "wasn't sure") misfires in both directions. It escalates easy cases that looked complex and keeps working on hard cases where the model was confidently wrong.

Trigger 1, explicit request.

  • Explicit request: escalate immediately. Don't say "let me just check one thing first", because investigating anyway treats the stated preference as an obstacle.
  • Frustrated but no request: not a trigger. Acknowledge the frustration, offer to resolve if it's within capability, and escalate only if they reiterate. What counts is what they asked for, not what tone implied.

Trigger 2, policy gap. The policy covers price drops on your own site and says nothing about competitor price matching. "Policy doesn't forbid it" invents unapproved policy. "Not covered, so no" denies something the business might want. Both are wrong because the agent lacks authority either way, so the policy's silence is itself the trigger.

Trigger 3, no progress. It is measured by outcome (tools and reasonable alternatives tried and exhausted), not by a forecast before trying.

The exam names two invalid proxies:

  • Sentiment. It doesn't correlate with complexity. An angry customer can have a trivial issue, and a calm one a case the agent shouldn't handle alone.
  • Self-reported confidence. A model wrong about a hard case is often wrong about being wrong. The limitations that made the case hard also corrupt its self-assessment, so it says "confident" on exactly the cases it's likely to get wrong.

The fix is explicit escalation criteria in the system prompt, plus few-shot examples in both directions: a case that looks complex but resolves cleanly, and one that looks simple but hits a policy gap.

Python

ESCALATION_SYSTEM_PROMPT = """
Escalate to a human agent only when one of the following is true:
1. The customer explicitly asks to speak with a human agent.
2. The applicable policy is silent or ambiguous on the customer's specific
   request (not merely complex to apply).
3. You have attempted a resolution using the tools available and cannot
   make further progress.

Do not escalate based on how difficult the case seems, how frustrated the
customer sounds, or how confident you feel. Frustration without an explicit
request for a human should be met with acknowledgment and an offer to
resolve; escalate only if the customer repeats the request for a person.

Example — escalate:
  Customer: "I want a price match against a competitor's listing."
  Policy covers only same-site price drops. -> Escalate: policy gap.

Example — do not escalate:
  Customer (frustrated): "This is the third time I've had to explain this,
  just fix it!"
  Issue: a standard, in-policy refund. -> Acknowledge frustration, resolve
  the refund, do not escalate.
"""

TypeScript

const ESCALATION_SYSTEM_PROMPT = `
Escalate to a human agent only when one of the following is true:
1. The customer explicitly asks to speak with a human agent.
2. The applicable policy is silent or ambiguous on the customer's specific
   request (not merely complex to apply).
3. You have attempted a resolution using the tools available and cannot
   make further progress.

Do not escalate based on how difficult the case seems, how frustrated the
customer sounds, or how confident you feel. Frustration without an explicit
request for a human should be met with acknowledgment and an offer to
resolve; escalate only if the customer repeats the request for a person.

Example -- escalate:
  Customer: "I want a price match against a competitor's listing."
  Policy covers only same-site price drops. -> Escalate: policy gap.

Example -- do not escalate:
  Customer (frustrated): "This is the third time I've had to explain this,
  just fix it!"
  Issue: a standard, in-policy refund. -> Acknowledge frustration, resolve
  the refund, do not escalate.
`;

Multiple matches from get_customer: don't use a heuristic (most recent, closest name). It can silently act on the wrong customer's data, and near process_refund that causes real harm. Ask for an additional identifier (order number, billing zip) and disambiguate deterministically.

Human review workflows and confidence calibration

The aggregate-accuracy trap. "97% accurate" invites cutting human review. But 97% overall is consistent with 99.5% on the dominant invoice type and 60% on a rare type or a hard field (foreign-currency total, handwritten date, vendor-specific field). Automate on the aggregate and you remove oversight exactly where it's needed. Validate accuracy by document type and by field, and confirm it holds in every segment before reducing review on any.

Two mechanisms need to run continuously:

  • Stratified random sampling of high-confidence extractions, the ones that would otherwise go unseen. That's how you measure the true error rate on the bucket you aren't otherwise checking, and catch new error patterns early.
  • Field-level confidence, calibrated on a labelled validation set. Per-field scores are checked against ground truth, and 0.9 should mean right about 90% of the time, not 65%. Once calibrated, route low-confidence fields and documents with contradictory sources to humans first, so limited reviewer time goes where errors are most likely.

Python

def route_for_review(extraction: dict, calibration: dict) -> str:
    """calibration maps confidence buckets to measured accuracy from a
    labeled validation set -- e.g. {0.9: 0.97, 0.8: 0.88, 0.7: 0.61}."""
    field_conf = extraction["fieldConfidence"]  # per-field raw score
    lowest = min(field_conf.values())
    measured_accuracy = calibration_lookup(calibration, lowest)

    if extraction.get("sourceContradictory"):
        return "human_review"          # contradictory source, regardless of score
    if measured_accuracy < 0.90:
        return "human_review"          # calibrated accuracy below threshold
    if is_in_stratified_sample(extraction["documentId"]):
        return "human_review"          # sampled even though it looks safe
    return "auto_accept"

TypeScript

function routeForReview(
  extraction: Extraction,
  calibration: Map<number, number>,
): "human_review" | "auto_accept" {
  const fieldConf = extraction.fieldConfidence;
  const lowest = Math.min(...Object.values(fieldConf));
  const measuredAccuracy = calibrationLookup(calibration, lowest);

  if (extraction.sourceContradictory) return "human_review";
  if (measuredAccuracy < 0.90) return "human_review";
  if (isInStratifiedSample(extraction.documentId)) return "human_review";
  return "auto_accept";
}

This confidence number is trustworthy where the escalation one wasn't, because it has been checked against labelled data (see the final section).

Provenance in multi-source synthesis

Subagents gather from different sources and synthesis combines them. The failure is easy to miss, because the result is a right answer that can't say where it came from.

Attribution gets lost because summarisation's job is to compress, and a citation looks like a detail that is safe to drop, since the claim survives without it. After two or three passes, the report is full of confident, unattributed claims indistinguishable from verified ones.

The structural fix is that every subagent outputs claim-source mappings (claim + source URL or document name + supporting excerpt), and every downstream step, including synthesis, preserves and merges them. "Summarize these findings" invites loss. "Merge these findings; every claim must carry the source mapping it arrived with" makes a dropped citation a visible, checkable defect.

Python

class ClaimSource(TypedDict):
    claim: str
    sourceUrl: str
    excerpt: str
    publicationDate: str | None   # required when available -- see below

class SubagentFinding(TypedDict):
    topic: str
    claims: list[ClaimSource]

def synthesize(findings: list[SubagentFinding]) -> dict:
    # Merge, don't summarize away, the claim-source pairs.
    all_claims = [c for f in findings for c in f["claims"]]
    conflicts = detect_conflicting_values(all_claims)
    contested_claims = {c["claim"] for c in conflicts}
    return {
        "wellEstablished": [c for c in all_claims if c["claim"] not in contested_claims],
        "contested": conflicts,   # annotated, not resolved by picking one
        "allSources": all_claims,
    }

TypeScript

interface ClaimSource {
  claim: string;
  sourceUrl: string;
  excerpt: string;
  publicationDate?: string;
}

interface SubagentFinding {
  topic: string;
  claims: ClaimSource[];
}

function synthesize(findings: SubagentFinding[]) {
  const allClaims = findings.flatMap((f) => f.claims);
  const conflicts = detectConflictingValues(allClaims);
  const contestedClaims = new Set(conflicts.map((c) => c.claim));
  return {
    wellEstablished: allClaims.filter((c) => !contestedClaims.has(c.claim)),
    contested: conflicts,
    allSources: allClaims,
  };
}

Built-in citations cover a different case. When Claude reads a source directly via a document content block with citations: {"enabled": true}, the API returns citation blocks automatically (cited text, document, location). Anthropic reports up to 15% better recall than prompt-based citation. Use it when Claude is reading the document. Keep ClaimSource for subagent handoffs, where what's passed is a prior finding with no document to cite into.

Conflicts: annotate, don't resolve. One source says 12% growth, another 9%. Both may be credible, measuring slightly different things or different times. Report both values with sources rather than picking the more reputable-sounding one. A labelled contested finding is useful, while a confident one that settled a disagreement without saying so hides a judgement call (compare Chapter 4's coverage gaps). Upstream document analysis should do the same: complete the analysis with both values marked as conflicting and pass them up, because the coordinator holds the full picture.

Figures that look contradictory may be temporal differences, measurements taken on different dates. Drop the dates and a normal trend reads as inconsistency. Require a publication or data-collection date on every claim (publicationDate) so synthesis can tell "sources disagree" from "the world changed". Structure the report with sections separating well-established from contested findings.

Render content types natively: financial figures as tables, news as prose, multi-part technical findings as lists. Keep the source's characterisation and methodology (survey, regulatory filing, estimate). It is part of the claim.

Reconciling the confidence-score rules

The chapter appears to contradict itself:

  • Self-reported confidence is not a valid escalation trigger (5.2).
  • Yet field-level confidence drives review routing (5.5), and having review passes self-report confidence is good design (4.6).

  • Uncalibrated self-report is the model's guess about itself, made by the system whose competence is in question. It is least reliable on exactly the cases where the model is wrong, since the same limitation affects both.

  • A calibrated score is computed the same way, but it is checked against a labelled validation set and shown to track the true error rate (0.9 ≈ 90% correct on ground truth). At that point it is a measurement, like any instrument verified before use.

Never let a raw, unchecked self-report make an autonomous decision to escalate or to call code correct. Once calibrated against ground truth, a score is a legitimate routing input for human review.

What the exam tests

  • 4.6: independent instances beat self-review because retained reasoning context biases the generator, not because of effort. Extended thinking is no substitute. Multi-pass review is per-file plus cross-file integration.
  • 5.2: the three triggers are a closed set. Sentiment and self-reported confidence are the plausible wrong answers. Explicit request (immediate) vs frustration (acknowledge, offer resolution). A silent policy means escalate the gap. Multiple matches means ask for another identifier.
  • 5.5: aggregate accuracy masks segment failures. Validate per type and field, use stratified sampling of high-confidence items, and route on calibrated field-level confidence.
  • 5.6: claim-source mappings survive synthesis. Conflicts are annotated, not resolved. Publication dates are required. Sections separate established from contested. Format matches content type.
  • Confidence scores: self-reported confidence is invalid as an autonomous trigger but valid as a calibrated routing input.

Exercises

  1. Take code from one Claude Code session and have a fresh session with no shared history review it. Compare with the original session reviewing itself, and find one issue only the fresh instance caught.
  2. Draft an escalation prompt for process_refund/escalate_to_human with few-shot examples for all three triggers plus negative examples (sentiment, confidence). Test an angry customer with a simple in-policy request and a calm one asking about something the policy never covers.
  3. Build extract_invoice with per-field confidence and a 20-document labelled set. Compute accuracy per confidence bucket, decide whether the raw scores are calibrated, and choose a routing threshold.
  4. Run a three-subagent research task with likely disagreement. Require claim-source mappings with dates, and have synthesis produce wellEstablished and contested sections. Rerun with "summarize the findings" and compare what survives.

Study with Claude

Unrestricted GPT-4 access raised practice scores but cut later unaided exam scores by 17% once the tool was removed, and students did not notice (Bastani et al., PNAS 2025). A tutor limited to hints caused no such drop. A structured AI tutor beat in-class active learning in a Harvard physics RCT (Kestin et al., Sci Reports 2025). In both studies the tutor withheld the answer and the learner did the retrieval.

Run every prompt below in Claude Code from the repo root.

Rules

  1. Attempt first. Write your answer and your reason before Claude says anything. Never let Claude solve an item before you do.
  2. Ground it. Generation and grading use only the exam guide PDF and study-guide/. Tell Claude to cite file and section, and to refuse when the sources do not support an answer.
  3. Check every generated item for flaws. One study found 49% of ChatGPT-written MCQs had item-writing flaws and 22% had factual or conceptual errors. Expect to fix or drop a good share.
  4. Verify answer-deciding facts against the PDF. Parameters, flags and file names that decide an answer must trace to it or to current docs. If Claude, the study guide and the PDF disagree, the PDF wins, then current docs, then the study guide, then Claude's memory of its own product.
  5. Calibrate on the official items. The 12 official samples are the only items with the real exam's style. Re-do your misses without notes.
  6. Stop generating new items in the last 24 hours. Review your error log and the guide instead.

Prompts

Replace the bracketed parts. @ paths assume the repo root.

1. Socratic tutor on one task statement

When: you have read a task statement once and want to find out what you can recall.

Act as a tutor for CCAR-F task statement [e.g. 1.4]. Sources: @Claude+Certified+Architect+–+Foundations+Exam+Guide.pdf (section 6) and @study-guide/01-domain-1-agentic-architecture.md.
Rules: ask me one question at a time, never tell me the answer first, and give hints only after I have tried. Start with a scenario-based question from the "Skills in" bullets, then move to "Knowledge of". After each reply say what was right, what was missing, and ask the next question. After six questions, list the bullets I handled poorly.

2. Grade my teach-back

When: after Drill's Recall cards, for any task statement you rated shaky.

I will explain task statement [e.g. 3.2] from memory. Grade it against the "Knowledge of" and "Skills in" bullets in section 6 of @Claude+Certified+Architect+–+Foundations+Exam+Guide.pdf.
Return a table: each bullet, then covered / partial / missing / wrong, with a one-line reason. Quote the PDF for anything missing or wrong. Do not fill in gaps with explanations; end with the two bullets I should re-study.

My explanation:
[write it before sending; no notes]

3. Analyse my error log

When: after a Mixed set, Domain focus or Timed mock, with at least 15 logged errors.

Below is my error and confidence log exported from the Drill Dashboard. Read it with @study-guide/bank/AUTHORING.md for the trap slugs.
1. Group my errors by trap and by task statement. Name my top 3 misconceptions in one sentence each.
2. Flag high-confidence wrong answers separately, because I need to fix those first.
3. Write a 20-minute plan: for each misconception, point to the file and section to reread and the Drill mode to run (Relearn, Domain focus, Recall cards). Do not write new questions.

[paste Dashboard Markdown]

4. Generate new items

When: you have run out of unseen items for a weak task statement, and it is more than 24 hours before the exam.

Write [3] new CCAR-F items for task statement [e.g. 2.3]. Ground each in a bullet from section 6 of @Claude+Certified+Architect+–+Foundations+Exam+Guide.pdf and quote that bullet. Follow the item-writing rules in @study-guide/bank/AUTHORING.md:
- scenario-based, one of the six exam scenarios, testing a design choice
- one deciding phrase in the stem; exactly one defensible answer
- distractors are the exam's own wrong patterns (prompt where a guarantee is needed, extra infrastructure, wrong scope, real-but-irrelevant parameters)
- options similar in length; no "all/none of the above"; no absolute words only in distractors; no negative stems
- nothing out of scope (see @study-guide/00-exam-overview.md)

Before showing an item, audit it silently: single best answer? any length or wording cue? fact current and in the PDF's terms? Rewrite or drop it if it fails. Show only the stem and options. Hide the answer and explanation until I reply with my choice and reasoning. Then grade me and cite the file and section.

5. Audit an item

When: any generated item, or any official item whose key you doubt.

Audit this item. Check, with evidence:
(a) exactly one best answer, and the argument an expert would make for each distractor
(b) cueing: longest option, absolute words, grammar hints, stem words repeated in the key
(c) in scope per @study-guide/00-exam-overview.md and the task statement bullets in the PDF
(d) every fact current; mark each as confirmed in the PDF, in study-guide only, or unverifiable
Verdict: KEEP / FIX / DROP, with the fix.

[paste item]

6. Why is my answer wrong

When: after a miss in Drill, before you read the stored explanation.

I answered [X] on this item; the key is [Y]. My reasoning: [one or two sentences].
Do not restate the key. Name the deciding phrase in the stem. Say what misconception my reasoning rests on. Then describe a variant of the scenario where my choice would be the best answer. Finish with one question to check I have the distinction.

[paste item]

7. Blank-page concept map check

When: end of a study block, per domain.

I will list the concepts of Domain [N] and how they connect, from memory. Compare my map with the task statements in section 6 of @Claude+Certified+Architect+–+Foundations+Exam+Guide.pdf and the matching domain page (e.g. @study-guide/03-domain-3-claude-code.md). Tell me which task statements are absent, which links are wrong, and which comparisons I ignored (for example hook vs prompt, resume vs fork). Do not draw the full map for me; give a list of gaps only.

My map:
[paste]

8. Scenario walk-through

When: you can answer single items but lose points when several choices interact.

Play the lead engineer of the "[scenario name]" scenario from the exam guide. Describe the system in four sentences, then raise one design problem at a time. After each problem, wait for my design choice and reason. Challenge it with a realistic constraint, then say which task statement it exercised. After five problems, grade my choices against the PDF and list which task statements I should drill next. Do not reveal the intended answer before I commit.

What not to do

  • Asking Claude to summarise chapters. A summary is easy to read and gives you no retrieval practice. Have it quiz you, and read the chapter yourself.
  • Judging progress by sessions that went well. Count only unaided Drill results, because a session full of hints feels productive without showing what you can recall.
  • Treating generated items as the exam. They check recall of the guide. Difficulty and style come from the 12 official samples.

Practice Questions

Sixty original scenario questions, a full-length mock, written against the v1.0 task statements and in the style of the exam guide's own samples, split D1 ×15, D2 ×11, D3 ×12, D4 ×11, D5 ×11. Eight items are multiple-response (Q11, Q34, Q46, Q48, Q50, Q53, Q55, Q58), as v1.0 says to expect.

These are not Anthropic's questions. The twelve official sample questions are in §9 of the exam guide PDF; do those too, because their answer explanations show best how the exam chooses between options.

Answers and explanations follow all the questions, at the end of the page.

This page is the printable version. For practice, use the Drill page, which shuffles the options, asks for your confidence, explains every option, and brings back your misses.


Domain 1 — Agentic Architecture & Orchestration

Q1. (Customer Support Resolution Agent) Your agentic loop terminates as soon as the assistant response contains any text, on the theory that Claude only writes prose when it has a final answer. Support engineers report the agent frequently replies "Let me look that up for you" and then stops. What is the correct fix?

A. Add a system prompt instruction telling Claude not to narrate before calling tools. B. Continue while stop_reason is "tool_use"; terminate only on "end_turn". C. Set a minimum of three iterations before the loop is allowed to exit, whatever the stop reason. D. Strip text content blocks from the response before evaluating the termination condition.


Q2. (Multi-Agent Research System) Your coordinator delegates to a web search subagent and then a synthesis subagent. The synthesis subagent's prompt is "Synthesize the research findings into a report." Its output is generic and cites nothing. What is the root cause?

A. The synthesis subagent's model is too small for synthesis work, so it falls back on generic summaries. B. The synthesis subagent needs the WebSearch tool so it can gather its own material. C. The coordinator left prior findings out of the synthesis prompt; subagents do not inherit context. D. The coordinator should invoke synthesis in the same turn as web search so that both share context.


Q3. (Multi-Agent Research System) You want four subagents to run concurrently rather than one after another. How does the coordinator achieve this?

A. Emit multiple Task tool calls in a single coordinator response. B. Set parallel: true in each AgentDefinition. C. Issue one Task call per turn and rely on the SDK to overlap them. D. Set disable_parallel_tool_use to false on the coordinator's requests.


Q4. (Customer Support Resolution Agent) Company policy is that refunds above $500 require human approval. Your system prompt says so clearly. Over three months, four refunds above $500 were processed autonomously. Choose the most appropriate remediation.

A. Restate the rule at both the beginning and end of the system prompt for position emphasis. B. Add few-shot examples showing the agent escalating a $600 refund. C. Intercept outgoing process_refund calls and block any exceeding $500, redirecting to escalation. D. Add a PostToolUse hook on process_refund that flags refunds above $500 for after-the-fact audit.


Q5. (Developer Productivity with Claude) Different MCP servers return timestamps as Unix epochs, ISO 8601 strings, and human-readable dates. The agent frequently miscompares dates. What is the cleanest fix?

A. A PostToolUse hook that normalizes timestamp formats before the model reads the results. B. A system prompt section explaining all three formats and how to convert between them. C. Rewrite each MCP server so that it emits ISO 8601 timestamps. D. A tool-call interception hook that rejects calls to tools known to return non-ISO timestamps.


Q6. (Code Generation with Claude Code) You analyzed a codebase in a session two days ago. Since then, six files in the authentication module were rewritten. You want to continue the investigation. What is the best approach?

A. Resume the session with --resume and say nothing; Claude will re-read any files it needs as it goes. B. Resume with --resume and name the changed files so it re-analyzes those targets. C. Fork the session so the original analysis is preserved and the work continues on a branch. D. Always start fresh; resumed sessions cannot be trusted after code changes.


Q7. (Developer Productivity with Claude) You want to compare two refactoring strategies, both starting from the same completed codebase analysis, and keep the analysis session intact. Which mechanism?

A. --resume twice, each with a different follow-up prompt about one strategy. B. fork_session to create independent branches from the shared baseline. C. /compact with instructions focused on each strategy in turn. D. Two new sessions, each seeded with a copy of the analysis summary.


Q8. (Multi-Agent Research System) The coordinator always runs the full pipeline (web search, document analysis, synthesis, report generation) even for a query like "what year was the GDPR enacted?" Latency and cost are high for trivial queries. What should change?

A. Reduce each subagent's max_tokens so that trivial queries cost less to run. B. Have the coordinator assess query complexity and select which subagents to invoke. C. Add a caching layer in front of the web search subagent to avoid repeated searches. D. Merge web search and document analysis into one subagent to halve the pipeline.


Q9. (Code Generation with Claude Code) A task reads: "Add comprehensive tests to this legacy service." You do not know the module structure, the existing coverage, or the dependency graph. Which decomposition strategy fits?

A. A fixed prompt chain: generate unit tests, then integration tests, then end-to-end tests for the service. B. Dynamic decomposition: map the structure, rank high-impact areas, then adapt the plan as you go. C. Parallel subagents, one per directory, each writing tests independently for its own directory. D. A single prompt with the whole repository loaded into context so that nothing is missed or skipped.


Q10. (Customer Support Resolution Agent) A customer writes: "My order arrived damaged, I was double-charged, and I want to change my shipping address." Your agent addresses the damage and ignores the other two. Which design change follows the exam guide's recommended pattern?

A. Instruct the agent to ask the customer to submit one issue per message so each is handled alone. B. Split into items, investigate each in parallel with shared context, and synthesize one reply. C. Escalate any message that contains more than one issue to a human agent to handle. D. Process the issues strictly in the order stated, one per turn, finishing each before the next.


Q11. (Customer Support Resolution Agent) Select TWO. Your agent is escalating to human agents who cannot see the conversation transcript. Which two elements belong in the handoff?

A. The complete raw conversation transcript, verbatim. B. Root cause analysis. C. The agent's self-reported confidence score for the escalation decision. D. Customer ID, refund amount, and recommended action. E. A list of all tools the agent called, with timings.


Domain 2 — Tool Design & MCP Integration

Q12. (Multi-Agent Research System) Two tools, analyze_content ("Analyzes content") and analyze_document ("Analyzes documents"), are selected almost at random. What is the most effective first step?

A. Merge them into a single analyze tool that decides internally which backend to call. B. Rename them and rewrite the descriptions so each purpose is distinct, e.g. extract_web_results. C. Put a deterministic keyword-matching router in front of the model to pre-select the tool each turn. D. Set tool_choice: "any" so the model must commit to calling one of the two on every turn.


Q13. (Customer Support Resolution Agent) lookup_order times out against a flaky backend. Which error response best enables agent recovery?

A. A bare {"error": "Operation failed"} with no category, retry flag, or detail on what was attempted. B. isError: true, errorCategory: "transient", isRetryable: true, and what was attempted. C. Return an empty order list so the agent treats it as a successful query with no matches and carries on. D. Raise the exception to the top level and end the session so an operator can inspect the failing backend.


Q14. (Customer Support Resolution Agent) A customer requests a refund on an item outside the 30-day return window. The tool correctly refuses. What should it return?

A. errorCategory: "transient" with isRetryable: true, so the agent retries after a short backoff. B. errorCategory: "business", retriable: false, and a customer-friendly policy explanation. C. A generic failure message so the agent escalates the refund request to a human automatically. D. A successful empty result, because the call did not create any refund record.


Q15. (Multi-Agent Research System) Your synthesis agent has been given all 18 tools available in the system "so it never gets stuck." It now attempts web searches mid-synthesis and produces inconsistent reports. What is the best correction?

A. Add a system prompt line telling the synthesis agent not to run web searches, and keep the full 18-tool set. B. Scope it to its role's tools plus one narrow verify_fact tool, and route complex verification through the coordinator. C. Remove all tools from the synthesis agent so it can only write the final report from the findings it receives. D. Increase the synthesis agent's context window so it retains its instructions better across long inputs.


Q16. (Developer Productivity with Claude) Your team's custom MCP server exposes a search_internal_docs tool with the description "Searches docs." The agent keeps using Grep on the local repo instead. What fixes this?

A. Move the server from user scope to project scope in .mcp.json so the agent ranks it above built-in tools. B. Remove Grep from allowedTools so the agent has no other way of searching the documentation. C. Expand the tool's description to explain what it covers, what it returns, and when to prefer it over Grep. D. Set tool_choice: "any" to force the agent to call some tool on every turn.


Q17. (Developer Productivity with Claude) You need a shared GitHub MCP server available to the whole team, authenticated with a token that must not be committed. What do you do?

A. Add it to ~/.claude.json with the token inline and tell everyone to copy the file. B. Add it to .mcp.json with ${GITHUB_TOKEN} and commit that file. C. Add it to .mcp.json with the token inline and add .mcp.json to .gitignore. D. Write a custom MCP server that reads the token from a vault at runtime.


Q18. (Developer Productivity with Claude) You need to find every caller of calculatePremium() across a large TypeScript monorepo. Which tool?

A. Glob with **/*.ts B. Grep C. Read on each file, following imports D. Bash with find


Domain 3 — Claude Code Configuration & Workflows

Q19. (Code Generation with Claude Code) You documented your team's commit message conventions and Claude follows them perfectly for you. Two new hires report Claude ignores them entirely. Where are the conventions?

A. In .claude/rules/ without a paths: field. B. In ~/.claude/CLAUDE.md. C. In .claude/CLAUDE.md, but the new hires haven't run /memory. D. In a skill that requires manual invocation.


Q20. (Code Generation with Claude Code) Your repository has React components, API handlers, and database models, each with different conventions. Test files sit next to the code they test, scattered across every directory, and must all follow one shared testing convention. What is the most maintainable configuration?

A. A separate CLAUDE.md in each subdirectory, one per code type. B. All conventions in the root CLAUDE.md under a clear header per code type. C. .claude/rules/ files with paths: globs, including **/*.test.tsx. D. A skill per code type that holds that type's conventions.


Q21. (Code Generation with Claude Code) A /codebase-map skill produces 8,000 tokens of directory listings and dependency graphs every time it runs, crowding out the actual work. Which frontmatter option addresses this?

A. allowed-tools: Read, Grep B. argument-hint: [directory] C. context: fork D. model: haiku


Q22. (Code Generation with Claude Code) Which statement correctly distinguishes skills from CLAUDE.md?

A. Skills load at every session start; CLAUDE.md loads only when a task calls for it. B. Skills load on demand for task workflows; CLAUDE.md always loads universal standards. C. Skills can only be project-scoped; CLAUDE.md can be user- or project-scoped. D. Skills replace CLAUDE.md in projects that use them, making that file unnecessary there.


Q23. (Code Generation with Claude Code) You need to migrate the application from one HTTP client library to another. It touches roughly 45 files, and there are two viable migration strategies with different testing implications. What is the right approach?

A. Direct execution with one very detailed instruction listing every file and its change. B. Plan mode to design the approach, then direct execution to carry it out. C. Direct execution first, switching to plan mode only if something unexpected appears. D. Split the work into 45 single-file tasks, each run in direct execution.


Q24. (Claude Code for CI) Your pipeline runs claude "Review this PR" and the job times out with no output. What is the fix?

A. claude -p "Review this PR" B. export CLAUDE_HEADLESS=true C. claude --batch "Review this PR" D. claude "Review this PR" < /dev/null


Q25. (Claude Code for CI) You want Claude Code's review findings posted as inline PR comments by a downstream script. Which combination produces reliably parseable output?

A. --output-format json together with --json-schema B. A prompt instructing Claude to reply in JSON only C. --output-format text and a regex parser D. --json-schema alone, since it implies JSON output


Q26. (Claude Code for CI) Every time a developer pushes a new commit, the review bot re-posts the same twelve comments from the previous run. What change fixes this?

A. Run the review on the first push of each PR only, and skip every later commit. B. Feed in the prior findings and report only new or unaddressed issues. C. Deduplicate the comments in the posting script by hashing the text of each one. D. Limit the review scope to the files that were changed in the latest commit.


Q27. (Code Generation with Claude Code) You describe a data transformation in prose three times and get three different interpretations. What is the exam guide's recommended technique?

A. Move the description into CLAUDE.md so it is always loaded. B. Provide 2–3 concrete input/output examples. C. Ask Claude to restate the requirement before implementing. D. Increase specificity by adding more prose constraints.


Domain 4 — Prompt Engineering & Structured Output

Q28. (Claude Code for CI) Your review bot flags too many false positives on the "misleading comment" category. Your current instruction is "check that comments are accurate." Which change most improves precision?

A. Append "be conservative and only report findings you are highly confident are real problems." B. Replace it with "flag comments only when the claimed behavior contradicts the actual code behavior." C. Require the model to output a confidence score with each finding and filter out anything below 0.8. D. Run the review three times and report only the issues that appear in all three runs.


Q29. (Claude Code for CI) Six months in, the "security" category is 92% precise and the "performance" category is 30% precise. Developers have started ignoring all bot comments. What is a legitimate immediate action?

A. Temporarily disable the performance category while you improve its prompt. B. Lower the overall reporting threshold so fewer findings show up across all categories. C. Switch to a larger model for the performance pass only. D. Post all findings but mark performance ones as "low confidence."


Q30. (Structured Data Extraction) Your extractor returns well-formed JSON that validates against the schema, but on 8% of invoices the line items do not sum to the stated total. What addresses this?

A. Enable strict mode on the tool schema so every field must match its declared type. B. Extract calculated_total alongside stated_total and flag discrepancies in a validation step. C. Add additionalProperties: false to the schema so unexpected fields are rejected. D. Switch tool_choice from "auto" to "any" so the model must always call a tool.


Q31. (Structured Data Extraction) Roughly a fifth of your source contracts have no governing-law clause. The model invents a plausible jurisdiction for those. The governing_law field is currently required. What is the fix?

A. Add a few-shot example showing a contract with no governing law. B. Make governing_law optional/nullable so the model can return null. C. Add "do not guess" to the system prompt. D. Lower the temperature to 0.


Q32. (Structured Data Extraction) You receive three document types and cannot tell which type a document is until you read it. You have a distinct extraction tool per type and must never get conversational prose back. Which tool_choice?

A. "auto" B. "any" C. {"type": "tool", "name": "extract_invoice"} D. "none" with a prefilled response


Q33. (Structured Data Extraction) Validation fails because total_paid is absent. Investigation shows the figure is in a payment remittance file that was never supplied to the model. What should the pipeline do?

A. Retry with the validation error appended to the prompt; the model will locate the figure. B. Retry up to five times with exponential backoff between attempts. C. Recognize that retry cannot recover absent data and route the document to human review. D. Make the field nullable and accept null as the correct extraction result.


Q34. (Structured Data Extraction) Select TWO. Which statements about the Message Batches API are accurate?

A. It offers 50% cost savings. B. It guarantees completion within one hour. C. It supports multi-turn tool calling within a single request. D. custom_id correlates requests with responses. E. It is appropriate for blocking pre-merge checks because it is usually fast.


Q35. (Structured Data Extraction) A batch of 100 documents completes with 7 failures, all because the documents exceeded context limits. What is the right recovery?

A. Resubmit the entire batch of 100 with a larger model that has more context. B. Identify the 7 by custom_id, chunk them, and resubmit only those. C. Process all 100 documents synchronously instead of through the batch. D. Reduce max_tokens and resubmit the whole batch to shorten each response.


Q36. (Claude Code for CI) Claude generates a module, then you ask it in the same session to review its own work. It finds nothing. What is the most effective architecture?

A. Enable extended thinking on the review request so the model reasons longer. B. Ask it to review the code three times in the same session and take the union of findings. C. Send the code to a second Claude instance that lacks the generator's reasoning. D. Add "be critical of your own work" to the review prompt in the same session.


Domain 5 — Context Management & Reliability

Q37. (Customer Support Resolution Agent) Forty turns into a billing dispute, the agent refers to "the disputed amount" and "the charge from last month" instead of $247.80 and 14 August. Summarization has been running throughout. What fixes it?

A. Truncate the history to the last ten turns and drop everything earlier, so recent detail stays sharp. B. Keep amounts, dates, order numbers and statuses in a case-facts block sent with every prompt. C. Instruct the summarizer to be more detailed and to keep more of each turn from every earlier turn. D. Move to a model with a larger context window so the full history fits without summarization.


Q38. (Customer Support Resolution Agent) Your agent responds to "I've had enough, put me through to a person" by first running lookup_order and offering a replacement. What is correct?

A. Escalate immediately, without first attempting investigation. B. Attempt one resolution; escalate if the customer repeats the request. C. Run sentiment analysis to confirm the frustration is genuine before escalating. D. Escalate only if the agent's confidence in resolving the case is below threshold.


Q39. (Multi-Agent Research System) A document analysis subagent times out after processing 3 of 5 papers. Which propagation approach best enables coordinator recovery?

A. Return structured error context: failure type, what was attempted, the 3 completed analyses, and alternatives. B. Retry internally with exponential backoff, then return a generic "document analysis unavailable" status once retries run out. C. Return the 3 completed analyses as a successful, complete result so the coordinator can proceed without delay. D. Propagate the timeout to a top-level handler that aborts the research run and reports the failure.


Q40. (Multi-Agent Research System) Two credible sources report the market size as $4.2B and $6.8B. Your synthesis agent silently picks $4.2B. What should it do instead, and what would have prevented the misreading in the first place?

A. Average the two figures into a single estimate; require confidence scores from each source. B. Pick the higher figure as a conservative upper bound in the report; require source URLs from subagents. C. Annotate both values with their sources and present the conflict; require publication or collection dates. D. Escalate to a human reviewer to pick one value; require the coordinator to deduplicate sources before synthesis.


Additional items — under-drilled task statements

Q41. (D4 · Claude Code for CI) Your review criteria are specific and well-defined, and precision is good on clear-cut cases. But on borderline code (a broad catch that is arguably intentional, a magic number that may be a domain constant) the bot's verdicts swing run to run. What most improves consistency?

A. Add "when uncertain, do not report the issue" to the criteria for borderline code. B. Add 2–4 few-shot examples of ambiguous cases that show the reasoning behind each verdict. C. Enumerate every borderline pattern in the codebase as an explicit exception list in the prompt. D. Increase the number of review passes and report only the findings that recur across them.


Q42. (D5 · Developer Productivity with Claude) Three hours into exploring a legacy payments service, the agent starts answering questions with "typically, services like this validate at the controller layer" instead of naming the PaymentValidator class it identified earlier. What is happening, and what fixes it?

A. The system prompt has been diluted over many turns; restate it every ten turns to refresh it. B. Context degradation; keep a scratchpad file of key findings and consult it for later questions. C. The model is hallucinating under uncertainty; lower the temperature to make answers more deterministic. D. Tool results were malformed along the way; add a PostToolUse normalization hook to clean them.


Q43. (D5 · Structured Data Extraction) Your extraction pipeline reports 97% overall accuracy and you want to cut human review. What must you verify first?

A. That the 3% of errors are evenly distributed across the human reviewers who handle them. B. That accuracy holds across document types and fields, since an aggregate can mask weak segments. C. That the model's self-reported confidence exceeds 0.9 on the 97% of correct extractions. D. That the validation set is at least 10,000 documents so the 97% figure is statistically stable.


Q44. (D5 · Structured Data Extraction) Having reduced review to low-confidence extractions only, how do you keep detecting new error patterns in the high-confidence bucket you no longer inspect?

A. Wait for downstream systems to report anomalies. B. Stratified random sampling of high-confidence extractions for ongoing error-rate measurement and novel pattern detection. C. Re-run every high-confidence extraction with a second model and compare. D. Raise the confidence threshold each quarter.


Q45. (D2 · Developer Productivity with Claude) Your internal MCP server fronts a documentation system with 4,000 pages across a deep hierarchy. Agents burn six or seven exploratory search_docs calls just working out what topics exist before asking a useful question. What is the appropriate MCP mechanism?

A. Increase the result count returned by search_docs so each call displays more of the hierarchy. B. Expose the documentation hierarchy as an MCP resource so agents see the catalog up front. C. Add a list_topics tool and instruct the agent to always call it before running any search. D. Cache search_docs results so repeated exploratory calls are cheaper and faster.


Additional items — full-length mock (Q46–Q60)

Q46. (D3 · Claude Code for CI) Select TWO. A nightly job runs claude -p to propose tests for the modules changed that day. The proposals keep repeating scenarios the suite already covers, pad the output with trivial getter tests, and ignore the team's shared fixtures. Which two changes address this?

A. Run the generation three times and keep only the tests that appear in every run. B. Pass the existing test files for the changed modules into the invocation's context so already-covered scenarios are not suggested again. C. Append "write high-quality, non-redundant tests" to the invoking prompt. D. Document the testing standards, what makes a test worth writing, and the available fixtures in CLAUDE.md so the CI-invoked session loads them. E. Pass the entire repository's source tree as context so the model sees every module.


Q47. (D3 · Claude Code for CI) A pipeline step runs claude -p "Implement the ticket" and, in the same job, resumes that session with "Now review the changes you just made and list any defects." The review step reports zero defects on 90% of runs, yet post-merge bugs keep appearing. Which redesign is most effective?

A. Keep the resumed session but turn on extended thinking for the review turn, so the model reasons longer about its own implementation before listing defects. B. Replace the review prompt with an adversarial persona ("You are a harsh reviewer; assume the code has bugs") that must list at least five findings, still in the resumed session. C. Run the review as a separate claude -p invocation that receives only the diff and the CLAUDE.md review criteria, without the implementing session's reasoning. D. Resume the same session and run the review turn three times with different phrasings, merging the findings so a defect missed once can be caught by another pass.


Q48. (D3 · Claude Code for CI) Select TWO. After each push, your review bot re-posts findings the developer already fixed or dismissed, and it also nitpicks formatting in generated protobuf files the team never reviews. Which two changes fix both complaints?

A. Supply the previous run's findings as context and instruct Claude to report only issues that are new or still unaddressed. B. Require a confidence score and drop any finding below 0.8. C. Restrict each re-run to the files touched by the latest commit only. D. Deduplicate in the posting script by hashing each comment's text. E. Record the review criteria in CLAUDE.md, including that generated files and formatting-only issues are out of scope.


Q49. (D1 · Customer Support Resolution Agent) Your system prompt says close_account must not run until verify_identity has succeeded and check_balance has confirmed nothing is owed. Measurement shows closures still run before both checks in about 3% of conversations. What is the most appropriate fix?

A. Add a programmatic prerequisite gate that blocks close_account until verify_identity and check_balance have both succeeded this session. B. Restate the two-step rule in capitals at the start and end of the system prompt and add three few-shot examples showing the checks called in the correct order. C. Add a PostToolUse hook on close_account that flags closures lacking a prior identity or balance check in a nightly audit report for the compliance team. D. Switch to a larger model, which follows multi-step ordering instructions more reliably, and re-measure the skip rate on a sample of conversations.


Q50. (D1 · Customer Support Resolution Agent) Select TWO. lookup_order returns status as numeric codes (1, 2, 3) on one backend and as strings on another, with dates as Unix epochs or ISO 8601. Separately, finance requires that refunds above $500 never run without a human. Which two mechanisms give guaranteed behavior for these needs?

A. A system prompt table mapping every status code and date format, plus the $500 rule stated twice. B. A PostToolUse hook on process_refund that flags refunds above $500 for audit. C. A hook intercepting outgoing process_refund calls, blocking amounts above $500 and redirecting to escalate_to_human. D. A PostToolUse hook that converts numeric codes and epoch dates to one standard format before the model reads the results. E. Few-shot examples of the agent escalating a $700 refund and reading mixed formats correctly.


Q51. (D1 · Code Generation with Claude Code) A file watcher deploys any change under db/migrations/ the moment it is written. Your CLAUDE.md says migrations must never be edited without human approval, yet about one session in thirty edits one anyway. Which approach guarantees the rule holds?

A. Repeat the rule in capitals at the top and bottom of CLAUDE.md and add a short example of a refused migration edit. B. Run sessions in plan mode so a human reviews the proposed plan, including any migration edits, before execution begins. C. Add a PostToolUse hook on Write and Edit that detects changes under db/migrations/ and reverts them, then notifies the on-call engineer. D. Add a tool-call interception hook on Write and Edit that denies paths under db/migrations/ and tells the agent to request human approval.


Q52. (D5 · Structured Data Extraction) Your extractor emits a self-reported confidence between 0 and 1 per field. You route anything below 0.9 to reviewers, but reviewers find that invoice_total is wrong in 1 of 12 documents the model rated 0.95, while vendor_name is rarely wrong even at 0.7. You must spend limited review capacity where errors actually occur. What is the best remedy?

A. Raise the single threshold to 0.97 for all fields so fewer uncertain extractions slip through. B. Measure actual accuracy per field on labeled validation data and set each review threshold from it. C. Ask the model to double-check each extraction and re-score it before routing to reviewers. D. Replace the extractor with a larger model, whose confidence scores are better calibrated.


Q53. (D5 · Structured Data Extraction) Select TWO. Overall accuracy is 97%, but your reviewers can only handle 15% of volume. A spot check shows handwritten scans are right only 71% of the time and make up 4% of traffic. Which two practices fit the plan to automate high-confidence extractions?

A. Route extractions with low field-level confidence, and those drawn from ambiguous or contradictory source documents, to human review first. B. Review a uniform random 15% of all documents and treat the overall error rate as the success metric. C. Review the longest documents first, since length correlates with error. D. Keep a stratified random sample of auto-approved extractions, stratified by document type and field, for continuing error-rate measurement. E. Automate everything above 0.9 confidence because the aggregate accuracy already exceeds 95%.


Q54. (D2 · Code Generation with Claude Code) You must change a 120-line configuration module in which the statement return DEFAULT_TIMEOUT; appears in six functions, and you want to alter only the one in loadRemoteConfig. Edit fails because the anchor text is not unique. What is the reliable approach?

A. Retry Edit with a shorter anchor so the match is more likely to succeed. B. Use Grep to find the line number and pass it to Edit instead of text. C. Read the whole file, make the change in context, and write it back with Write. D. Use Glob to locate the module file, then retry Edit with the same anchor text.


Q55. (D2 · Developer Productivity with Claude) Select TWO. You must find every place the pricing logic calculatePremium is used. pricing/index.ts re-exports it under the names calculatePremium, quotePremium, and premiumFor, and most callers import through that module. Which two steps trace usage reliably without reading the whole repository?

A. Grep for calculatePremium only, since renamed re-exports resolve to the same function. B. Read pricing/index.ts to identify every exported name for the function. C. Glob for **/*pricing* and read every matching file. D. Read every file under src/ first, then reason about which ones call it. E. Grep for each exported name separately across the codebase, then Read the callers to follow the flow.


Q56. (D5 · Multi-Agent Research System) A research subagent queries three sources (a patent database, a news archive and an academic index). Two return results; the academic index times out even after the subagent's retries. What should the subagent return to the coordinator?

A. The two sources' results plus structured error context for the third: failure type, query attempted, retries, coverage gap. B. Only the two successful sources' results, reported as overall success, since a failure note would add noise and the coordinator can ask again if it notices a gap. C. A generic "research failed" status for the whole task, discarding the two successful results so the coordinator never works from incomplete data. D. Nothing yet: keep retrying the academic index with growing backoff until it responds, then return all three sources' results together.


Q57. (D2 · Developer Productivity with Claude) Your documentation agent is given a generic fetch_url tool. Logs show it fetching arbitrary external pages and internal admin URLs when it only ever needs to load your knowledge-base documents. Which change fits scoped tool design?

A. Add a system prompt section listing hostnames it must never fetch, and tell it to refuse them. B. Replace fetch_url with a load_document tool that validates the URL is a knowledge-base document. C. Remove fetch_url entirely and have the agent answer from existing knowledge, asking users to paste documents. D. Keep fetch_url and give the coordinator the same tool to audit the agent's fetches afterwards.


Q58. (D1 · Multi-Agent Research System) Select TWO. Your coordinator hands a synthesis subagent a prompt of the form "Step 1: read the notes. Step 2: list three themes. Step 3: write 400 words." Output is thin and loses attribution. Which two changes improve it?

A. Add a fourth step telling the subagent to search the web for additional detail on each theme, so thin sections are filled in with its own research. B. Give the subagent every tool in the system, including search and document analysis, so it can fill gaps itself instead of relying on the coordinator. C. Pass the complete findings from the search and document analysis agents in the prompt, in a structured format separating content from metadata (source URL, document name, page number). D. Replace the numbered steps with a longer, more detailed list of procedures covering each theme, a word budget per section and the exact citation format. E. State the research goal and quality criteria, such as coverage and well-supported claims, rather than prescribing steps, so the subagent can adapt.


Q59. (D4 · Structured Data Extraction) Contracts arrive continuously. Business requires extracted results within 28 hours of arrival. You use the Message Batches API, which can take up to 24 hours with no latency guarantee, and you submit on a fixed schedule. How often must you submit to guarantee the requirement?

A. Every 24 hours. B. Every 12 hours. C. Every 6 hours. D. Every 4 hours.


Q60. (D5 · Multi-Agent Research System) Your pipeline compresses each subagent's findings into a short summary before synthesis. The final report states figures confidently but cannot say which source each came from. Which change preserves provenance?

A. Have subagents emit structured claim-source mappings (URL, document, excerpt) that summarization and synthesis preserve and merge. B. Ask the summarizer to "cite sources where possible" in prose, and tell the synthesis agent to keep any citations it finds in the summaries it receives. C. Append an alphabetical bibliography of every source consulted to the end of the report, with a note that the figures come from these sources. D. Have the synthesis agent re-run searches for each figure to rediscover where it came from, and attach the URLs it finds to the final report.



Answers

Q1 — B. Loop control flow must be driven by stop_reason. The exam guide names "checking for assistant text content as a completion indicator" as an anti-pattern, because a tool_use response can and often does also contain text. A treats a structural bug as a prompting problem; C substitutes an arbitrary iteration count for a real signal; D still uses the wrong signal, just more carefully.

Q2 — C. Subagents operate with isolated context and do not inherit the coordinator's conversation history. The coordinator must include complete findings from prior agents directly in the synthesis prompt. B would work around the problem by making synthesis re-do the research, which over-provisions it and violates separation of concerns. D misunderstands the mechanism; there is no shared turn context.

Q3 — A. Parallel execution means emitting multiple Task tool calls in a single coordinator response. B invents a field. C is exactly the sequential pattern. D names a real Messages API parameter, but it governs parallel tool calls, not subagent orchestration, and it appears nowhere in the exam guide's API appendix.

Q4 — C. Deterministic compliance is required because the consequence is financial, and prompt instructions have a non-zero failure rate. Tool-call interception blocks the action before it happens. A and B are both probabilistic. D uses the right mechanism too late: PostToolUse fires after execution, when the refund has already gone out.

Q5 — A. PostToolUse hooks intercepting tool results for transformation before the model processes them is the exam guide's named pattern for normalizing heterogeneous formats from different MCP tools. B is probabilistic and burns tokens on every turn. C may be impossible for third-party servers and is more work. D blocks useful tools rather than fixing their output.

Q6 — B. Resume when prior context is mostly valid, and inform the resumed session about specific file changes for targeted re-analysis rather than full re-exploration. A wastes the benefit and risks stale reasoning. C solves a different problem (divergent branches). D overstates the case. The exam guide prefers a fresh session with a structured summary only when prior tool results are stale, and six changed files in a whole codebase do not necessarily make all of them stale.

Q7 — B. fork_session creates independent branches from a shared analysis baseline for exploring divergent approaches, which is the use case the exam guide states. A pollutes one session with two strategies. C compresses, it doesn't branch. D discards the detailed analysis you already paid for.

Q8 — B. The coordinator's role includes deciding which subagents to invoke based on query complexity, dynamically selecting rather than always routing through the full pipeline. A degrades quality without addressing the structure. C and D are narrower optimizations that leave the routing problem intact.

Q9 — B. Open-ended investigation calls for dynamic adaptive decomposition: map structure, identify high-impact areas, build a prioritized plan that adapts as dependencies are discovered. This is the exam guide's own worked example for "add comprehensive tests to a legacy codebase." A imposes a fixed chain on an unpredictable task. C guarantees duplication and no coherent prioritization. D invites attention dilution.

Q10 — B. Decompose multi-concern requests into distinct items, investigate each in parallel using shared context, then synthesize a unified resolution. A and C push work back onto the customer or a human for a case the agent can handle. D is serial and slow, and risks the same drop-off.

Q11 — B and D. The exam guide specifies structured handoff summaries containing customer ID, root cause, refund amount, and recommended action, for human agents who lack transcript access. A is the raw dump the pattern exists to replace. C is an unreliable signal. E is operational telemetry that the human does not need in order to act.

Q12 — B. Tool descriptions are the primary mechanism for tool selection; the exam guide's own repair for this exact pair is renaming and rewriting to eliminate functional overlap. A (consolidation) is a defensible architecture but more work than a first step. C over-engineers and bypasses the model's language understanding. D forces a tool call without improving which tool.

Q13 — B. Structured error metadata (category, retryable flag, human-readable description of what was attempted) is what lets the agent make an appropriate recovery decision. A is the named anti-pattern ("Operation failed"). C silently suppresses a failure as success. D terminates the workflow over a recoverable fault.

Q14 — B. Business errors are non-retryable and need a customer-friendly explanation so the agent can communicate rather than retry. A would cause pointless retries of a policy decision. C throws away the reason. D conflates a policy refusal with an empty result set.

Q15 — B. Too many tools degrades selection, and out-of-specialization tools get misused. The exam guide's pattern is scoped tool access plus a limited cross-role tool for a specific high-frequency need. A is probabilistic. C removes a real capability the agent needs 85% of the time. D misdiagnoses the cause.

Q16 — C. The named skill is enhancing MCP tool descriptions to explain capabilities and outputs in detail, to prevent the agent from preferring built-in tools like Grep. A confuses scope with priority, since scope controls sharing, not selection. B cripples a useful tool. D forces a call without steering which.

Q17 — B. Project-scoped .mcp.json for shared team tooling, with environment variable expansion for credentials so nothing secret is committed. A is user scope and leaks the token. C loses the sharing that motivated the change. D reinvents what env var expansion already does.

Q18 — B. Grep searches file contents for patterns such as function names. Glob matches file paths, which finds the files but not the callers. C is the read-everything-upfront anti-pattern. D shells out where a dedicated tool exists.

Q19 — B. User-level ~/.claude/CLAUDE.md applies only to that user and is not shared via version control. The exam guide names this exact diagnostic. A would still be committed and would still load (a rule without paths: just isn't conditionally scoped). C is contradicted by "ignores them entirely." D doesn't match "follows them perfectly for you" without manual invocation.

Q20 — C. .claude/rules/ with glob patterns applies conventions by file type regardless of directory location, which matters when test files are spread throughout. A is directory-bound and can't cover scattered tests. B relies on inference rather than explicit matching. D requires invocation and is not automatic.

Q21 — C. context: fork runs the skill in an isolated sub-agent context, preventing verbose skill output from polluting the main conversation. A restricts tools, not context. B prompts for arguments. D is not one of the frontmatter options the exam guide tests, and a cheaper model would still emit 8,000 tokens.

Q22 — B. Skills are on-demand invocation for task-specific workflows; CLAUDE.md is always-loaded universal standards. A inverts it. C is false, because personal skills go in ~/.claude/skills/. D is not a trade-off the exam guide draws.

Q23 — B. Plan mode is for large-scale changes with multiple valid approaches and architectural implications; combining plan mode for investigation with direct execution for implementation is a named skill. A assumes you already know the answer. C ignores that the complexity is stated in the requirements, not something that might emerge. D loses the cross-file coherence the migration needs.

Q24 — A. -p / --print is the documented non-interactive mode. CLAUDE_HEADLESS and --batch do not exist. Redirecting stdin is a Unix workaround that doesn't address the command syntax.

Q25 — A. --output-format json with --json-schema produces machine-parseable structured findings for automated posting as inline PR comments. B is unenforced. C is fragile. D drops half of the exam guide's pairing, and the guide only ever names the two flags together.

Q26 — B. Include prior review findings in context and instruct Claude to report only new or still-unaddressed issues. A abandons the feature. C hides the symptom and breaks when wording shifts slightly. D loses issues in unchanged files that interact with the change.

Q27 — B. Concrete input/output examples are the exam guide's remedy when prose descriptions are interpreted inconsistently, and it specifies 2–3 of them. A moves the ambiguous description somewhere else without making it any less ambiguous. C is the interview pattern, which is for finding considerations you have not thought of, not for pinning down a transformation you already know. D is more of what already failed.

Q28 — B. Explicit categorical criteria beat vague instructions. This is the exam guide's own before/after pair. A is named as a technique that fails to improve precision. C relies on poorly calibrated self-reported confidence. D suppresses real bugs that only some passes catch (the exam guide's reason for rejecting 2-of-3 consensus in sample question 12, the 14-file review).

Q29 — A. Temporarily disabling high false-positive categories to restore developer trust while improving those prompts is a named skill. High false positives in one category undermine confidence in the accurate ones, so protecting trust is the priority. B degrades the good category too. C treats a criteria problem as a capability problem. D still spends the developer's attention.

Q30 — B. Schemas eliminate syntax errors but not semantic ones; the exam guide's named self-correction flow is extracting calculated_total alongside stated_total to flag discrepancies. A, C and D are all schema-level, and the JSON already validates.

Q31 — B. Designing fields as optional/nullable where source documents may not contain the information is the exam guide's stated way to prevent the model fabricating values to satisfy required fields. A helps at the margin but leaves the structural pressure in place. C is a prompt instruction against a schema-level incentive. D is not a lever the exam guide tests and wouldn't stop the fabrication.

Q32 — B. tool_choice: "any" guarantees the model calls a tool while letting it choose which. This is the exam guide's use for this case (multiple extraction schemas, unknown document type). A permits a text reply. C forces the wrong schema two thirds of the time. D disallows tools entirely.

Q33 — C. Retries are ineffective when the required information is simply absent from the source; the skill tested is identifying when retry will not help. A and B burn cost on an unrecoverable case. D would silently record a null for a value that exists, which is wrong and hides a real data-pipeline gap.

Q34 — A and D. 50% cost savings and custom_id correlation are both stated. B is wrong, since batches can take up to 24 hours with no guaranteed latency SLA. C is wrong because batch does not support multi-turn tool calling within a request. E is the argument sample question 11 (batch vs a blocking pre-merge check) rejects.

Q35 — B. Handle batch failures by resubmitting only the failed documents, identified by custom_id, with appropriate modifications such as chunking oversized documents. A, C and D all reprocess 93 documents that already succeeded.

Q36 — C. A model retains its reasoning context from generation and is less likely to question its own decisions in the same session; independent review instances are more effective than self-review instructions or extended thinking. A is named as inferior. B is still self-review, three times. D is a self-review instruction.

Q37 — B. Extracting transactional facts into a persistent case-facts block included in each prompt, outside the summarized history, is the fix the exam guide gives for progressive summarization condensing numbers and dates. A discards exactly the early transactional detail the customer will hold you to, and the exam guide is explicit that complete conversation history matters for coherence. C asks the summarizer to keep the detail that summarizing exists to drop. D adds capacity when the problem is what gets kept.

Q38 — A. Honor explicit customer requests for a human immediately, without first attempting investigation. B is the correct behaviour for frustration without an explicit request, and this customer asked. C and D are the two named unreliable proxies.

Q39 — A. Structured error context (failure type, what was attempted, partial results, potential alternatives) lets the coordinator decide whether to retry, substitute, or proceed with coverage annotations. B's generic status hides that context. C is silent suppression, the named anti-pattern. D terminates the whole workflow over one recoverable failure.

Q40 — C. Conflicting statistics from credible sources should be annotated with source attribution rather than arbitrarily resolved, and requiring publication or data-collection dates in structured outputs prevents temporal differences from being misread as contradictions. A fabricates a figure neither source reported. B is arbitrary selection with a rationalization. D escalates a case the system is designed to represent, and deduplication does nothing about sources that measured different things.

Q41 — B. Few-shot examples are the exam guide's named technique for demonstrating ambiguous-case handling, and it specifies 2–4 targeted examples that show the reasoning for why one action was chosen over a plausible alternative. A is a "be conservative"-class instruction, which the exam guide says fails to improve precision. C doesn't generalize to novel patterns, which is the benefit few-shot is credited with. D suppresses real intermittent findings.

Q42 — B. The exam guide describes context degradation in extended sessions as models "giving inconsistent answers and referencing typical patterns rather than specific classes discovered earlier", and the stem shows that symptom. The named remedy is scratchpad files persisting key findings across context boundaries. A, C and D all misdiagnose a context problem as a prompting, sampling, or data-format problem.

Q43 — B. Aggregate accuracy metrics can mask poor performance on specific document types or fields; the exam guide requires analyzing accuracy by document type and field before reducing human review. A misreads whose errors matter. C leans on uncalibrated confidence. D is volume without segmentation, which is exactly what hides the problem.

Q44 — B. Stratified random sampling of high-confidence extractions is the named technique for ongoing error-rate measurement and novel pattern detection. A is reactive and slow. C costs as much as the review you just eliminated. D trades recall for a false sense of safety without ever looking at what's in the bucket.

Q45 — B. MCP resources expose content catalogs (the exam guide's examples are issue summaries, documentation hierarchies, and database schemas) specifically to reduce exploratory tool calls. Resources are for content catalogs and tools are for actions. A returns more noise per call. C adds a tool where a resource is the designed mechanism. D makes an unnecessary pattern cheaper rather than removing it.

Q46 — B and D. Providing existing test files in context stops the generator proposing scenarios the suite already covers, and documenting testing standards, valuable-test criteria and fixtures in CLAUDE.md is how CI-invoked Claude gets project context and avoids low-value output (3.6). A discards real but run-dependent tests and does not address redundancy against the suite. C is a vague quality instruction with no new information. E dilutes attention and still does not show what is already tested.

Q47 — C. The session that generated the code retains its reasoning and is less likely to question its own decisions (session context isolation), so an independent review instance, given only the diff and the documented criteria, is more effective (3.6, 4.6). A and B are self-review instructions in the same biased context, and the exam guide names independent instances as better than both. D repeats the same bias three times.

Q48 — A and E. Including prior findings and instructing Claude to report only new or still-unaddressed issues fixes the re-posting, and CLAUDE.md is the mechanism for giving CI-invoked Claude review criteria, including what to skip (3.6). B depends on poorly calibrated self-reported confidence and would not remove already-reported findings. C misses issues in unchanged files that interact with the change. D hides the duplicates only while the wording stays identical and does nothing about generated-file nitpicks.

Q49 — A. Identity verification and a balance check before an irreversible account action need deterministic compliance, and prompt instructions have a measured non-zero failure rate; a programmatic prerequisite gate blocks close_account until both prerequisite calls have succeeded (1.4). B is still probabilistic. C is a PostToolUse audit that fires after the account has already been closed. D treats an enforcement problem as a capability problem.

Q50 — C and D. A hook intercepting outgoing process_refund calls blocks the over-threshold refund and redirects to human escalation, and a PostToolUse hook normalizes heterogeneous status codes and timestamps before the model processes them (1.5). A and E rely on probabilistic prompt compliance for both needs. B uses the right mechanism too late: it runs after the refund has executed.

Q51 — D. A guarantee needs a hook that intercepts the outgoing tool call and blocks the policy-violating action before it executes, then redirects to an alternative workflow, here human approval (1.5, 1.4). A is probabilistic. B shapes how work proceeds but does not deterministically block the write. C runs after the write, and the watcher has already deployed the file.

Q52 — B. Field-level confidence scores must be calibrated against labeled validation sets, and thresholds set from the measured accuracy per field, to route limited reviewer attention where errors occur (5.5). A applies one threshold to fields with very different error rates. C asks the same uncalibrated source to re-score itself. D assumes a bigger model's confidence is calibrated, which only a labeled set can show.

Q53 — A and D. Routing low-confidence and ambiguous or contradictory-source extractions to reviewers prioritizes scarce capacity, and stratified sampling by document type and field measures error rates in the auto-approved bucket and detects novel patterns (5.5). B samples uniformly, so the 4% handwritten segment is barely seen and the aggregate metric stays flattering. C uses an arbitrary proxy. E falls into the aggregate-accuracy trap, where 97% overall hid 71% on one segment.

Q54 — C. When Edit cannot find unique anchor text, Read the full file and Write it back with the change (2.5). A shorter anchor is more likely to match in several places. B is wrong because Edit matches on text, not line numbers. D finds a file path, which was never the problem. Read + Write is the exam guide's answer; current Claude Code would instead retry Edit with a longer unique anchor (or replace_all), which is not tested.

Q55 — B and E. Trace usage across wrapper modules by first identifying all exported names, then searching for each name across the codebase, reading callers to follow the flow (2.5). A misses callers that use the renamed exports. C is Glob, which matches file names rather than content. D is the read-everything-upfront anti-pattern; build understanding incrementally from Grep entry points.

Q56 — A. Subagents retry transient failures locally and propagate what they cannot resolve as structured error context (failure type, what was attempted, partial results), so the coordinator can retry, substitute or proceed with an explicit coverage annotation, and can tell a failed source from a valid empty one (5.3). B is silent suppression, the named anti-pattern, so the gap is invisible. C ends the whole task over one recoverable failure and discards good partial results (the other named anti-pattern). D never returns, so the coordinator is blocked on a source that has already failed after retries.

Q57 — B. Replace a generic tool with a constrained alternative, such as load_document validating document URLs, so the agent has only what its role needs (2.3). A is a probabilistic prompt guard. C removes a capability the agent needs. D spreads the over-broad tool to another agent.

Q58 — C and E. Subagents do not inherit context, so the coordinator must include complete prior findings in the prompt, in structured form that separates content from source metadata to preserve attribution, and should specify goals and quality criteria rather than step-by-step procedure so subagents can adapt (1.3). A and B give the synthesis agent out-of-specialization tools and repeat the over-provisioning mistake. D adds more procedure, the opposite of the guidance.

Q59 — D. A document can wait one full submission interval before being batched and then take up to 24 hours, so interval + 24 must not exceed 28; the interval is at most 4 hours (4.5). A, B and C allow worst cases of 48, 36 and 30 hours.

Q60 — A. Attribution is lost when compression drops claim-source mappings; requiring structured mappings (URL, document name, excerpt) that downstream agents preserve and merge keeps provenance through summarization and synthesis (5.6). B is an unenforced prose request. C lists sources without tying any claim to one. D reconstructs provenance after the fact, expensively and unreliably.


Scoring yourself

Out of 60. Below 45, go back to the domain page for the section you missed most. Above 52, spend the remaining time on the domain pages' decision tables and "Things to remember" lists and the twelve official sample questions.

Resources — official prep, community material, and what to ignore

Checked 2026-10-21. Star counts are from the same date. Everything outside the exam guide is unofficial; the domain mappings below are interpretation.

Official (Anthropic)

Resource Notes
Exam guide PDF, v1.0 The authority for what is tested. Linked from the certification page. No newer version exists. Re-check before booking.
Certification FAQ Live logistics (fee, retakes, cancellation). Outranks the PDF where they differ.
Practice exam None. The old one was retired in the move to Pearson. The only official sample items are the 12 in the exam guide.

Partner Academy prep courses

Prep page, 7 courses, all under anthropic-partners.skilljar.com.

Course Plausibly helps Value
Building with the Claude API (/claude-with-the-anthropic-api) D1, D2, D4, D5 High
Claude Code in Action (/claude-code-in-action) D3 High
Introduction to Model Context Protocol (/introduction-to-model-context-protocol) D2 High
AI Fluency: Framework & Foundations General Low
Claude 101 General Low
Claude on Google Cloud None Low: cloud config is out of scope
Claude with Amazon Bedrock None Low: cloud config is out of scope

The Claude Architect learning path has 10 courses, including Introduction to subagents. Its last three (Product Foundations, Deployment Architecture, Delivery Methodology) look partner-delivery oriented and map to no task statement.

Docs worth reading

Topic Source
Memory, skills, sub-agents, hooks, headless, CLI reference, MCP, GitHub Actions Claude Code docs
Agent SDK Overview
Tool use Define tools
Structured outputs, batch processing platform.claude.com docs
Design principles anthropic.com/engineering: "Building effective agents", "Writing tools for agents"
Worked examples github.com/anthropics/claude-cookbooks

Community repos on GitHub

Unofficial. Of ~45 repos surveyed, only 14 were actually read.

Repo What it is Best use / caveats
aderegil/claude-certified-architect Six runnable Python labs, one per scenario; table maps to all 30 task statements Six runnable labs, one per scenario. Needs an API key.
utkarsh1agarwal/claude-architect-exam-guide Six 60-question original mocks (~360 items, 10 per scenario, some "select two"); explicit no-recalled-items rule Largest free volume (~360 items).
SGridworks/claude-certified-architect-training ~150 items (ten 10-question topic tests, one 50-question exam), 12-week plan Says single-answer only (wrong).
OlivierAlter/Claude-Certified-Architect-Foundations-Certification-Exam 77 scenario MCQs with explanations, plus a Claude Code skill that runs the quiz Says single-answer only (wrong).
hong-chu/claude-certified-architect-foundations-llm-wiki Distilled reasoning heuristics (label the failure mode, fix the earliest controllable layer) Good for exam technique.
ahmdhsn-dev/ccaf-study-guide Trap taxonomy and ~45-row anti-pattern catalogue tied to task statements Good for distractor review.
abiodedeyi/cca-f-exam-prep Claude Code study skill with spaced repetition and a 19-pattern named distractor library
daronyondem/claude-architect-exam-guide Long judgment-oriented teaching guide Skip its model-selection/streaming section (out of scope).
vkorost/claude-certified-architect-guide Book with an "Enforcement Ladder" card
dennismyself/ccar-foundations-labs Break-it labs Not reviewed.
dnacenta/claude-certified-architect Most current on product behaviour Heavy out-of-scope drift. Skim its changelog corrections only.

Use with caution

Repo Problem
paullarionov/claude-certified-architect Most-starred (~5k) but claims "4 of 8 scenarios", single-answer only, out-of-scope drift, and solicits recalled exam items (NDA concern). Topic checklist at most.
timothywarner-org/claude-architect O'Reilly course repo, 121 items. Its ProctorFree, $99 and one-attempt details are out of date.

Community articles and practice-test vendors

  • Experience write-ups (themes only, unofficial): re-cinq.com/blog/claude-certified-architect-foundations-exam; itone.substack.com/p/lessons-from-the-anthropic-architect; levelup.gitconnected.com (2026-06-24); medium.com/@johnelanji (2026-05-20).
  • Udemy practice tests: few reviews, unverified accuracy.
  • Third-party banks contain wrong answers. Claude-generated drills run easier than the real exam.

How to use these

  • Read the exam guide first.
  • Do this guide's 60 practice questions.
  • Add one external bank for volume: utkarsh1agarwal.
  • Use the aderegil labs to close the API/SDK hands-on gap.
  • Check any external "fact" against the exam guide and the out-of-scope list before adopting it.

Claims to ignore

Claim Correct value Source
77 questions 60 Exam guide
"4 of 8" or "13" scenarios 4 of 6 Exam guide
Single-answer only Multiple-choice and multiple-response Exam guide v1.0
$99 fee $125 (since 2026-06-30) FAQ
ProctorFree, one attempt only Pearson OnVUE or test centre; up to 4 attempts per rolling 12 months FAQ
6-month retake cool-off 14 days, then 30, then 90 FAQ
Results in 2 or 10 days Score shown on screen at the end FAQ

NDA

Never use or share recalled exam items. Brain dumps break the confidentiality agreement and risk the credential.