Back to Hub
Certification Study Notes

Claude Agent SDK & Agentic Orchestration

Building agents on Claude's harness: the agentic loop and stop_reason, the Agent SDK API, coordinator–subagent orchestration, AgentDefinition and context passing, hooks as deterministic guardrails, session resume and fork, structured tool errors, tool scoping and tool_choice, escalation, error propagation, long-context management, provenance, and hosted Managed Agents.

Author: Michaël Bettan
Track: Anthropic / Applied AI
Test Yourself: Claude Agent SDK Practice Quiz →
00

Overview & Wiring Paths

Definition: The Claude Agent SDK (TypeScript @anthropic-ai/claude-agent-sdk · Python claude-agent-sdk, which replaces claude-code-sdk) packages the Claude Code agent harness as a library: the agent loop, built-in tools, permissions, hooks, subagents, sessions and MCP, running inside your process. This guide covers the SDK API and the architecture patterns you build with it: loops, coordinator–subagent orchestration, deterministic enforcement, session state, tool error design, escalation, error propagation, long-context management and provenance.

Three ways to wire an agent:

Wiring pathWho runs the loopYou ownPick when
Raw Messages API loopYour codeTool execution, stop_reason handling, state, retries, compactionFull control, minimal dependencies
Claude Agent SDKSDK in your processHosting, filesystem isolation, storageYou want Claude Code's tools, hooks, subagents and sessions in your own infrastructure
Claude Managed AgentsAnthropic server-sideAgent config, custom tool handlers, session lifecycleLong-running hosted sandbox sessions without running containers yourself

Mental model

The model decides (which tool, which subagent, when to stop); the harness enforces (permissions, hooks, budgets). Anything that must hold 100% of the time belongs in code (hooks, tool scoping, prerequisite gates), never only in the prompt.

Scope note

Claude Code configuration (permission modes, CLAUDE.md, skills, plugins, CLI flags) lives in the Claude Code guide. MCP protocol internals live in the MCP guide.

01

1. The Agentic Loop & stop_reason

Raw Messages API loop (what the SDK does for you):

01

Call

Send messages + tools to Claude.

02

Inspect

Read stop_reason on the response.

03

If "tool_use"

Execute every tool_use block, append the assistant turn, then append one user turn containing a tool_result block per call (matched by tool_use_id). Go to 1.

04

If "end_turn"

The model is done. Use the final text.

stop_reasonMeaningLoop action
tool_useClaude wants tools runExecute, append tool_results, call again
end_turnNatural completionStop; return the answer
max_tokensHit your max_tokens cap mid-responseRaise the cap or continue the response; output is truncated
pause_turnA server-tool loop hit its iteration limitSend the assistant content back unchanged to let Claude continue
refusalClaude declinedHandle explicitly (read stop_details, fallback model or human)
stop_sequenceOne of your stop_sequences firedRead stop_sequence
model_context_window_exceededResponse filled the context windowTreat as truncated; compact or split the task

Decision rule

The loop's termination signal is stop_reason, not the text. Continue on tool_use; stop on end_turn; handle the rest explicitly.

Exam traps (loop anti-patterns)

(1) parsing natural language ("I'm done", "task complete") to decide termination; (2) treating "the response contains text" as completion (Claude often writes text and calls tools in the same turn); (3) using an arbitrary iteration cap as the primary stop condition (keep a cap only as a safety bound); (4) dropping tool results instead of appending them, so the model cannot reason about what happened.

Model-driven vs pre-configured: In an agent loop Claude chooses the next tool from context. A fixed decision tree or hard-coded tool sequence is a workflow, not an agent; use it only when the order is known in advance (see §3).

The SDK version of the loop: query() runs the loop and streams messages (AssistantMessage, ResultMessage, …). Check ResultMessage.subtype first:

ResultMessage.subtypeWhat happenedresult present?
successFinished normallyYes
error_max_turnsHit max_turns / maxTurnsNo
error_max_budget_usdHit max_budget_usd / maxBudgetUsd (subagent spend counts toward the cap)No
error_during_executionLoop interrupted (cancelled request, crash)No
error_max_structured_output_retriesNo schema-valid structured output within the retry limitNo
02

2. Agent SDK Core API

setting_sources / settingSources ("user" | "project" | "local"):

