Français
Applied AI · Advanced 🔮 · Session 3
📝 Teacher's Guide
← Return to program 📄 Source .md

Trainer Guide — Session 3 (Advanced Level)

Claude Agent SDK: Building agentic systems in production

Program : Applied AI — Yann Isola Level : Advanced — solutions architects preparing for certification Claude Certified Architect Duration : 2:00 a.m. Prerequisites: Sessions 1–2 (agentic architectures, multi-agent orchestration), intermediate Python, notions of API (Application Programming Interface) LLM (Large Language Model).


1. Educational objectives

At the end of the session, participants will know:

  1. Describe the architecture of the Claude Agent SDK (Software Development Kit): Agent , Runner , tools, handoffs, guardrails, hooks, context variables.
  2. To implement an agent with typed tools via the decorator @tool .
  3. Design handoffs between agents and justify this choice in the face of the classic tool call.
  4. To set down entry and exit guardrails with waste management.
  5. Orchestrate multi-agent patterns: coordinator + sub-agents, pipeline, parallel execution.
  6. Manage errors: tool failure, agent failure, timeout, fallback strategies.

Certification link: these six objectives cover the “Agent Design” and “SDK Implementation” areas of the repository Claude Certified Architect ⚠ (reference subject to change — check the current version on the official Anthropic website).


2. Timed course (120 min)

# Sequence Duration Format
0 Home + reminder session 2 5 mins Plenary
1 Anatomy of the SDK: Agent, Runner, agentic loop 20 mins Presentation + live demo
2 Tools : @tool , schemas, docstrings 15 mins Live coding
3 Handoffs: transfer of control between agents 15 mins Presentation + demo
— Break 10 mins —
4 Guardrails and hooks 15 mins Presentation + live coding
5 Multi-agent patterns: coordinator, pipeline, parallel 20 mins Presentation + interactive page
6 Error handling + anti-patterns 10 mins Presentation + discussion
7 Launch of exercises (to be completed independently) 8 mins Workshops
8 Summary + anchor quiz 2 mins Plenary

3. Detailed content

Sequence 1 — Anatomy of the SDK (20 min)

3.1.1 Why a dedicated SDK?

Starting point: remember that calling an LLM API “by hand” requires rewriting the agentic loop yourself (message sending → tool call detection → execution → returning the result → iteration). The Claude Agent SDK is the official Python framework that industrialize this loop and adds the production building blocks: validation, observability, multi-agent delegation.

Key message to hammer home: the SDK is not a magical abstraction — it is the agentic loop of session 1, packaged, tested and tooled.

3.1.2 The class Agent

An agent is defined by four attributes:

from claude_agent_sdk import Agent

agent_support = Agent(
    name="support-client",                      # identifiant unique
    model="claude-sonnet-4-5",                  # ⚠ nom de modùle volatile
    instructions=(
        "Tu es un agent de support de la société Acme. "
        "Réponds en français, cite toujours la source interne utilisée. "
        "Si la demande concerne un remboursement, transfĂšre Ă  l'agent facturation."
    ),
    tools=[chercher_kb, creer_ticket],           # liste de fonctions décorées @tool
)

Question to ask the room: “Where would you put the rule “never disclose personal data”: in instructions or in a guardrail? » — Expected response in sequence 4: both ; the instructions guide, the guardrail guarantees.

3.1.3 The Runner : the loop

from claude_agent_sdk import Runner

resultat = Runner.run(
    agent_support,
    "Mon abonnement a été facturé deux fois ce mois-ci.",
)
print(resultat.final_output)

Roll out on the board what Runner.run() actually does:

  1. Send user message + instructions + tool diagrams to the model.
  2. The model responds: either a final text, or one or more tool calls .
  3. The Runner runs the tools, returns their results to the model.
  4. Loop until a final response is obtained (or a handoff is triggered, or the ceiling is exceeded max_turns ).

Diagram to draw (included in the interactive page):

Utilisateur → [Guardrail entrĂ©e] → Agent (modĂšle)
                                      │
                    ┌─── appel outil ───── handoff ───→ Autre agent
                    â–Œ                 │
                ExĂ©cution outil       â–Œ
                    │           RĂ©ponse finale
                    └── rĂ©sultat ──→ (boucle)
                                      │
                              [Guardrail sortie] → Utilisateur

Certification pitfall: Runner.run() is synchronous; Runner.run_async() (asyncio) is required for parallel execution (sequence 5). A typical question asks you to choose the correct variation depending on the scenario.


Sequence 2 — Tools: @tool (15 mins)

3.2.1 The decorator

from claude_agent_sdk import tool

@tool
def chercher_kb(requete: str, max_resultats: int = 5) -> str:
    """Recherche dans la base de connaissances interne d'Acme.

    Args:
        requete: termes de recherche en langage naturel.
        max_resultats: nombre maximal de documents retournés.
    """
    docs = kb_client.search(requete, limit=max_resultats)
    return "\n---\n".join(d.snippet for d in docs)

