Domain 1 โ Facts
Agentic Architecture ยท the things to remember, in plain words
Each item is one rule that decides exam questions. Read the short headline, say the rule to yourself, then read the line to check. Open why if the reason is not clear, and follow the question link to see the rule in action. Collected from the lesson, the practice questions and two full third-party mock exams. They help you remember; they do not guarantee a score.
Back to Domain 1 lesson ยท Domain 1 โ Questions ยท Domain Q&A
Workflow vs agent โ choosing the pattern
- Agent or workflow? Ask one thing: who decides the next step? If your code already knows the steps, it's a workflow, even with many LLM calls. If the model looks at the situation and picks the next step and tool, it's an agent. Watch for answers that swap the two. Practice Q&A ยท Q38 ยท โ wrong choice to avoid: an answer that swaps the two: a workflow that steers itself, an agent that follows fixed code
why
This is the dividing line in Anthropic's Building Effective Agents. Both can call tools, loop, and run things in parallel, so those features don't tell them apart. Choose by how well you know the path: known path, workflow; path found along the way, agent. The wrong answer just swaps the two definitions, which looks right if you read fast.
- Steps already known? If you know the steps before any input arrives (summarize โ extract โ format; classify โ extract โ validate), use prompt chaining: your code calls step A, feeds its output to step B, and checks the result in code between steps. Not orchestrator-workers, not one agent with three tools. Practice Q&A ยท Q2 ยท Practice Q&A ยท Q33 ยท โ wrong choice to avoid: orchestrator-workers, or one agent given three tools
why
When the order is fixed, your code can just write it down as a chain, and a code check between steps catches bad output early. An orchestrator adds a model call to decide a split you already know. One agent with three tools makes the model rediscover the same order every run, and it may skip or reorder steps. For big volume, run the same chain in many parallel workers, not a planner.
- The five patterns Anthropic names five patterns: prompt chaining, routing, parallelization, orchestrator-workers, and evaluator-optimizer. Parallelization has two flavors (sectioning and voting), but they count as one pattern.
why
Chaining is a fixed sequence. Routing sorts the input into a category and sends it to a specialised prompt or model. Parallelization either splits independent subtasks (sectioning) or runs the same task N times and combines the answers (voting). Orchestrator-workers lets a model split the work at runtime and then combine results; evaluator-optimizer loops a writer with a critic. A table may show six rows, but it is still five patterns. Each one answers a question: is the order known, does input fall into categories, are subtasks independent, can you predict them up front, is there a clear rubric to iterate against?
- When orchestrator-workers? Use orchestrator-workers only if a model must decide the split while running, because the subtasks depend on the input ("find out why checkout dropped"). If the job is fixed and would just be faster in parallel, that's not a reason. โ wrong choice to avoid: choosing an orchestrator just because a fixed job would be faster in parallel
why
The orchestrator's extra model call pays off only when nobody can list the subtasks in advance: it reads the situation, decides what to look into, hands out work, then combines the results. "Why did checkout conversion drop?" is like that, because the next questions depend on what the first look finds. "This fixed job would be faster in parallel" is a parallelization problem; the harness (your code that runs the model) solves it with a worker pool. A planner there just re-creates a split you already wrote.
- Big volume, same steps? Here's the thing: 100,000 identical invoices still use the SAME fixed chain. The harness runs it many times side by side (queue, worker pool, Batch API for overnight work). An orchestrator would spend a model call deciding what a for-loop already knows. โ wrong choice to avoid: an orchestrator-workers design picked to handle scale
why
Volume is a throughput problem, and throughput is your code's job: put documents on a durable queue, run a limited number of workers that each run classify โ extract โ validate, and use the Batch API for work that can finish overnight. A model orchestrator would pay a call per invoice for a split that never changes. Later stages still need earlier results, so a batch design sends the classification jobs, saves the results, then sends the extraction jobs that qualify and validates them in code.
- Plan as you go? Use dynamic adaptive decomposition (build and change the plan while working) if the useful subtasks only show up from what you find midway, OR the task is open-ended, like writing tests for an unmapped legacy codebase. Either signal is enough. If the step order is stable, use a fixed pipeline. โ wrong choice to avoid: seeing only the "subtasks come from findings" signal and missing that open-ended work also counts
why
Adaptive means: first map the area, then make a prioritised plan that changes as you learn about dependencies and findings. Two things each point to it on their own: subtasks that only appear after early results, and a task with no existing map (full tests for an old codebase nobody has charted). If every run checks the same predictable things, or each step feeds the next in a stable order, a fixed pipeline fits and the adaptive setup is wasted.
- Small question, full pipeline? If a coordinator runs all five subagents even for a simple factual question, add a step that classifies the request first and then calls only the subagents it needs. Not two separate systems, not caching, not smaller budgets per subagent. โ wrong choice to avoid: a second lightweight system, a cache, or smaller per-subagent budgets
why
Calling every specialist on every request means even a one-line answer pays the full time and token cost. A classification step at the start lets cheap questions take the cheap path. Two separate systems duplicate logic, a cache helps only when the exact same request repeats, and cutting each subagent's budget makes every answer worse while the unneeded work still runs.
- Critic loop or code check? Evaluator-optimizer means: write a draft, a critic checks it against clear criteria, then revise, until the critic accepts or you hit a revision limit. Use it when "good" is a judgement call. If the check is arithmetic, your code does it, not a critic loop. Practice Q&A ยท Q33 ยท โ wrong choice to avoid: a critic loop that reviews extractions until the totals add up
why
A critic model scores the draft against an explicit rubric and sends it back for changes. This pays off for translation, writing, and research, where quality is a judgement. Checking that invoice totals add up is math your code can check exactly and for free; a model critic looping until the numbers match swaps a sure check for a slow, maybe-correct one.
- Is one agent enough? If the task fits in one context window and one tool set, use a single agent loop. Answers like "when subagents are unavailable", "when latency doesn't matter", or "never at scale" are wrong reasons. Practice Q&A ยท Q7 ยท โ wrong choice to avoid: "when subagents are unavailable", "when latency doesn't matter", or "never at scale"
why
A single agent loop is one model, one conversation, one set of tools, looping on
tool_useuntilend_turn. It stays right as long as the instructions, tool results, and reasoning all fit in one context window with one coherent tool set. More agents add coordination code, handoff bugs, and cost; add them only when work is independent enough to run in parallel, or the raw material would overflow one context. Whether subagents exist, whether speed matters, or how big the deployment is does not decide it.
The agent loop, messages array & stop reasons
- What does stop_reason say? Always check
stop_reasonfirst.tool_useโ run the tool, append atool_result, call again.end_turnโ deliver the answer.max_tokensโ output is cut off, never treat it as complete.stop_sequenceโ your own rule decides.refusalโ don't retry automatically.why
stop_reasonis the API telling your code why the model stopped, so your harness must branch on it before using the content.max_tokensmeans the output hit the cap you set for this call, so the text is incomplete.stop_sequencemeans one of your custom stop strings matched, and your own protocol decides what that means.refusalmeans the model said no; retrying automatically is the wrong reflex. - When does the loop stop? Keep looping while you get
"tool_use"; stop on"end_turn". Looking for "refactoring complete" in the text, scanning for XML tags, or usingmax_turns: 8as the main stop are anti-patterns. Keep a turn cap only as a safety net. โ wrong choice to avoid: parsing the reply for a "done" phrase, or relying on a fixed max_turns capwhy
The API's own signal is the real exit: keep going on
tool_use, stop onend_turn. Searching the reply for a phrase or tag ties the stop to wording the model can change. A hardmax_turnsas the main stop can cut off work that needed one more step, or let a stuck loop waste eight turns before anyone notices. Keep the cap only to stop runaway cost; the structured stop reason is what controls the loop. - Random JSON parse failures? If JSON parsing fails now and then and the logs show
stop_reason: max_tokens, the output was cut off but treated as complete. It's not hallucination, not a too-strict schema, not temperature, not checkpoint corruption. Practice Q&A ยท Q4 ยท Practice Q&A ยท Q40 ยท โ wrong choice to avoid: model hallucination, a too-strict schema, or temperaturewhy
When output hits
max_tokensthe model stops mid-sentence; JSON cut at the right spot can even parse, just with fields missing. The clue is in the logs: every failed response hasstop_reason: max_tokens, and a harness that never checks this field accepts it silently. Hallucination wouldn't line up with the stop reason, a strict schema rejects instead of cutting, temperature changes wording not length, and checkpoints don't touch the response. Fix: branch onstop_reason, then raise the budget or make the task smaller. - What max_tokens means
max_tokenslimits the output of one call, nothing else. If the input is bigger than the context window, the API rejects the request with an error before generating anything. If your custom stop string matches, you getstop_sequence. Practice Q&A ยท Q37 ยท โ wrong choice to avoid: reading max_tokens as "the input outgrew the context window"why
max_tokenssays nothing about how big themessagesarray is. An input that's too large gets an error up front, not a response with a stop reason. Somax_tokensas a stop reason means exactly one thing: generation reached the ceiling you set, and the text is incomplete. - What goes in before call 2? Before the next call, append two things at the END: the assistant response exactly as returned (including the tool_use block), then a
user-role message with thetool_result. If a tool_result has no matching tool_use in the history, the API rejects it. Practice Q&A ยท Q35 ยท โ wrong choice to avoid: appending only the tool_result, or putting it before the assistant turnwhy
The API is stateless (it remembers nothing between calls), so call 2 must tell the whole story in order: the assistant turn with its
tool_useblock andid, then theusermessage carrying thetool_result. Both go at the end because the array is in time order. Sending only thetool_result, or putting it before the assistant turn, leaves a result with nothing to pair with, and the API rejects it; putting things at the start scrambles the conversation. - Who makes the tool id? The Claude API creates the
id: "toolu_โฆ"on every tool_use block. Your tool_result copies it back astool_use_id. Tool definitions have no id, and even a single tool call needs this pairing. Practice Q&A ยท Q32 ยท โ wrong choice to avoid: your harness or the tool definition creating the id, or ids mattering only with several callswhy
Each
tool_useblock gets a fresh id liketoolu_01A, made by the Claude API, not by your code and not written in the tool definition (which only hasname,description, andinput_schema). Your harness copies that id into thetool_result'stool_use_idso the API can match result to request. This is required even when there is only one tool call in the response. - Does the API remember? No. The API is stateless: you resend the whole
messagesarray every call.systemandtoolsare separate request fields (the stable part that caching can reuse). Tool results go in asuser; there is notoolrole. โ wrong choice to avoid: a "tool" role, or the API remembering earlier turnswhy
The Claude API keeps no conversation between calls, so a 40-turn conversation is a 40-turn array sent every time.
systemandtoolstravel next to it as separate fields, so they stay byte-identical and prompt caching can reuse them whilemessagesgrows. For the model, tool results are news from the outside world, and the outside world always speaks asuser. - Who runs the tool? Your code does. The model only asks for an action by writing
tool_usedata. Your harness checks it againstinput_schema, checks permission, runs it, and checks the result. The Claude API never runs your client tool. Practice Q&A ยท Q31 ยท โ wrong choice to avoid: the model or the Claude API checking and running the toolwhy
A
tool_useblock is a request, not an action: the model writes the tool name and arguments as data and stops. Everything after that is your harness: validate the arguments againstinput_schema, check this agent may do this action with these values, run the tool, and look at the result before appending it. Strict tool inputs can shape the argument structure, but they never approve a shipment or prove a business value is right. - Does tool order matter? No. The
toolsarray is a menu, not a run order. Where a tool sits in the list creates no dependency, andstrict: trueonly shapes the arguments. If one step must come before another, your code enforces it. โ wrong choice to avoid: expecting tool order or strict schemas to force a call sequencewhy
The
toolslist only tells the model what it may call.strict: truecontrols the shape of the arguments, but it doesn't pick the right business task, check permission, or force one call before another. If step B must never run before step A, put that rule in your harness: check the state before running the tool.
Orchestrator, subagents & context handoff
- Do subagents share context? No. A subagent never inherits the coordinator's context or another subagent's. Anything it needs (findings, file paths, generated code, artifact revision) your coordinator must put into its prompt explicitly. โ wrong choice to avoid: assuming the later agent inherits, or can look up, what an earlier agent produced
why
A subagent starts blank: it sees only what the coordinator writes into its prompt, not the coordinator's conversation and not what sibling agents found. So every input is carried by hand: the findings a synthesis step must cite, the paths and schema mappings a test agent needs, the code to test, the revision to read. If a later agent acts like an earlier result never existed, first check whether the prompt ever included it.
- Where did content go missing? Walk back to the earliest failure. Topic never assigned โ fix the split/coverage plan. Assigned and done but not passed on โ fix collection/handoff. Passed in but left out โ look at synthesis. โ wrong choice to avoid: blaming synthesis or the workers when logs show the topic was never assigned or never passed on
why
Check the coordinator's logs stage by stage. If no worker was ever given the topic, the plan was too narrow; the workers did their jobs. If a worker finished it but the output never reached the next prompt, collecting results or the handoff is broken. Only if the content was passed in and still disappeared from the report is the synthesis step to blame.
- Only the first result used? All workers "returned successfully, no tool errors", but the report covers only auth? The coordinator sent only the first result to synthesis. A max_turns or rate-limit failure doesn't fit the question. โ wrong choice to avoid: blaming the workers: max_turns running out or silent rate-limit failures
why
Every worker finished cleanly, yet only the first module shows up: the coordinator took one result and moved on. Subagents return output to the coordinator, but the coordinator must collect all of them and pass them on together; nothing does this automatically. A
max_turnsor rate-limit failure would show up as an error or an incomplete result, which contradicts "all returned successfully, no tool errors". - Tests use old schema names? If the tests use schema names that were already renamed, the coordinator didn't pass the migration result (the mapping and the revision) to the test agent. Just running them in order doesn't fix it, and a message bus breaks hub-and-spoke. โ wrong choice to avoid: running the migration and test agents one after the other instead of in parallel
why
In hub-and-spoke (all messages go through the coordinator), the coordinator is the only channel, so it must put the migration agent's output (the schema mapping and the revision) into the test agent's prompt. Running the two agents one after the other changes timing but still passes nothing. A shared message bus lets workers talk directly, which breaks the hub-and-spoke model and adds complexity. Letting the test agent dig through migration files works around a problem the coordinator owns.
- What's a good handoff? A handoff needs three things: the earlier step is ready, the result is spelled out (
schema_mapping,changed_files,artifact_revision,checks,open_issues), and the next agent can read that exact revision. Just saying "done" is too vague. โ wrong choice to avoid: passing "done" as the handoffwhy
A handoff can break in three places, and "done" covers none of them. Ready: the earlier change must really be complete enough to build on. Context: the next prompt must carry the concrete result and the acceptance criteria. Files: the next agent must be able to read that exact revision, which isn't automatic when agents work in separate worktrees, and old tool results don't refresh by themselves.
- What do workers send back? Workers send back a short structured summary (~150 tokens), never their full transcript. A 12K-token page stays inside the worker's own window. In hub-and-spoke, workers never talk to each other. Practice Q&A ยท Q3 ยท Practice Q&A ยท Q34 ยท โ wrong choice to avoid: one agent looping through every source with heavy compaction, or a chain going through them in one conversation
why
That's the point of a worker: it reads and digests the 12K-token page in its own context window and returns only a small summary with sources, so the coordinator's context stays small enough to combine many workers. Sending back the raw transcript would blow the coordinator's budget. All findings go up to the coordinator and, if needed, down in the next prompt. Summaries lose detail, so check they're complete; isolation doesn't guarantee quality.
- How to run subagents in parallel? Subagents run at the same time only if the coordinator puts several Task/Agent calls in ONE response. Raising
max_tokens, shortening timeouts, or merging everything into one prompt doesn't make it parallel. โ wrong choice to avoid: raising max_tokens, shortening timeouts, or merging the jobs into one promptwhy
In the Agent SDK and Claude Code, the coordinator must emit several
Task(nowAgent) tool calls in a single response; calls spread over separate turns run one after another.max_tokensonly changes output length, a shorter timeout just cuts research short, and merging jobs into one prompt brings back the single-agent bottleneck. For independent work, total time drops to about the slowest worker. - Delegate or do it yourself? Send the 60-file rename to a subagent with focused context; the coordinator does the one-file health check itself. Size alone doesn't force a subagent, and doing both in the coordinator spreads its attention too thin. โ wrong choice to avoid: handling both tasks in the coordinator to avoid overhead
why
Delegating costs something: writing the context, starting the agent, collecting the result. It's worth it when the task is big or needs focus. A rename across 60 files would crowd the coordinator, so a subagent takes it; a one-file health-check endpoint is tiny, so the coordinator just does it. Delegating both wastes overhead on the small task, doing both pulls 60 files through the coordinator, and swapping them sends each task the wrong way.
- Agent uses the wrong tools? If a search agent keeps calling
summarise/sentiment_analysis, remove those analysis tools from its list and give them to the analysis agent. A prompt reminder or a PreToolUse block still leaves the tools in view. โ wrong choice to avoid: system-prompt reminders to use only the fetching toolswhy
The more tools an agent sees, the less reliably it picks the right one, and an agent that sees
summariseandsentiment_analysiswill call them sometime, even if its job is only fetching raw data. The sure fix is to cut its list to the four or five fetching tools. A prompt reminder is probabilistic (it may or may not work) and has already failed; a PreToolUse hook that blocks the calls is over-built and still shows tools the model shouldn't consider. Removing tools doesn't stop the model writing a summary in plain text, so still check the output to keep the handoff raw evidence. - Report has gaps? If the final report has unsupported claims or thin subtopics, the coordinator sends focused follow-ups for just those gaps, then re-runs synthesis. Don't restart from scratch, don't let the model fill gaps from its own knowledge, don't accept it to save budget. โ wrong choice to avoid: restarting from scratch, filling gaps from the model's own knowledge, or accepting the thin report
why
The coordinator looks at what's missing, sends narrow follow-up searches and analysis for exactly those gaps, then combines again with the extra evidence. Starting over throws away good work and may repeat the same gaps. Filling holes from the model's own knowledge creates claims with no source. Accepting the report to save budget ships known problems.
- Fix loop never converges? If a fix-until-tests-pass loop writes brand-new code every round, the coordinator isn't passing the previous code + the specific failing tests into the next prompt. More rounds or a Read tool won't fix it. โ wrong choice to avoid: allowing more rounds, or adding a Read tool
why
The loop only gets closer to passing if each round starts from the last attempt, so the worker edits instead of starting over. Without that, each round writes fresh code that fails in new ways, so the number of rounds doesn't matter. A Read tool doesn't help either: the last attempt lives in a finished context, not in a file the worker can find.
Gates, HITL & idempotency
- Approve before or after? Before. A $600 replacement with a $100 auto-approve line โ the gate fires before anything runs: the run pauses, a human gets the handoff packet, nothing has executed yet. Run-then-sign-off, flag-for-audit-later, and "ask the customer" are all wrong. Practice Q&A ยท Q36 ยท โ wrong choice to avoid: running under an idempotency key and getting sign-off afterwards, or flagging the run for audit later
why
The check is simple math your harness does on the
tool_useblock'sinputbefore running it: $600 is over $100, so the run parks, and the pending block plus a handoff packet go to the approval queue for a human. Running first and getting sign-off later, or flagging for audit afterwards, both ship before permission exists; an idempotency key stops duplicates but is not permission. Asking the model to check with the customer gives the decision to the wrong person. - Where does a hard rule go? A rule like "never credit above $50 without sign-off" goes in a code gate in your harness + a human approval queue. Not the system prompt, not a few-shot refusal, not CLAUDE.md. Prompts guide; your code enforces. Practice Q&A ยท Q1 ยท โ wrong choice to avoid: putting the rule in the system prompt, a few-shot refusal example, or CLAUDE.md
why
A prompt, an example refusal, or a CLAUDE.md line can describe the $50 rule, but the model can still propose a $75 credit; text guides behaviour, it doesn't control the payment system. The rule holds only if backend code checks the amount before issuing the credit and, above the limit, saves the request for an authorised human. Also, CLAUDE.md is a Claude Code memory file; a plain Messages API loop never loads it.
- Does waiting cost anything? No. Between one response and the next request nothing is running. Save the
messagesarray up to the pending tool_use, itsid, and the handoff packet. No tokens are spent while waiting, and the process can exit. โ wrong choice to avoid: keeping a model call or a process open while the human decideswhy
An agent run is only alive during a model call or a tool run. So a parked approval is free: your harness saves the
messagesarray through the assistant turn holding the pendingtool_use, that block'sid, and the handoff packet (who, what, why, amount) to a queue or store. Days later a worker loads the samerun_idand continues. No request is open, so nothing times out on the API side. - How to resume after approval? Resume by building the missing
tool_result. Approved โ run it NOW with an idempotency key and return the real outcome under the pendingtool_use_id. Denied โ atool_resultthat says so. Don't ask the model to request it again; "approved" alone doesn't prove the money moved. โ wrong choice to avoid: a new model turn that asks for the action again, or treating approval as proof it ranwhy
The pending
tool_useis still waiting for its answer. If approved, your harness runs the tool now with an idempotency key (a unique id so a repeat does nothing twice) and reports the real result, like a replacement id or refund status. If denied, thetool_resultsays so and the model explains it to the customer next turn. A new model turn asking for the refund again wastes a turn and might produce a different action. "Approved" is permission, not proof the money moved. - Refunded twice after retry? If a retry after a timeout refunds or ships twice, you're missing idempotency: a stable key per operation plus a record of effects, at the point where the real action happens. Not checkpointing, HITL threshold, turn budget, temperature, timeout, or a "never refund twice" prompt. Practice Q&A ยท Q6 ยท Practice Q&A ยท Q39 ยท โ wrong choice to avoid: missing checkpointing, lower temperature, a longer timeout, or a "never refund twice" prompt
why
After a timeout your code doesn't know if the action happened, and a simple retry does it again. The fix is where the action hits the outside system: give each operation a stable key, save its result in an effects ledger, and a retry with the same key returns the saved result instead of acting again. Checkpointing resumes progress but can't tell whether an uncertain external write happened. HITL thresholds, turn budgets, temperature, timeouts, and prompts all work on the model side and can't stop a duplicate backend call.
- Who approves? A human, using plain plumbing: a queue row, an approval screen, and a resume endpoint. An "approval agent" may advise but never decide. Approve-and-train turns past decisions into new code rules, offline. โ wrong choice to avoid: an "approval agent" that decides, or auto-approving similar cases next time
why
The approval path is normal code with a person deciding: queue row, notification, approval UI, and an endpoint that continues the same
run_id. A second model judging the first model's action is like a student grading their own homework and defeats the gate; it may summarise or suggest, never decide. Approve-and-train keeps the gate as code but lets it improve: each decision is logged and later, offline and with review, turned into new gate rules. - When to hand off to a human? Escalation is a planned ending, not a crash: open a ticket, attach the handoff packet, tell the user honestly. Trigger it when confidence is below the floor, the request is outside the agent's job, a needed tool is down, the user asks for a person, or a gate blocks. โ wrong choice to avoid: treating escalation as an error case instead of a planned outcome
why
Build escalation into the design from the start, not as an error handler added later. The triggers are concrete: confidence under the set floor, a request type outside the agent's charter, a dependency like the order-lookup tool being down, the user asking for a human, or a gate blocking the action. Each ends the same way: a ticket, a handoff packet (conversation summary, evidence, actions tried), and an honest message about what happens next.
State, budgets, sessions & recovery
- Job crashed midway? If a 12-step job dies after step 7, you need a checkpoint after each step, so you resume at step 8 and don't redo anything with side effects. Retrying the whole run, a bigger model, or emailing an admin all fail. Practice Q&A ยท Q8 ยท โ wrong choice to avoid: retrying the whole run, a bigger model, or emailing an admin
why
Save the state after every finished step; then a crash after step 7 restarts at step 8 with earlier work kept. Don't blindly repeat a step with side effects: if step 7's result is unclear, check its status or rely on idempotency before repeating it. Rerunning everything repeats seven real actions, a bigger model doesn't stop crashes, and emailing an admin isn't recovery.
- Returned means finished? No.
WAITING_FOR_APPROVALandWAITING_FOR_RETRYend the function call, not the task; onlyCOMPLETEDis the end. To resume, load the savedrun_idand continue from its phase, never from the top. โ wrong choice to avoid: treating a returned call as a finished task, or restarting the run from the topwhy
A durable agent keeps the function call separate from the real task. On a pending approval the controller sets the phase to
WAITING_FOR_APPROVAL, saves, and returns: the call is over, the task isn't.WAITING_FOR_RETRYworks the same, with anext_retry_at. OnlyCOMPLETED(or a failure end state) closes the task. On resume, load therun_id, check phase and pending work first, and continue; never start a new conversation or replay the first prompt. - Do budgets reset on resume? No. Save both the limit and what's used (
max_model_turns: 30,completed_model_turns: 10โ 20 left). Approval doesn't give new turns. Going over any budget is a final state (BUDGET_EXHAUSTED), not a reason to keep going quietly. โ wrong choice to avoid: resetting the turn count on resume, or logging a warning and continuing past the limitwhy
You can only enforce a budget if the run saves both the ceiling and the usage, so 20 turns remain after resume however long the approval took. A human approval unblocks one pending tool; it doesn't reset the counter. When any budget (model turns, tool calls, cost) runs out, the run goes to an end state like
BUDGET_EXHAUSTEDfor a person to review. A warning that lets the loop continue is not a budget. - Retry which failures? It depends on the failure type. Temporary tool error โ limited retry with backoff and jitter, or
WAITING_FOR_RETRY+next_retry_at. Bad arguments โ errortool_result, new model turn. Permission denied โ never retry. Out of attempts โDEAD_LETTER. A tool retry is not a model turn. โ wrong choice to avoid: retrying every failure the same way, or retrying a permission denialwhy
A timeout or 503 gets a limited retry with backoff and jitter (waiting longer each time, with some randomness), or the run parks; retrying the tool uses no model turn. Bad arguments are the model's mistake, so send an error
tool_resultand let it try again next turn. A permission denial won't change, so never retry it. When attempts run out, the run moves toDEAD_LETTERfor human review instead of looping forever. - Fork or fresh session? Want to try two strategies from the same good starting point? Use
fork_session(an SDK option used withresume=). Old file reads are stale? Start a fresh session with a summary of checked findings and re-read the changed files. โ wrong choice to avoid: exporting the session findings to a file and pointing two new sessions at itwhy
fork_sessioncopies the live session so each strategy starts from the same baseline without affecting the other. Exporting findings to a markdown file and opening new sessions loses detail and is manual, and telling the model "forget strategy A" doesn't erase context. When early file reads are out of date because a colleague pushed changes, start fresh with a verified summary of findings and decisions, then re-read the changed files. A fork would copy the stale reads, and staying in the old session keeps conflicting content in context. - What a fork can't do A fork copies the conversation, not your files, so use separate git worktrees for editing experiments. An export is a lossy document, not a session; compaction shrinks history but doesn't branch; and a fork copies stale reads too. โ wrong choice to avoid: using a fork to separate file edits, or to get rid of stale context
why
Two forks editing the same files would clash, so give each editing experiment its own git worktree. Exporting a session gives you a document that misses the real state's nuance. Compaction shortens history in place and makes no branch. Because a fork copies everything, it copies old tool results too, which is why recovering from stale context uses a fresh session.
- Faster but untested? The 97% invoice accuracy belongs to the OLD chain; the merged prompt's 1.8s is an unmeasured guess. So reject the merge, or test it first: "single-purpose chain steps outperform multi-task prompts". โ wrong choice to avoid: accepting the merge only for low-risk invoice types
why
The 1.8 seconds is a projection, and the merged prompt's accuracy was never measured. Putting classification, extraction, and validation into one prompt splits the model's attention three ways, and focused chain steps with checks between them do better than multi-task prompts. Using the merge only for low-risk types creates two inconsistent pipelines; the 68% contract accuracy is a separate problem, and adding more context per step doesn't fix a focus problem.
- Code check or model judge? If code can see the failure, code catches it: rate limits, tool timeouts, JSON-schema violations. Subtle factual drift in a summary needs a semantic evaluator (a model judge) calibrated against human labels. Practice Q&A ยท Q5 ยท โ wrong choice to avoid: spending a model judge on rate limits, timeouts, or schema errors that code already catches
why
Your harness catches rate limits, timeouts, and bad JSON exactly. A summary sentence that sounds fine but misstates the source is invisible to code, so you need an independent model judge whose scores are checked against human-labeled examples. You don't need a different model family, and a judge is no guarantee; the calibration is what makes the score mean something.
- Coordinator never delegates? No delegation and no errors?
allowedToolsis missingTask(nowAgent). A prompt can't create a tool that isn't there. A custom Messages-API harness delegates by calling its own worker function; no special tool name needed. โ wrong choice to avoid: adding stronger "delegate this" wording to the promptwhy
In Claude Code and the Agent SDK, the coordinator starts a subagent by calling
Task(now namedAgent). Without it inallowedTools, the model can't delegate, so it quietly does everything itself and no error appears. A harness built directly on the Messages API has no such built-in tool; it delegates through its own worker function, or a delegation tool you design. Whether the tool exists and whether it's permitted are two separate checks.
Review sources
Domain 1 lesson and worked explanations ยท Official reference↗
Fact wording rewritten as concrete numbered facts on 25 September 2026. The mock providers' original keys are preserved on their review pages; these recall lines retain the engineering caveats.