IAMaîtriser l'IA générative Plan du corpus
Accueil/Agents et sous-agents/Le Claude Agent SDK : construire ton propre agent

Le Claude Agent SDK : construire ton propre agent

Quand Claude Code en interactif ne suffit plus et que tu veux mettre un agent dans ton pipeline, ton CI ou ton API interne.

Temps de lecture : 18 min | Niveau : Avancé

Ce que tu sauras faire après

  • Choisir entre le SDK, la CLI, le Client SDK et les agents hébergés.
  • Installer le SDK et faire tourner un agent minimal en Python ou TypeScript.
  • Lire le flux de messages et savoir si la tâche a réussi, coûté trop cher ou tourné trop longtemps.
  • Écrire un outil personnalisé qui interroge ton entrepôt de données en lecture seule.
  • Poser des garde-fous : outils autorisés, mode de permission, plafond de tours et de budget.

1. À quoi sert le SDK, et quand il ne sert à rien

Le Claude Agent SDK te permet d'embarquer la boucle d'agent de Claude Code dans ton propre programme. La documentation le formule ainsi :

« An agent is an application that completes a task by planning its own steps and calling tools that read files, run commands, or edit code. The Agent SDK gives you the same tools, agent loop, and context management that power Claude Code, programmable in Python and TypeScript. »

Il existe quatre façons de construire avec Claude. La documentation officielle les compare :

Tu veux…UtiliseCe que tu obtiens
Embarquer l'agent de Claude Code dans ton application Python ou TypeScript, dans un process que tu exploitesAgent SDKUne bibliothèque qui lance le binaire Claude Code, avec ses outils intégrés, ses permissions, ses sessions et ses hooks
Travailler en interactif ou lancer des tâches ponctuelles depuis un terminalCLI Claude CodeL'interface terminal, pensée pour l'usage quotidien
Appeler l'API Claude directement depuis ton codeClient SDKAccès direct à l'API. Tu écris la boucle d'outils toi-même
Faire héberger l'agent par Anthropic, configuré via l'APIManaged AgentsUn harnais hébergé qui exécute la boucle

La question à te poser

« Est-ce que j'ai besoin de la boucle et des outils fichiers/bash, dans un programme à moi ? »

  • Oui → Agent SDK.
  • Non, je veux juste une réponse structurée d'un modèle → Client SDK, ou même un simple appel API. Beaucoup moins de complexité.
  • Non, je travaille à la main dans mon terminal → reste sur la CLI.

Et si tu n'écris ni Python ni TypeScript

La documentation donne la solution officielle : lancer la CLI comme un sous-processus avec l'option -p et --output-format json.

claude -p "Audite models/marts/mart_ca_mensuel.sql" --output-format json

C'est parfaitement viable depuis du Go, du Java, du Scala ou un opérateur Airflow qui appelle un shell.


2. Installation

Prérequis documentés : Node.js 18+ ou Python 3.10+, et un compte Anthropic.

Python

# Avec uv (le gestionnaire recommande par la doc)
uv init
uv add claude-agent-sdk
# Avec pip, sous Windows
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install claude-agent-sdk

Si PowerShell bloque Activate.ps1 avec une erreur de politique d'exécution, la documentation indique de lancer d'abord Set-ExecutionPolicy -Scope Process RemoteSigned.

TypeScript

npm init -y
npm pkg set type=module
npm install @anthropic-ai/claude-agent-sdk
npm install --save-dev tsx

La clé d'API

# macOS / Linux
export ANTHROPIC_API_KEY=ta-cle-api
# Windows PowerShell
$env:ANTHROPIC_API_KEY = "ta-cle-api"

⚠️ Piège numéro un. La documentation est explicite : « The SDK reads the key from the environment of the process that runs your agent; it doesn't load .env files automatically. » Si tu gardes ta clé dans un .env, charge-le toi-même, par exemple avec dotenv, avant d'appeler le SDK.

Le SDK supporte aussi Amazon Bedrock (CLAUDE_CODE_USE_BEDROCK=1), Google Cloud's Agent Platform (CLAUDE_CODE_USE_VERTEX=1) et Microsoft Foundry (CLAUDE_CODE_USE_FOUNDRY=1).


3. L'agent minimal

Trois éléments seulement : query, prompt, options.

Python

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ResultMessage