Three mechanisms to explain:

  1. The docstring becomes the description of the tool sent to the model. It is an artifact of prompt engineering , not a comment: she must say When use the tool, not only what he does .
  2. Type annotations generate JSON schema (JSON — JavaScript Object Notation, data exchange format): str → "type": "string", int → "type": "integer", default values ​​→ optional parameters. Complex types: use Pydantic or TypedDict .
  3. The return value is returned to the model as is (converted to text). Return structured, concise content — not a 50 KB JSON dump.

3.2.2 Good practices (to be dictated)

Flash exercise (3 min): have this docstring criticized: """Cherche des trucs.""" — wait: no use cases, no description of parameters, no limit.


Sequence 3 — Handoffs (15 min)

3.3.1 Concept

A handoff is a transfer of control : Agent A decides that Agent B is in a better position and passes the conversation to him. Fundamental difference with the tool call:

Tool call Handoff
Who keeps the hand? The calling agent The target agent
Back to the first agent? Yes, automatic No (except explicit return handoff)
Context transmitted Tool arguments Chat history
Use cases Spot capacity Change of specialty

3.3.2 Implementation

The SDK expresses the handoff via the list handoffs of the agent and, in routing tool signatures, via the return type annotation pointing to an agent:

from claude_agent_sdk import Agent, handoff

agent_facturation = Agent(
    name="facturation",
    model="claude-sonnet-4-5",  # ⚠ volatile
    instructions="Tu traites remboursements et litiges de facturation. "
                 "Tu as accĂšs Ă  l'historique complet de la conversation.",
    tools=[consulter_factures, initier_remboursement],
)

agent_triage = Agent(
    name="triage",
    model="claude-haiku-4-5",   # ⚠ volatile — modĂšle lĂ©ger pour router
    instructions="Analyse la demande et route vers le bon spécialiste. "
                 "Ne tente JAMAIS de rĂ©soudre toi-mĂȘme.",
    handoffs=[handoff(agent_facturation), handoff(agent_support)],
)

Points to highlight:

3.3.3 Anti-pattern: the amnesic subagent

Bad (to project):

# ❌ Le coordinateur dĂ©lĂšgue sans contexte
Runner.run(agent_redacteur, "Rédige la section 2.")
# → l'agent ne sait ni de quel document il s'agit, ni le ton, ni le plan

Good :

#  Contexte complet dans le prompt de délégation
Runner.run(agent_redacteur, f"""
Mission : rédiger la section 2 du rapport « {titre} ».
Plan global : {plan}
Sections déjà rédigées (résumé) : {resume_sections}
Ton : formel, public : direction financiùre. Longueur : 400–600 mots.
Livrable : Markdown uniquement, sans préambule.
""")

Rule to note: a subagent does not share your working memory. Everything he needs to know must be in his prompt or in the context transmitted. This is the No. 1 source of failure of multi-agent systems in production.


Sequence 4 — Guardrails and hooks (15 min)

3.4.1 Guardrails

Validators executed Before (input guardrail) or After (output guardrail) the passage through the model.

from claude_agent_sdk import input_guardrail, output_guardrail, GuardrailTripwire

@input_guardrail
def bloquer_donnees_carte(ctx, agent, message: str):
    """Rejette tout message contenant un numéro de carte bancaire."""
    if re.search(r"\b\d{4}[ -]?\d{4}[ -]?\d{4}[ -]?\d{4}\b", message):
        return GuardrailTripwire(
            triggered=True,
            message="Ne transmettez jamais de numéro de carte. "
                    "Utilisez le portail sécurisé.",
        )
    return GuardrailTripwire(triggered=False)

@output_guardrail
def verifier_pas_de_promesse(ctx, agent, sortie: str):
    """EmpĂȘche l'agent de promettre un remboursement non validĂ©."""
    verdict = petit_modele_classifieur(sortie)   # LLM léger en juge
    return GuardrailTripwire(triggered=verdict == "promesse_engageante")

To explain:

Defense in depth (diagram to draw): instructions (soft) → guardrails (hard) → tool permissions (hard) → audit via hooks (a posteriori).

3.4.2 Hooks

Lifecycle callbacks for observability and control:

from claude_agent_sdk import RunHooks

class HooksAudit(RunHooks):
    async def on_tool_start(self, ctx, agent, tool):
        logger.info("agent=%s outil=%s args=%s", agent.name, tool.name, ctx.tool_args)

    async def on_tool_end(self, ctx, agent, tool, result):
        metrics.timing(f"tool.{tool.name}.latency", ctx.elapsed_ms)

    async def on_handoff(self, ctx, source, cible):
        logger.info("handoff %s → %s", source.name, cible.name)

resultat = Runner.run(agent_triage, message, hooks=HooksAudit())

Main hooks: on_agent_start , on_agent_end , on_tool_start , on_tool_end , on_handoff . Use cases: audit logs (compliance), metrics (latency, cost), context injection, kill-switch.

Certification distinction: guardrail = blocking control over content ; hook = observation/instrumentation of the life cycle . A hook should not carry core security logic.

3.4.3 Context variables

