& the Agent SDK
Stop hand-rolling the agent loop. The claude-agent-sdk package gives you the same engine Claude Code itself runs — with hooks, sessions, subagents, and MCP wired in. Seven concepts, from "why the SDK exists" to "when to leave it."
The 7 concepts of the Agent SDK
Tap any concept to jump to its 5-card cluster.
- 1Why the SDK ExistsThe loop you no longer write
- 2Build the UCC AgentYour first
claude-agent-sdkagent - 3Hooks
HookMatcherfor Pre/Post Tool Use - 4SessionsMulti-turn flows you control
- 5Declarative Subagents
.claude/agents/<name>.md - 6The Production StackSix layers, one shipped agent
- 7When to Leave the SDKThe cases for the raw loop
The loop stopped teaching you
By M15B you'd built the agent loop by hand — while True, read stop_reason, dispatch the tool, append the result, recurse. The first time, that loop is the lesson. The third time, you're just typing the same sixty lines again before getting to the actual work: tools, prompts, guardrails.
The claude-agent-sdk package is the loop you no longer write. It manages tool dispatch, message bookkeeping, retries, MCP routing, hooks, and subagents — the same engine Claude Code itself uses, exposed for your agents. You keep designing the things only you can design; the SDK handles the boilerplate that stopped teaching you anything after the second build.
From hand saw to track saw
BEFORE: Picture a carpenter on house number one. Every cut is measured by tape, marked by pencil, and made by hand saw. Each joint takes ten minutes. The hand-built feel is the lesson — you learn what a good cut is.
PAIN: By house number ten, the saw isn't teaching you anything new. Your back hurts, the build is still slow, and you're not designing better houses — you're just typing more of the same cut. The marginal lesson per cut has dropped to zero.
MAPPING: The SDK is the track saw with a digital fence. The wood comes out the same — same tools, same tool-use loop semantics, same model behavior — but you finish in a fraction of the time. The judgment about where joints go (system prompt, tool design, guardrails) is still yours; only the manual measuring is gone.
What the SDK actually provides
- An async
query()generatorYou writeasync for msg in query(...). The SDK readsstop_reason, dispatches each tool call, appends the result, and recurses — until the model returns its final answer. - A
@tooldecorator + in-process MCP serverTools are now async functions registered throughcreate_sdk_mcp_server. Schemas come from your type hints. No more hand-written JSON Schema dicts. HookMatcherfor lifecycle eventsPreToolUseandPostToolUseevents fire structured payloads at well-defined moments — guardrails stop being scatteredif-statements and become declared, swappable functions.- Declarative subagentsDrop a markdown file into
.claude/agents/<name>.mdwith frontmatter. The coordinator can invoke that subagent by name with an isolated context window. M14's hand-built coordinator pattern collapses to a directory of files.
Raw loop vs SDK
# Question: which entry point should this agent use? IF agent has 1–10 tools AND standard tool-use loop semantics: USE claude-agent-sdk # default for >90% of agents ELIF need hooks, subagents, MCP, or sessions: USE claude-agent-sdk # built in — no rewrite later ELIF need custom mid-loop streaming OR racing parallel tools OR vendor APM around every API call: USE raw messages.create() loop # SDK abstracts these away ELSE: DEFAULT TO claude-agent-sdk # raw loop is the exception, not the start
The cert exam treats the SDK as the default. The raw loop is justified only when you've identified a specific need the SDK can't express — not because it feels lower-level.
Misconceptions
messages.create() in a function called query() and call it the SDK."claude-agent-sdk manages an MCP-shaped tool dispatch, fires hooks at lifecycle points, supports forking, and discovers subagents from .claude/agents/ — none of which a wrapper around messages.create does. Per project Tier Policy, you never simulate the SDK and call it the SDK.max_turns. The SDK runs the dispatch you used to type by hand — it does not make decisions for you.The SDK is the default entry point. The raw messages.create loop you wrote in M12 and M15B was the lesson. The SDK is the tool you ship.
You stop typing the loop and start designing the things only you can design: tools, prompts, hooks, subagents. Same engine Claude Code uses — in your code.
Three files, one shipped agent
The whole first agent is three files: mock_data.py (the rows you'd query), tools.py (three @tool functions wrapped in create_sdk_mcp_server), and run_agent.py (a single async for msg in query(...) driving everything). The same UCC research agent M15B took 300 lines to build by hand fits comfortably in 80.
What changes is everything around the business logic. The tool-use loop, message bookkeeping, MCP routing, schema generation — all gone. What stays is the part that's actually yours: which tools to expose, what each one does, how the system prompt frames the task.
The IKEA flat-pack of agents
BEFORE: In M15B you milled every shelf, joined every joint, and routed every edge by hand. Six dozen lines of dispatch code surrounding fifteen lines of actual agent behavior.
PAIN: Every new agent meant re-milling the same shelves. The carpentry wasn't the value; the design was. But you couldn't get to the design until the carpentry was done.
MAPPING: The SDK ships the shelves pre-milled, pre-joined, pre-edged. You arrive with the design (tools + prompt + guardrails) and snap it together. Same finished bookshelf as the M15B hand-build — same UCC research agent, same answers — in a fraction of the work, with the boring carpentry already solved.
The three-file build
- Define the data layerA
mock_data.pymodule with eleven UCC filings (nine ACME variants + two noise records). Realistic enough to exercise partial-name search; small enough to run on a plane. - Decorate three tools
search_filings(partial debtor-name match),get_filing_details(lookup by filing number), andcalculate_risk_score(aggregate active filings into a HIGH/MEDIUM/LOW score). Each is an async function with a@tooldecorator. - Register them as one MCP server
create_sdk_mcp_server(name="ucc_tools", tools=[...])bundles the three functions into an in-process MCP server. The SDK routes tool calls through it — same wire format as a real MCP server, no separate process. - Configure and runBuild a
ClaudeAgentOptionswith the system prompt, the MCP server, the allowed tools, and amax_turnsguard. Drive it withasync for msg in query(prompt=..., options=OPTIONS). Print eachAssistantMessageas it arrives. - Verify behaviorAsk "What is the lien exposure for Acme Corporation?" The agent searches with name variations, fetches details on the strongest hits, calculates a score, and returns a narrative report citing specific filings — same answer M15B produced, with no loop code in your project.
Pseudocode
Three layers: a tool, an MCP server, a query call. The loop is invisible.
# LAYER 1 — declare a tool DEFINE TOOL search_filings: description: "Search UCC filings by debtor name" schema: { debtor_name, state? } body(args): RETURN rows WHERE debtor_name CONTAINS args.debtor_name # LAYER 2 — bundle tools into an in-process MCP server ucc_server = CREATE MCP SERVER( name="ucc_tools", tools=[search_filings, get_filing_details, calculate_risk_score]) # LAYER 3 — configure + drive with the SDK options = CONFIGURE( system_prompt="You are a UCC research agent...", mcp_servers={ ucc: ucc_server }, allowed_tools=["mcp__ucc__*"], max_turns=8) FOR EACH msg IN sdk.query(prompt=user_question, options=options): IF msg IS AssistantMessage: PRINT msg.text # <-- the while-True loop, stop_reason check, and dispatch are gone
Misconceptions
from anthropic import Agent — that's the SDK, right?"claude_agent_sdk (Python) or @anthropic-ai/claude-agent-sdk (Node), and it exports query, tool, create_sdk_mcp_server, ClaudeAgentOptions, HookMatcher. If you see @agent.tool or @agent.hook decorators, that is fictional API.mcp__<server-name>__<tool-name>. So search_filings registered on the ucc_tools server appears in allowed_tools as mcp__ucc__search_filings. Forget the prefix and the SDK silently refuses to expose the tool.Three layers, no loop. Define tools with @tool, register them with create_sdk_mcp_server, configure ClaudeAgentOptions, drive with query().
The first agent fits in 80 lines including the data layer. Everything you remember from the M15B hand-build is still there — just hidden behind one async generator.
Hooks are middleware for the agent loop
A hook is a function the SDK calls at a well-defined moment in the tool-use lifecycle. The two main events are PreToolUse (fires before a tool runs — can log, modify input, or feed into a permission decision) and PostToolUse (fires after the tool returns, before its result reaches the message stream — can redact, transform, or annotate).
You register hooks via HookMatcher: pair a matcher (a tool name or "*" for all) with a list of hook functions. They become observability and policy declared on the outside of the loop — not if-statements scattered through dispatch code.
The building's sprinkler system
BEFORE: The electrician wires every outlet in the building. The appliances plug into those outlets and just work — the wiring is invisible to them.
PAIN: Now you need fire safety. If you ask the appliance maker to add a thermal cutoff to every coffee maker, fridge, and lamp, you'll never finish — and a new appliance ships unprotected. The cross-cutting concern doesn't belong inside each appliance.
MAPPING: Hooks are the sprinkler system layered on top. They fire on lifecycle signals (a tool is about to run, a tool just returned), based on a name pattern (matcher="*" = every room; matcher="search_filings" = only the kitchen). The can_use_tool callback is the sprinkler valve — it can hard-deny the tool call before it ever fires.
The lifecycle
- Claude emits a tool_useThe model decides to call a tool. The SDK reads the
tool_useblock from the response. HookMatchermatches by nameThe SDK walks your registered matchers. Anything wherematcherequals the tool name (or"*") gets queued to fire.PreToolUsehooks fireEach receivesinput_data(a dict containingtool_nameandtool_input),tool_use_id, andcontext. Returning{}means "continue unchanged"; returning a payload can block.- The tool runsYour
@toolfunction executes against the (possibly modified) input. Its result is captured. PostToolUsehooks fireEach receives the tool's response ininput_data["tool_response"]. Whatever you return astool_responsein your output dict is what the agent actually sees — so this is where PII redaction lives.
Cert tip: a separate can_use_tool callback runs alongside hooks and returns PermissionResultAllow() or PermissionResultDeny(message=...). The denial reason flows back to Claude so it can adapt.
Pseudocode
# PreToolUse hook — observes / modifies inputs DEFINE HOOK log_call(input_data, tool_use_id, ctx): PRINT "PRE " + input_data.tool_name RETURN {} # {} = continue unchanged # PostToolUse hook — transforms outputs DEFINE HOOK redact_pii(input_data, tool_use_id, ctx): out = input_data.tool_response out = REPLACE(SSN_pattern, "[SSN]", out) out = REPLACE(phone_pattern, "[PHONE]", out) RETURN { tool_response: out } # Permission gate — binary allow / deny DEFINE GATE check(tool_name, tool_input, ctx): IF tool_name == "search_filings" AND length(tool_input.debtor_name) < 3: RETURN Deny(message="Query too broad") RETURN Allow() # Wire all three into ClaudeAgentOptions options = CONFIGURE( hooks={ PreToolUse: [HookMatcher(matcher="*", hooks=[log_call])], PostToolUse: [HookMatcher(matcher="*", hooks=[redact_pii])] }, can_use_tool=check)
Misconceptions
can_use_tool are the same thing."PreToolUse hooks run and can side-effect (log, modify input); their return value is mostly informational. can_use_tool is a binary gate — Allow or Deny — and the denial reason is fed back to Claude for adaptation. Use hooks for instrumentation; use can_use_tool for authorization.tool_response in your output dict is what flows back to Claude — so this is where PII gets redacted before it ever enters the message stream, the trace, or any downstream consumer. Logging is a side effect, not the point.HookMatcher declares lifecycle interception on the outside of the loop. PreToolUse observes/modifies inputs. PostToolUse transforms outputs. can_use_tool hard-gates with Allow or Deny.
Cross-cutting concerns — logging, redaction, policy — stop being if-statements scattered through dispatch and become declared, swappable functions.
Multi-turn flows you compose
The SDK does not ship a Session class. The "session" is the message stream returned by query(). Multi-turn flows are something you build by managing the prompt — either by maintaining a transcript yourself (Pattern A) or by passing the SDK's resume token to continue a prior session id (Pattern B).
Pattern A is simpler to teach and easier to fork — deep-copy the transcript list and you have a what-if branch the parent never sees. Pattern B is more efficient for long sessions because it doesn't re-send the full history. The cert exam tests both, and especially tests that you know the SDK's session is the message stream, not a database row.
Git branches for conversations
BEFORE: A conversation is linear — each turn appends to the last. To "go back and try a different question," you'd have to delete history and re-walk it.
PAIN: Real workflows aren't linear. A loan officer wants to ask "what if the debtor had only NY filings?" without losing the actual conversation about all states. A QA engineer wants to replay yesterday's session against a new model. Linear history blocks both.
MAPPING: A SessionManager with send + fork is git for conversations. send is a commit on the current branch. fork is git checkout -b — a deep-copied transcript that diverges from the parent. The parent keeps moving; the fork explores alternatives. No history is lost.
The two patterns
- Pattern A: transcript compositionKeep a running list of USER/ASSISTANT lines in your
SessionManager. On each new turn, build a single prompt string from the transcript plus the new user input, and pass that toquery(). The agent doesn't know about prior turns — it sees one big prompt. - Pattern B: resume tokensThe SDK emits a
session_idon the final result message of each query. Pass it back asClaudeAgentOptions(resume=session_id)on the next call — the SDK reconstructs context server-side and you only send the new user turn. - Forking with Pattern A is one line
list(self.transcript)deep-copies the list. Mutate the copy in the fork; the parent stays untouched. Branches and parents diverge without contaminating each other. - Pick by use casePattern A for forking and replay. Pattern B for long sessions where re-sending the whole history wastes tokens. Both are valid; the cert exam expects you to know the difference.
Pseudocode
# Pattern A — transcript composition + fork CLASS SessionManager: fields: transcript = [], options ASYNC METHOD send(user_text): transcript.append("USER: " + user_text) prompt = JOIN(transcript, sep="\n") reply = "" FOR EACH msg IN sdk.query(prompt, options): IF msg IS AssistantMessage: reply += msg.text transcript.append("ASSISTANT: " + reply) RETURN reply METHOD fork(): # branch the conversation new = SessionManager(options) new.transcript = DEEP COPY(transcript) RETURN new # parent untouched # Pattern B — SDK resume tokens result = sdk.query(prompt="first turn", options=opts) sid = result.session_id # later, even in a different process: sdk.query(prompt="follow-up", options=CONFIGURE(resume=sid))
Misconceptions
Session class with .send() and .fork()."query(). SessionManager with send/fork is a thin pattern you write on top — about 30 lines. The cert exam asks this exact distinction.list(self.transcript) creates an independent list — mutations in the fork never reach the parent. A shallow assignment (new.transcript = self.transcript) would silently share the same list, and your branches would corrupt each other.Sessions are composed, not provided. Pattern A maintains a transcript and passes it back as the next prompt — trivial to fork. Pattern B uses an SDK resume token — cheaper for long histories.
Both are valid. Pick A when you need branching or replay; pick B when token-cost dominates.
Specialists in markdown files
A subagent is a markdown file in .claude/agents/<name>.md. Frontmatter declares its name, description, allowed tools, and (optionally) a model. The body is its system prompt. The coordinator can invoke it by name — the SDK starts a fresh conversation with the subagent's prompt and only the input the coordinator passes in.
Critically, the subagent gets a fresh, isolated context window. It does not see the coordinator's history. M14's hand-built hub-and-spoke coordinator pattern collapses to a directory of files: declare each specialist once, in markdown, and the SDK takes care of routing.
The general contractor
BEFORE: A general contractor builds a house with one crew — everyone does framing, plumbing, electrical, and finishing. Every worker carries every blueprint in their head.
PAIN: Specialization is impossible — nobody can be deeply expert in everything. The plumber doesn't need the architectural plans; they need the bathroom diagram and a fixture list. Loading the whole house into every worker's head is wasteful and error-prone.
MAPPING: The coordinator agent is the general contractor — it knows the user's broader goal, the conversation history, the messy context. Subagents are the specialists. The coordinator hands the risk-analyst a brief ("here's an entity, here are some filings, return a one-paragraph profile") and gets back a structured result. The specialist's reasoning stays in its head, not the coordinator's.
Declaration to invocation
- Drop a file in
.claude/agents/Name it after the subagent:risk-analyst.md. Frontmatter declaresname,description,tools(a subset of the coordinator's tools), and an optionalmodel(often a cheaper model like Haiku for narrow tasks). - Body becomes the system promptEverything below the frontmatter is what the subagent reads as its system prompt — usually a tight, role-specific brief: "You are the Risk Analyst. Your job is narrow. Do X, do not do Y."
- Coordinator references it by nameThe coordinator's system prompt instructs it to delegate — "When you have gathered the filings, hand off to the
risk-analystsubagent for scoring." The SDK exposes the subagent as a callable specialist. - SDK spawns it with isolated contextThe subagent starts with a fresh context window. It sees its own system prompt and the input the coordinator passed in — nothing else. No prior turns, no other tool results, no user history.
- Result returns to coordinatorThe subagent's final answer is appended to the coordinator's conversation as a tool-result-shaped payload. The coordinator stitches it into its narrative and continues.
Pseudocode
# FILE: .claude/agents/risk-analyst.md --- name: risk-analyst description: Score lien exposure for an entity, return profile. tools: [calculate_risk_score] model: haiku --- SYSTEM PROMPT: You are the Risk Analyst. Your job is narrow. 1. Receive entity name + filings from coordinator. 2. Call calculate_risk_score(entity_name). 3. Return one paragraph: score, top 2 factors, recommendation. Do NOT search or fetch additional data. # SDK behavior at runtime — WHEN coordinator delegates to "risk-analyst": spawn fresh subagent process: system_prompt = body of risk-analyst.md allowed_tools = [calculate_risk_score] # subset only context_window = empty + coordinator's brief # NO prior history RUN subagent UNTIL final answer RETURN answer to coordinator as tool result
Misconceptions
@subagent.".claude/agents/<name>.md with frontmatter. The SDK auto-discovers them when query() runs in a directory containing .claude/. Run from the wrong directory and the subagent silently disappears.Subagents are markdown, not Python. Drop a file in .claude/agents/, declare a tight role with a tool subset, and the SDK exposes it to the coordinator.
Each invocation gets a fresh, isolated context. M14's hand-built hub-and-spoke pattern is now a directory of files — the cert exam's hub-and-spoke favorite, made declarative.
Six layers, one shipped agent
A production agent — the kind that ships behind a FastAPI endpoint — is just the previous five concepts stacked. Each layer adds one capability without disturbing the layers below: tools at the bottom, then the in-process MCP registry, then options, then hooks + permission gate, then sessions + subagents, then an HTTP wrapper at the top.
Crucially, the same engine runs at every layer. You don't refactor when traffic shows up — the lab agent and the production agent share the same SDK call. Layer 6 just wraps Layers 1–5 in an async POST /query endpoint and a POST /chat for multi-turn.
The TCP/IP layer cake
BEFORE: Networking pre-OSI was a tangle — if you wanted reliable delivery over Ethernet, you wrote everything yourself, all the way down to flipping bits on the wire.
PAIN: Every new application reinvented retransmission, ordering, addressing, and physical encoding. Nothing was reusable, every change rippled through every layer.
MAPPING: The production agent stack is the same insight applied to LLM agents. Layer 1 is the wire (your tools). Layer 2 is the bus (MCP). Layer 3 is the transport (options + the agent loop). Layer 4 is policy (hooks + permission). Layer 5 is sessions + delegation. Layer 6 is presentation (HTTP). Each layer minds its own concern; you swap any one without breaking the others.
The six layers, bottom-up
- Layer 1 —
@toolfunctionsPure async business logic. Each one does exactly one thing and returns an MCP-shaped response. No SDK plumbing in here — just the work. - Layer 2 —
create_sdk_mcp_serverBundles the tools into an in-process MCP server. The SDK routes calls through it using the same wire format a remote MCP server uses. - Layer 3 —
ClaudeAgentOptionsSystem prompt, model, allowed tools, max turns. The contract between you and the agent loop. - Layer 4 — hooks +
can_use_toolLifecycle interception (log, redact) plus the binary permission gate. Cross-cutting policy declared on the outside of the loop. - Layer 5 —
SessionManager+.claude/agents/Multi-turn flows + delegated specialists. The coordinator gets durable memory; the subagents get fresh focus. - Layer 6 — FastAPI wrapperAsync
POST /queryfor one-shot calls andPOST /chatfor multi-turn. Same SDK call inside both endpoints — the engine doesn't change.
Pseudocode
# LAYER 1 — tools DEFINE TOOL search_filings ... DEFINE TOOL ml_risk_predict # pickled RandomForest as a tool # LAYER 2 — MCP registry ucc_server = CREATE MCP SERVER(tools=[...]) # LAYER 3 — agent options options = CONFIGURE( system_prompt="You are the UCC research coordinator...", mcp_servers={ ucc: ucc_server }, allowed_tools=["mcp__ucc__*"], max_turns=8, # LAYER 4 — hooks + permission gate hooks={ PreToolUse: [HookMatcher("*", [log_call])], PostToolUse: [HookMatcher("*", [redact_pii])] }, can_use_tool=permission_gate) # LAYER 5 — sessions + subagents declared in .claude/agents/ session = SessionManager(options) # LAYER 6 — HTTP wrapper DEFINE ENDPOINT POST /chat(user_text): reply = AWAIT session.send(user_text) RETURN { reply }
Misconceptions
query() call) and keeps Layer 5 instances alive across requests instead of creating one per call. No engine swap, no rewrite.@tool at Layer 1 and let Claude call it. Deterministic ML where deterministic ML belongs (the score), Claude where Claude belongs (the reasoning, the narrative, the multi-turn UX).Six layers, one engine. Tools, MCP, options, hooks, sessions/subagents, HTTP. Each layer adds one capability without touching the others.
The production agent is the lab agent with a server wrapper. No "production rewrite" milestone — just stack one more layer on top.
The SDK is the default — until it isn't
For the >90% of agent code that does standard tool-using work, the SDK is the right entry point — including everything in this course's Tier 3 modules and capstones 1–5 / 7. But there are real cases where the abstraction gets in your way and you want the raw messages.create() loop back.
The decision is mechanical: you leave the SDK when you've identified a specific behavior the SDK abstracts away that you actually need control of — not because raw feels lower-level. The cert exam tests the rubric: name the scenarios where raw beats SDK, and recognize when those scenarios apply.
Manual transmission vs automatic
BEFORE: An automatic transmission picks the right gear for 99% of driving — commutes, highway, parking. You stop thinking about gears at all and focus on where you're going.
PAIN: Some specific scenarios — rally racing, towing a heavy trailer up a mountain pass, engine braking on a long descent — need finer control than the automatic exposes. Forcing those through an automatic is slower, hotter, and less safe than dropping to a manual.
MAPPING: The SDK is the automatic. It picks the right "gear" (loop dispatch, retries, message bookkeeping) for almost everything. The raw messages.create() loop is the manual — you reach for it when you need custom mid-loop streaming, racing parallel tools, or vendor APM around every API call. Right tool for the right job; the manual isn't "more advanced," it's "more specialized."
The four legitimate exit cases
- Custom mid-loop streamingYou need to emit tokens to an SSE stream while parallel tools run in the background. The SDK serializes; you need raw control over the message generator to interleave.
- Non-standard parallel-tool aggregationYou want to race two tools and take whichever returns first — or fan out to ten tools and gate on a quorum. The SDK runs tools sequentially within a turn; the raw loop lets you write custom logic.
- Vendor APM that wraps every API callYour observability stack expects a wrapper around every
messages.createcall site. Hooks see tool calls, not API calls — so this instrumentation can't be expressed as a hook. - You're learningThe first one or two times you build an agent, the raw loop is the lesson. M12 and M15B exist for this reason. After build three, the lesson stops; default to the SDK.
If your reason for leaving the SDK isn't on this list, you probably shouldn't.
The exit rubric
# Question: should this agent use raw messages.create()? IF need custom mid-loop streaming OR racing/quorum parallel-tool aggregation OR vendor APM around every API call OR currently learning the loop semantics: USE raw messages.create() loop ELIF need any of: hooks, can_use_tool, subagents, MCP, sessions, declarative permission modes: USE claude-agent-sdk # building these by hand re-implements the SDK ELIF standard tool-using agent (1–10 tools): USE claude-agent-sdk # default for >90% of agent code ELSE: DEFAULT TO claude-agent-sdk
Cert exam: any scenario that mentions "hook," "subagent," "permission gate," "MCP," or "resume" is an SDK answer. Any scenario that mentions "race tools" or "stream tokens mid-loop" is a raw-loop answer.
Misconceptions
@anthropic-ai/claude-agent-sdk. Same surface area: query, tool, createSdkMcpServer, HookMatcher. Language is not a reason to pick the raw loop.Default to the SDK. Leave only for a named reason. Custom mid-loop streaming, parallel-tool racing, or vendor APM around every API call — those are legitimate exits.
Everything else — including the agents you'll build for the cert exam — uses claude-agent-sdk. The raw loop is the exception, not the starting point.
Tap a card to reveal the answer
query(), tool, HookMatcher, and ClaudeAgentOptions?claude-agent-sdk (Python) / @anthropic-ai/claude-agent-sdk (Node). The lower-level anthropic package only gives you the Messages API. There is no anthropic.Agent import — if you see one, it's fictional API.PreToolUse hook receives input_data. Which two keys can you reliably read from it?tool_name and tool_input. PreToolUse sees what's about to happen — the tool's name and the input the model wants to pass. tool_response is a PostToolUse key, since the response only exists after the tool runs.search_filings call where debtor_name is shorter than 3 characters. Which mechanism fits best?can_use_tool callback returning PermissionResultDeny(message=...). The denial reason flows back to Claude so it can adapt — reformulate the search or apologize gracefully. PreToolUse hooks observe and modify; can_use_tool is the binary authorization gate the cert exam tests.claude-agent-sdk project?.claude/agents/<name>.md. Frontmatter declares name, description, tools, and (optionally) model; the body is the system prompt. The SDK auto-discovers them when query() runs in the same working directory — no Python decorators, no ClaudeAgentOptions(subagents=[...]).query(). The SDK does not ship a Session class. Multi-turn flows are built either by composing the prompt yourself (transcript pattern) or by passing a SDK resume token to continue a prior session id.messages.create() loop?HookMatcher). Single-tool agents don't justify leaving. TypeScript isn't a reason — the SDK ships for both languages.Open the full module on desktop
The desktop version is the full claude-agent-sdk build — tools, hooks, sessions, subagents, and the production stack — in Python and TypeScript, with the cert exam tips you need to pass Track 9. You'll wire HookMatcher for PreToolUse/PostToolUse, fork sessions with SessionManager, declare a risk-analyst subagent, and stack everything behind a FastAPI endpoint.
Module 26 of 30 · Track 9: Cert Prep