Option (Python / TS)What it controls & critical SDK traps
toolsBuilt-in tool availability in context (["Read", "Grep"], or [] to strip all built-ins so Claude sees only MCP tools).
allowed_tools / allowedToolsPre-approves matching calls; unlisted tools stay in context and fall through to permission_mode / can_use_tool. No effect in bypassPermissions.
disallowed_tools / disallowedToolsBare names ("Bash") remove the tool from context; scoped rules ("Bash(rm *)") keep the tool and deny matching calls in every mode, including bypassPermissions. File-write rules must use Edit(//abs/path) (Write(...) never matches).
permission_mode / permissionMode"default", "acceptEdits", "plan", "auto", "dontAsk", "bypassPermissions". In TS, "bypassPermissions" throws unless allowDangerouslySkipPermissions: true is also set.
can_use_tool / canUseToolAsync callback returning PermissionResultAllow(updated_input=...) or PermissionResultDeny(message=...). Fires only when the harness would prompt; never in dontAsk, bypassPermissions, or for tools matched by allowed_tools.
hooksIn-process callbacks (PreToolUse, PostToolUse, Stop, SubagentStop, UserPromptSubmit, …) registered with HookMatcher (see §5).
mcp_servers + create_sdk_mcp_serverExternal or in-process custom tools (@tool + create_sdk_mcp_server(name="srv", tools=[fn]); TS tool() + createSdkMcpServer()). Pre-approve as mcp__<srv>__<tool>.
agents (AgentDefinition)Programmatic subagents (see §4). Python quirk: multi-word fields use camelCase (disallowedTools, mcpServers, maxTurns, permissionMode, initialPrompt).
max_turns / max_budget_usdSafety bounds; produce error_max_turns / error_max_budget_usd.
output_format / outputFormat{"type": "json_schema", "schema": ...} → validated payload in ResultMessage.structured_output.
envPython merges env onto the process environment; TypeScript replaces process.env — spread { ...process.env, MY_VAR: "1" } or PATH is lost and the CLI fails with ENOENT.
03

3. Coordinator–Subagent Orchestration & Decomposition

Hub-and-spoke: the main agent is the only node that talks to every subagent. Children report back to it and never to each other, which keeps one place to log, budget and merge results.

Coordinator responsibility Do Avoid
Delegate only when it pays Answer a one-line lookup in the main agent; spawn when the work is verbose or parallel A subagent per request "just in case": every child costs a fresh context and its own API calls
Non-overlapping briefs Give two children disjoint directories, services or questions Two children reading the same files and returning conflicting edits
Route Write each AgentDefinition.description as a "use when…" sentence so Claude can tell agents apart Two agents with near-identical descriptions, so delegation is a coin toss
Brief Say what "done" looks like and what to return Scripting every command, so the child cannot react to what it finds
Bound Cap spend with max_budget_usd and nesting with CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH An unbounded tree: subagents can spawn their own subagents

Choosing a decomposition pattern:

Pattern Use when Example
Fixed workflow (code decides the order) The steps never change Lint → type-check → summarize, each as its own query() call
Agent-led delegation What to do next depends on what was found Incident triage: the agent reads alerts, then decides whether a log-reader or a config-diff subagent is worth spawning
Parallel fan-out Children do not depend on each other style-checker, security-scanner and test-coverage subagents on the same change

Exam trap — one context for everything

stuffing every log, diff and doc into the main agent fills its window and triggers compaction early. Push verbose reading into subagents so only their short reports land in the parent.

04

4. Subagent Invocation, AgentDefinition & Context Passing

Python
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Grep", "Glob", "Agent"],   # the coordinator needs the Agent tool
    agents={
        "code-reviewer": AgentDefinition(
            description="Security and quality review. Use for code review tasks.",  # WHEN to delegate
            prompt="You are a code review specialist...",                           # subagent system prompt
            tools=["Read", "Grep", "Glob"],                                         # least privilege
            model="sonnet",
        ),
    },
)
A (non-fork) subagent receives It does not receive
Its own system prompt (AgentDefinition.prompt) + the Agent tool's prompt string The parent's conversation history or tool results
Project CLAUDE.md (via setting_sources, unless omitClaudeMd) The parent's system prompt
Its tool definitions (inherited or the tools subset) Skill content, unless listed in skills

Decision rule — context passing

A fresh subagent knows only what the Agent prompt string tells it. If the parent already found the failing test name, the stack trace or the file that owns a function, that text has to be copied into the prompt; a reference such as "the error above" points at nothing. Ask for a fixed-shape final report so the parent can parse it.

05

5. Hooks: Lifecycle & Deterministic Enforcement

