Créer un agent de support nommé

CloudflareBeginner
Pratiquer maintenant

Introduction

On décrit souvent un agent IA comme un modèle capable de raisonner ou d’utiliser des outils. Avant d’ajouter un modèle, une application doit toutefois répondre de manière fiable à une question plus simple : quelle session en cours doit recevoir cette requête ? Une application de support doit renvoyer chaque interaction destinée à planning vers la même session logique, tout en gardant billing séparée.

L’Agents SDK de Cloudflare fournit une classe Agent de plus haut niveau pour cette tâche. Chaque agent nommé repose sur une instance SQLite Durable Object. Le SDK gère l’état enregistré et le routage des requêtes, tandis que les Durable Objects fournissent l’identité stable et le stockage sous-jacents. Vous observerez ces deux couches au lieu de considérer le SDK comme une boîte noire.

Vous allez créer une petite application de support, volontairement sans LLM :

  1. SupportAgent définit ce qu’une session de support stocke et fait.
  2. Le binding SupportAgent représente l’espace de noms de la classe.
  3. /agents/support-agent/planning sélectionne l’instance nommée planning.
  4. initialState, this.state et setState() permettent au SDK de conserver le petit état de cette instance.

Vous écrirez deux notes dans une session nommée, vous prouverez qu’une autre session reste isolée, vous arrêterez puis redémarrerez l’environnement local complet, vous déploierez le même code sur Cloudflare, vous inspecterez son binding et son espace de noms réels dans le Dashboard, puis vous supprimerez toutes les ressources temporaires.

Avant de commencer ce cours, terminez Connect LabEx to Your Cloudflare Account. Ce laboratoire présente le terminal de la VM LabEx, l’autorisation de l’appareil avec Wrangler, la confirmation du compte et la configuration de l’ID du compte. Vous devez déjà comprendre le fonctionnement d’un petit Worker TypeScript et le modèle d’identité des Durable Objects étudié dans O01–O06. Aucune connaissance préalable de l’Agents SDK, de React ou des modèles n’est requise.

La documentation officielle rend actuellement les Durable Objects reposant sur SQLite disponibles avec Workers Free. Ce laboratoire crée un seul espace de noms de classe temporaire, quelques petites instances d’Agent et uniquement des requêtes limitées. Il n’appelle aucun modèle et ne nécessite pas Workers Paid. La configuration installe Node.js 22.22.0, Agents SDK 0.23.0 et Wrangler 4.134.0, installé localement au projet, dans /home/labex/project/named-support-agent ; elle ne se connecte pas, ne crée pas d’état cloud, ne déploie pas de code et ne réalise pas l’implémentation demandée à l’apprenant.

Autoriser la VM et configurer l’Agent

Dans cette étape, vous allez autoriser Wrangler, confirmer le compte d’apprentissage prévu et décrire une classe d’Agent sans rien déployer pour le moment. Cette nouvelle VM possède son propre système de fichiers : le fait d’être connecté au Cloudflare Dashboard ne suffit donc pas à autoriser son terminal.

Accédez au projet préparé et vérifiez les versions imposées :

cd /home/labex/project/named-support-agent
node --version
npx wrangler --version
npm list agents --depth=0

Vous devez obtenir Node.js v22.22.0, Wrangler 4.134.0 et agents@0.23.0. Le verrouillage des versions est important, car l’Agents SDK évolue plus rapidement que les API de base des Workers.

Démarrez le processus d’autorisation de l’appareil :

npx wrangler login --device --browser=false

Wrangler affiche une URL de navigateur et un court code d’appareil. Ouvrez cette URL, saisissez le code, vérifiez que le compte sélectionné est votre compte d’apprentissage dédié et examinez les autorisations demandées avant d’autoriser l’accès. Ne saisissez jamais de mot de passe Cloudflare ni de jeton API dans le terminal.

Lorsque le navigateur indique que l’opération a réussi, revenez au terminal et attendez que Wrangler termine. Demandez les informations d’identité structurées :

npx wrangler whoami --json

Vérifiez que loggedIn: true apparaît. Affichez ensuite uniquement les noms des comptes et sélectionnez en privé l’ID correspondant à LabEx Learning :

WHOAMI="$(npx wrangler whoami --json)"
printf '%s\n' "$WHOAMI" | jq '{loggedIn, authType, accounts: [.accounts[] | {name}]}'
ACCOUNT_ID="$(printf '%s\n' "$WHOAMI" | jq -r '.accounts[] | select(.name == "LabEx Learning") | .id')"
test -n "$ACCOUNT_ID"