async def main():
    # query() est le point d'entree : il cree la boucle d'agent
    # et renvoie un iterateur asynchrone de messages.
    async for message in query(
        prompt="Relis utils.py et corrige les bugs qui provoqueraient un crash.",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Edit", "Glob"],  # ces outils sont pre-approuves
            permission_mode="acceptEdits",           # les modifications de fichier passent sans demander
        ),
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if hasattr(block, "text"):
                    print(block.text)               # le raisonnement de Claude
                elif hasattr(block, "name"):
                    print(f"Outil : {block.name}")  # l'outil appele
        elif isinstance(message, ResultMessage):
            print(f"Termine : {message.subtype}")


asyncio.run(main())

TypeScript

import { query } from "@anthropic-ai/claude-agent-sdk";

// La boucle d'agent : chaque message est emis au fur et a mesure.
for await (const message of query({
  prompt: "Relis utils.py et corrige les bugs qui provoqueraient un crash.",
  options: {
    allowedTools: ["Read", "Edit", "Glob"], // ces outils sont pre-approuves
    permissionMode: "acceptEdits"           // les modifications passent sans demander
  }
})) {
  if (message.type === "assistant" && message.message?.content) {
    for (const block of message.message.content) {
      if ("text" in block) {
        console.log(block.text);
      } else if ("name" in block) {
        console.log(`Outil : ${block.name}`);
      }
    }
  } else if (message.type === "result") {
    console.log(`Termine : ${message.subtype}`);
  }
}

Lancement : uv run agent.py (ou python agent.py) en Python, npx tsx agent.ts en TypeScript.

La différence de nommage à retenir

ConceptPythonTypeScript
Outils pré-approuvésallowed_toolsallowedTools
Mode de permissionpermission_modepermissionMode
Plafond de toursmax_turnsmaxTurns
Plafond de budgetmax_budget_usdmaxBudgetUsd
Prompt systèmesystem_promptsystemPrompt
Serveurs MCPmcp_serversmcpServers

Python utilise le snake_case, TypeScript le camelCase. Une exception documentée : dans AgentDefinition côté Python, les noms multi-mots comme disallowedTools et mcpServers gardent le camelCase pour coller au format de transport.


4. Lire le flux de messages

Le SDK émet cinq types de messages. Les connaître t'évite de coder à l'aveugle.

TypeQuandÀ quoi ça te sert
SystemMessageÉvénements de cycle de vie. Sous-type "init" au démarrage, "compact_boundary" après compactionRécupérer l'identifiant de session, savoir qu'une compaction a eu lieu
AssistantMessageUn par bloc de contenu produit par Claude (texte ou appel d'outil)Afficher la progression
UserMessageAprès chaque exécution d'outil, avec le résultat renvoyé à ClaudeJournaliser ce que les outils ont retourné
StreamEventSeulement si les messages partiels sont activésAffichage en direct, caractère par caractère
ResultMessageFin de la boucleLe message qui compte : succès ou échec, coût, tokens, identifiant de session

Les sous-types de résultat

C'est la table à garder sous la main. Le champ result n'existe que sur success.

Sous-typeCe qui s'est passéresult disponible ?
successTâche terminée normalementOui
error_max_turnsPlafond de tours atteintNon
error_max_budget_usdPlafond de budget atteintNon
error_during_executionUne erreur a interrompu la boucleNon
error_max_structured_output_retriesAucune sortie structurée valide après les tentativesNon

⚠️ Piège numéro deux. Un query() en un coup lève une exception après avoir émis un résultat d'erreur. La documentation précise que ce comportement est volontaire. Entoure toujours ta boucle d'un try.

Autre point documenté : quelques événements système peuvent arriver après le ResultMessage. Parcours le flux jusqu'au bout plutôt que de sortir de la boucle dès le résultat.


5. Les garde-fous : la partie qu'on oublie

Les outils

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Grep", "Glob"],  # analyse en lecture seule
)

Combinaisons documentées :

OutilsCe que l'agent peut faire
Read, Glob, GrepAnalyse en lecture seule
Read, Edit, GlobAnalyser et modifier du code
Read, Edit, Bash, Glob, GrepAutomatisation complète

Attention à la nuance documentée entre disponibilité et permission :

  • L'option tools contrôle ce qui apparaît dans le contexte de Claude.
  • allowedTools contrôle ce qui s'exécute sans demander.
  • Un outil non listé dans allowedTools reste disponible : son appel passe par le flux de permission.

Pour retirer complètement un outil intégré, la documentation donne deux moyens équivalents : l'omettre de tools, ou lister son nom nu dans disallowedTools.

Le mode de permission

