Dérive silencieuse d’un agent (régressions) + détection + code

  • Repère la panne tôt, avant que la facture grimpe.
  • Comprends ce qui casse en prod, et pourquoi.
  • Copie des garde-fous : budgets, stop reasons, validation.
  • Sache quand ce n’est pas la vraie cause.
Signaux de détection
  • Tool calls/run explosent (ou se répètent avec args hash).
  • Spend/tokens montent sans amélioration des outputs.
  • Retries passent de rares à constants (429/5xx).
Les agents ne cassent pas d’un coup. Ils dérivent via changements de modèle/tools/prompts jusqu’à shipper une régression en prod. Canary, golden tasks, replay et métriques détectent tôt.
Sur cette page
  1. Le problème (côté prod)
  2. Pourquoi ça casse en prod
  3. Exemple d’implémentation (code réel)
  4. Incident réel (avec chiffres)
  5. Compromis
  6. Quand NE PAS l’utiliser
  7. Checklist (copier-coller)
  8. Config par défaut sûre (JSON/YAML)
  9. FAQ (3–5)
  10. Pages liées (3–6 liens)
Flux interactif
Scénario:
Étape 1/2: Execution

Normal path: execute → tool → observe.

Le problème (côté prod)

Rien n’a changé.

Sauf :

  • un prompt “un peu”
  • un tool output qui a un nouveau champ
  • une version de modèle
  • un index de retrieval

L’agent “marche”. Mais différemment. Et tu ne le vois pas tout de suite.

Pourquoi ça casse en prod

  • output modèle non stable
  • tools qui driftent
  • prompts traités comme du texte, pas comme du code
  • drift visible d’abord sur coût/latence/tool calls, pas sur “success rate”

Fix : golden tasks + replay + canary + alerting sur deltas.

Exemple d’implémentation (code réel)

Harness minimal de drift (baseline vs candidate) :

PYTHON
from dataclasses import dataclass


@dataclass(frozen=True)
class GoldenTask:
  id: str
  input: str


def run_agent(version: str, task: GoldenTask) -> dict:
  return agent_run(version=version, input=task.input)  # (pseudo)


def score(run: dict) -> dict:
  return {
      "stop_reason": run.get("stop_reason"),
      "tool_calls": int(run.get("tool_calls", 0)),
      "tokens": int(run.get("tokens_total", 0)),
  }


def drift_check(tasks: list[GoldenTask], *, baseline: str, candidate: str) -> None:
  for t in tasks:
      b = score(run_agent(baseline, t))
      c = score(run_agent(candidate, t))

      if c["stop_reason"] != b["stop_reason"]:
          raise RuntimeError(f"[{t.id}] stop_reason drift: {b['stop_reason']} -> {c['stop_reason']}")

      if c["tool_calls"] > b["tool_calls"] + 3:
          raise RuntimeError(f"[{t.id}] tool_calls drift: {b['tool_calls']} -> {c['tool_calls']}")
JAVASCRIPT
export function score(run) {
return {
  stopReason: run.stop_reason,
  toolCalls: Number(run.tool_calls || 0),
  tokens: Number(run.tokens_total || 0),
};
}

export function driftCheck(tasks, { baseline, candidate, runAgent }) {
for (const t of tasks) {
  const b = score(runAgent(baseline, t));
  const c = score(runAgent(candidate, t));

  if (c.stopReason !== b.stopReason) {
    throw new Error("[" + t.id + "] stop_reason drift: " + b.stopReason + " -> " + c.stopReason);
  }

  if (c.toolCalls > b.toolCalls + 3) {
    throw new Error("[" + t.id + "] tool_calls drift: " + b.toolCalls + " -> " + c.toolCalls);
  }
}
}

Incident réel (avec chiffres)

Upgrade de modèle sur un agent support. Pas de canary, pas de golden tasks.

Le nouveau modèle était “plus thorough” et appelait search.read plus souvent.

Impact (24h) :

  • tool calls/run : 2.8 → 9.6
  • latence p95 : 2.7s → 7.4s
  • spend : +$460