Si votre compte d’apprentissage dédié possède un autre nom d’affichage, remplacez LabEx Learning uniquement après avoir confirmé le nom correct. L’ID du compte est une valeur de configuration, pas un secret, mais cette commande évite de l’afficher inutilement.

Créez un nom de Worker temporaire et unique :

RUN="labex-c11-s01-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"

Créez wrangler.jsonc :

cat > wrangler.jsonc <<JSON
{
  "\$schema": "./node_modules/wrangler/config-schema.json",
  "name": "$RUN",
  "account_id": "$ACCOUNT_ID",
  "main": "src/index.ts",
  "compatibility_date": "2026-09-18",
  "compatibility_flags": ["nodejs_compat"],
  "workers_dev": true,
  "preview_urls": false,
  "observability": {
    "enabled": true,
    "head_sampling_rate": 1
  },
  "durable_objects": {
    "bindings": [
      { "name": "SupportAgent", "class_name": "SupportAgent" }
    ]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["SupportAgent"] }
  ]
}
JSON

Le binding SupportAgent est la référence du Worker vers l’espace de noms de la classe. La migration v1 indique à Cloudflare de créer cette classe avec un stockage SQLite. Les Agents utilisent l’infrastructure des Durable Objects ; le SDK ne supprime pas cette couche de ressources. L’option nodejs_compat est actuellement requise par le SDK. Aucune de ces déclarations ne crée de ressource cloud avant le déploiement.

Implémenter l’agent de support nommé

Dans cette étape, vous allez implémenter l’état et le comportement HTTP communs à toutes les sessions de support nommées. Une classe d’Agent définit un comportement réutilisable, tandis qu’une instance d’Agent correspond à une session nommée, telle que planning. Cloudflare peut exécuter de nombreuses instances de la même classe, et chaque instance possède son propre état.

Créez src/index.ts :

cat > src/index.ts <<'TS'
import { Agent, routeAgentRequest } from "agents";

export interface SupportState {
  status: "new" | "active";
  noteCount: number;
  lastNote: string | null;
}

interface Env {
  SupportAgent: DurableObjectNamespace<SupportAgent>;
}

function json(value: unknown, init: ResponseInit = {}): Response {
  const headers = new Headers(init.headers);
  headers.set("content-type", "application/json; charset=utf-8");
  return new Response(JSON.stringify(value, null, 2), { ...init, headers });
}

export class SupportAgent extends Agent<Env, SupportState> {
  initialState: SupportState = {
    status: "new",
    noteCount: 0,
    lastNote: null
  };

  async onRequest(request: Request): Promise<Response> {
    if (request.method === "GET") {
      console.log(JSON.stringify({ event: "support_agent_read", instance: this.name, noteCount: this.state.noteCount }));
      return json({ instance: this.name, ...this.state });
    }

    if (request.method === "POST") {
      const body = await request.json<{ note?: unknown }>().catch(() => null);
      const note = typeof body?.note === "string" ? body.note.trim() : "";
      if (note.length < 1 || note.length > 120) {
        return json({ error: "note must contain 1-120 characters" }, { status: 400 });
      }

      this.setState({
        status: "active",
        noteCount: this.state.noteCount + 1,
        lastNote: note
      });
      console.log(JSON.stringify({ event: "support_agent_updated", instance: this.name, noteCount: this.state.noteCount }));
      return json({ instance: this.name, ...this.state });
    }

    return json({ error: "method not allowed" }, { status: 405 });
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname === "/health") {
      return json({ status: "ok" });
    }

    const agentResponse = await routeAgentRequest(request, env, {
      onBeforeRequest(incoming, { name }) {
        if (!/^[a-z][a-z0-9-]{1,31}$/.test(name)) {
          return json({ error: "invalid support session name" }, { status: 400 });
        }
        return incoming;
      }
    });
    return agentResponse ?? json({ error: "not found" }, { status: 404 });
  }
} satisfies ExportedHandler<Env>;
TS

Lisez les éléments importants de l’intérieur vers l’extérieur :

  • initialState est la valeur utilisée par une instance nommée nouvellement créée.
  • this.state lit l’état actuel de cette instance, géré par le SDK.
  • setState() valide et enregistre de manière synchrone le nouvel état dans le stockage SQLite de l’instance ; les prochains laboratoires le synchroniseront également avec les clients connectés.
  • this.name est le nom d’instance stable sélectionné par le routage. Ce n’est ni un nom de classe ni un identifiant aléatoire de processus.
  • routeAgentRequest() associe /agents/<binding>/<name> à l’Agent approprié. Le binding SupportAgent devient support-agent dans l’URL.
  • onBeforeRequest rejette les noms incorrects avant la sélection d’une instance de Durable Object, ce qui évite de créer des identités durables indésirables.

