What you will be able to do
- Trace the messages query() yields from the init SystemMessage to the ResultMessage
- Configure tool access, turn limits and budget limits in query() options, and read the result subtype to see which limit stopped the run
- Register hook callbacks for logging, context injection and lifecycle tasks
- Declare subagents with the agents option and recognise their invocation in the message stream
1.How query() runs the agent turn by turn
The Claude Agent SDK is a Python and TypeScript library that embeds Claude Code's prebuilt agent loop, with its built-in tools, permissions, sessions and hooks, inside an application you operate. You get a working agent without writing the loop yourself, and the rest of this page shows how to steer it.
In the Claude Agent SDK, query() is the agent loop. You pass a prompt and options, and you get back an async stream of messages while the SDK runs the tool-use cycle from [The tool-use (agentic) loop](lesson:8.1) for you. The cycle itself is fixed. You still control which tools run, how far the loop may go, what context it starts with, and how your code reacts to each message.
Here is the sequence:
1. The SDK yields a SystemMessage with subtype init that carries session metadata.
2. Claude evaluates the state and responds with text, tool calls or both. Each content block arrives as its own AssistantMessage.
3. The SDK runs each requested tool and yields a UserMessage with the result.
4. Steps 2 and 3 repeat, and each full cycle counts as one turn. This continues until Claude responds with no tool calls.
5. A final AssistantMessage holds the text answer, followed by a ResultMessage with the result, token usage, cost and session ID.
Which messages you handle depends on what you are building. A batch job only needs the ResultMessage. A progress display reads each AssistantMessage to show which tools Claude called. A live UI turns on partial messages (includePartialMessages in TypeScript) to receive StreamEvent text deltas. In TypeScript, assistant and user messages wrap the raw API message, so content blocks are at message.message.content. Do not stop reading at the ResultMessage: a few system events such as prompt_suggestion can arrive after it.
Checkpoint 1 of 7· Put it in order
Put the messages of one query() run in the order the SDK yields them
- 1.ResultMessage with result, usage, cost and session ID
- 2.SystemMessage with subtype init
- 3.UserMessage with the tool result
- 4.AssistantMessage carrying a tool call
- 5.Final text-only AssistantMessage
Init comes first. Tool calls and their results then alternate for as many turns as needed. A text-only response ends the loop and is followed by the ResultMessage.
“The SDK yields a final AssistantMessage with the text response (no tool calls), followed by a ResultMessage”Source: code.claude.com
2.Options for tools, limits and context
Tool access is set with three options, which apply in the SDK the rules taught in [Permission rules (allow / ask / deny)](lesson:7.1):
- allowedTools auto-approves the listed tools. Tools that are not listed are still available. Calls to them that need approval fall through to the permission mode and the canUseTool callback.
- disallowedTools blocks the listed tools, whatever the other settings say.
- permissionMode sets the level of human oversight, using the modes from [Permission modes and auto mode](lesson:3.1). The SDK checks it together with your allow and deny rules in a fixed order.
canUseTool is a callback you supply that is asked to approve or reject a tool call that needs approval and is not already covered by your allow rules. In default mode, a call that needs approval with no callback registered is denied.
for await (const message of query({
prompt: "Find and fix the bug causing test failures in the auth module",
options: {
allowedTools: ["Read", "Edit", "Bash", "Glob", "Grep"], // Listing tools here auto-approves them (no prompting)
settingSources: ["project"], // Load CLAUDE.md, skills, hooks from current directory
maxTurns: 30, // Prevent runaway sessions
effort: "high" // Thorough reasoning for complex debugging
}
})) {settingSources: ["project"] is the main context lever. It loads CLAUDE.md, skills and filesystem hooks from the working directory, so the agent starts out following your project's conventions. effort is a cost lever. Setting it to "low" for agents that only read files or list directories cuts token use. Two options stop a run that goes on too long: maxTurns caps the number of turns and maxBudgetUsd caps spend. An open-ended prompt with neither one set can keep running and spending until the task is done.
| Result subtype | What happened | result field available? |
|---|---|---|
| success | Claude finished the task normally | Yes |
| error_max_turns | Hit the maxTurns limit before finishing | No |
| error_max_budget_usd | Hit the maxBudgetUsd limit before finishing | No |
| error_during_execution | An error interrupted the loop (for example, a cancelled request) | No |
| error_max_structured_output_retries | No valid structured output was produced within the configured retry limit | No |
The subtype field tells you which limit was hit. Only success includes the result text. A single-shot query() yields the error result and then raises an error such as "Reached maximum number of turns", so put the loop in a try block. Your subtype branches still run before the exception. To continue after error_max_turns, save the session ID and resume that session with a higher limit.
Checkpoint 2 of 7· Exam question
A startup wants to offer an AI teammate feature that runs long, asynchronous multi-hour sessions for its customers, but the small engineering team does not want to build or operate any sandboxing, container orchestration, or session-storage infrastructure themselves. Which deployment model best fits their constraints?
Correct answer: B — Adopt Managed Agents, Anthropic's hosted REST API, so Anthropic runs the agent and a per-session sandbox while the team's application sends events and streams results.
- A. Lambda imposes execution-time limits and has no built-in notion of an agent sandbox or session log; the team would still be responsible for building and operating the SDK-based orchestration they wanted to avoid.
- B. Correct. Managed Agents is described as a hosted REST API where Anthropic runs the agent and a managed sandbox per session and hosts the event log, which is specifically positioned for production agents without operating sandbox or session infrastructure, including long-running asynchronous sessions.
- C. A self-managed EC2 instance with a custom queue is exactly the kind of sandboxing and session-tracking infrastructure the team explicitly wants to avoid building.
- D. bypassPermissions is a tool-approval setting that controls whether prompts appear before tool calls; it has no bearing on who operates the underlying sandbox or session-storage infrastructure.
Checkpoint 3 of 7· Check yourself
A read-only agent sets allowedTools to ["Read", "Glob", "Grep"] and nothing else. Claude asks to run Bash. What happens?
allowedTools auto-approves the listed tools and restricts nothing. To block Bash outright, put it in disallowedTools.
“Tools not listed are still available, and calls to them that need approval fall through to the permission mode and canUseTool.”Source: code.claude.com
Sources2
3.Hooks as callbacks for logging, context and lifecycle
Hooks are your own code that runs at fixed points in the loop, no matter what the model decides. The event list and when each event fires are covered in [Hook events and when they fire](lesson:7.3). Blocking a dangerous command is the best-known use, but in an SDK app hooks also do the routine work that should never depend on the model remembering it.
Common non-guardrail uses include:
- Audit logging: a PostToolUse callback writes every file change to an audit trail. The tool use ID links it to the matching PreToolUse event.
- Context injection: UserPromptSubmit adds extra context to each prompt before Claude sees it.
- Transcript archiving: PreCompact saves the full conversation before the SDK summarizes older history. The stream then shows a compact_boundary system message once compaction finishes.
- Lifecycle tasks: Stop saves session state, and SubagentStop collects results from parallel subagents.
options = ClaudeAgentOptions(
hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[my_callback])]}
)Every callback receives the input data, a tool use ID and a context object. All inputs include session_id, cwd and hook_event_name, and agent_id is filled in when the hook fires inside a subagent. The callback returns a decision. Returning {} lets the action go ahead. The top-level systemMessage field shows the user a message, and continue (continue_ in Python) decides whether the agent keeps running. The two SDKs do not support the same events. SessionStart, SessionEnd and PostCompact exist in TypeScript only, so a Python app has to set up telemetry somewhere else.
Checkpoint 4 of 7· Exam question
An SRE wants an autonomous Claude Code agent to never modify secrets files. Regardless of what the system prompt tells the model, any attempt to write to a file named .env anywhere in the working tree must be rejected before it executes, and the block must hold even if a teammate later runs the same session in bypassPermissions mode. Which implementation satisfies this?
Correct answer: B — Register a PreToolUse hook matched to Write|Edit that inspects tool_input.file_path and returns permissionDecision "deny" whenever the file name is .env.
- A. A system-prompt instruction is a guideline the model can deviate from under pressure or a crafted prompt; it is not enforced by the runtime and does not guarantee the block holds in every mode.
- B. Correct. Hooks run before permission-mode evaluation and a hook's deny decision is enforced regardless of the active permission mode, including bypassPermissions, so a PreToolUse deny on the .env file name blocks the write deterministically.
- C. This scoped rule only denies the specific Bash invocation "rm .env"; it does nothing to stop the same file from being overwritten through the Write or Edit tools.
- D. Plan mode still routes file-edit attempts to the canUseTool callback for a human decision rather than guaranteeing an outright block, and the restriction disappears once the session leaves plan mode.
Checkpoint 5 of 7· Fill the gap
This callback must run before every Bash command executes. Which event name completes the registration?
for await (const message of query({
prompt: "Your prompt",
options: {
hooks: {
? : [{ matcher: "Bash", hooks: [myCallback] }]
}
}
})) {
console.log(message);
}PreToolUse fires when a tool call is requested and before it runs, and the matcher narrows it to Bash. PostToolUse fires only after the tool returns.
Source: code.claude.com4.Declaring subagents with the agents option
An orchestrator hands focused subtasks to specialised child agents. The reasons for that design are covered in [Orchestrator–subagent hierarchy](lesson:1.1). In the Agent SDK you declare each child in the agents option, keyed by name. Its description tells Claude when to use it, prompt sets its behaviour, tools limits what it can do, and model can override the default model.
for await (const message of query({
prompt: "Use the code-reviewer agent to review this codebase",
options: {
allowedTools: ["Read", "Glob", "Grep", "Agent"],
agents: {
"code-reviewer": {
description: "Expert code reviewer.",
prompt: "Analyze code quality and suggest improvements.",
tools: ["Read", "Glob", "Grep"]
}
}
}
})) {The orchestrator starts a subagent by calling the Agent tool. That is why Agent appears in allowedTools above. Older SDK versions called this tool Task, so code that watches the stream should match both names. In the stream, the call shows up as a tool_use block whose input names the subagent_type. Messages produced inside the child carry a parent_tool_use_id.
Subagents also help manage context. A child's isolated work stays out of the main conversation, which keeps the orchestrator's context small on long tasks. To read a child's transcript afterwards, resume the same session, because each query() call starts a new session by default.
Checkpoint 6 of 7· Exam question
A team wants every Write tool call the agent makes to be transparently redirected into a /sandbox subdirectory, without the model being told about the redirection or being asked to approve each individual write. Which hook return value accomplishes this?
Correct answer: B — hookSpecificOutput with permissionDecision "allow" and updatedInput containing the file_path rewritten to prepend /sandbox, so the modified input is auto-approved.
- A. "ask" still routes the call through canUseTool for human approval, which contradicts the requirement that no per-write approval be requested.
- B. Correct. updatedInput must be paired with permissionDecision "allow" to auto-approve the modified arguments; this rewrites file_path to the sandboxed path and executes it without prompting or informing the model of the substitution.
- C. Leaving hookSpecificOutput empty means the hook has no opinion on the call; the original, unmodified file_path is what proceeds to the rest of the permission flow, not the rewritten one.
- D. "defer" ends the query so it can be resumed later; per the documented behavior, updatedInput is ignored when the decision is "defer", so the rewrite would never actually be applied.
Checkpoint 7 of 7· Check yourself
In the code-reviewer example, what decides when the orchestrator hands work to the code-reviewer subagent?
description tells the orchestrator when to delegate. prompt defines how the subagent behaves once it is running, and tools limits what it can do.
“description tells Claude when to use this subagent”Source: code.claude.com
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Listing tools in allowedTools restricts the agent to only those tools.Why is that wrong?
allowedTools only auto-approves. Unlisted tools are still available and go through the permission mode and canUseTool. disallowedTools is the option that blocks.
Covered in Options for tools, limits and context
2.The stream is finished once the ResultMessage arrives, so you can break out of the loop there.Why is that wrong?
A few trailing system events can arrive after the ResultMessage, so keep reading until the stream ends.
Covered in How query() runs the agent turn by turn
3.Every hook event is available in both the Python and TypeScript Agent SDKs.Why is that wrong?
Some events, including SessionStart, SessionEnd and PostCompact, are TypeScript-only, so a Python app cannot register them.
Covered in Hooks as callbacks for logging, context and lifecycle
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://code.claude.com/docs/en/agent-sdk/overviewOfficial docs
“Embed Claude Code’s agent in your own Python or TypeScript application, in a process you operate”
↩︎ How query() runs the agent turn by turn - 2.https://code.claude.com/docs/en/agent-sdk/agent-loopOfficial docs
“Each full cycle is one turn.”
↩︎ How query() runs the agent turn by turn“Each carries a single content block, such as text or a tool call”
↩︎ How query() runs the agent turn by turn“Tool calls that need approval and aren’t covered by allow rules trigger your canUseTool callback; no callback means deny”
↩︎ Options for tools, limits and context“Hit the maxBudgetUsd limit before finishing”
↩︎ Options for tools, limits and context“A single-shot query() call yields the final result message, then raises an error”
↩︎ Options for tools, limits and context“Archive full transcript before summarizing”
↩︎ Hooks as callbacks for logging, context and lifecycle“Offload isolated work to subagents to keep your main context lean.”
↩︎ Declaring subagents with the agents option“blocks listed tools, regardless of other settings”
↩︎ Exam trap 1“iterate the stream to completion rather than breaking on the result”
↩︎ Exam trap 2“The SDK yields a final AssistantMessage with the text response (no tool calls), followed by a ResultMessage”
↩︎ Checkpoint“Tools not listed are still available, and calls to them that need approval fall through to the permission mode and canUseTool.”
↩︎ Checkpoint - 3.https://code.claude.com/docs/en/agent-sdk/hooksOfficial docs
“Log and audit every tool call for compliance, debugging, or analytics”
↩︎ Hooks as callbacks for logging, context and lifecycle“continue (continue_ in Python) determines whether the agent keeps running after this hook”
↩︎ Hooks as callbacks for logging, context and lifecycle“PostCompact | No | Yes | Conversation compaction completes”
↩︎ Exam trap 3 - 4.https://code.claude.com/docs/en/agent-sdk/subagentsOfficial docs
“emitted "Task", current versions emit "Agent".”
↩︎ Declaring subagents with the agents option“Each query() call starts a new session by default”
↩︎ Declaring subagents with the agents option“description tells Claude when to use this subagent”
↩︎ Checkpoint