ModeComportementQuand l'utiliser
"default"Les appels qui demandent une approbation passent par ton callback canUseTool ; sans callback, c'est refuséApplication interactive
"acceptEdits"Approuve automatiquement les modifications de fichier et les commandes de système de fichiers courantes (mkdir, touch, mv, cp) ; les autres commandes Bash suivent les règles par défautPrototypage, répertoire isolé
"plan"Claude explore et planifie sans modifier tes sourcesRevue de code, tu veux valider avant
"dontAsk"Ne demande jamais. Ce qui est pré-approuvé tourne, tout le reste est refuséAgent sans interface, surface d'outils figée
"auto"Un classificateur modèle approuve ou refuseAgent autonome avec garde-fous
"bypassPermissions"Tout passe. En TypeScript, exige aussi allowDangerouslySkipPermissions: trueUniquement en CI ou conteneur isolé

La documentation est nette sur bypassPermissions : « Use only in isolated environments where the agent's actions can't affect systems you care about ». Et c'est interdit en root sous Unix.

Les plafonds

Deux options qui t'évitent une mauvaise surprise sur la facture :

OptionCe qu'elle contrôleDéfaut
max_turns / maxTurnsNombre maximum d'allers-retours avec outilsAucune limite
max_budget_usd / maxBudgetUsdDépense maximale avant arrêtAucune limite

Le plafond de budget couvre aussi les sous-agents : leurs dépenses comptent dans le total. Une fois le plafond atteint, lancer un nouveau sous-agent échoue avec Budget limit reached.

« Setting a budget is a good default for production agents. » Suis ce conseil.

Deux plafonds de plus se règlent par variables d'environnement, passées au SDK via l'option env :

VariableCe qu'elle limiteDéfaut
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTHProfondeur d'imbrication des sous-agents (1 = tes sous-agents n'en lancent aucun)3
CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTSSous-agents lancés en même temps20

⚠️ Les deux SDK ne traitent pas env pareil. En TypeScript, il remplace l'environnement du sous-processus : étale ...process.env dedans pour garder PATH. En Python, il est fusionné avec l'environnement hérité.

Le niveau d'effort

L'option effort règle la profondeur de raisonnement : "low", "medium", "high", "xhigh", "max". Pour un agent qui ne fait que lister des fichiers, "low" réduit le coût et la latence. Tous les modèles ne supportent pas ce paramètre.


6. Un outil personnalisé : interroger ton entrepôt en lecture seule

C'est le vrai intérêt du SDK pour un data engineer : donner à Claude un accès contrôlé à ta base.

Le mécanisme documenté tient en trois gestes :

  1. Tu définis une fonction avec le décorateur @tool (Python) ou l'assistant tool() (TypeScript).
  2. Tu l'emballes dans un serveur MCP en-process avec create_sdk_mcp_server / createSdkMcpServer.
  3. Tu passes ce serveur à query via l'option mcpServers.

Un outil se définit par quatre parties : nom, description, schéma d'entrée, handler.

⚠️ Le schéma d'entrée en Python. Le dict simple ({"sql": str}) est pratique, mais la documentation prévient qu'il rend toutes les clés obligatoires et ne gère ni les énumérations ni les plages de valeurs. Pour un paramètre optionnel : laisse-le hors du schéma, décris-le dans le texte de la description, et lis-le avec args.get("limite", 50). Pour une énumération, passe un vrai JSON Schema à la place du dict.

Le code, commenté ligne par ligne

import asyncio
from typing import Any
import psycopg
from claude_agent_sdk import (
    query,
    tool,
    create_sdk_mcp_server,
    ClaudeAgentOptions,
    ResultMessage,
)

DSN = "postgresql://lecteur_seul@entrepot:5432/analytics"


# 1) DEFINITION DE L'OUTIL
#    Les 3 premiers arguments : nom, description, schema d'entree.
#    La description est lue par Claude pour decider QUAND appeler l'outil :
#    ecris-la avec soin, elle vaut autant que le code.
@tool(
    "executer_select",
    "Execute une requete SELECT en lecture seule sur l'entrepot analytics "
    "et renvoie au maximum 50 lignes. Refuse toute requete qui n'est pas un SELECT.",
    {"sql": str},  # schema simple : un dict nom -> type, converti en JSON Schema par le SDK
)
async def executer_select(args: dict[str, Any]) -> dict[str, Any]:
    sql = args["sql"].strip()

    # 2) GARDE-FOU COTE CODE, pas cote prompt.
    #    Un prompt peut etre contourne ; ce test, non.
    if not sql.lower().startswith("select"):
        return {
            "content": [
                {"type": "text", "text": "Refuse : seules les requetes SELECT sont autorisees."}
            ],
            "is_error": True,  # marque l'appel comme un echec pour que Claude reagisse
        }

    try:
        with psycopg.connect(DSN) as conn:
            with conn.cursor() as cur:
                cur.execute(sql)
                colonnes = [d[0] for d in cur.description]
                lignes = cur.fetchmany(50)  # plafond dur : protege ton contexte
    except Exception as e:
        # 3) ON COMPOSE LE MESSAGE D'ERREUR QUE CLAUDE VA LIRE.
        #    Une exception non rattrapee arriverait a Claude sans aucun contexte.
        return {
            "content": [{"type": "text", "text": f"Erreur SQL : {e}"}],
            "is_error": True,
        }

    entete = " | ".join(colonnes)
    corps = "\n".join(" | ".join(str(v) for v in l) for l in lignes)
    return {"content": [{"type": "text", "text": f"{entete}\n{corps}\n({len(lignes)} lignes)"}]}


