AI

AgentOps : valider la sortie d'un outil avant une écriture de production

Un runbook de production pour qualifier la sortie structurée d'un outil d'agent IA avec schéma, sources, diff, idempotence, policy, traces, validation humaine et rollback avant d'écrire en production.

31 juil. 2026 aiagentopsagentstoolsmcpmicrosoft-foundryautomationguardrailsobservabilityevaluationsecurityrunbookrollbackproduction

Un agent IA peut appeler le bon outil, recevoir une réponse valide, puis préparer une mauvaise écriture. Le danger n’est pas toujours l’échec visible. Il est souvent dans une sortie qui paraît exploitable : un patch de configuration, une liste de ressources à nettoyer, une règle à publier, un ticket de changement prérempli ou une commande de rollback. Si cette sortie n’est pas qualifiée avant exécution, l’équipe transforme une suggestion probabiliste en changement de production.

Le cas d’usage est un assistant d’exploitation connecté à Microsoft Foundry, à un serveur MCP ou à un orchestrateur interne. Il lit des runbooks, interroge des logs, construit un diff et propose une action bornée : désactiver une feature flag, ajuster une règle WAF, relancer un job, modifier une policy ou préparer un rollback. L’objectif du runbook est de décider si la sortie de l’outil peut devenir une écriture, doit rester en brouillon, doit être recalculée, ou doit être bloquée jusqu’à correction du contrat.

Figer la sortie comme un artefact de changement

Traitez la sortie de l’outil comme un artefact de production, pas comme une phrase de conversation. Elle doit porter son intention, son périmètre, ses sources, son diff, sa clé d’idempotence et son plan de rollback.

yaml tool-output-change-card.yml
change_candidate:
id: cand-20260731-0842
agent: ops-assistant-prod
conversation_id: conv-20260731-0834
tool: build_waf_policy_patch
transport: mcp
environment: production
requested_intent: reduire un faux positif WAF sur /partner/comment
proposed_write: ajouter une exclusion ciblee dans wafpol-app-prod
execution_mode: draft_only

required_fields:
- approved_sources
- proposed_diff
- target_resource
- risk_level
- validation_checks
- idempotency_key
- human_approval
- rollback_patch

hard_stop_when_missing:
- target_resource
- rollback_patch
- source_evidence
- validation_checks

Une sortie sans rollback n’est pas une action candidate. C’est une hypothèse. Elle peut alimenter une revue, pas une écriture de production.

Valider le schéma avant le sens métier

Avant de juger si la proposition est intelligente, vérifiez qu’elle respecte un contrat strict. Le schéma doit empêcher les champs libres dangereux, les cibles ambiguës et les modes d’exécution implicites.

json production-write-output-schema.json
{
"tool": "build_waf_policy_patch",
"schema_version": "2026-07-31",
"required": [
  "target_resource_id",
  "operation",
  "proposed_diff",
  "evidence",
  "validation",
  "rollback"
],
"allowed_operations": [
  "create_draft_patch",
  "validate_existing_patch"
],
"forbidden_fields": [
  "execute_now",
  "disable_all_rules",
  "unbounded_scope",
  "secret_value",
  "approval_override"
],
"target_constraints": {
  "environment": "production",
  "resource_allowlist_required": true,
  "change_ticket_required": true
}
}

Le contrat doit être vérifié par le runtime ou par le tool gateway, pas seulement par le prompt. Si le modèle peut produire execute_now, le backend doit refuser ce champ mécaniquement.

Relier chaque proposition à une preuve

Une sortie d’outil exploitable doit expliquer pourquoi elle propose ce changement. Les sources doivent être identifiables : trace, log, runbook, policy actuelle, diff Git, ticket ou observation humaine. Une phrase comme “cela devrait corriger le problème” ne suffit pas.

json tool-output-evidence.json
{
"candidate_id": "cand-20260731-0842",
"evidence": [
  {
    "kind": "kql_result",
    "source_id": "waf-blocks-20260731-0800",
    "claim": "rule 942430 blocks only /partner/comment for partner.example.com",
    "time_window": "2026-07-31T07:40:00Z/2026-07-31T08:20:00Z"
  },
  {
    "kind": "current_config",
    "source_id": "wafpol-app-prod@8f42c31",
    "claim": "no existing exclusion covers RequestArgNames comment"
  },
  {
    "kind": "runbook",
    "source_id": "azure-waf-add-targeted-owasp-crs-exclusion",
    "claim": "prefer narrow exclusion over disabling managed rule"
  }
],
"unsupported_claims": []
}

Demandez explicitement les claims non prouvés. S’il en reste, la sortie peut être conservée comme brouillon, mais elle ne doit pas déclencher l’action.

Comparer le diff à l’intention

Le diff est le coeur de la validation. Une sortie peut être conforme au schéma et pourtant dépasser l’intention initiale : changer plusieurs ressources, modifier un environnement voisin, supprimer une règle ou toucher un champ non demandé.

