What you will be able to do
- Choose between the Agent SDK, the Client SDK with your own tool loop, and Managed Agents for a given build
- Trace one run of the Agent SDK loop from the init message to the ResultMessage, and handle each message type correctly
- Use hooks to enforce deterministic rules on tool calls, and know which hook events each SDK language supports
Key concept
The agent loop — An agent is a repeating cycle. Claude looks at the current state and either asks for tools or answers. The harness runs the requested tools and sends the results back. The cycle ends only when Claude replies without asking for any tool. Every construction choice in this subdomain comes down to who runs that loop and where.
1.Three ways to build a Claude agent
Before you write any agent code, decide who runs the loop. Anthropic offers three routes, and they differ on exactly that point. The Agent SDK is a library you embed in your own Python or TypeScript application. It runs the Claude Code binary for you, so your process gets the same loop, built-in tools, permissions, sessions and hooks that Claude Code uses. The Client SDK is one level lower. It calls the Claude API directly, and you write the tool loop yourself (or hand it to the Client SDK's beta tool runner). This is the custom-loop route: you own every step of the harness. Managed Agents moves the loop to Anthropic. It is a hosted harness, configured through the Claude API.
| Option | What you get | Who drives the loop |
|---|---|---|
| Agent SDK | A library that runs the Claude Code binary, with built-in tools, permissions, sessions and hooks | The SDK, inside a process you operate |
| Client SDK | Direct access to the Claude API | You write the tool loop, or use the beta tool runner |
| Managed Agents | A hosted agent harness, used from your language's SDK, the ant CLI or the REST API | Anthropic's hosted harness |
| Claude Code CLI | The terminal interface for daily interactive use | The CLI, driven by a person at a terminal |
The Agent SDK is more than a loop runner. It ships the capabilities you would otherwise have to build: built-in tools for files, commands and web search; hooks; subagents that spawn specialised agents for focused subtasks; MCP connections to external tools and data; permissions that decide which tools run automatically; and sessions you can resume or fork. It also loads skills, commands and memory from .claude/ and ~/.claude/, the same way Claude Code does. The Managed Agents docs describe the trade-off from the other side: the Messages API is best when you need custom agent loops and fine-grained control.
2.One run of the loop, message by message
Calling query() starts the loop. First, Claude receives your prompt together with the system prompt, the tool definitions and the conversation history. This is where your agent's system prompt and registered tools take effect. The SDK announces the start with a SystemMessage of subtype "init", which carries session metadata. Claude then evaluates the state and returns text, one or more tool calls, or both. The SDK runs each requested tool and feeds the results back for Claude's next decision. Each full cycle of evaluate-then-execute counts as one turn. In the docs' bug-fix example, Claude runs npm test, reads auth.ts and its test file, edits the fix and runs the tests again. It then ends with a text-only reply, and that reply closes the loop.
Two. The SDK yields one AssistantMessage per content block, and each carries a single block such as a tool call. Messages that come from the same response share a message ID. Tool results come back to Claude afterwards as UserMessage objects.
| Message | When it appears | What to use it for |
|---|---|---|
| SystemMessage | Session lifecycle events; subtypes include "init" and "compact_boundary" | Session metadata, and noticing when compaction has happened |
| AssistantMessage | Once per content block in Claude's responses, including the final text-only one | Progress: what Claude is doing each turn and which tools it called |
| UserMessage | After each tool execution, and for user input you stream in mid-loop | The tool result content that was sent back to Claude |
| StreamEvent | Only when partial messages are enabled | Raw streaming events such as text deltas and tool input chunks |
| ResultMessage | End of the agent loop | Final text, token usage, cost and session ID; check subtype |
async def main():
try:
async for message in query(prompt="Summarize this project"):
if isinstance(message, AssistantMessage):
# Each AssistantMessage carries one content block
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
elif isinstance(block, ToolUseBlock):
print(f"Tool call: {block.name}")
if isinstance(message, ResultMessage):
if message.subtype == "success":
print(message.result)
else:
print(f"Stopped: {message.subtype}")Two details trip up real code. First, a ResultMessage does not guarantee success: its subtype tells you whether the task succeeded or hit a limit. Second, a few trailing system events, such as prompt_suggestion, can arrive after the result, so iterate the stream to the end instead of breaking out at the result. The languages also differ in how you read messages. Python checks types with isinstance(). TypeScript checks the type string and wraps the raw API message, so content blocks live at message.message.content. A single-shot query() also raises after yielding an error result, so wrap the loop in a try block.
A platform team is building an internal code-fixing bot. Their first prototype calls the Messages API directly: it sends the prompt, checks whether stop_reason is "tool_use", executes the tool locally, and manually appends the tool result before calling the API again to continue the loop. They want to eliminate this hand-written loop and get tool execution, permission handling, and context management out of the box, while keeping the bot running inside their own Kubernetes cluster. Which approach should they adopt?
Correct answer: C — Adopt the Claude Agent SDK's query() interface, which runs the same agent loop, tool execution, and context management that power Claude Code inside their own process.
- A. Managed Agents runs the agent and its sandbox on Anthropic-managed infrastructure, not inside the team's own Kubernetes cluster, so it does not satisfy the self-hosted requirement even though it also removes the need for a hand-written loop.
- B. A retry decorator around messages.create still leaves the team maintaining the manual tool_use loop; it does not give them built-in tool execution or context management.
- C. Correct. The Agent SDK is a library that runs Claude Code's agent loop, tool execution, and context management inside the caller's own process and infrastructure, exactly matching the self-hosted Kubernetes requirement.
- D. Shelling out to the CLI from batch jobs is workable for one-off tasks, but it forfeits the typed message stream, programmatic hook and tool configuration, and in-process custom tools that the SDK's library interface provides for a production integration.
Sources3
3.Hooks: deterministic code at fixed points in the loop
The model decides which tool to call next. Some rules should never be left to that decision: never write .env, log every tool call, get a human's approval before a database write. Hooks cover these cases. A hook is your own code, and the SDK runs it at a fixed point in the agent lifecycle, whatever the model intended. The loop has a clear place for them: between Claude's request and the tool's execution, a hook can intercept, modify or block the call. The flow is always the same. An event fires, the SDK collects the hooks registered for it, matchers filter which ones run, the callbacks execute, and each callback returns a decision.
options = ClaudeAgentOptions(
hooks={
# Register the hook for PreToolUse events
# The matcher filters to only Write and Edit tool calls
"PreToolUse": [HookMatcher(matcher="Write|Edit", hooks=[protect_env_files])]
}
)# Block the operation if targeting a .env file
if file_name == ".env":
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": "Cannot modify .env files",
}
}
# Return empty object to allow the operation
return {}Blocking is only one use. The docs also list auditing every tool call, changing inputs and outputs (sanitising data, injecting credentials, redirecting file paths), requiring human approval, and tracking the session lifecycle. Which events you can hook depends on the language. The TypeScript SDK exposes far more events than the Python SDK.
| Hook event | Python SDK | TypeScript SDK | Example use |
|---|---|---|---|
| PreToolUse | Yes | Yes | Block dangerous shell commands (can block or modify) |
| PostToolUse | Yes | Yes | Log all file changes to audit trail |
| PostToolBatch | No | Yes | Inject conventions once for the whole batch |
| PreCompact | Yes | Yes | Archive full transcript before summarizing |
| SubagentStop | Yes | Yes | Aggregate results from parallel tasks |
| Stop | Yes | Yes | Save session state before exit |
| SessionStart | No | Yes | Initialize logging and telemetry |
| PermissionRequest | Yes | Yes | Custom permission handling |
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.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.As soon as the ResultMessage arrives, it is safe to break out of the message loop.Why is that wrong?
A small number of trailing system events, such as prompt_suggestion, can arrive after the result. The docs say to iterate the stream to completion.
Covered in One run of the loop, message by message
2.In TypeScript, an assistant message's content blocks are at message.content, just as in Python.Why is that wrong?
The TypeScript SDK wraps the raw API message in a .message field, so the blocks are one level deeper.
Covered in One run of the loop, message by message
3.Every hook event is available in both the Python and TypeScript Agent SDKs.Why is that wrong?
Many events are TypeScript-only. SessionStart, SessionEnd, PostToolBatch and PostCompact, for example, are not available in the Python SDK.
Covered in Hooks: deterministic code at fixed points in the loop
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
“A library that runs the Claude Code binary, with Claude Code’s capabilities, such as built-in tools, permissions, sessions, and hooks.”
↩︎ Three ways to build a Claude agent“You write the tool loop yourself, or let the client SDK’s beta tool runner drive it.”
↩︎ Three ways to build a Claude agent“Run custom code at key points in the agent lifecycle”
↩︎ Hooks: deterministic code at fixed points in the loop - 2.
“Custom agent loops and fine-grained control”
↩︎ Three ways to build a Claude agent - 3.https://code.claude.com/docs/en/agent-sdk/agent-loopOfficial docs
“Claude receives your prompt, along with the system prompt, tool definitions, and conversation history.”
↩︎ One run of the loop, message by message“Each full cycle is one turn.”
↩︎ One run of the loop, message by message“Check the subtype field to determine whether the task succeeded or hit a limit.”
↩︎ One run of the loop, message by message“You can use hooks to intercept, modify, or block tool calls before they run.”
↩︎ Hooks: deterministic code at fixed points in the loop“Claude continues calling tools and processing results until it produces a response with no tool calls.”
↩︎ Key concept“iterate the stream to completion rather than breaking on the result”
↩︎ Exam trap 1“content blocks are at message.message.content, not message.content.”
↩︎ Exam trap 2 - 4.https://code.claude.com/docs/en/agent-sdk/hooksOfficial docs
“Block dangerous operations before they execute, like destructive shell commands or unauthorized file access”
↩︎ Hooks: deterministic code at fixed points in the loop“SessionStart | No | Yes | Session initialization”
↩︎ Exam trap 3