kisenon
Agent-Safe Change Control

Pièges courants

Anti-patrons qui défont discrètement le scale-to-zero et la sûreté des agents — et comment les éviter.

Ces patrons ne produisent pas d'erreur. Ils passent la CI, ils paraissent raisonnables dans un diff, et ils annulent discrètement une garantie que vous payez — les économies du scale-to-zero ou la sûreté des agents. Chacun ci-dessous : ce qui se passe, pourquoi ça fait mal, et le correctif.

Le code keep-alive défait le scale-to-zero

Un agent ajoute « obligeamment » du code qui touche la base de données sur un minuteur. Chaque requête périodique compte comme de l'activité et réinitialise le minuteur de suspension, de sorte qu'un point de terminaison que vous avez conçu pour descendre à zéro ne dort jamais — et l'inactivité cesse d'être gratuite.

Cela arrive sous plusieurs formes. Un heartbeat frontend ou backend :

// runs forever, holds the compute awake forever
setInterval(async () => {
  await pool.query("SELECT 1");   // "health check"
}, 30_000);

Un moniteur de disponibilité externe pointé vers un point de terminaison dont le gestionnaire se trouve interroger la BD — donc chaque sonde réveille le calcul :

app.get("/health", async (_req, res) => {
  await pool.query("SELECT 1");   // monitored every 60s → never idle
  res.send("ok");
});

Ou des réglages de pool de connexions qui maintiennent une connexion vivante avec des requêtes de validation — un min au-dessus de zéro, ou un « warmer » serverless :

const pool = new Pool({
  min: 2,                         // keeps ≥2 connections open + validated
  // a validation/keep-alive query on each idle connection resets the timer
});

Correctif : auditez les diffs écrits par l'agent pour tout accès périodique à la base de données. Gardez les vérifications de vivacité à la couche HTTP — sondez l'application, pas la BD — et laissez le pool se vider jusqu'à zéro (min: 0). La vue de calcul de la console montre pourquoi un point de terminaison ne descend pas à zéro, ce qui fait vite surgir un heartbeat égaré.

Confier à l'agent les identifications de main court-circuite ASCC

Donner à un agent la chaîne de connexion de la branche main — ou le laisser exécuter keon connecté en tant que votre propre utilisateur — contourne tout ce qu'ASCC applique : isolation de sandbox, budgets LEASH, vérifications LINT et BLAST, portes de promotion, attribution, et ancres d'annulation. L'agent écrit désormais directement en production, et rien de la boucle qui rend les changements d'agent sûrs ne s'exécute jamais.

L'agent ne devrait jamais détenir d'identification capable d'écrire sur main. La promotion est côté serveur par conception — cp est le seul rédacteur en production.

Correctif : forgez une clé API à portée d'agent (capability = agent, scope = un projet, expiration courte) dans Settings → API keys, remettez à l'agent uniquement des chaînes de connexion de sandbox, et retirez les identifications plus puissantes de son environnement (pas de DATABASE_URL complet, pas de ~/.pgpass, pas de session keon connectée dans le même shell). Voir Confier à un agent une identification à portée limitée et la section SCOPE de Guardrails.

Tempêtes de réessais autour du réveil

La première connexion après une suspension doit réveiller le calcul, elle est donc plus lente qu'une connexion chaude. Un agent qui voit une seule connexion lente peut la « corriger » — avec des délais d'attente client agressifs et des boucles de réessai qui martèlent le point de terminaison, ou en ajoutant un keep-alive (piège #1) pour ne jamais avoir à se réveiller du tout. Les deux échangent les économies pour masquer un coût ponctuel.

Correctif : fixez un connect_timeout client autour de 10s et soyez patient à la première connexion. Le démarrage à froid est inférieur à la seconde en même région une fois les chemins chauds en jeu — une connexion légèrement lente est attendue, pas un défaut à contourner par de l'ingénierie.

postgresql://…?connect_timeout=10

Identifications validées dans le code

Les agents codent en dur des chaînes de connexion et des clés API dans les sources et les fichiers .env qui atteignent ensuite git. Les clés fuitées sont récoltées en quelques heures après avoir atteint un dépôt public (ou ultérieurement public) — la fenêtre se mesure en heures, pas en jours.

Correctif : injectez les secrets via l'environnement, ne les validez jamais. Gardez les fichiers .env hors de git. À toute fuite, effectuez une rotation immédiate depuis Settings → API keys — révoquez la clé exposée et forgez-en une neuve.

Le DDL direct sur main contourne la boucle de promotion

Exécuter une migration directement contre main saute la capture qui rend un changement réversible et auditable. Vous perdez la migration up/down émise (EMIT), l'entrée de registre signée (LEDGER), et l'ancre d'annulation (UNDO). Le schéma a changé, mais il n'existe aucun enregistrement que la plateforme puisse rejouer ou annuler.

Correctif : sandbox → promote, même pour un changement de schéma « trivial ». La boucle de promotion est ce qui produit la migration, l'entrée de registre et l'ancre ; un ALTER direct n'en produit aucun.

Les écritures dans une transaction annulée atterrissent quand même

La capture enregistre les instructions que l'agent exécute — elle n'a aucune conscience des frontières de transaction. Les instructions exécutées dans un BEGIN … ROLLBACK sont toujours capturées, et si ce sandbox est promu, elles sont rejouées et appliquées. Un ROLLBACK dans le sandbox ne les dé-capture pas.

BEGIN;
UPDATE users SET plan = 'pro';   -- captured
ROLLBACK;                        -- does NOT remove it from the capture

Correctif : ne comptez pas sur ROLLBACK dans un sandbox pour « annuler » un travail exploratoire. Si vous voulez que le travail disparaisse, jetez le sandbox — c'est le chemin d'élimination que la capture respecte.

Garer du travail dans un sandbox au-delà de son budget ou de son TTL

Les sandboxes sont éphémères. Quand un sandbox épuise son budget LEASH, il est auto-jeté (status=discarded), et tout travail non promu en son sein est perdu. Traiter un sandbox comme une branche à longue durée de vie que vous « reprendrez » est la façon dont les changements d'une journée disparaissent.

Correctif : promouvez-le, ou re-capturez le travail dans un sandbox neuf. Traitez les sandboxes comme jetables par défaut — l'artefact durable est la promotion, pas le sandbox.

Mal interpréter les garanties

Trois pièges spécifiques dans ce que les vérifications promettent réellement :

  • L'essai à blanc du rayon d'explosion est consultatif. Il mesure ce qu'une promotion ferait — verrous, durées, nombres de lignes réels — mais il ne garde jamais la promotion. Un essai à blanc propre est une information, pas un feu vert ; et le fait qu'un essai à blanc soit requis avant la promotion est la décision de POLICY, pas celle de l'essai à blanc.

  • Approve ne peut pas outrepasser un blocage de politique. Un Approve humain ne peut jamais faire passer un block de politique ou une instruction hors couloir. La seule façon de dépasser un blocage est un changement de politique audité — il n'y a pas d'interrupteur de contournement manuel, par conception.

  • Un fork masqué masque les valeurs, pas la forme. Sur un fork masqué, les valeurs sont masquées, donc le DML dépendant des données que l'agent écrit contre ces valeurs masquées ne correspondra pas aux vraies lignes sur le parent au rejeu. Le travail de couloir de schéma est indépendant des valeurs et se rejoue fidèlement ; le DML dépendant des valeurs développé contre des données masquées, non.