FranΓ§ais

Claude Agent SDK

Building agentic systems in production

Applied AI β€” Advanced level Β· Session 3
Yann Isola Β· Preparation Claude Certified Architect

Slide 2 β€” Session Objectives

At the end of the 2 hours, you will know:

  1. Describe the SDK architecture: Agent, Runner, tools, handoffs, guardrails, hooks, context
  2. Implement typed tools with @tool
  3. Design handoffs and justify the choice vs. tool call
  4. Install entry/exit guardrails
  5. Orchestrate coordinator, pipeline, parallel execution
  6. Manage errors, timeouts, fallbacks

SDK = Software Development Kit

Slide 3 β€” Why a dedicated SDK?

Calling the API (Application Programming Interface) β€œby hand” requires rewriting:

  • the sending loop β†’ tool call detection β†’ execution β†’ reinjection β†’ iteration
  • serialization of tool diagrams
  • management of errors, retries, timeouts
  • multi-agent orchestration

The SDK is not magic: it is the agentic loop from session 1, packaged and equipped for production.

Slide 4 β€” The 7 bricks of the SDK

Brick Role
Agent Configuration: name, model, instructions, tools
Runner The execution loop
@tool Python functions β†’ model capabilities
Handoffs Transfer of control between agents
Guardrails Validation before/after the model
Hooks Lifecycle callbacks (audit, metrics)
Typed context Shared, invisible state of the model

Slide 5 β€” The class Agent

from claude_agent_sdk import Agent

agent_support = Agent(
    name="support-client",           # routing, logs, handoffs
    model="claude-sonnet-4-5",       # ⚠ volatile identifier
    instructions=(
        "You are Acme's support agent. Reply in English. "
        "Always cite the internal source you used."
    ),
    tools=[search_kb, creer_ticket],
)

instructions = the behavioral contract. Anything not there is left to interpretation of the model.

Slide 6 β€” The Runner: the agentic loop

from claude_agent_sdk import Runner

res = Runner.run(agent_support, "Billed twice this month.")
print(res.final_output)

What Runner.run() does:

  1. Send message + instructions + tool diagrams
  2. The model responds: final text or tool calls
  3. Runs tools, returns results
  4. Loop until final response, handoff, or max_turns

Slide 7 β€” The complete flow

        User β†’ [Guardrail input] β†’ Agent (model)
                                     β”‚
                   β”Œβ”€β”€ tool call ─────── handoff ──→ Other agent
                   β–Ό                 β”‚
              Tool execution         β–Ό
                   β”‚           Final response
                   └── result ──→ (loop)
                                     β”‚
                             [Guardrail output] β†’ User

The hooks observe each arrow. The typed context circulates everywhere β€” never in the prompt.

Slide 8 β€” ⚠ Certification trap: sync vs async

  • Runner.run() β€” synchronous, simple scripts
  • Runner.run_async() β€” asyncio, mandatory for parallel

Typical question: β€œFive independent analyzes to be launched simultaneously β€” which method?” β†’ run_async + asyncio.gather

Slide 9 β€” Tools: the decorator @tool

@tool
def search_kb(query: str, max_results: int = 5) -> str:
    """Search the internal knowledge base.

    Use for any product or procedure question.

    Args:
        query: natural-language search terms.
        max_results: maximum number of documents.
    """
    ...
  • Docstring β†’ description sent to model
  • Type hints β†’ JSON schema (JavaScript Object Notation)
  • Default value β†’ optional parameter

Slide 10 β€” The docstring is prompt engineering

❌ """Searches for stuff."""

βœ… Tells when to use the tool, describes each parameter, specifies the limits

The docstring is not a comment for your colleagues. This is an instruction for the pattern.

Slide 11 β€” Tool errors: business vs. technical

Error Type Treatment Example
Profession Return actionable text β€œNo results for X. Check SETL-XXX format.”
Technical Raise an exception Database unreachable

Actionable text allows the model to self-correct (rephrase, reask).

Slide 12 β€” Good tool practices

  • One tool = one responsibility (no do_everything(action))
  • Naming from a model perspective (query, not q)
  • Always limited: max_results, timeouts, pagination
  • Concise and structured feedback β€” not a 50 KB JSON dump

Slide 13 β€” Handoffs: the concept

A handoff = transfer of control: agent A passes the conversation to agent B.

Tool call Handoff
Who keeps the hand? The caller The target
Automatic return? Yes No
Context transmitted Arguments Complete History
Use cases One-time capacity Change of specialty

Slide 14 β€” Handoffs: implementation

from claude_agent_sdk import Agent, handoff

agent_triage = Agent(
    name="triage",
    model="claude-haiku-4-5",   # ⚠ router = simple task β†’ light model
    instructions="Route to the right specialist. "
                 "NEVER solve it yourself.",
    handoffs=[handoff(agent_facturation),
              handoff(agent_conformite)],
)

The model sees each handoff as a pseudo-tool transfer_to_X β€” it is he who decides to route.

Slide 15 β€” Handoffs: three architectural points

  1. Target agent inherits history β€” a specialist who re-questions = poorly configured handoff
  2. Handoff return possible (target β†’ source) β†’ risk of ping-pong β†’ bound with max_turns
  3. Circular reference: wire the return handoff after the instantiation of the agents

Slide 16 β€” Anti-pattern: the amnesic subagent

❌ Bad

Runner.run(agent_redacteur, "Write section 2.")

βœ… Good

Runner.run(agent_redacteur, f"""
Mission: write section 2 of the report "{titre}".
Overall outline: {plan}
Sections already written (summary): {resume}
Tone: formal. Audience: finance leadership. 400-600 words.
Deliverable: Markdown, no preamble.
""")

A subagent does not share your working memory. #1 cause of multi-agent failure in production.