Le journal enregistre uniquement un nom d’instance synthétique et un compteur. Le texte des notes en est volontairement exclu afin que l’exercice ultérieur sur le Dashboard ne conserve pas de contenu de support.

Générer les types et construire le projet avant l’exécution

Dans cette étape, vous allez générer des types tenant compte de la configuration et construire le Worker sans le déployer. Les types Worker générés relient la configuration à TypeScript et détectent une faute de frappe dans un binding ou une classe avant qu’un processus local ou un déploiement cloud ne fasse perdre du temps.

Générez les types à partir de wrangler.jsonc :

npx wrangler types

Wrangler écrit worker-configuration.d.ts. Vérifiez que ce fichier contient le binding d’Agent configuré, sans afficher d’autre contenu généré :

grep -n "SupportAgent" worker-configuration.d.ts | head

Lancez le compilateur TypeScript :

npm run check

Si aucune sortie n’apparaît après l’en-tête du script, le compilateur n’a détecté aucune erreur. Demandez maintenant à Wrangler de construire le bundle de déploiement sans contacter Cloudflare ni créer de ressource :

npx wrangler deploy --dry-run --outdir .labex/dry-run

Vous devez obtenir un résumé réussi de la taille à téléverser ainsi que le binding Durable Object SupportAgent. Un dry run valide localement le bundling et la configuration ; il ne prouve ni l’autorisation, ni le stockage distant, ni le comportement en périphérie.

Prouver l’identité locale et la persistance après redémarrage

Dans cette étape, vous allez démontrer trois propriétés distinctes : l’utilisation répétée d’un même nom atteint un même état, un nom différent reste isolé et l’état enregistré survit au redémarrage complet du processus de développement.

Démarrez l’environnement d’exécution local des Workers en arrière-plan :

npm run dev > .labex/dev.log 2>&1 &
echo $! > .labex/dev.pid
for attempt in $(seq 1 30); do
  if curl --silent --fail http://127.0.0.1:8787/health; then
    break
  fi
  sleep 1
done

Vous devez obtenir {"status":"ok"}. Lisez le nouvel Agent planning :

curl --silent http://127.0.0.1:8787/agents/support-agent/planning | jq

Il commence avec status: "new", noteCount: 0 et lastNote: null. Ajoutez deux notes synthétiques :

curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Customer cannot open the invoice"}' \
  http://127.0.0.1:8787/agents/support-agent/planning | jq
curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Asked customer to retry"}' \
  http://127.0.0.1:8787/agents/support-agent/planning | jq

La deuxième réponse indique instance: "planning", status: "active", noteCount: 2 et la deuxième note. Les deux requêtes utilisent le même nom dans l’URL : elles ont donc atteint le même Agent logique.

Lisez une autre instance :

curl --silent http://127.0.0.1:8787/agents/support-agent/support | jq

support conserve son propre état initial, avec un compteur à 0. Les deux noms partagent le comportement de la classe, mais pas les valeurs enregistrées.

Rejetez un nom incorrect :

curl --silent --write-out '\nHTTP %{http_code}\n' \
  http://127.0.0.1:8787/agents/support-agent/INVALID

Vous devez obtenir invalid support session name et le code HTTP 400.

Arrêtez précisément le processus que vous avez démarré, puis lancez un nouveau processus avec le même répertoire de persistance locale :

kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
npm run dev > .labex/dev-restart.log 2>&1 &
echo $! > .labex/dev.pid
for attempt in $(seq 1 30); do
  if curl --silent --fail http://127.0.0.1:8787/health; then
    break
  fi
  sleep 1
done
curl --silent http://127.0.0.1:8787/agents/support-agent/planning | jq
curl --silent http://127.0.0.1:8787/agents/support-agent/support | jq

Après un redémarrage complet de Wrangler, planning reste à 2 tandis que support reste à 0. Il s’agit d’un indice plus solide qu’une double lecture dans un seul processus JavaScript : les données ont été relues depuis le répertoire de persistance local du Durable Object.

Déployer et tester les instances d’Agent dans le cloud

Dans cette étape, vous allez déployer l’application inchangée et tester de véritables instances d’Agent gérées dans le cloud. Les résultats locaux ne peuvent pas prouver que le compte Cloudflare sélectionné possède la ressource ni que l’environnement d’exécution en périphérie fournit la même identité nommée.