# 4) ON EMBALLE L'OUTIL DANS UN SERVEUR MCP EN-PROCESS.
#    "en-process" = il tourne dans ton programme, pas dans un process separe.
serveur_entrepot = create_sdk_mcp_server(
    name="entrepot",
    version="1.0.0",
    tools=[executer_select],
)


async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"entrepot": serveur_entrepot},
        # 5) NOM COMPLET DE L'OUTIL : mcp__{nom_du_serveur}__{nom_de_l_outil}
        #    La cle de mcp_servers ("entrepot") devient le segment du milieu.
        allowed_tools=["mcp__entrepot__executer_select"],
        system_prompt=(
            "Tu es analyste de donnees. Tu reponds en citant la requete SQL "
            "que tu as executee, puis le resultat, puis ton interpretation. "
            "Tu ne devines jamais un chiffre : tu l'obtiens par une requete."
        ),
        max_turns=12,         # l'agent ne peut pas boucler indefiniment
        max_budget_usd=0.50,  # plafond de depense pour cette execution
    )

    try:
        async for message in query(
            prompt="Quel est le chiffre d'affaires par mois sur les 6 derniers mois ?",
            options=options,
        ):
            if isinstance(message, ResultMessage):
                if message.subtype == "success":
                    print(message.result)
                else:
                    print(f"Arret : {message.subtype}")
                if message.total_cost_usd is not None:
                    print(f"Cout : ${message.total_cost_usd:.4f}")
    except Exception as e:
        # Un query() en un coup leve apres un resultat d'erreur : c'est voulu.
        print(f"Session terminee en erreur : {e}")


asyncio.run(main())

Les quatre décisions de conception à retenir

DécisionPourquoi
Le filtre « SELECT uniquement » est dans le code, pas dans le promptUn garde-fou en dur ne se contourne pas par une formulation habile
fetchmany(50)Les grosses sorties d'outils consomment énormément de contexte
is_error: True avec un message composéClaude lit ton message au lieu d'une exception brute et peut réagir
max_turns et max_budget_usdDeux filets différents : l'un contre la boucle infinie, l'autre contre la facture

À adapter chez toi. Le squelette est celui de la documentation officielle : @tool, create_sdk_mcp_server, le format de retour content / is_error, et le nom complet mcp__entrepot__executer_select. En revanche, le client PostgreSQL utilisé dedans (psycopg) et la chaîne de connexion sont un choix d'illustration : remplace-les par le connecteur de ton entrepôt.

L'annotation qui accélère

Si ton outil ne modifie rien, déclare-le. La documentation indique que readOnlyHint contrôle si l'outil peut être appelé en parallèle d'autres outils en lecture seule.

from claude_agent_sdk import tool, ToolAnnotations

@tool(
    "executer_select",
    "Execute une requete SELECT en lecture seule.",
    {"sql": str},
    annotations=ToolAnnotations(readOnlyHint=True),
)
async def executer_select(args):
    ...

Attention à la mise en garde de la documentation : « Annotations are metadata, not enforcement. A tool marked readOnlyHint: true can still write to disk if that's what the handler does. » Garde l'annotation honnête.


7. Template de démarrage

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["[OUTIL_1]", "[OUTIL_2]"],   # ex. "Read", "Grep", "Glob"
        permission_mode="[default | acceptEdits | plan | dontAsk | auto]",
        system_prompt="[ROLE_DE_L_AGENT + CE_QU_IL_NE_DOIT_JAMAIS_FAIRE + FORMAT_DE_SORTIE]",
        setting_sources=["project"],   # charge CLAUDE.md, skills et hooks du projet
        max_turns=[NOMBRE],
        max_budget_usd=[MONTANT],
        effort="[low | medium | high]",
    )

    try:
        async for message in query(prompt="[TON_OBJECTIF]", options=options):
            if isinstance(message, ResultMessage):
                if message.subtype == "success":
                    print(message.result)
                else:
                    print(f"Arret : {message.subtype}")
    except Exception as e:
        print(f"Erreur : {e}")