Hook lifecycle (Python + TypeScript unless noted):

Event Fires when Typical production use
UserPromptSubmit A prompt is submitted Inject tenant or ticket context
PreToolUse Before a tool runs (can block or rewrite) Guard shell commands, rewrite paths into a sandbox
PermissionRequest A call needs a permission decision Route approvals to your own UI
PostToolUse / PostToolUseFailure After a tool succeeds / fails Audit log, redact secrets, normalize formats
SubagentStart / SubagentStop A subagent spawns / finishes Trace fan-out; SubagentStop carries agent_transcript_path
PreCompact Before history is summarized (trigger: manual / auto) Archive the full transcript first
Stop / Notification Main agent stops / emits a status message Persist state, page an operator
SessionStart, SessionEnd, PostCompact TypeScript only Telemetry setup, cleanup, log the summary
Need Mechanism Why
A rule that must hold on every call (no deletes outside staging, no writes to .env) PreToolUse hook, scoped deny rule or narrower tool Prompt instructions are followed most of the time, not all of the time
Clean or annotate tool output before the model reads it PostToolUse hook (updatedToolOutput, additionalContext) One place to strip secrets or convert units
Style, tone, preferred approach System prompt / CLAUDE.md Guidance; occasional deviation is acceptable
Python
from claude_agent_sdk import ClaudeAgentOptions, HookMatcher

async def guard_kubectl(input_data, tool_use_id, context):
    cmd = input_data["tool_input"].get("command", "")
    if "kubectl delete" in cmd and "-n staging" not in cmd:
        return {"hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": "Deletes run only with -n staging. "
                "For other namespaces, write the planned change to ops/pending.md."}}
    return {}  # no opinion: the normal permission flow continues

options = ClaudeAgentOptions(
    hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[guard_kubectl])]})

Exam trap

"Add a stronger instruction in the system prompt (MUST, NEVER)" is the wrong answer whenever a scenario needs a guarantee. The permissionDecisionReason on a deny should name the allowed path, so Claude changes course instead of retrying the same call.

06

6. Session State: Resume, Fork & Interrupt

Operation SDK Claude Code CLI Effect
Continue continue_conversation=True claude --continue (-c) Most recent session in the current directory; same session ID
Resume resume=session_id claude --resume <id-or-name> (-r), /resume Specific session; same ID; full prior context
Fork resume=id, fork_session=True (forkSession) --resume <id> --fork-session, /branch New session ID with a copy of the history; original unchanged
Name rename_session() (TS renameSession()) claude -n <name> / --name, /rename <name> Resume later with claude --resume <name>

Decision rule — resume or not

a resumed session trusts every file it read earlier. If the code moved on, either name the changed paths in your first message so it re-reads them, or open a new session seeded with a short brief of what still holds; old tool output that contradicts the disk is worse than none.

07

7. Custom Tools & Tool Errors

In-process tools: decorate an async handler with @tool(name, description, input_schema) (TS tool() with a Zod schema), wrap it with create_sdk_mcp_server(name=..., version=..., tools=[...]), pass the server in mcp_servers, and reference each tool as mcp__<server>__<tool> in allowed_tools and hook matchers. The handler returns {"content": [{"type": "text", "text": ...}]}.

Python
from claude_agent_sdk import tool, create_sdk_mcp_server, ToolAnnotations

@tool("stock_level", "Units on hand for one SKU in one warehouse",
      {"sku": str, "warehouse": str}, annotations=ToolAnnotations(readOnlyHint=True))
async def stock_level(args):
    try:
        units = await inventory.count(args["sku"], args["warehouse"])
    except TimeoutError:
        return {"content": [{"type": "text", "text":
                "Inventory API timed out: count UNKNOWN, not zero. Retry in about 30s."}],
                "is_error": True}
    return {"content": [{"type": "text", "text": f"{units} units"}]}

inv = create_sdk_mcp_server(name="inv", version="1.0.0", tools=[stock_level])
# ClaudeAgentOptions(mcp_servers={"inv": inv}, allowed_tools=["mcp__inv__stock_level"])

Write the error for the next decision. Ask "who can fix this?" and say so in the message:

Who can fix it Example What the message should tell Claude
Claude, by changing arguments SKU format wrong, unknown warehouse code The accepted format or valid values
The user Missing account number Exactly which detail to ask for (AskUserQuestion)
Time Upstream 503, timeout That the value is unknown, plus when to retry
Nobody in this session Caller not authorized, action outside policy To stop this path and report it, not retry

