CertSafari
    Anthropic Claude Certified Developer – Foundations (CCDV-F)· Lessons

    Domain 1 · Lesson 2/25

    The Agent SDK query() loop, hooks and subagents

    Agent Construction with Claude

    12 min read
    4.9% of exam
    4 sources
    Published 12 Oct 2026
    Docs as of 30 Sep 2026

    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. 1.ResultMessage with result, usage, cost and session ID
    2. 2.SystemMessage with subtype init
    3. 3.UserMessage with the tool result
    4. 4.AssistantMessage carrying a tool call
    5. 5.Final text-only AssistantMessage

    Sources12

    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.

    A debugging agent with auto-approved tools, project settings and a turn limittypescript
    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.

    ResultMessage subtypes and what each one tells you
    Result subtypeWhat happenedresult field available?
    successClaude finished the task normallyYes
    error_max_turnsHit the maxTurns limit before finishingNo
    error_max_budget_usdHit the maxBudgetUsd limit before finishingNo
    error_during_executionAn error interrupted the loop (for example, a cancelled request)No
    error_max_structured_output_retriesNo valid structured output was produced within the configured retry limitNo

    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?

    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?

    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.

    Registering a callback: event name as key, a list of matchers as valuepython
    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?

    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);
    }

    Sources32

    4.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.

    A read-only code reviewer declared as a subagenttypescript
    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?

    Checkpoint 7 of 7· Check yourself

    In the code-reviewer example, what decides when the orchestrator hands work to the code-reviewer subagent?

    Sources42

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 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. 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. 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. 1.
      “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. 2.
      “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. 3.
      “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. 4.
      “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

    Ready to test yourself?

    Practise the 20 questions on this subdomain.

    Spotted a mistake, or was something unclear? Tell us.