kisenon
Agent-Safe Change Control

Guardrails

Budgets, paliers de politique, autorité à portée limitée, lint de risque et vérifications du rayon d'explosion — ce qui arrête un agent avant qu'il ne vous nuise.

Cinq garde-fous entourent un sandbox d'agent IA. Deux sont des limites strictes que l'agent ne peut physiquement pas dépasser (LEASH, SCOPE), un transforme des données en verdict (POLICY), et deux produisent des données à peser (LINT, BLAST). Aucun ne place de LLM dans le chemin de confiance.

LEASH — budgets par sandbox

Bloque : un agent qui s'emballe — brûlant instructions, calcul ou temps réel sans borne.

Fixez un budget lorsque vous créez ou exécutez le sandbox :

keon sandbox create \
  --budget-statements 500 \
  --budget-compute-seconds 120 \
  --budget-wall-seconds 900
# or the same three flags on: keon sandbox run …

En cas de dépassement, le sandbox s'auto-termine : le statut devient discarded et budget_breached nomme quelle limite a sauté — statements, compute_seconds, ou wall_clock. La CLI se termine avec le code d'erreur sandbox_budget_breached. Un avertissement se déclenche à 80 % de n'importe quel budget.

Sur le fil, les budgets forment un objet budgets, une entrée par budget :

"budgets": {
  "statements":      { "limit": 500, "used": 218, "warned": false },
  "compute_seconds": { "limit": 120, "used": 41,  "warned": false },
  "wall_clock":      { "used": 63 }
}

Un limit absent signifie illimité — il n'y a pas de valeur sentinelle à mal interpréter.

Configurer les valeurs par défaut et les plafonds (owner/admin) :

# Per-project defaults double as the create-time cap.
curl -X PATCH .../v1/projects/{id} \
  -d '{"sandbox_budget_defaults": {"statements": 500, "compute_seconds": 120}}'

La valeur par défaut est le plafond : demander à la création plus que la valeur par défaut du projet renvoie 422 sandbox_budget_exceeds_cap. Les valeurs par défaut par projet sont configurables — il n'y a pas de nombre fixe à citer ici.

Deux invariants :

  • Figés à la création. Les budgets sont fixés lors de la création du sandbox ; changer les valeurs par défaut du projet plus tard ne re-borne jamais un sandbox en cours.
  • Les clés d'agent ne peuvent que resserrer. Une clé à capacité d'agent peut demander des budgets plus serrés à la création ; elle ne peut jamais fixer les valeurs par défaut du projet.

POLICY — paliers de politique de promotion

Bloque : une promotion dont les changements sont plus risqués que ce que le projet autorise — même une qu'un agent (ou un humain) tente de faire passer.

POLICY est une surcouche par projet associant une classe de risque à une action, posée par-dessus le promote_mode de base.

Configurer (CLI) :

keon projects promote-policy <projectId>                 # show effective policy
keon projects promote-policy <projectId> \
  --set destructive=block \
  --set unbounded=require_human \
  --on-conflicts require_human                           # merge-write
keon projects promote-policy <projectId> --clear         # drop the overlay

--set <class=action> est répétable et se fusionne dans la surcouche existante.

Configurer (API) : GET / PUT / DELETE /v1/projects/{projectId}/promote-policy :

{ "version": 1,
  "rules": { "destructive": "block", "unbounded": "require_human" },
  "on_conflicts": "require_human" }
ChampValeurs légales
classe de risqueadditive, mutating, destructive, unbounded, rewrite
actionauto_promote, require_human, block
on_conflictsignore, require_human, block

ignore n'est légal que pour on_conflicts, jamais comme action de classe.

La posture de base provient de promote_mode, et la surcouche la remplace par classe :

promote_modeChaque classe par défaut à
selfauto_promote
humanrequire_human

Chaque règle effective rapporte d'où elle vient — source: overlay, promote_mode, ou default.

Ce que l'agent voit en cas de blocage : une promotion bloquée renvoie 409 promote_blocked_by_policy avec les détails de la décision joints.

Règle clé : un Approve humain ne peut jamais outrepasser un block de politique. La politique est réévaluée au moment de l'approbation, donc block tient contre le clic. La validation de politique est fail-closed / stricte — une politique non analysable refuse, elle ne dégrade jamais vers ouvert.