Arrêtez le processus local et déployez :

kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
npx wrangler deploy

Wrangler applique la migration v1, crée l’espace de noms de classe SupportAgent reposant sur SQLite et affiche une URL publique workers.dev. Enregistrez cette URL exacte en remplaçant l’exemple :

WORKER_URL="https://YOUR_WORKER_URL"

Attendez que la route de vérification sans état soit disponible :

for attempt in $(seq 1 30); do
  if curl --silent --fail "$WORKER_URL/health"; then
    break
  fi
  sleep 2
done

Testez maintenant les instances gérées dans le cloud avec des données synthétiques :

curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Cloud planning note one"}' \
  "$WORKER_URL/agents/support-agent/planning" | jq
curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Cloud planning note two"}' \
  "$WORKER_URL/agents/support-agent/planning" | jq
curl --silent --request POST --header 'content-type: application/json' \
  --data '{"note":"Independent support note"}' \
  "$WORKER_URL/agents/support-agent/support" | jq

Lisez les deux instances :

curl --silent "$WORKER_URL/agents/support-agent/planning" | jq
curl --silent "$WORKER_URL/agents/support-agent/support" | jq

L’Agent cloud planning possède un compteur de 2, tandis que l’Agent indépendant support possède un compteur de 1. Le stockage local et le stockage cloud sont volontairement séparés, mais les deux environnements appliquent le même contrat entre nom et instance.

Le vérificateur crée également deux noms d’Agent uniques pour cette exécution et répète les tests sur l’état initial, la persistance avec le même nom, l’isolation entre noms différents et le rejet des noms incorrects. Il ne considère jamais un fichier local ou l’historique des commandes comme une preuve du comportement distant.

Relier les résultats de l’environnement d’exécution au Dashboard

Dans cette étape, vous allez relier le comportement observé dans le terminal au binding, à l’espace de noms et aux journaux visibles dans le Dashboard. Les noms, horodatages et totaux des captures d’écran sont des exemples issus de l’exécution testée ; utilisez le nom unique labex-c11-s01-... obtenu dans votre propre terminal.

Ouvrez Workers & Pages dans le Cloudflare Dashboard et sélectionnez votre Worker temporaire. Sa vue d’ensemble identifie l’application déployée et le trafic récent.

The deployed named support Agent Worker in Workers and Pages

Ouvrez l’onglet Bindings du Worker. Repérez SupportAgent, connecté à la classe Durable Object SupportAgent. Le premier libellé est le nom visible par le code du Worker et le routage ; le nom de classe identifie l’implémentation exportée depuis src/index.ts.

The SupportAgent binding connected to its Durable Object class

Ouvrez Durable Objects depuis la navigation Developer Platform et sélectionnez l’espace de noms appartenant à votre Worker exact. Confirmez la classe SupportAgent et Storage: SQL. L’espace de noms est la collection au niveau de la classe ; planning, support et les noms utilisés par le vérificateur sont des instances individuelles qui s’y trouvent. L’image d’exemple masque l’ID d’espace de noms propre à l’exécution, pour des raisons de confidentialité.

The SupportAgent namespace showing SQL storage

Revenez au Worker et ouvrez Observability → Logs. Recherchez un événement applicatif support_agent_read ou support_agent_updated, puis développez-le. Comparez son instance synthétique et son noteCount avec une requête limitée. L’application n’enregistre volontairement aucun texte de note.

A structured support Agent event with its instance name and note count

Les métriques et les journaux du Dashboard peuvent apparaître avec un délai ; un graphique récent vide ne permet donc pas de conclure. L’API authentifiée, la propriété de l’espace de noms et les vérifications de l’environnement d’exécution réel restent les sources faisant foi. Les captures d’écran indiquent où retrouver visuellement ces mêmes relations ; elles ne constituent pas des éléments à remettre.

Supprimer l’espace de noms de l’Agent et le Worker

Dans cette étape, vous allez supprimer définitivement l’espace de noms exact de l’Agent et le Worker, tant que la VM est encore autorisée. L’état de l’Agent appartient à l’espace de noms de la classe Durable Object : supprimer uniquement le script du Worker ne constitue donc pas une demande explicite d’effacer cet état enregistré. Les migrations Cloudflare sont en ajout uniquement : conservez v1, puis ajoutez une migration de suppression v2 pour la classe exacte.

Créez un point d’entrée de nettoyage minimal, sans export d’Agent :

cat > src/cleanup.ts <<'TS'
export default {
  fetch() {
    return Response.json({ status: "cleanup" }, { status: 410 });
  }
};
TS