Typed state shared between agents, tools, guardrails and hooks of the same run — never sent to model (unlike the prompt):

from dataclasses import dataclass
from claude_agent_sdk import Agent, Runner, RunContextWrapper

@dataclass
class ContexteClient:
    client_id: str
    tier: str            # "standard" | "premium"
    langue: str

@tool
def consulter_factures(ctx: RunContextWrapper[ContexteClient]) -> str:
    """Liste les factures du client authentifié."""
    return facturation_api.factures(ctx.context.client_id)  # jamais demandé au modÚle !

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

Safety message: the identity of the client comes from the application context (authenticated session), Never of a parameter that the model fills — otherwise a prompt injection can read other people's invoices. It’s a great exam classic.


Sequence 5 — Multi-agent patterns (20 min)

Project the interactive page (webpage/index.html ) and unfold the flow simulator.

3.5.1 Coordinator + sub-agents

The coordinator breaks down, delegates, aggregates. In the Claude execution environment, delegation goes through the tool Task : the coordinator must therefore have it in his authorized tools.

options_coordinateur = {
    "allowedTools": ["Read", "Grep", "Task"],   # ⬅ "Task" = droit de dĂ©lĂ©guer
    "maxTurns": 40,
}

Examination point: a coordinator whose allowedTools does not include "Task" cannot not create subagents — it will try to do everything itself, silently. Typical symptom: “my multi-agent architecture only uses one agent”. Cause: missing permission, not model bug.

3.5.2 Pipeline

Sequential chain: output of agent N = input of agent N+1.

brut     = Runner.run(agent_extracteur, document).final_output
analyse  = Runner.run(agent_analyste,  f"Données extraites :\n{brut}").final_output
rapport  = Runner.run(agent_redacteur, f"Analyse :\n{analyse}\nRédige le rapport.").final_output

Advantages: each step can be tested in isolation, models sized by step (light extractor, powerful analyst). Disadvantage: cumulative latency, propagated upstream error — hence the interest of an output guardrail between steps .

3.5.3 Parallel

Independent tasks → concurrent execution with asyncio :

import asyncio
from claude_agent_sdk import Runner

async def analyser_dossier(chunks: list[str]):
    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 ok, echecs

To highlight: return_exceptions=True — a failure must not cancel the N−1 successes. Then an aggregator agent merges the ok and points out the echecs .

Cost/latency trade-off: parallel divides the perceived latency but multiplies the tokens consumed simultaneously (be careful of throughput limits — rate limits ⚠, variable depending on the account level).

3.5.4 Choice grid (to be copied)

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

Sequence 6 — Error handling (10 min)

Three families:

  1. Tool error. Exception in tool code. By default the SDK returns the error to the model, which can retry or work around. To control the message: decorate with a try/except and return an actionable text (“The invoice service is unavailable, try again in 30 seconds or inform the user”).
  2. Agent failure. Infinite loop or drift → bound with max_turns ; invalid output → output guardrail + a controlled restart, then fallback (degraded response, human escalation).
  3. Timeout. Always wrap: asyncio.wait_for(Runner.run_async(...), timeout=120). Provide for the idempotence of tools with side effects (a re-attempted reimbursement must not be issued twice → idempotence key).
try:
    res = await asyncio.wait_for(Runner.run_async(agent, msg), timeout=120)
except asyncio.TimeoutError:
    res = reponse_degradee("Analyse trop longue, version abrégée fournie.")
except OutputGuardrailTripwireTriggered:
    res = escalade_humaine(msg)

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


Sequence 7 — Exercises (8 min)

Present the three exercises (exercises/exercises.md ) :

  1. Agent with tools (@tool , schematics, docstrings) — 45 min estimated.
  2. Triage handoffs → specialists — 60 min.
  3. Input/output guardrails + audit hooks — 60 min.

Indicative scale and commented solutions included in the exercise document.


4. Material and logistics

5. Common participant pitfalls

Trap Correction to be made
Empty or vague tool docstrings Remember: the docstring IS the tool prompt
Handoff / tool call confusion Return to the comparison table (who keeps control?)
User identity passed as tool parameter Context variables + injection demonstration
Coordinator without "Task" In allowedTools Reproduce the symptom, then correct
asyncio.gather without return_exceptions=True Simulate failure on 1 task out of 5
Context-free delegated subagent Project the Bad vs Good of sequence 3

6. Likely Questions (Trainer FAQ)

“What is the difference between guardrail and system instructions? » The instructions influence the (probabilistic) model; guardrail is deterministic code that blocks. Compliance requires both.

“Can we do a return handoff? » Yes — the target agent can list the source agent in its own handoffs . Be careful with loops: limit with max_turns and log via on_handoff .

“Handoff or agent-as-tool?” » Handoff = definitive transfer of the conversation. Agent-as-tool = the coordinator consults an agent and keeps control. If the user must continue to communicate with the specialist → handoff.

“Are context variables visible to the model? » No, never serialized in the prompt. This is precisely their interest (secrets, identifiers). Only what tools return reached the model.


End of the trainer guide — Session 3, advanced level.