Idée en 30 secondes
Write Access by Default est un anti-pattern où l'agent reçoit des write-tools par défaut, sans policy-gate ni vérification avant exécution.
Résultat : une erreur du modèle ou un mauvais routage ne donne pas seulement une mauvaise réponse, mais un vrai changement d'état externe.
Règle simple : le write ne doit pas être "par défaut". Il doit passer par une voie séparée et contrôlée avec vérification explicite des droits, du contexte et des conditions d'exécution.
Exemple d'anti-pattern
L'équipe construit un agent de support qui lit le statut d'une commande et peut fermer un ticket ou envoyer un email si nécessaire.
L'agent reçoit les write-tools immédiatement, sans étape dédiée de vérification avant action.
decision = agent.decide_next_action(user_message)
# route erronée, mais write reste disponible
result = run_tool(decision.tool, decision.args)
return result
Dans ce schéma, la protection de base manque :
# pas de deny-by-default pour les write-tools
# pas de approval_required pour les actions risquées
# pas de idempotency_key pour les retries
# pas de tenant/env scope strict
Pour ce cas, il faut un policy-gate avant toute étape write :
if decision.tool in WRITE_TOOLS and not is_write_allowed(ctx, decision):
return stop("approval_required")
Si les conditions ne sont pas remplies, le run ne doit pas passer à une action write externe.
Dans ce cas, Write Access by Default ajoute :
- risque de changements indésirables dans les systèmes externes
- opérations write dupliquées lors des retries
- blast radius plus large en environnement multi-tenant
Pourquoi ça apparaît et ce qui se passe mal
Cet anti-pattern apparaît souvent quand l'équipe veut un agent "le plus autonome possible" et ouvre l'accès write avant de construire les limites de contrôle.
Causes typiques :
- approche démo : donner tous les tools d'abord, ajouter les limites ensuite
- absence de séparation explicite des routes
readetwrite - règles d'accès décrites dans le prompt, pas imposées dans le gateway
- absence d'
idempotency_keyobligatoire pour les write-opérations
Résultats :
- side effects dangereux (changements d'état) - l'agent peut écrire alors qu'il devait seulement lire
- actions répétées - retry ou loop répète la même write-opération
- blast radius élevé - sans scope strict, l'erreur touche le mauvais tenant/env
- analyse d'incident difficile - compliqué de prouver pourquoi write a été autorisé
- perte de prévisibilité - l'équipe ne contrôle pas quand le système passe en write
Contrairement à Blind Tool Trust, le problème principal ici n'est pas la validation de payload, mais l'accès write ouvert par défaut.
Signaux de production typiques d'un contrôle write faible :
- des write-tools sont appelés même dans des scénarios censés être read-only
- les logs montrent beaucoup d'appels write avec le même
args_hashou sansidempotency_key approval_requiredapparaît presque jamais alors que la part de write est élevée- les blocked write attempts sont rares alors que le système propose souvent des risky actions
- les audit-logs ne montrent pas quelle règle policy a autorisé le write
- l'équipe ne peut pas expliquer clairement pourquoi un write précis a été autorisé dans ce run
- les erreurs sont détectées après l'action externe, pas au policy-gate
Point important : chaque write-call modifie l'état externe et n'a souvent pas de rollback simple. Sans deny-by-default et write path contrôlé, une inference ratée devient un incident production.
Bonne approche
Commencez par un modèle read-first : les read-tools sont accessibles par route, et les étapes write passent une vérification dédiée via policy-gate.
Cadre pratique :
- appliquez deny-by-default à tous les write-tools
- séparez les routes
read_onlyetwrite_candidate - exigez approval pour les write-actions risquées
- prenez
tenant_idetenvuniquement depuis le contexte authentifié, jamais depuis model output - ajoutez
idempotency_keyà chaque write-opération - loggez
stop_reasonet la décision policy-gate pour chaque write-step
WRITE_TOOLS = {"ticket.close", "refund.create", "email.send"}
def execute_action(user_message: str, ctx: dict):
decision = agent.next_action(user_message)
if decision.tool in WRITE_TOOLS:
if not is_write_allowed(ctx, decision): # policy gate: role, route allowlist, tenant/env scope
return stop("approval_required")
scoped_args = enforce_scope(
decision.args,
tenant_id=ctx["tenant_id"],
env=ctx["env"],
)
scoped_args["idempotency_key"] = make_idempotency_key(ctx["run_id"], decision)
return run_tool(decision.tool, scoped_args)
return run_tool(decision.tool, decision.args) # read-only tool from allowed set
Dans ce schéma, l'étape write devient contrôlée : le système exécute de façon sûre ou arrête le run de manière transparente.
Test rapide
Si la réponse à ces questions est "oui", vous avez un risque d'anti-pattern Write Access by Default :
- Un write-tool peut-il être appelé sans vérification explicite policy/approval ?
- Un retry répète-t-il parfois la même write-action sans
idempotency_key? - L'équipe ne peut-elle pas expliquer rapidement pourquoi un write précis a été autorisé ?
Différence avec les autres anti-patterns
Blind Tool Trust vs Write Access by Default
| Blind Tool Trust | Write Access by Default |
|---|---|
| Problème principal : le tool output est accepté sans validation. | Problème principal : l'accès write est ouvert par défaut. |
| Quand il apparaît : quand les checks parse/schema/invariant manquent avant la décision. | Quand il apparaît : quand l'étape write ne passe pas par deny-by-default et approval-gate. |
En bref : Blind Tool Trust concerne la qualité des données avant action, alors que Write Access by Default concerne les droits de l'action elle-même.
Agents Without Guardrails vs Write Access by Default
| Agents Without Guardrails | Write Access by Default |
|---|---|
| Problème principal : les limites runtime et le contrôle policy manquent globalement. | Problème principal : les write-opérations n'ont pas de contour d'accès strict. |
| Quand il apparaît : quand le système manque de safety-policy claire pour l'execution. | Quand il apparaît : quand write est autorisé comme chemin standard au lieu d'une exception via policy-gate. |
En bref : Agents Without Guardrails est un problème plus large de limites d'exécution, alors que Write Access by Default cible un modèle d'accès write dangereux.
Tool Calling for Everything vs Write Access by Default
| Tool Calling for Everything | Write Access by Default |
|---|---|
| Problème principal : les tools sont appelés inutilement, même quand on peut s'en passer. | Problème principal : une fois l'appel tool fait, write peut passer sans contrôle suffisant. |
Quand il apparaît : quand il n'y a pas de route no_tool stable pour les cas simples. | Quand il apparaît : quand le système ne sépare pas les niveaux d'accès read et write. |
En bref : Tool Calling for Everything augmente le volume d'appels, alors que Write Access by Default augmente le coût d'erreur de chaque write-call.
Auto-vérification : avez-vous cet anti-pattern ?
Vérification rapide de l'anti-pattern Write Access by Default.
Cochez les points pour votre système et regardez le statut ci-dessous.
Vérifiez votre système :
Progression: 0/8
⚠ Il y a des signes de cet anti-pattern
Essayez de déplacer les étapes simples dans un workflow et de garder l'agent uniquement pour les décisions complexes.
FAQ
Q : Est-ce que cela veut dire que l'agent ne doit jamais faire d'actions write ?
R : Non. Les actions write sont possibles, mais seulement via un chemin contrôlé : policy-gate, approval (si nécessaire), scope enforcement et idempotency.
Q : Quelle différence entre policy-gate et approval ?
R : Policy-gate est un contrôle déterministe des règles à l'exécution. Approval est une confirmation séparée pour une action risquée précise. Ce sont deux niveaux de contrôle différents.
Q : Quel minimum implémenter d'abord ?
R : Commencez avec deny-by-default pour write, idempotency_key obligatoire, tenant/env scope strict et logging stop_reason pour les tentatives write bloquées.
Et ensuite
Anti-patterns proches :
- Blind Tool Trust - quand le système agit sur un tool output non validé.
- Agents Without Guardrails - quand l'execution tourne sans limites runtime claires.
- Tool Calling for Everything - quand les tools sont appelés sans besoin explicite.
Ce qu'il faut construire à la place :
- Allowed Actions - comment fixer les actions autorisées via des règles explicites.
- Tool Execution Layer - où centraliser policy, scope et idempotency.
- Stop Conditions - comment arrêter un run en sécurité quand write n'est pas autorisé.