Exam trap — unknown is not empty

a tool that answers 0 or [] when its backend is unreachable gives Claude a confident wrong fact (it may reorder stock that exists). Return is_error with "unknown". A vague message has the same flaw in a different form: Claude has no way to know whether waiting would help.

08

8. Tool Scoping & tool_choice

tool_choice (Messages API):

Value Behavior Use for
{"type": "auto"} Claude decides whether to call a tool (default when tools are provided) Normal agent turns
{"type": "any"} Must call some tool, Claude picks which Guarantee a structured tool call when several output schemas exist
{"type": "tool", "name": "classify_ticket"} Must call that tool Force one specific call on this turn, then return to auto
{"type": "none"} No tool calls (default when no tools) Pure text turns

Exam traps

Forcing a tool every turn turns an agent into a fixed pipeline (it can never finish with end_turn text). Forced calls skip thinking. Manual extended thinking does not support any / tool, and some newer models reject them with a 400: use auto with strict tool definitions there (verify per model).

09

9. Permission Modes, Approvals & Clarifying Questions

Evaluation order for every tool call:

Step Check Notes
1 Hooks Can deny outright; a hook allow does not skip steps 2–3
2 Deny rules (disallowed_tools, settings) Deny even in bypassPermissions and even after a hook allow
3 Ask rules Fall through to canUseTool, even in bypassPermissions
4 Permission mode bypassPermissions approves; acceptEdits approves file operations; plan sends edits to the callback
5 Allow rules (allowed_tools, settings) Approve matching calls
6 canUseTool callback Skipped in dontAsk (the call is denied)
Mode Behavior Fits
default No mode-based approvals; unmatched calls go to canUseTool Interactive apps with an approval UI
dontAsk Anything that would prompt is denied Locked-down workers with an explicit allowed_tools list
acceptEdits File edits and filesystem operations auto-approved Trusted coding tasks in a sandbox
plan Explores and plans; source edits never auto-approved "Show me the plan first" flows
auto A model classifier approves or blocks reviewed actions Fewer prompts where available
bypassPermissions Calls reaching step 4 run, apart from a few documented exceptions (TS also needs allowDangerouslySkipPermissions) Disposable sandboxes only

Exam traps

allowed_tools=["Read"] with bypassPermissions still approves Bash, because unlisted tools fall through to the mode. A bare allowed_tools entry pre-approves that tool, so canUseTool never sees its calls. Checks that must see every call belong in a PreToolUse hook.

10

10. Subagent Failures, Limits & Partial Results

What the parent can observe: only the subagent's final message (plus parent_tool_use_id on streamed messages from inside it), the SubagentStart / SubagentStop hooks, and limit messages returned as Agent tool results.

Situation What happens Design response
Subagent hits its maxTurns Output comes back marked partial Resume it by agentId so it keeps its history, rather than spawning a fresh one
Spawn refused at the concurrency cap Tool result: Concurrent subagent limit reached Wait for running agents, or batch the work
Spend cap reached Budget limit reached; background subagents stop; the query ends Read total_cost_usd; finalize with what returned
Subagent prose says "mostly done" Parent must guess what is missing Require a fixed status block in every report
JSON
{ "status": "partial",
  "done": ["services/auth", "services/billing"],
  "not_done": ["services/search"],
  "reason": "maxTurns reached while tracing search indexer",
  "resume_hint": "agentId a1b2: continue from indexer/worker.py" }
11

11. Long Sessions & Context Compaction

Automatic compaction: near the context limit the SDK summarizes older history and emits a SystemMessage with subtype: "compact_boundary". Instructions given early in the conversation may not survive the summary.

Technique How
Persistent rules in CLAUDE.md Rules that must survive compaction live in CLAUDE.md (loaded via setting_sources), not in the first user message
Steer the summary Add a CLAUDE.md section telling the compactor what to keep (open TODOs, file paths, exact IDs)
PreCompact hook Archive the full transcript before summarizing; trigger is manual or auto, custom_instructions carries any focus text
Manual /compact Send /compact as the prompt string to compact on demand, e.g. between phases
Pinned facts file Agent writes exact values (IDs, versions, thresholds) to a file and re-reads it; summaries blur numbers
Subagent delegation Verbose exploration ("find every caller of parse_token") runs in a subagent; only the report enters the parent context
Lower a subagent's window TS AgentDefinition.autoCompactWindow compacts a long-running subagent earlier
Trim tool output Return only the fields Claude needs from custom tools

