Ne codez plus votre propre Tool Loop ! Analyse approfondie du SDK Claude Code Agent et guide de mise en production
Si vous en êtes encore à écrire votre propre boucle while avec le SDK natif d'Anthropic pour analyser les tool_use, gérer des rappels d'outils complexes et la gestion de l'état du contexte, alors cet article est fait pour vous.
Récemment, Anthropic a discrètement lancé claude-agent-sdk (anciennement le SDK Claude Code). Ce n'est pas qu'un simple wrapper d'API — c'est le SDK central qui expose de manière entièrement programmatique la même boucle agentique, la chaîne d'outils intégrée, les hooks d'interception de permissions et le mécanisme de restauration du contexte de session que l'on trouve sous le capot du CLI Claude Code.
Cet article couvre les principes d'architecture, les mécanismes fondamentaux, l'interception de sécurité des hooks de niveau production, ainsi que des mises en pratique complètes avec GitHub Actions CI/CD et la collaboration multi-agents. Allons de zéro jusqu'à la maîtrise de ce SDK.
1. Pourquoi tout le monde parle de claude-agent-sdk
Si vous avez utilisé le SDK natif d'Anthropic, vous savez que pour construire un Agent, vous devez :
- écrire une boucle
whileet appeler/v1/messagesen boucle - analyser les blocs
tool_useettool_resultde la réponse API - maintenir votre propre historique de messages et fenêtre de contexte
- gérer divers cas limites et erreurs
La première fois que vous exécutez cela, ça semble impressionnant. La deuxième fois, vous réalisez quelque chose : tout le monde — Anthropic, OpenAI, Gemini — fait la même chose. La vraie différence ne réside pas dans le cœur du LLM, mais dans l'ingénierie derrière l'Agent.
Le claude-agent-sdk est un SDK qui expose directement la boucle agentique de Claude Code. Cela signifie que vous n'avez plus besoin de réinventer toute l'ingénierie des Agents : vous pouvez obtenir un agent de codage de qualité production avec seulement quelques lignes de code.
Cela soulève naturellement deux questions :
Quelle est la relation entre claude-agent-sdk et Claude Code ?
Claude Code est un outil de codage agentique en CLI. C'est l'assistant de codage en terminal dont on entend souvent parler. Le claude-agent-sdk peut être considéré comme une exposition du cœur de Claude Code sous forme de fonction dans votre code. Il peut être utilisé comme outil en ligne de commande dans votre terminal, intégré à des pipelines CI/CD, ou incorporé dans des applications.
Quelle est la relation entre claude-agent-sdk et le SDK Anthropic ?
Le SDK Anthropic est un SDK d'API de bas niveau permettant d'appeler directement les modèles Claude. Il fournit l'API Messages, le streaming et les outils. Le claude-agent-sdk est construit par-dessus, mais à un niveau d'abstraction bien plus élevé.
Une analogie : le SDK Anthropic revient à vous donner un moteur et à vous demander de construire vous-même une voiture. claude-agent-sdk, c'est comme si on vous fournissait un châssis complet, une chaîne de traction et un module de conduite autonome.
2. Principe d'architecture fondamental : tout est une session
Le concept de conception le plus important de claude-agent-sdk est : tout est une session.
Qu'est-ce qu'une session ? Une session est un contexte d'exécution autonome qui maintient tout l'état d'une exécution d'Agent. Elle comprend :
- l'historique de conversation (liste des messages)
- les paramètres de permissions et les états d'approbation
- le statut d'exécution des outils
- les modifications du système de fichiers (en interne, les sessions peuvent suivre l'état des fichiers)
- le contexte complet nécessaire pour reprendre une exécution interrompue
Avec les SDK de bas niveau, pour reprendre après une interruption, vous devez sérialiser vous-même tout l'historique des messages. Mais dans claude-agent-sdk, l'objet Session est comme un point de sauvegarde dans un jeu : vous pouvez reprendre à tout moment depuis n'importe quel point.
Créer une session
import { query } from "@anthropic-ai/claude-agent-sdk";
const session = await query({
prompt: "Please write a Python script that prints 1 to 100",
options: {
allowedTools: ["Bash", "Read"],
},
});
Un détail à noter : au premier appel, le SDK initialise automatiquement l'environnement (installation des dépendances, synchronisation du CLI, etc.). Cela peut prendre quelques secondes à quelques minutes selon les conditions réseau.
Événements de streaming
for await (const message of session) {
if (message.type === "stream_event") {
console.log(JSON.stringify(message.event, null, 2));
}
}
Ici, on voit une philosophie de conception importante : le SDK n'est pas seulement une API requête-réponse. C'est un SDK streaming-first qui vous donne une visibilité complète sur ce que fait l'agent. Il ne masque pas la complexité ; il vous offre une transparence totale.
Cycle de vie d'une session
Lorsque l'agent a terminé tout son travail, la session se termine. Mais le SDK vous permet aussi de reprendre explicitement une session :
const resumed = await query(
"Based on the previous answer, change the script to print 1 to 1000",
{
sessionId: session.sessionId,
options: { allowedTools: ["Bash", "Read", "Write"] },
}
);
Attendez, ces deux appels ne se ressemblent-ils pas ? Oui, mais il y a une différence cruciale : le premier appel crée une nouvelle session, tandis que le second utilise sessionId. C'est exactement ce que signifie « tout est une session » : l'agent peut reprendre son travail exactement là où il s'était arrêté. Le SDK gère toute la sérialisation et la restauration du contexte pour vous.
3. Mécanismes fondamentaux : outils, permissions et hooks
3.1 Outils intégrés : une chaîne d'outils d'agent complète
Si vous utilisez directement le SDK natif d'Anthropic, vous devez définir chaque outil vous-même. Il faut trouver comment exécuter Bash, comment gérer les entrées/sorties de fichiers, et comment implémenter les contrôles de permissions.
Le claude-agent-sdk est livré avec la chaîne d'outils complète de Claude Code dès la sortie de la boîte :
| Tool Name | Description | Permission Level |
|---|---|---|
| Bash | Exécuter des commandes shell | DefaultAsk |
| Read | Lire des fichiers | DefaultAllowed |
| Edit | Modifier des fichiers | DefaultAsk |
| Write | Écrire des fichiers | DefaultAsk |
| Glob | Trouver des fichiers par motif | DefaultAllowed |
| Grep | Rechercher du contenu dans les fichiers | DefaultAllowed |
| WebFetch | Récupérer des URLs web | DefaultAsk |
| WebSearch | Rechercher sur le web | DefaultAsk |
| TodoList | Décomposer les tâches | DefaultAllowed |
Ce sont exactement les outils que Claude Code utilise dans le terminal. Le SDK vous donne les mêmes outils, mais dans le contexte de votre propre application.
3.2 Système de permissions : deux modes de fonctionnement
Lorsqu'un outil nécessitant une permission est invoqué, le SDK doit décider comment le gérer. Il existe deux modes :
Mode 1 : Permission automatique (recommandé pour CI/CD)
const session = await query({
prompt: "Initialize a git repository and commit all files",
options: {
allowedTools: [
{ toolName: "Bash", isAllowed: () => true },
"Read",
"Write",
],
},
});
Dans ce mode, toutes les commandes Bash sont automatiquement autorisées. Vous pouvez aussi définir des fonctions personnalisées :
allowedTools: [
{
toolName: "Bash",
isAllowed: async (input) => {
const command = input.command;
return command.startsWith("git ") || command.startsWith("npm ");
},
},
]
Ce mode convient aux scénarios automatisés comme les pipelines CI/CD où l'environnement est connu et sûr.
Mode 2 : Hooks — interception des permissions de niveau production
Si vous avez besoin d'implémenter un contrôle de permissions dynamique, des règles de liste noire/liste blanche, ou une approbation humaine dans la boucle, les Hooks sont le mécanisme central.
const session = await query({
prompt: "Run all tests and fix any failures",
options: {
hooks: {
PreToolUse: [
{
matcher: "Bash",
hook: async (input) => {
const command = input.tool_input.command;
if (command.includes("rm -rf")) {
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason:
"rm -rf is not allowed in our CI environment",
},
};
}
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
},
};
},
},
],
},
},
});
Attendez, certains lecteurs vont demander : puisque isAllowed peut déjà gérer cela, pourquoi avoir besoin des Hooks ?
Bonne question. La différence est :
isAllowedne gère que le cas où l'outil est déjà dansallowedTools. Il ne peut pas définir de nouveaux outils autorisés qui ne sont pas dans la liste, ni modifier dynamiquement le périmètre des permissions en cours d'exécution.- Les Hooks sont exécutés avant que l'outil ne s'exécute réellement et peuvent intercepter à différents stades du cycle de vie (PreToolUse, PostToolUse, etc.). Ils peuvent aussi modifier l'état de la session, bloquer l'exécution, ou implémenter des workflows plus complexes comme demander une approbation humaine.
En bref, les hooks sont un mécanisme plus puissant, plus flexible, et orienté production.
3.3 Types d'événements de Hook
PreToolUse: déclenché avant qu'un outil ne soit exécuté.PostToolUse: déclenché après qu'un outil a été exécuté.Notification: déclenché lorsque l'agent envoie une notification.UserPromptSubmit: déclenché lorsqu'un utilisateur soumet un prompt.Stop: déclenché lorsque l'agent a terminé.
Le cycle de vie complet des hooks est bien plus granulaire. Vous pouvez consulter la documentation officielle pour la liste complète. L'important est de comprendre le principe : le SDK vous donne une observabilité et un contrôle complets sur le cycle de vie de la boucle.
4. Pratique des hooks en production : construire une passerelle de sécurité
Construisons maintenant une passerelle de sécurité pratique de niveau production. Cette passerelle intercepte chaque commande Bash et applique les règles suivantes :
- Refuser toute commande
rm -rf(particulièrement dangereuse) - Refuser toute commande écrivant en dehors du répertoire de travail (par exemple :
cd /etc && echo "hello" > test.txt) - Refuser la lecture d'identifiants depuis les fichiers de variables d'environnement (comme les fichiers
.envdans/etc, ou~/.aws/credentials) - Autoriser toutes les autres opérations
Voici une implémentation complète :
// secure-gate.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
const WORKSPACE = "/home/user/project";
const DENY_PATTERNS = [
/^\s*rm\s+(-[a-z]*\s+)*-\s*[a-z]*[r]/i,
/^\s*sudo\s+/i,
/^\s*curl\s+.*(\||>)/i,
/^\s*(cat|tail|head|less|more)\s+.*\.(env|pem|key|p12)/i,
];
const ALLOW_PATTERNS = [
/^\s*(cd|ls|pwd|git|npm|node|python3?|pip|bun|yarn|pnpm|tsc|eslint|prettier)\b/i,
];
function isSafeCommand(command: string): { allowed: boolean; reason?: string } {
const trimmed = command.trim();
// Block dangerous patterns
for (const pattern of DENY_PATTERNS) {
if (pattern.test(trimmed)) {
return {
allowed: false,
reason: `Command matches dangerous pattern: ${pattern}`,
};
}
}
// If it matches our allow-list of build/test commands, let it through
for (const pattern of ALLOW_PATTERNS) {
if (pattern.test(trimmed)) {
return { allowed: true };
}
}
// Otherwise, allow but with audit log
return { allowed: true, reason: "Allowed with audit" };
}
async function main() {
const session = await query({
prompt: "Run npm test and fix any errors",
options: {
allowedTools: ["Bash", "Read", "Write", "Edit"],
hooks: {
PreToolUse: [
{
matcher: "Bash",
hook: async (input) => {
const command = input.tool_input.command as string;
const result = isSafeCommand(command);
if (!result.allowed) {
console.warn(`[SECURITY] Blocked command: ${command}`);
console.warn(`[SECURITY] Reason: ${result.reason}`);
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: result.reason,
},
};
}
if (result.reason === "Allowed with audit") {
console.log(`[AUDIT] ${command}`);
}
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
},
};
},
},
],
},
},
});
for await (const message of session) {
// consume stream
}
}
main().catch(console.error);
Dans cet exemple :
- Nous avons utilisé une combinaison de liste noire et de liste blanche basées sur des regex.
- La liste noire bloque les commandes dangereuses ; la liste blanche laisse passer directement les commandes courantes de build/test, évitant ainsi les demandes de permission inutiles.
- Pour tout le reste, nous autorisons mais journalisons à des fins d'audit.
- Les résultats de la décision de permission sont renvoyés à l'agent afin qu'il puisse adapter son comportement.
Un vrai système de production irait bien plus loin : vous pourriez utiliser un moteur de politiques externe (comme OPA), intégrer un SIEM, ou router les demandes de décision vers un service d'approbation humaine. Mais le principe est exactement celui-ci : un contrôle complet sur la boucle de l'agent.
5. GitHub Actions CI/CD : l'agent comme développeur automatisé
Examinons maintenant un scénario de production réel : utiliser claude-agent-sdk dans GitHub Actions CI/CD pour créer automatiquement une Pull Request pour les modifications de code.
Utiliser le CLI avec --output-format stream-json
En CI, le plus simple est d'utiliser directement le CLI. Le CLI et le SDK sont les deux faces d'une même pièce. Dans un workflow GitHub Actions, vous pouvez exécuter :
name: Agent Code Review & Fix
on:
push:
branches: [main]
jobs:
agent-code-review:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
npx -y @anthropic-ai/claude-code@latest \
--output-format stream-json \
--allowedTools "Read,Write,Edit,Bash" \
--permission-mode acceptEdits \
"Review the code, find bugs, fix them, and create a commit"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
branch: agent-fixes
base: main
title: "Agent auto-fix: code review & bug fixes"
body: "This PR is automatically generated by claude-agent-sdk"
Points importants à considérer :
- N'utilisez pas directement les identifiants de commit générés par l'agent. Utilisez plutôt l'action
create-pull-requestqui gère automatiquement l'authentification et l'identité du commit. - Le
--permission-mode acceptEditspermet à l'agent de modifier des fichiers sans demander la permission. Pour des workflows plus critiques en termes de sécurité, vous pouvez utiliser--permission-mode planpour que l'agent se contente de produire un plan, que vous appliquerez ensuite manuellement. - Si vous souhaitez diffuser tout le processus dans les logs CI, utilisez
--output-format stream-json. - En CI, vous ne devez pas exécuter de commandes interactives. Assurez-vous que toutes les permissions sont pré-autorisées.
En fait, il y a encore un point important : dans les environnements CI, la toute première exécution du CLI initialise l'environnement. Cette étape d'initialisation peut prendre 30 à 60 secondes. Si vous exécutez fréquemment, envisagez de mettre en cache le répertoire ~/.claude :
- name: Cache Claude Code
uses: actions/cache@v4
with:
path: ~/.claude
key: ${{ runner.os }}-claude-${{ hashFiles('**/package-lock.json') }}
Utiliser le SDK dans un environnement CI Node.js
Si vous préférez écrire un script CI personnalisé en Node.js, vous pouvez utiliser directement le SDK :
// ci-agent.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
import { execSync } from "node:child_process";
import * as fs from "node:fs";
async function runAgent() {
const prompt = `
You are working in a Node.js project.
Please complete the following tasks:
1. Read the current git diff
2. Look for any obvious bugs or type errors
3. Fix them
4. Write a test for the changes
5. Run the tests to make sure they pass
`;
const session = await query({
prompt,
options: {
allowedTools: ["Bash", "Read", "Write", "Edit"],
hooks: {
PreToolUse: [
{
matcher: "Bash",
hook: async (input) => {
const command = input.tool_input.command;
if (!["git", "npm", "node", "npx"].some((cmd) => command.trim().startsWith(cmd))) {
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: `Command not allowed in CI: ${command}`,
},
};
}
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
},
};
},
},
],
},
},
});
for await (const message of session) {
if (message.type === "stream_event") {
// You can send this to your log aggregation system
console.log(JSON.stringify(message.event));
}
}
const diff = execSync("git diff --stat").toString();
console.log("===== Agent Changes =====");
console.log(diff);
}
runAgent().catch(console.error);
Dans ce script :
query démarre une session et exécute l'agent. Les hooks garantissent que l'agent ne peut utiliser que les commandes git, npm, node, npx, ce qui réduit considérablement le risque d'exécution de code arbitraire. Enfin, nous récupérons le diff et l'utilisons pour décider de créer une PR ou de pousser directement.
6. Collaboration multi-agents : envoyer des tâches entre agents
Le modèle multi-agents le plus pratique n'est pas le modèle complexe du « superviseur », mais plutôt sous-agent + pipeline. Voici ce que cela signifie : au lieu de mettre toutes les responsabilités dans un seul prompt géant, divisez le travail en plusieurs exécutions d'agents indépendantes, chacune avec son propre objectif et son périmètre de permissions.
Exemple : pipeline de refactorisation de code
Supposons que vous ayez une exigence : « Refactoriser le module de paiement, extraire la logique commune et écrire des tests. »
Vous pourriez diviser cela en trois agents :
- Agent Explorateur : lire le code et produire un plan de refactorisation
- Agent Implémenteur : exécuter le plan et modifier le code
- Agent Relecteur : examiner les modifications et générer un rapport
Chaque agent a un rôle, un périmètre de permissions et un répertoire de travail différents. Voyons comment les orchestrer :
// multi-agent.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
import * as fs from "node:fs";
interface AgentResult {
sessionId: string;
finalText: string;
output: string;
}
async function runAgent(
prompt: string,
allowedTools: any[],
sessionId?: string
): Promise<AgentResult> {
const session = await query({
prompt,
options: {
allowedTools,
...(sessionId ? { resume: sessionId } : {}),
hooks: {
PreToolUse: [
{
matcher: "Bash",
hook: async (input) => {
if (input.tool_input.command.includes("rm -rf")) {
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "rm -rf is prohibited",
},
};
}
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
},
};
},
},
],
},
},
});
const messages = [];
for await (const message of session) {
if (message.type === "stream_event") {
messages.push(message.event);
if (message.event.type === "result") {
return {
sessionId: session.sessionId,
finalText: message.event.result,
output: JSON.stringify(messages),
};
}
}
}
throw new Error("Agent did not produce a result");
}
async function main() {
// Step 1: Explorer Agent — read and plan
const explorerResult = await runAgent(
`You are the code explorer. Read the payment module under src/payments/.
Analyze the current structure and potential issues. Output a concise
refactoring plan with specific file paths and actions.`,
["Read", "Glob", "Grep"]
);
console.log("===== Explorer Agent Plan =====");
console.log(explorerResult.finalText);
// Step 2: Implementer Agent — execute the plan
const implementerResult = await runAgent(
`You are the implementer. Here is the refactoring plan from the explorer:
${explorerResult.finalText}
Implement the plan. Edit the code, make the changes, and run the tests
to make sure everything passes.`,
["Read", "Write", "Edit", "Bash"]
);
console.log("===== Implementer Agent Result =====");
console.log(implementerResult.finalText);
// Step 3: Reviewer Agent — review the changes
const reviewResult = await runAgent(
`You are the code reviewer. Here is what the implementer did:
${implementerResult.finalText}
Review the current git diff and the files that were changed.
Look for bugs, security issues, and code quality issues.
Write a detailed review report.`,
["Read", "Bash", "Glob", "Grep"]
);
console.log("===== Reviewer Agent Report =====");
console.log(reviewResult.finalText);
}
main().catch(console.error);
Pourquoi ce modèle est-il plus maintenable ?
- Contexte isolé à chaque exécution : chaque agent a un contexte neuf, il n'a pas besoin de transporter toutes les données d'une exécution précédente, ce qui évite le problème du dépassement du contexte.
- Limites de tâches : en séparant « analyse », « exécution » et « relecture », vous obtenez naturellement une séparation des tâches et des permissions. L'implémenteur n'a pas besoin de pouvoir parcourir tout Internet ; le relecteur n'a pas besoin de pouvoir écrire des fichiers.
- Observabilité : chaque étape produit une sortie claire (plan, modifications, rapport). Elles peuvent être stockées, comparées et auditées.
- Gestion élégante des échecs : vous pouvez envelopper chaque étape avec une logique de nouvelle tentative, ou si l'implémenteur échoue, redémarrer à partir de la sortie de l'explorateur sans réinjecter tout l'historique de l'agent.
7. Résumé : le SDK est le billet d'entrée dans l'ère des agents
Récapitulons ce que nous avons vu :
- Principes d'architecture : tout est une session. Le SDK gère la gestion d'état pour vous ; l'amont et l'aval ne sont que des
sessionId. - Mécanismes fondamentaux : outils intégrés, système de permissions, hooks. Vous pouvez contrôler la boucle de l'agent avec
isAllowed(statique) et les hooks (dynamique). - Pratique de production : avec les hooks, vous pouvez construire des passerelles de sécurité ; avec
query(), vous pouvez intégrer des agents dans du CI/CD ; et avec plusieurs sessions, vous pouvez construire des pipelines multi-agents. - Modèles d'intégration : le CLI convient aux intégrations simples, le SDK convient aux intégrations personnalisées.
Dois-je encore écrire ma propre boucle d'Agent ?
Non. C'est comme demander « Dois-je encore écrire ma propre pile TCP alors que j'ai HTTP ? » — Vous n'avez besoin d'écrire une logique personnalisée que lorsque vous avez des exigences vraiment particulières. Le claude-agent-sdk est déjà suffisamment modulaire. Si vous avez vraiment besoin d'un contrôle plus bas niveau, vous pouvez utiliser directement le SDK officiel d'Anthropic. Mais pour des applications agents de niveau production, construire sur claude-agent-sdk est le choix le plus efficace, robuste et maintenable.
L'avenir de l'ingénierie IA ne repose pas sur le battage médiatique, mais sur les processus, les permissions, l'observabilité et le pragmatisme. Le SDK n'est pas un jouet, c'est l'avenir de l'ingénierie logicielle.
Références
Cet article a été traduit par Claude. Si vous trouvez des problèmes de traduction, n'hésitez pas à me le faire savoir.
Auteur :Jarvis
Lien :https://www.airouter.me/blog/claude-code-agent-sdk
Réutilisation non commerciale avec attribution (CC BY-NC-SA 4.0).