Fix :

  1. golden tasks avec seuils de drift (tool calls, stop reasons)
  2. canary 1% + auto-rollback sur spikes
  3. replay de traces réelles redacted
  4. dashboards (tokens, tool calls, stop reasons, latence)

Compromis

  • maintenance des golden tasks
  • complexité canary/rollback
  • drift peut être “bon”, mais il faut le mesurer pour décider

Quand NE PAS l’utiliser

  • low-stakes : tu peux être plus souple (surveille le spend quand même)
  • distrib de tâches instable : commence par smoke tests
  • replay impossible (PII) : tâches synthétiques + budgets stricts

Checklist (copier-coller)

  • [ ] Golden tasks “réalistes”
  • [ ] Replay set redacted
  • [ ] Canary + rollback triggers
  • [ ] Seuils drift (tool calls/tokens/latence/stop reasons)
  • [ ] Versions pin
  • [ ] Revue “what changed” régulière

Config par défaut sûre (JSON/YAML)

YAML
releases:
  canary_percent: 1
  rollback_on:
    tool_calls_per_run_increase_pct: 50
    tokens_per_request_increase_pct: 50
    latency_p95_increase_pct: 50
eval:
  golden_tasks_required: true
  drift_thresholds:
    tool_calls_delta: 3
    stop_reason_changes: 0

FAQ (3–5)

La dérive est toujours mauvaise ?
Non. Mais la dérive non mesurée est mauvaise. Sans métriques, tu ne sais pas si c’est mieux ou juste plus cher.
Quoi monitorer en premier ?
Tool calls/run, tokens/request, latence p95, stop reasons. Ça bouge avant les plaintes.
Canary pour chaque edit de prompt ?
Pour du high-stakes/high-traffic : oui. Les prompts, c’est du code.
Replay de traces en sécurité ?
Redaction PII, args hash quand possible, et snapshots des tool outputs.

Q : La dérive est toujours mauvaise ?
R : Non. Mais la dérive non mesurée est mauvaise. Sans métriques, tu ne sais pas si c’est mieux ou juste plus cher.

Q : Quoi monitorer en premier ?
R : Tool calls/run, tokens/request, latence p95, stop reasons. Ça bouge avant les plaintes.

Q : Canary pour chaque edit de prompt ?
R : Pour du high-stakes/high-traffic : oui. Les prompts, c’est du code.

Q : Replay de traces en sécurité ?
R : Redaction PII, args hash quand possible, et snapshots des tool outputs.

Pages liées (3–6 liens)

Pas sur que ce soit votre cas ?

Concevez votre agent ->
⏱️ 4 min de lectureMis à jour Mars, 2026Difficulté: ★★☆
Implémenter dans OnceOnly
Guardrails for loops, retries, and spend escalation.
Utiliser dans OnceOnly
# onceonly guardrails (concept)
version: 1
budgets:
  max_steps: 25
  max_tool_calls: 12
  max_seconds: 60
  max_usd: 1.00
policy:
  tool_allowlist:
    - search.read
    - http.get
controls:
  loop_detection:
    enabled: true
    dedupe_by: [tool, args_hash]
  retries:
    max: 2
    backoff_ms: [200, 800]
stop_reasons:
  enabled: true
logging:
  tool_calls: { enabled: true, store_args: false, store_args_hash: true }
Intégré : contrôle en productionOnceOnly
Ajoutez des garde-fous aux agents tool-calling
Livrez ce pattern avec de la gouvernance :
  • Budgets (steps / plafonds de coût)
  • Kill switch & arrêt incident
  • Audit logs & traçabilité
  • Idempotence & déduplication
  • Permissions outils (allowlist / blocklist)
Mention intégrée : OnceOnly est une couche de contrôle pour des systèmes d’agents en prod.
Exemple de policy (concept)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}
Auteur

Cette documentation est organisée et maintenue par des ingénieurs qui déploient des agents IA en production.

Le contenu est assisté par l’IA, avec une responsabilité éditoriale humaine quant à l’exactitude, la clarté et la pertinence en production.

Les patterns et recommandations s’appuient sur des post-mortems, des modes de défaillance et des incidents opérationnels dans des systèmes déployés, notamment lors du développement et de l’exploitation d’une infrastructure de gouvernance pour les agents chez OnceOnly.