Symptoms of context degradation: inconsistent answers, generic advice instead of the specific files found earlier, forgotten constraints. Fix the cause (summary lost the detail) rather than repeating the question.

12

12. Structured Results from Agents

JSON
{ "finding": "Token refresh skips expiry check", "severity": "high",
  "evidence": { "file": "auth/refresh.py", "line": 88, "observed": "2026-03-02" },
  "confidence_basis": "read code path; no test covers it" }
13

13. Managed Agents

All Managed Agents endpoints require the beta header managed-agents-2026-04-01 (set automatically by client.beta.agents / environments / sessions).

PrimitiveEndpoint & mechanics
1. AgentPOST /v1/agents — versioned definition (model, system string — no CLAUDE.md hierarchy, tools, mcp_servers, skills). Built-in sandbox tools use {"type": "agent_toolset_20260401"} (in /workspace); custom tools ({"type": "custom", ...}) pause the session and emit agent.custom_tool_use until your backend posts user.custom_tool_result. Per-tool permission_policy (always_allow, always_ask, auto) gates execution via user.tool_confirmation.
2. EnvironmentPOST /v1/environments — Anthropic-managed cloud container (type: "cloud", networking: {"type": "limited"} allowlists) or self-hosted runner.
3. SessionPOST /v1/sessions — binds a pinned agent version + environment_id, mounted resources (file, github_repository, memory_store), vault_ids (Vaults inject MCP/API credentials into outbound requests outside the container so raw secrets never enter the sandbox), and a spend budget.
4. EventsPOST /v1/sessions/{id}/events & SSE /events/stream — user.message, agent.message, agent.custom_tool_use → user.custom_tool_result, user.tool_confirmation, session.status_idle with stop_reason.type == "end_turn".

Compliance trap

Managed Agents is stateful by design (conversation history, container state and outputs stored server-side) and cannot be used under a HIPAA BAA or Zero Data Retention (ZDR) agreement. For regulated PHI or ZDR-governed data, run the loop yourself (Agent SDK or Messages API) on an eligible configuration.

14

14. Failure Modes & Exam Traps

Symptom / scenarioRoot cause → correct fixTempting wrong answer
Loop exits early whenever Claude says "Let me check…" Termination keyed on text → loop on stop_reason == "tool_use", stop on end_turn Raising the iteration cap
Coordinator never delegates Agent (alias Task) tool not in context/approved → add "Agent" to the explicit tools list (and allowed_tools) Rewriting subagent descriptions only
Debug subagent re-runs the whole test suite to find the failure the parent already saw Fresh subagents get only the Agent prompt → paste the failing test and stack trace into it Telling it to "check the error above"
Four calls to a custom read-only tool run one at a time Custom tools default to sequential → set readOnlyHint=True Splitting the tool into four tools
kubectl delete ran in production despite a prompt rule Prompt rules are guidance → PreToolUse hook on Bash denies with a redirecting reason Repeating the rule in capitals
Agent reorders stock after the inventory API timed out Tool returned 0 on timeout → is_error saying the count is unknown, with a retry hint Retrying forever inside the handler
Hook path rewrite silently ignored updatedInput paired with "defer" → pair with "allow" or "ask" Moving the rewrite into the prompt
Resumed session calls a function a teammate deleted yesterday History holds old file reads → name the changed paths so it re-reads them, or seed a new session with a short brief /compact the old session
canUseTool never fires in production Mode is dontAsk / bypassPermissions, or the tool has a bare allowed_tools entry → use default and drop the entry, or move the check to a hook Adding a second callback
Agent "forgets" a rule after a long run Rule lived in the first prompt and was summarized away at compact_boundary → move it to CLAUDE.md Raising max_turns
Multi-tenant SDK app leaks memory across tenants or fails with ENOENT in TS setting_sources=[] still loads auto memory, ~/.claude.json, claude.ai MCP; TS env replaces process.env mcp_servers={} without strict_mcp_config=True
PHI workload on Managed Agents "with deletion after each run" Managed Agents is not BAA/ZDR eligible → Agent SDK / Messages API on eligible setup Picking a US region
15

15. Glossary & Self-Assessment

