Study guide
A walkthrough of all five domains. Read it once end to end for the shape of the blueprint, then come back to whichever section your mock results say is weakest.
D1Agentic Architecture & Orchestration
27% of the exam · 27 practice questionsChoosing between a single call, a workflow, and an agent; the tool-use loop; sub-agents; session and state design.
Choosing a tier
The single most-tested judgement in this domain is knowing when not to build an agent. Work up from the simplest thing that meets the need:
- Single call— classification, extraction, summarisation, Q&A. One request in, one response out.
- Workflow — multi-step, but you own the control flow in code. Predictable, debuggable, cheap.
- Agent— the model owns the control flow because the path can't be enumerated in advance.
Before committing to an agent, check four things: is the task genuinely hard to specify up front; does the outcome justify the cost and latency; is Claude capable at this task type; and can errors be caught and recovered? That last one is the criterion people skip. A task where mistakes are silent and irreversible is a bad agent candidate no matter how complex it is.
The tool-use loop
The API is stateless, so your harness owns the conversation. The loop: call the API, check stop_reason, execute any requested tools, append results, repeat until end_turn. Three details cause most bugs:
- Append the full
response.contentas the assistant turn — not the extracted text. Tool-use blocks must survive so their ids can be matched. - Return all tool results from a parallel batch in a single user message. Splitting them across messages works, but quietly trains Claude to stop making parallel calls.
- A failed tool still needs a
tool_result, markedis_error: true. Everytool_useid requires a matching result, and a described error lets Claude adapt.
Know all the stop reasons: end_turn, tool_use, max_tokens, stop_sequence, pause_turn, and refusal. pause_turnmeans a server-side tool loop hit its iteration cap — append the assistant turn and re-send; don't add a “Continue.” message.
Three ways to build an agent
Two questions separate them: who supplies the harness, and who supplies the deployment?
- Manual loop — you write everything. Total control, most code.
- Tool Runner— the SDK drives the loop over tools you define; you still host it. Per-turn hooks cover approval gates, error interception, and result modification, so “I need control” is rarely a reason to avoid it.
- Claude Agent SDK — a separate product: Claude Code as a library, with built-in file, bash, and search tools. Also harness-only; you host it.
- Managed Agents — the only option that supplies the harness and managed deployment, running the loop and hosting a per-session sandbox.
Expect at least one question that hinges on the Tool Runner / Agent SDK distinction. They sound alike and are different packages.
Managed Agents in practice
The flow is non-negotiable: create the agent once — model, system prompt, tools, MCP servers, and skills all live on the agent — then create a session per run that references it by id plus an environment id. Calling create on every request accumulates orphaned objects and throws away the versioning that makes runs reproducible.
Client-side, three patterns come up repeatedly:
- Stream first, then send. The event stream carries only what happens after it opens — there is no replay.
- Don't break on the first idle. Sessions go idle transiently while waiting on you; continue when
stop_reasonisrequires_action. - Reconnect by consolidating. Reopen the stream, list past events, dedupe by id, then tail. A tool call lost in the gap deadlocks the session.
Delegation and budgets
Sub-agents buy context isolation: exploration happens in their window and only the conclusion returns. They cost real overhead, though — each one re-establishes context, re-explores, and reports back. Delegate for genuinely independent, sizeable tracks; keep a few tool calls in the main loop.
For spend, distinguish max_tokens (an enforced per-response cap the model never sees) from a task budget (a countdown the model does see, so it paces itself and wraps up gracefully).
D2Tool Design & MCP Integration
18% of the exam · 18 practice questionsWriting tool schemas and descriptions, scoping the tool surface, server vs client tools, and wiring MCP servers.
Descriptions are the interface
Claude picks tools almost entirely from their descriptions. The highest-leverage change you can make is stating whento call a tool, not just what it does — “call this when the answer depends on information not in the conversation” beats “searches the knowledge base” by a wide margin.
Beyond that: name tools for the action, describe every property, use enum for fixed value sets, and mark only genuinely required parameters required. Keep the tool set focused — too many tools degrades selection accuracy.
Strict schemas and scale
strict: true is a top-level field on the tool, not on tool_choice, and it requires additionalProperties: false plus an explicit required array. It guarantees the input validates; it says nothing about whether the tool gets called.
When the library grows past what fits comfortably in context, use tool search: mark most tools defer_loading: true and let Claude discover what it needs. Because discovered schemas are appended rather than swapped in, the prompt cache survives. At least one tool must stay non-deferred, and the search tool must never defer itself.
Server-side vs client-side
Server-sidetools run on Anthropic's infrastructure with no execution loop on your side: web search, web fetch, code execution, tool search. Client-executed tools are Anthropic-defined but run by you: bash, text editor, memory, computer use — plus every custom tool you write.
Two traps: bash and the text editor are schema-less — declare them by versioned type and fixed name, never with your own input_schema. And server-tool errors don't raise; they arrive as HTTP 200 with an error object where a list would normally be.
Security is your job
Tool inputs are untrusted model output. For a bash handler, use an allowlist of executables and reject shell operators — blocklists are trivially bypassed. For a text editor handler, resolve the model-supplied path to canonical form and verify it stays inside the project root.
MCP and credentials
The connector needs both halves: an mcp_servers entry defining the connection, and a matching mcp_toolset entry in tools. One without the other is a validation error.
In Managed Agents, credentials never go on the agent — they live in vaultsattached to the session, matched to servers by URL, with OAuth refresh handled for you. The security property worth understanding: secrets never enter the sandbox at all. A proxy injects them at egress, so even a prompt-injected agent can't read them. Where a vault doesn't fit, keep the secret host-side behind a custom tool your orchestrator executes. Never put keys in prompts — those persist in event history and summaries.
D3Claude Code Configuration & Workflows
20% of the exam · 20 practice questionsCLAUDE.md, settings and permissions, hooks, skills, sub-agents, and putting Claude Code into CI.
Context vs configuration
The organising distinction for this whole domain: CLAUDE.md is context the model reads, while settings.json is configuration the harness enforces. Project-level memory is checked into the repo and shared; user-level memory follows the developer across projects.
It follows that anything phrased as “always do X after Y” is a hook, not a memory entry. Hooks are executed by the runtime, so they fire deterministically no matter what the model decides. PreToolUse can block a call before it runs; PostToolUse reacts afterwards. Hook output is fed back as feedback, which is what lets a failing check steer the model into fixing it.
Skills and progressive disclosure
A skill's description sits in context permanently; its body loads only when the task calls for it. That makes skills the right home for situational depth, and CLAUDE.md the right home for durable, broadly applicable conventions.
When a skill never triggers, the description is nearly always the problem — it is the entire routing signal. Write it around triggers and situations, not contents.
This also answers the bloated-CLAUDE.md scenario: everything in that file competes for attention on every request, so the fix is moving situational rules into skills — not escalating the language. Which brings us to…
Why forceful prompts backfire
Current models follow the system prompt closely. Instructions written to overcome older models' reluctance — CRITICAL, YOU MUST, “if in doubt, use the tool” — now cause over-triggering. If a tool or skill is firing too often, dial the language back and describe conditions instead of adding more guardrails.
Permissions, plan mode, and CI
Permissions live in settings, with a git-ignored local variant for machine-specific overrides. Narrow allowlists for genuinely safe, repeated commands remove friction without removing the gate where it matters; blanket approval removes it exactly where it counts.
Plan modeseparates understanding from doing — read-only exploration, then an approved plan, then edits. It's the standard guard for changes that are expensive to reverse.
For CI, use non-interactive mode with the prompt supplied up front and permissions pre-scoped. The classic symptom of getting this wrong is a job that hangs with no output: something is waiting on a permission prompt that nothing will ever answer. For scheduled, unattended work, reach for a scheduled cloud agent rather than a long-lived local session.
D4Prompt Engineering & Structured Output
20% of the exam · 20 practice questionsSystem prompts, examples, structured outputs, strict tool schemas, effort and thinking, and the Batch API.
Structured output
Constrain responses with output_config.format and a JSON schema. The older top-level output_format is deprecated, and last-turn assistant prefill returns 400 on current models — so the old trick of seeding an opening brace is gone. Use structured outputs for shape, and a system-prompt instruction for skipping preambles.
Know the limits: no recursive schemas, no numeric bounds, no string length constraints. Enum, const, anyOf, allOf, $ref, and the standard string formats all work. Structured outputs are incompatible with citationsand a schema's first use pays a one-time compilation cost that is then cached for about a day.
If a response comes back with max_tokens, treat the JSON as suspect — the guarantee covers what was generated, not output that was cut off.
Thinking and effort
budget_tokens is removed on current models and returns 400. Use thinking: {type: "adaptive"} and control depth with effort, which lives inside output_config (not at the request root) and defaults to high.
If a streaming UI shows a long pause with empty thinking blocks, the fix is display: "summarized" — the default is omitted. Display affects visibility only; thinking is billed the same either way.
temperature, top_p, and top_kare also gone. Variety now comes from prompting — asking for several genuinely distinct options works; a generic “be creative” just relocates the default.
Batch processing
The Batches API gives 50% off all token usage for asynchronous work: up to 100k requests or 256MB, usually finishing within an hour and capped at 24. The detail most often tested: results arrive in any order, so key them by custom_id — positional matching silently corrupts your data.
On failures, distinguish invalid_request(a validation problem in that request — fix it, don't retry) from server-side errors, which are safe to retry unchanged.
Prompt hygiene
Count tokens with the count_tokens endpoint using the same model id you will call — counts are model-specific, and tiktoken is the wrong tokenizer entirely. Put examples where prose is ambiguous (edge cases, formatting, missing data) rather than piling on near-identical cases, and keep them in the stable prefix so they stay cacheable.
D5Context Management & Reliability
15% of the exam · 15 practice questionsPrompt caching, compaction, context editing, memory, error handling, stop reasons, and retries.
Caching is a prefix match
Almost every caching rule follows from one fact: any byte change invalidates everything after it. Render order is tools → system → messages, so stable content must physically precede volatile content.
Practical consequences worth memorising:
- Freeze the system prompt. A timestamp, UUID, or username interpolated near the front makes every request unique. Inject that later, in messages.
- Don't change tools mid-conversation. Tools render at position zero, so any change — or a model switch — wipes everything.
- Cut at the end of the shared prefix. With a fixed preamble and a varying question, a breakpoint after the question makes every request a fresh write with no reads.
- Serialise deterministically. Unsorted JSON keys and set iteration produce different bytes each time.
Economics: writes cost roughly 1.25× at the 5-minute TTL and 2× at an hour; reads about 0.1×. So a 5-minute entry breaks even on the second request and a 1-hour entry needs at least three. Below the model's minimum cacheable prefix, caching silently does nothing — no error, just no cache creation in the usage block. Verify with cache_read_input_tokens; a persistent zero means a silent invalidator.
Two subtler ones: a breakpoint looks back at most 20 content blocks, so long agentic turns need intermediate breakpoints; and an entry only becomes readable once the first response starts streaming, so a burst of identical concurrent requests all pay full price.
Managing a growing context
Compaction summarises earlier context as the window fills. Context editing clears stale tool results or thinking blocks outright. Both operate within a session; memory is the cross-session mechanism.
The compaction mistake to avoid: append the full response.content, not just the text. Compaction blocks must be passed back or the state is lost — silently, with nothing to catch.
Failing well
Retryable: 429, 5xx, and connection errors — the SDKs back off automatically. Not retryable: 400, 401, 403, 404. Catch a chain of typed exceptions rather than one base class, or you collapse “fix this” and “wait and retry” into the same path.
Finally: a refusal arrives as a successful HTTP 200 with stop_reason: "refusal" and content that may be empty or partial. Check stop_reasonbefore reading content — and don't depend on stop_details, which can be null even then.