Lisez le nom exact et le compte depuis la configuration d’origine, puis créez wrangler.cleanup.jsonc :

RUN="$(node -e 'console.log(JSON.parse(require("fs").readFileSync("wrangler.jsonc", "utf8")).name)')"
ACCOUNT_ID="$(node -e 'console.log(JSON.parse(require("fs").readFileSync("wrangler.jsonc", "utf8")).account_id)')"
cat > wrangler.cleanup.jsonc <<JSON
{
  "\$schema": "./node_modules/wrangler/config-schema.json",
  "name": "$RUN",
  "account_id": "$ACCOUNT_ID",
  "main": "src/cleanup.ts",
  "compatibility_date": "2026-09-18",
  "compatibility_flags": ["nodejs_compat"],
  "workers_dev": true,
  "preview_urls": false,
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["SupportAgent"] },
    { "tag": "v2", "deleted_classes": ["SupportAgent"] }
  ]
}
JSON

Conservez v1 : l’historique des migrations est une séquence, et non une description à réécrire. v2 supprime définitivement l’espace de noms de la classe ainsi que toutes les instances nommées temporaires qu’il contient.

Déployez la migration de suppression :

npx wrangler deploy --config wrangler.cleanup.jsonc

Lisez la sortie de migration et confirmez que seul SupportAgent de votre Worker unique est supprimé. Supprimez ensuite le Worker de nettoyage sans état :

npx wrangler delete --config wrangler.cleanup.jsonc --force

Confirmez l’application exacte labex-c11-s01-... si Wrangler vous le demande. Dans Workers & Pages, vérifiez que le Worker exact a disparu. Ce compte de test ne contient aucune autre application ; après une exécution acceptée, toute la liste devient donc vide. Un compte contenant d’autres projets doit conserver les lignes correspondantes.

Workers and Pages showing no projects after the disposable Worker was removed

Ouvrez Durable Objects et vérifiez que l’espace de noms appartenant au Worker supprimé a également disparu. Le compte de test accepté ne contient aucun autre espace de noms : sa liste n’affiche donc aucun Durable Object. Ne supprimez pas un espace de noms appartenant à un autre projet simplement pour reproduire cet exemple.

Durable Objects showing no namespaces after the SupportAgent class was removed

Les journaux historiques peuvent rester temporairement disponibles ; ils ne constituent pas des ressources actives.

Exécutez la vérification authentifiée de l’absence avant de vous déconnecter :

python3 .labex/verify.py deleted

Seul PASS: deleted prouve que le compte sélectionné ne contient plus aucune des deux ressources détenues. Une erreur 404 due à une autorisation perdue ou une erreur réseau ne constitue pas une preuve de suppression acceptée.

Révoquer l’autorisation de cette VM

Dans cette étape, vous allez supprimer l’autorisation OAuth enregistrée dans cette VM temporaire. Le nettoyage des ressources cloud et celui des identifiants locaux répondent à deux problèmes différents ; le Worker et l’espace de noms ont déjà été supprimés.

Déconnectez-vous :

npx wrangler logout

Demandez à Wrangler un état structuré :

npx wrangler whoami --json

Le résultat doit contenir explicitement "loggedIn": false. Cette valeur structurée est plus fiable qu’un message convivial, car la version de Wrangler testée peut produire une sortie ordinaire dans plusieurs états d’authentification. Une défaillance réseau ne permet pas de conclure et doit être réessayée plutôt qu’interprétée comme une déconnexion.

Vous avez supprimé les deux types d’état créés par ce laboratoire : l’espace de noms et le Worker de l’Agent de support distants, ainsi que l’autorisation locale de la VM.

Résumé

Vous avez construit le premier Agent Cloudflare du cours sans masquer ses fondations derrière le vocabulaire de l’IA. Vous avez appris qu’une classe Agent définit le comportement, que son binding expose un espace de noms SQLite Durable Object, qu’un nom stable dans l’URL sélectionne une instance logique et que l’Agents SDK conserve les mises à jour de initialState via this.state et setState().

Vous avez prouvé localement la persistance avec le même nom, l’isolation entre noms différents et la durabilité après redémarrage du processus. Vous avez répété ce contrat dans votre compte Cloudflare d’apprentissage, relié les résultats d’exécution au binding, à l’espace de noms et aux journaux limités pour préserver la confidentialité dans le Dashboard, puis supprimé les ressources cloud ainsi que l’autorisation de la VM. Le prochain laboratoire connectera des clients de navigateur à cet état et introduira une synchronisation en temps réel contrôlée.