text diff-intent-review.txt
Intention demandee
Corriger un faux positif WAF sur partner.example.com /partner/comment
Garder OWASP/CRS en prevention
Ne pas modifier les autres hosts
Ne pas ajouter d'allowlist IP

Diff propose
Cible: wafpol-app-prod
Ajout: exclusion ruleId 942430
Variable: RequestArgNames
Selector: comment
Scope: listener partner.example.com
Aucun changement de mode prevention/detection
Aucun changement de custom rule prioritaire

Decision
Compatible avec l'intention si les logs prouvent le meme ruleId et le meme champ
Rejeter si la sortie elargit a tous les hosts, a tous les arguments ou au managed rule set complet

La bonne question n’est pas “le patch fonctionne-t-il ?” mais “le patch correspond-il exactement au problème prouvé ?”.

Tester l’idempotence et les collisions

Une écriture agentique doit pouvoir être rejouée ou annulée sans produire un deuxième effet. Même un patch de configuration doit porter une clé stable et vérifier l’état courant avant application.

yaml idempotency-and-collision-checks.yml
idempotency:
key: wafpol-app-prod:rule-942430:RequestArgNames:comment
safe_when:
  - proposed_patch_already_present_with_same_scope
  - current_config_sha_matches_reviewed_sha
  - no_parallel_change_for_target_resource
unsafe_when:
  - target_policy_changed_after_diff
  - another_candidate_changes_same_rule
  - patch_removes_existing_control
  - rollback_patch_does_not_restore_previous_state

pre_write_checks:
- refresh_current_config
- compare_config_sha
- search_open_change_candidates
- recompute_diff_if_target_changed

Si la configuration courante a changé depuis la génération de la sortie, la décision la plus propre est de recalculer. Appliquer un vieux diff validé dans un nouvel état crée une dette difficile à expliquer.

Tracer la sortie, pas seulement l’exécution

L’observabilité doit montrer la sortie proposée avant l’écriture. Sinon, l’équipe voit seulement l’action finale et perd le raisonnement qui a transformé la réponse de l’outil en changement.

kusto agent-tool-output-validation.kql
let CandidateId = "cand-20260731-0842";
AgentToolOutputEvents
| where TimeGenerated > ago(24h)
| where CandidateId == CandidateId
| project TimeGenerated,
        AgentName,
        ConversationId,
        ToolName,
        OutputSchemaVersion,
        TargetResource,
        ProposedOperation,
        EvidenceCount,
        UnsupportedClaimCount,
        DiffHash,
        CurrentConfigHash,
        IdempotencyKey,
        PolicyDecision,
        ApprovalState,
        RollbackHash
| order by TimeGenerated asc

Adaptez les noms de tables à votre pipeline. Le signal important est la transition : sortie générée, sortie validée, sortie approuvée, écriture exécutée, validation post-change, rollback prêt.

Faire passer la sortie par une policy

La policy doit refuser les écritures dangereuses même quand la réponse paraît bien rédigée. Elle doit borner les opérations, les ressources, les environnements, les champs modifiables et le niveau d’approbation.

yaml tool-output-policy.yml
policy: production_tool_output_gate
default: deny
allow_when:
- output_schema_valid
- target_resource_allowlisted
- current_config_hash_matches
- evidence_count_at_least: 2
- unsupported_claim_count: 0
- rollback_patch_present
- human_approval_state: approved

deny_when:
- operation_requests_direct_execution
- diff_touches_unrequested_resource
- output_contains_secret_or_token
- approval_scope_differs_from_target
- rollback_changes_more_than_forward_patch
- validation_checks_missing

write_modes:
draft: allowed_without_approval
plan: approval_required
apply: human_required_and_breakglass_forbidden

Cette étape évite de déplacer tout le contrôle dans la conversation. Le modèle propose, la policy décide, l’humain approuve le périmètre sensible.

Décider publier, recalculer ou rollbacker

Fermez le runbook avec une décision explicite. Une sortie validée n’est publiable que si le diff, les preuves, la policy et le rollback tiennent ensemble.

text tool-output-decision-card.txt
Publier l'ecriture
Schema valide et versionne
Cible production unique et allowlistee
Preuves reliees a chaque claim operationnel
Diff compatible avec l'intention initiale
Config courante identique a la config revue
Idempotence et collisions verifiees
Policy en allow et approbation humaine presente
Validation post-change et rollback testes

Recalculer la sortie
Config cible modifiee depuis le diff
Preuves incompletes ou fenetre de logs trop vieille
Changement concurrent detecte
Intention utilisateur reformulee

Bloquer ou rollbacker
Sortie hors schema
Champ d'execution directe present
Scope plus large que l'incident
Rollback absent ou plus risqué que le changement
Impossible d'expliquer la sortie a partir des sources

Le résultat attendu n’est pas de faire confiance à l’agent. C’est de rendre sa proposition exploitable : traçable, bornée, validable et réversible. Quand ces conditions ne sont pas réunies, garder la sortie en brouillon est une décision de production, pas un ralentissement.