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:
- Describe the architecture of the Claude Agent SDK (Software Development Kit):
Agent,Runner, tools, handoffs, guardrails, hooks, context variables. - To implement an agent with typed tools via the decorator
@tool. - Design handoffs between agents and justify this choice in the face of the classic tool call.
- To set down entry and exit guardrails with waste management.
- Orchestrate multi-agent patterns: coordinator + sub-agents, pipeline, parallel execution.
- 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
)
name: used for routing, logs and handoffs.model: the target model. â Model IDs change regularly (versions, snapshots) â always check the documentation.instructions: THE system prompt . Insist: this is the agentâs behavioral contract. Anything not included is left to interpretation of the model.tools: the list of abilities. An agent without tools is just a chatbot.
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:
- Send user message +
instructions+ tool diagrams to the model. - The model responds: either a final text, or one or more tool calls .
- The Runner runs the tools, returns their results to the model.
- 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:
- 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 .
- 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 orTypedDict. - 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)
- A tool = a responsibility. No
faire_tout(action: str). - Name the parameters from the model's point of view (
requete, notq). - Always limit:
max_resultats, timeouts, pagination. - Business errors return to text (âNo results forâŠâ); technical errors rise as an exception (managed in sequence 6).
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:
- The model âseesâ each handoff as a pseudo-tool (
transfer_to_facturation). This is the model that decided to route â hence the importance ofinstructionstriage. - The target agent inherits the history : no need to re-ask the customer the questions.
handoff()accepts options:on_handoff=(callback), history filter, welcome message.
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:
- A guardrail triggered (tripwire ) interrupts the run and throws a dedicated exception (
InputGuardrailTripwireTriggered/OutputGuardrailTripwireTriggered) that the application intercepts. - The input guardrails can rotate in parallel of the first model call (latency optimization): if the tripwire triggers, the call is canceled.
- A guardrail can itself call an LLM (âLLM-as-judgeâ pattern) â use a template fast and inexpensive so as not to double the latency.
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:
- 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â).
- Agent failure. Infinite loop or drift â bound with
max_turns; invalid output â output guardrail + a controlled restart, then fallback (degraded response, human escalation). - 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 ) :
- Agent with tools (
@tool, schematics, docstrings) â 45 min estimated. - Triage handoffs â specialists â 60 min.
- Input/output guardrails + audit hooks â 60 min.
Indicative scale and commented solutions included in the exercise document.
4. Material and logistics
- Python â„ 3.10,
pip install claude-agent-sdkâ (package name and version: check the official doc on the day). - API key per participant (or shared room proxy) â provide a token budget; the session consumes little (short agents).
- Projector + page
webpage/index.html(works offline). - Slides:
slides/slides.md(25+ slides, Marp/reveal format compatible).
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.