asyncio.run(main())

8. Ce qu'il faut savoir avant de se lancer

Le contexte s'accumule

La documentation le dit clairement : la fenêtre de contexte « does not reset between turns within a session. Everything accumulates ». Quand elle approche de sa limite, le SDK compacte automatiquement : il résume l'historique ancien.

Conséquence pratique et souvent douloureuse :

« Compaction replaces older messages with a summary, so specific instructions from early in the conversation may not be preserved. Persistent rules belong in CLAUDE.md [...] rather than in the initial prompt, because CLAUDE.md content is re-injected on every request. »

Traduction : tes règles importantes vont dans CLAUDE.md, pas dans le prompt initial.

Les sous-agents sont ton meilleur outil d'économie de contexte

Chaque sous-agent démarre avec une conversation neuve, et seule sa réponse finale revient au parent. Le contexte du parent grandit du résumé, pas de tout le transcript.

Les autres écosystèmes

Si ton équipe est déjà engagée ailleurs, voici ce que disent leurs documentations officielles :

SDKÉditeurLangagesInstallationPrimitives
Claude Agent SDKAnthropicPython, TypeScriptpip install claude-agent-sdk / npm install @anthropic-ai/claude-agent-sdkBoucle d'agent, outils intégrés, sous-agents, hooks, MCP
OpenAI Agents SDKOpenAIPython, TypeScriptpip install openai-agents / npm install @openai/agentsAgents (« LLMs equipped with instructions and tools »), Handoffs (délégation d'un agent à un autre), Guardrails (validation des entrées et sorties), Sessions (historique de conversation)
Microsoft Agent FrameworkMicrosoft.NET, Python, Go (préversion)pip install agent-frameworkAgents, Harness Agent (agent outillé pour les tâches longues), Workflows (graphes d'exécution explicites), intégrations. Microsoft le présente comme « the direct successor » de Semantic Kernel et d'AutoGen
Agent Development Kit (ADK)GooglePython, TypeScript, Go, Java, Kotlinpip install google-adkLlmAgent, agents de workflow : SequentialAgent, ParallelAgent, LoopAgent, systèmes multi-agents

Le « hello world » d'OpenAI, tel que publié dans leur documentation :

from agents import Agent, Runner

agent = Agent(name="Assistant", instructions="You are a helpful assistant")

result = Runner.run_sync(agent, "Write a haiku about recursion in programming.")
print(result.final_output)

Et celui de Google ADK :

from google.adk import Agent
from google.adk.tools import google_search

agent = Agent(
    name="researcher",
    model="gemini-flash-latest",
    instruction="You help users research topics thoroughly.",
    tools=[google_search],
)

Tu remarqueras la convergence : partout, un agent = des instructions + des outils + un exécuteur de boucle. Ce que tu apprends ici se transpose.

Un mot sur Microsoft : si tu tombes sur du Semantic Kernel, ce n'est pas faux, c'est l'ancêtre. Microsoft écrit noir sur blanc qu'Agent Framework est « the next generation of both Semantic Kernel and AutoGen ». Pour un nouveau projet .NET ou Python, pars d'Agent Framework.

Note d'honnêteté. Ces écosystèmes évoluent vite et leurs API changent plus souvent que celles d'un langage. Les noms de paquets et de classes ci-dessus ont été relus sur les documentations officielles en septembre 2026 ; vérifie la page de démarrage avant de t'engager sur une version.


À retenir

  • Le SDK te donne la boucle d'agent de Claude Code dans ton propre programme, en Python ou TypeScript. Pour les autres langages : la CLI en sous-processus avec -p --output-format json.
  • Trois éléments suffisent : query, un prompt, des options.
  • Le message qui compte est le ResultMessage : vérifie toujours son subtype avant de lire result.
  • Mets toujours max_turns et max_budget_usd sur un agent de production.
  • Un outil personnalisé = @tool + create_sdk_mcp_server + le nom complet mcp__serveur__outil dans allowed_tools.
  • Les garde-fous critiques vont dans le code du handler, pas dans le prompt.
  • Tes règles permanentes vont dans CLAUDE.md, parce que la compaction peut effacer les instructions du prompt initial.

Sources

Corpus personnel de formation · genere le 26/09/2026 · source : 04-agent-sdk.md