kisenon
Agent-Safe Change Control

よくある落とし穴

ゼロへのスケールとエージェント安全性をひそかに無効化するアンチパターン — そしてその回避方法。

これらのパターンはエラーになりません。CI を通過し、差分では妥当に見え、そしてあなたが対価を 払っている保証 — ゼロへのスケールによる節約、またはエージェント安全性 — をひそかに打ち消します。 以下の各項目: 何が起こるか、なぜそれが痛手か、そして修正方法。

キープアライブコードがゼロへのスケールを無効化する

エージェントが「親切に」タイマーでデータベースに触れるコードを追加します。すべての定期 クエリはアクティビティとしてカウントされサスペンドタイマーをリセットするので、ゼロへ スケールするよう設計したエンドポイントが決して眠らなくなり — アイドルが無料でなくなります。

これはいくつかの形でやって来ます。フロントエンドまたはバックエンドのハートビート:

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

ハンドラがたまたま DB にクエリを投げるエンドポイントに向けられた外部アップタイムモニター — そのためすべてのプローブがコンピュートを起こします:

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

あるいは、検証クエリでライブ接続を保つコネクションプール設定 — ゼロより大きい min、または サーバーレスの「ウォーマー」:

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

修正: エージェントが書いた差分で定期的なデータベースアクセスを監査してください。生存性 チェックは HTTP レイヤーに保ち — DB ではなくアプリをプローブし — プールをゼロまでドレイン させます(min: 0)。コンソールのコンピュートビューは、エンドポイントがなぜゼロへ スケールしていないかを表示し、迷子のハートビートを素早く表面化します。

エージェントに main 認証情報を渡すと ASCC が短絡する

エージェントに main ブランチの接続文字列を渡す — あるいはあなた自身のユーザーとしてログイン した状態で keon を実行させる — と、ASCC が強制するすべて: サンドボックス分離、LEASH 予算、 LINT と BLAST のチェック、promote ゲート、帰属付け、undo アンカーをバイパスします。エージェントは 今や本番を直接書き込んでおり、エージェントの変更を安全にするループは一切実行されません。

エージェントは main に書き込める認証情報を決して保持すべきではありません。プロモーションは 設計上サーバー側です — cp は本番への唯一の書き込み手です。

修正: Settings → API keysエージェントスコープの API キー(capability = agent、 scope = 特定のプロジェクト、短い有効期限)を発行し、エージェントにはサンドボックスの接続文字列 のみを渡し、その環境からより強力な認証情報を取り除きます(完全な DATABASE_URL なし、 ~/.pgpass なし、同じシェル内のログイン済み keon セッションなし)。 エージェントにスコープ限定の認証情報を渡すGuardrails の SCOPE セクションを参照してください。

起動時のリトライストーム

サスペンド後の最初の接続はコンピュートを起こす必要があるため、ウォームなものより遅くなります。 1 回の遅い接続を見たエージェントはそれを「修正」しようとするかもしれません — 攻撃的な クライアントタイムアウトとエンドポイントを叩くリトライループで、あるいはキープアライブ (落とし穴 #1)を追加して起動する必要が一切ないようにして。どちらも節約を手放して一度きりの コストを取り繕います。

修正: クライアントの connect_timeout を 10 秒程度に設定し、最初の接続では辛抱してください。 ウォームパスが働いていれば、同一リージョンでのコールドスタートはサブ秒です — わずかに遅い接続が 1 回あるのは想定内であり、設計で回避すべき欠陥ではありません。

postgresql://…?connect_timeout=10

コードにコミットされた認証情報

エージェントは接続文字列と API キーをソースや .env ファイルにハードコードし、それらが git に 到達します。漏洩したキーは、公開(またはのちに公開される)リポジトリに到達してから数時間以内に 収穫されます — ウィンドウは数日ではなく数時間で測られます。

修正: シークレットは環境経由で注入し、決してコミットしないでください。.env ファイルを git から除外します。漏洩があればいつでも、Settings → API keys から直ちにローテーションして ください — 露出したキーを失効させ、新しいものを発行します。

main への直接 DDL は promote ループをバイパスする

main に対して直接マイグレーションを実行すると、変更を巻き戻し可能かつ監査可能にするキャプチャ がスキップされます。発行される up/down マイグレーション(EMIT)、署名付き台帳エントリ(LEDGER)、 undo アンカー(UNDO)を失います。スキーマは変わりましたが、プラットフォームが再生またはロール バックできる記録はありません。

修正: 「些細な」スキーマ変更であっても、サンドボックス → promote です。promote ループこそが マイグレーション、台帳エントリ、アンカーを生成します。直接の ALTER はそのどれも生成しません。

ロールバックされたトランザクション内の書き込みも着地する

キャプチャはエージェントが実行するステートメントを記録します — トランザクション境界の認識は 持ちません。BEGIN … ROLLBACK 内で実行されたステートメントも依然としてキャプチャされ、その サンドボックスがプロモートされると、それらは再生され適用されます。サンドボックス内の ROLLBACK はそれらをキャプチャから取り消しません。

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

修正: サンドボックス内の ROLLBACK に頼って探索的な作業を「取り消す」ことをしないで ください。作業を消したいなら、サンドボックスを破棄してください — それがキャプチャが尊重する 廃棄の経路です。

予算や TTL を超えてサンドボックスに作業を留める

サンドボックスは一時的です。サンドボックスが LEASH 予算を使い果たすと自動破棄され (status=discarded)、その中のプロモートされていない作業は失われます。サンドボックスを 「後で戻ってくる」長命のブランチとして扱うのは、1 日分の変更が消える原因です。

修正: それをプロモートするか、新しいサンドボックスで作業を再キャプチャしてください。 サンドボックスはデフォルトで使い捨てとして扱ってください — 永続的なアーティファクトは サンドボックスではなく promote です。

保証の誤読

チェックが実際に約束することについての 3 つの具体的な罠:

  • 被害範囲のドライランは助言的です。 それは promote が何をするか — 実際のロック、継続時間、 行数 — を測定しますが、promote を決してゲートしません。クリーンなドライランは情報であって ゴーサインではありません。そして promote 前にドライランが必須かどうかは POLICY の決定で あって、ドライランの決定ではありません。

  • Approve はポリシーの block を上書きできません。 人間の Approve は、ポリシーの block やレーン外のステートメントを決して素通しさせられません。block を越える唯一の方法は監査対象の ポリシー変更です — 設計上、手動の上書きスイッチはありません。

  • マスク済みフォークは値をマスクし、形状はマスクしません。 マスク済みフォークではが マスクされるので、エージェントがそれらのマスク済み値に対して書くデータ依存の DML は、再生時に 親の実行に一致しません。スキーマレーンの作業は値に依存せず忠実に再生されます。マスク済み データに対して開発された値依存の DML はそうではありません。