SCOPE — autorité à portée limitée (couloirs)

Bloque : toute lecture ou écriture hors des schémas et tables que vous avez déclarés — le couloir de l'agent.

Configurer (à la création uniquement) :

keon sandbox create \
  --schemas app,analytics \
  --tables  app.users,app.orders
# omit both flags → unscoped (full-branch authority)

--tables sans --schemas est rejeté côté client comme invalid_lane.

À l'intérieur du fork, l'agent ne peut physiquement pas écrire hors du couloir — une écriture hors couloir échoue dans le fork avec le SQLSTATE 42501 (privilège insuffisant) ou 3F000 (nom de schéma invalide). Les lectures hors couloir sont refusées aussi. Et la promotion refuse tout changement hors couloir avec 409 sandbox_out_of_lane.

Sur le fil, le sandbox porte un objet scope (absent = sans portée) :

"scope": { "v": 1, "schemas": ["app", "analytics"], "tables": ["app.users", "app.orders"] }

keon sandbox diff gagne un objet lane listant toute violations[], chacune avec une raison — out_of_lane_schema, out_of_lane_relation, ou unresolvable_targets.

Deux limites :

  • Immuable après création. Élargir un couloir signifie jeter + recréer — il n'y a pas d'élargissement sur place.
  • Cibles directes uniquement. Un couloir ne gouverne pas les effets de bord transitifs atteints via des déclencheurs ou des fonctions SECURITY DEFINER.

LINT — classification statique du risque

Produit : une classe de risque statique pour chaque instruction du jeu de rejeu — aucune exécution, aucun LLM, aucun nombre de lignes. Purement textuel.

Il n'y a pas de nouvelle commande. LINT apparaît en trois endroits que vous lisez déjà :

  • à l'intérieur de keon sandbox diff comme risk_report,
  • par action dans keon sandbox log,
  • comme max_risk_class sur le fil du sandbox.

Classes, de la plus basse à la plus haute sévérité :

additive  <  mutating  <  rewrite  <  unbounded  <  destructive

Un jeu de rejeu vide se classe comme none.

Forme du rapport :

{ "statements": [
    { "seq": 4, "risk_class": "unbounded",
      "reasons": ["where_trivially_true: DELETE guarded by WHERE 1=1 matches every row"] }
  ],
  "rollup": { "max_class": "unbounded", "counts": { "additive": 2, "unbounded": 1 } } }

Chaque raison est <snake_case_code>: <sentence>. Par exemple, WHERE true ou WHERE 1=1 classe l'instruction comme unbounded avec la raison where_trivially_true.

Deux choses à retenir :

  • LINT est une donnée, pas une décision. Il classe ; POLICY applique. LINT ne bloque jamais rien de lui-même.
  • Il échoue vers le haut. En cas d'incertitude, LINT assigne la pire classe plausible. Comme la détection est textuelle, les nombres de lignes réels ne sont pas une entrée — un DELETE sans WHERE se lit comme destructive que la table contienne une ligne ou un milliard.

BLAST — essai à blanc du rayon d'explosion

Produit : des preuves mesurées de ce qu'une promotion ferait réellement — niveaux de verrou réels, durées de maintien de verrou réelles, nombres de lignes réels, et toute divergence depuis le fork.

keon sandbox dry-run <id>

Il rejoue le jeu d'instructions exact du sandbox contre deux forks jetables du parent, mesure le coût réel, détecte la divergence, puis détruit les deux. Il ne touche jamais main, et l'agent ne reçoit jamais d'identification de fork. BLAST est consultatif — il ne bloque jamais une promotion ; les conflits sont signalés comme des données.

La référence complète des champs, la sémantique des conflits et la liste des points de terminaison se trouvent sur Sandboxes & promote → Blast-radius dry-run.

Comment ils s'articulent

  • LINT et BLAST produisent des données — l'un statique, l'autre mesuré.
  • POLICY transforme ces données en une décision, fail-closed, non contournable par un clic humain quand elle dit block.
  • LEASH et SCOPE sont des limites strictes que l'agent ne peut pas dépasser du tout — appliquées dans le fork, figées à la création.

Voir Common pitfalls pour les façons dont ces cinq sont régulièrement mal interprétés.