Slide 17 β€” Guardrails: hard validation

@input_guardrail
def bloquer_pan(ctx, agent, message: str):
    """Reject any bank card number."""
    if PAN_RE.search(message):
        return GuardrailTripwire(triggered=True,
            message="Please use the secure portal.")
    return GuardrailTripwire(triggered=False)
  • Input guardrail: before the model Β· Output guardrail: after
  • Tripwire triggered β†’ run interrupted + dedicated exception to catch

Slide 18 β€” Guardrails: rules vs LLM-as-judge

Rules/regex Light LLM Judge
Latency ~0ms +200–800ms ⚠
Cost 0 1 call/answer ⚠
False negatives High (paraphrases) Weak

Production: defense in depth β€” rules in 1st line, judge in 2nd line.

Slide 19 β€” Instructions vs guardrails

β€œWhere to put: never disclose personal data?”

Both:

  • instructions β†’ orients the model (probabilistic)
  • guardrail β†’ blocking code (deterministic)

Regulatory compliance requires the deterministic layer.

Slide 20 β€” Hooks: lifecycle observability

class HooksAudit(RunHooks):
    async def on_tool_start(self, ctx, agent, tool):
        logger.info("agent=%s tool=%s", agent.name, tool.name)
    async def on_handoff(self, ctx, source, cible):
        logger.info("handoff %s β†’ %s", source.name, cible.name)

Runner.run(agent, msg, hooks=HooksAudit())

Uses: regulatory audit, latency/cost metrics, kill-switch.

Examination distinction: hook = observe Β· guardrail = block. The main safety goes into the guardrails.

Slide 21 β€” Context variables: shared typed state

@dataclass
class ContexteClient:
    client_id: str
    tier: str

@tool
def consulter_factures(ctx: RunContextWrapper[ContexteClient]) -> str:
    """List the invoices of the authenticated client."""
    return api.factures(ctx.context.client_id)  # never asked of the model

agent = Agent[ContexteClient](name="support", ...)
Runner.run(agent, msg, context=ContexteClient("C-4812", "premium"))

Slide 22 β€” ⚠ Safety rule (exam classic)

The identity of the user comes from the application context (authenticated session), NEVER from a parameter filled by the model.

Otherwise: a prompt injection reads other people's invoices.

The context is never serialized in the prompt β€” only what the tools return reaches the model.

Slide 23 β€” Pattern 1: coordinator + sub-agents

The coordinator breaks down, delegates, aggregates.

options_coordinateur = {
    "allowedTools": ["Read", "Grep", "Task"],  # β¬… "Task" = right to delegate
    "maxTurns": 40,
}

Examination pitfall: without "Task" in allowedTools, no subagents β€” the coordinator tries everything himself, silently. Symptom: β€œMy multi-agent only uses one agent”.

Slide 24 β€” Pattern 2: pipeline

brut    = Runner.run(agent_extracteur, document).final_output
analyse = Runner.run(agent_analyste,  f"Data:\n{brut}").final_output
rapport = Runner.run(agent_redacteur, f"Analysis:\n{analyse}").final_output

βœ… Steps testable in isolation, model sized per step
❌ Cumulative latency, upstream error propagated
β†’ exit guardrail between stages

Slide 25 β€” Pattern 3: parallel

taches = [Runner.run_async(agent_analyste, c) for c in chunks]
resultats = await asyncio.gather(*taches, return_exceptions=True)
ok     = [r.final_output for r in resultats if not isinstance(r, Exception)]
echecs = [r for r in resultats if isinstance(r, Exception)]
  • return_exceptions=True: a failure does not cancel the Nβˆ’1 successes
  • Latency Γ· N, but simultaneous tokens Γ— N β†’ be careful of rate limits ⚠

Slide 26 β€” Pattern choice grid

Need Pattern
Disjoint specialties, routing to entry Triage + handoffs
Dependent stages, progressive transformation Pipeline
Independent subtasks, volume Parallel + aggregator
Dynamic decomposition at runtime Coordinator + Task

Slide 27 β€” Error management: three families

  1. Tool error β†’ try/except in tool, actionable text returned to model
  2. Agent failure β†’ max_turns (loops), output guardrail + controlled restart, then fallback
  3. Timeout β†’ asyncio.wait_for(..., timeout=120) + idempotence of edge effect tools
try:
    res = await asyncio.wait_for(Runner.run_async(agent, msg), timeout=120)
except asyncio.TimeoutError:
    res = reponse_degradee(...)
except OutputGuardrailTripwireTriggered:
    res = escalade_humaine(msg)

Slide 28 β€” Idempotence: the case of reimbursement

A initier_remboursement tool retried after timeout does not have to pay twice.

Solution: idempotence key β€” the second call with the same key is ignored on the server side.

Summary sentence: In production, the question is not if an agent fails, but what next. A certifiable architecture defines the behavior of each failure.

Slide 29 β€” Summary: the 8 reflexes of the architect

  1. Tool Docstring = prompt engineering
  2. Business error in text, technical error in exception
  3. Handoff = transfer of control + history
  4. Full context in each delegation prompt
  5. Identity via typed context, never via model parameter
  6. Guardrails = deterministic Β· instructions = probabilistic Β· hooks = observation
  7. "Task" in allowedTools to delegate
  8. return_exceptions=True, max_turns, timeouts, idempotence

Slide 30 β€” And now

Exercises (to be completed independently):

  1. Agent + typed tools (45 min)
  2. Triage + handoffs (60 min)
  3. Guardrails + audit hooks (60 min)

Anchor quiz: 10 multiple choice questions β€” objective β‰₯ 8/10

Session 4: (see program) β€” bring your corrected exercises

⚠ Reminder: Model IDs, packet names and rate limits are changing β€” always check the official Anthropic documentation.