TermDefinition
stop_reason Why a response ended: tool_use (run tools, continue), end_turn (done), max_tokens, pause_turn, refusal, stop_sequence, model_context_window_exceeded
ResultMessage.subtype SDK loop outcome: success, error_max_turns, error_max_budget_usd, error_during_execution, error_max_structured_output_retries
Hub-and-spoke Coordinator routes all communication between isolated subagents
Agent tool (alias Task) Tool Claude uses to spawn a subagent; must be available/approved for the coordinator
AgentDefinition Programmatic subagent: description, prompt, tools, model, plus optional limits
HookMatcher Registers SDK hook callbacks for an event and tool-name matcher
permissionDecision PreToolUse hook verdict: deny > defer > ask > allow
fork_session Copies a session's history into a new session ID; original unchanged; filesystem not forked
isError MCP tool-result flag marking a failed call (is_error in Python SDK tools)
readOnlyHint Tool annotation that lets a custom tool run in parallel with other read-only calls
canUseTool / set_permission_mode Runtime approval callback (step 6) / switch permission mode mid-session
tool_choice auto / any / tool (forced) / none
compact_boundary / PreCompact System message emitted after automatic compaction / hook that runs before it
interrupt() Stops the running turn in streaming mode; drain buffered messages afterwards
agent_toolset_20260401 & Vaults Managed Agents built-in toolset (/workspace) and server-side credential injection (vault_ids)

Claude Agent SDK Architecture Walkthrough Video

A narrated visual walkthrough of the Claude Agent SDK execution loop, stop_reason handling, coordinator–subagent isolation, deterministic PreToolUse hooks, session resumption and forking, structured tool errors, and Claude Managed Agents.

Audio Deep Dive & Study Downloads

Listen to the 56-minute Claude Agent SDK audio deep dive, or open the 2-column study guide PDF, the 21-slide Claude Agent SDK Architecture & Orchestration Blueprint deck, and the Standard sketchnote infographic in the Hub Viewer.

Orchestrating Reliable Agents with the Claude Agent SDK (Podcast)
Open Study Notes, Slide Deck & Infographic in Hub Viewer Launch Interactive Claude Agent SDK Practice Quiz →

Self-Assessment Questions (Click to Reveal)

Q1. A coordinator's loop stops whenever the assistant text contains “done”. What is wrong, and what should drive termination?

Natural-language completion signals are unreliable: Claude often writes text and requests tools in the same turn. Continue while stop_reason == "tool_use" (execute every tool_use block and append matching tool_results); stop on "end_turn"; keep an iteration cap only as a safety bound.

Q2. A debugging subagent spends ten turns rediscovering a stack trace the parent already captured. Why, and what is the fix?

A non-fork subagent starts with a fresh context: it sees its own system prompt, project CLAUDE.md and the Agent tool's prompt string, never the parent's history. Copy the stack trace, failing test name and relevant file paths into that prompt.

Q3. A PreToolUse hook returns updatedInput with permissionDecision: "defer". What happens to the rewrite?

It is dropped: "defer" discards updatedInput. Pair the rewrite with "allow" to auto-approve it, "ask" to show it for approval, or omit the decision so it flows through normal permission evaluation.

Q4. An in-process inventory tool returns 0 when its backend times out. Why is that dangerous, and how should the handler respond?

Zero is a valid answer, so Claude treats it as fact and may act on it (for example, reorder stock). Catch the timeout and return is_error: True with text saying the count is unknown and when to retry. Uncaught exceptions also become error results, but with only the raw exception text.

Q5. In the Agent SDK, what four things can still load even when you pass setting_sources=[]?

(1) Managed policy settings, (2) ~/.claude.json global state (isolate via CLAUDE_CONFIG_DIR), (3) auto memory MEMORY.md (disable via CLAUDE_CODE_DISABLE_AUTO_MEMORY="1"), and (4) claude.ai MCP connectors (suppress via strict_mcp_config=True).

Q6. In Claude Managed Agents, how do you run a proprietary tool inside your own VPC while keeping third-party MCP credentials out of the cloud container?

Declare the VPC tool as {"type": "custom", ...} on the Agent so the session pauses and emits agent.custom_tool_use (your VPC executes it and posts user.custom_tool_result), and attach a Vault via vault_ids on the Session so credentials are injected server-side outside the sandbox.

Q7. After a long run the agent ignores a rule you gave in the first prompt. What likely happened, and where should the rule live?

Automatic compaction summarized early history (the stream shows a compact_boundary system message) and the rule did not survive. Put persistent rules in CLAUDE.md loaded through setting_sources, and use a PreCompact hook if you need the full transcript archived.