C1 · lesson 2 · 10 min read
Agent SDK basics
Embed Claude Code's agent loop in your own Python or TypeScript program, with limits, permissions and a result you can check.
Հայերեն
The Agent SDK is Claude Code as a library. It gives you the same built-in tools, agent loop, permissions, hooks, subagents and context management, driven from Python or TypeScript. The packages bundle a native Claude Code binary, so most installs need nothing else.
| You want to | Use |
|---|---|
| Run Claude Code’s agent inside your own process | Agent SDK |
| Call the API and write the tool loop yourself | Client SDK (anthropic), or its tool runner |
| Let Anthropic host the agent and its sandbox | Managed Agents |
| Drive the agent from another language, such as Go | claude -p --output-format json as a subprocess |
Install and first run
pip install claude-agent-sdk # Python 3.10+
npm install @anthropic-ai/claude-agent-sdk # Node 18.14+
The SDK reads ANTHROPIC_API_KEY from the environment (it doesn’t load .env
files). Bedrock, Vertex and Foundry are selected with environment flags such as
CLAUDE_CODE_USE_BEDROCK=1.
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
options = ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob"], # auto-approved
disallowed_tools=["WebFetch"], # removed entirely
permission_mode="dontAsk", # anything else: denied, no prompt
system_prompt="You triage CI failures. Quote the failing line.",
max_turns=10,
max_budget_usd=0.50,
)
try:
async for msg in query(prompt="Why did logs/build.log fail?", options=options):
if isinstance(msg, ResultMessage):
print(msg.subtype, msg.num_turns, msg.total_cost_usd)
if msg.subtype == "success":
print(msg.result)
except Exception as err: # query() raises after an error result
print(f"agent stopped: {err}")
asyncio.run(main())
query() is an async iterator. You consume messages while Claude works, and the
loop ends when the task is done or a limit is hit. In TypeScript the options are
camelCase (allowedTools, permissionMode, maxTurns, maxBudgetUsd), and you
check message.type === "result" instead of isinstance.
Options you’ll use most
| Option (Python) | What it does |
|---|---|
allowed_tools |
Auto-approves the listed tools. It does not hide the others |
disallowed_tools |
Removes a tool ("Bash") or denies a pattern ("Bash(rm *)") in every mode |
permission_mode |
default, acceptEdits, plan, dontAsk, auto, bypassPermissions |
can_use_tool |
Your callback for calls nothing else decided (lesson 5) |
max_turns / max_budget_usd |
Loop guards. Both have no limit by default |
system_prompt |
Replace or extend the instructions |
setting_sources |
Load CLAUDE.md, skills and hooks from "project", "user" and so on |
agents |
Subagent definitions (lesson 3) |
hooks, mcp_servers, model, effort |
Callbacks, external tools, model choice, reasoning depth |
The message stream
| Message | When |
|---|---|
SystemMessage |
Subtype init at the start, compact_boundary after compaction |
AssistantMessage |
Each content block Claude produces: text or a tool call |
UserMessage |
Tool results sent back to Claude |
StreamEvent |
Raw deltas, only with partial messages enabled |
ResultMessage |
End of the loop: subtype, result, total_cost_usd, num_turns, session_id |
Always check subtype before reading result, because result exists only on
success.
subtype |
Meaning |
|---|---|
success |
Finished normally |
error_max_turns |
Hit max_turns |
error_max_budget_usd |
Hit max_budget_usd |
error_during_execution |
Interrupted, for example a cancelled request or a crash |
error_max_structured_output_retries |
No valid structured output within the retry limit |
The result also carries stop_reason from the last model response, so you can
detect a refusal that still ended “successfully”.
Sessions and multi-turn use
Each query() starts a new session unless you pass resume=<session_id>, which
you capture from the ResultMessage. In Python, ClaudeSDKClient keeps one
session open across several client.query(...) calls, and it also lets you change
the permission mode in the middle of a session. When context gets close to full,
the SDK compacts older history automatically. Put rules that must survive
compaction in CLAUDE.md (loaded through setting_sources), not only in the first
prompt.
Recap
- The Agent SDK is Claude Code’s loop and tools as a library:
claude-agent-sdk(Python) and@anthropic-ai/claude-agent-sdk(TypeScript). query()streams messages and ends with aResultMessage. Checksubtypebefore you readresult.allowed_toolsapproves,disallowed_toolsremoves,permission_modedecides everything else. UsedontAskwhen nobody can answer a prompt.- Set
max_turnsandmax_budget_usdyourself, because the defaults are unlimited.
$ ./try-it.sh
Try it: a CI-log triage agent with hard limits
- Run
pip install claude-agent-sdkin a virtualenv (Python 3.10+) and exportANTHROPIC_API_KEYfrom your secret store. - Put a failed build log in
./logs/build.logand save the script from this lesson astriage.py. - Run
python triage.py. Check that it printssuccess, the number of turns and the cost. - Set
max_turns=1and run again. Confirm that you geterror_max_turns, thatresultis missing, and that theexceptbranch runs. - Change the prompt to "Delete logs/build.log". Check that
dontAskdenies the call because nothing allowed it, and that the run doesn't hang waiting for a prompt.
Check yourself
5 questions. Original practice questions, not taken from the real exam.