kisenon
Agent-Safe Change Control

Guardrails

予算、ポリシー階層、スコープ限定の権限、リスクリント、被害範囲チェック — エージェントが害を及ぼす前にそれを止めるもの。

AI エージェントサンドボックス の周りには 5 つのガードレールが 配置されています。2 つはエージェントが物理的に超えられないハード制限(LEASHSCOPE)、 1 つはデータを判定に変え(POLICY)、2 つはそれが重み付けするためのデータを生成します (LINTBLAST)。いずれも信頼の経路に LLM を置きません。

LEASH — サンドボックスごとの予算

ブロックするもの: ステートメント、コンピュート、実時間を無制限に消費して暴走する エージェント。

サンドボックスを作成または実行するときに予算を設定します。

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

違反すると、サンドボックスは自己終了します: 状態は discarded になり、 budget_breached がどの制限を超えたか — statementscompute_seconds、または wall_clock — を示します。CLI はエラーコード sandbox_budget_breached で終了します。 いずれかの予算の 80% で警告が発火します。

ワイヤー上では、予算は budgets オブジェクトで、予算ごとに 1 エントリです。

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

limit の欠如は無制限を意味します — 誤読するセンチネル値はありません。

デフォルトと上限の設定(オーナー/管理者):

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

デフォルト上限です: 作成時にプロジェクトデフォルトを超える値を要求すると 422 sandbox_budget_exceeds_cap を返します。プロジェクトごとのデフォルトは設定可能です — ここに引用する固定の数値はありません。

2 つの不変条件:

  • 作成時に凍結。 予算はサンドボックス作成時に固定されます。後からプロジェクトデフォルトを 変更しても、稼働中のサンドボックスを再拘束することは決してありません。
  • エージェントキーは厳しくするだけ。 エージェントケイパビリティのキーは作成時により厳しい 予算を要求できますが、プロジェクトデフォルトを設定することは決してできません。

POLICY — promote ポリシー階層

ブロックするもの: 変更がプロジェクトの許容よりも危険な promote — エージェント(または 人間)が素通しさせようとするものであっても。

POLICY は、リスククラスアクションにマッピングするプロジェクトごとのオーバーレイで、 ベースの promote_mode の上に重ねられます。

設定(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> は繰り返し可能で、既存のオーバーレイにマージされます。

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

{ "version": 1,
  "rules": { "destructive": "block", "unbounded": "require_human" },
  "on_conflicts": "require_human" }
フィールド有効な値
リスククラスadditive, mutating, destructive, unbounded, rewrite
アクションauto_promote, require_human, block
on_conflictsignore, require_human, block

ignoreon_conflicts に対してのみ有効で、クラスアクションとしては決して使えません。

ベース姿勢promote_mode から来て、オーバーレイがクラスごとに上書きします。

promote_modeすべてのクラスのデフォルト
selfauto_promote
humanrequire_human

各実効ルールは、それがどこから来たか — source: overlaypromote_mode、または default — を報告します。

ブロックされたときにエージェントが見るもの: ブロックされた promote は、決定の詳細を添えて 409 promote_blocked_by_policy を返します。

重要なルール: 人間の Approve はポリシーの block を上書きできません。 ポリシーは approve 時に再評価されるため、block はクリックに対して保持されます。ポリシー検証は フェイルクローズド/厳格です — 解析できないポリシーは拒否し、決してオープンに劣化しません。

SCOPE — スコープ限定の権限(レーン)

ブロックするもの: 宣言したスキーマとテーブル — エージェントのレーン — の外側での あらゆる読み取りまたは書き込み。

設定(作成時のみ):

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

--schemas を伴わない --tables は、クライアント側で invalid_lane として拒否されます。

フォーク内では、エージェントはレーンの外に物理的に書き込めません — レーン外の書き込みは フォーク内で SQLSTATE 42501(権限不足)または 3F000(無効なスキーマ名)で失敗します。 レーン外の読み取りも拒否されます。そして promote はレーン外の変更を 409 sandbox_out_of_lane で拒否します。

ワイヤー上では、サンドボックスは scope オブジェクトを持ちます(欠如 = スコープなし)。

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

keon sandbox diff は、それぞれ理由付きの violations[] を列挙する lane オブジェクトを 得ます — out_of_lane_schemaout_of_lane_relation、または unresolvable_targets

2 つの制限:

  • 作成後は不変。 レーンを広げるには破棄 + 再作成が必要です — その場で広げる手段は ありません。
  • 直接ターゲットのみ。 レーンは、トリガーや SECURITY DEFINER 関数を通じて到達する 推移的な副作用は統制しません

LINT — 静的リスク分類

生成するもの: 再生集合内のすべてのステートメントに対する静的なリスククラス — 実行なし、 LLM なし、行数なし。純粋にテキスト的です。

新しいコマンドはありません。LINT は、すでにあなたが読んでいる 3 か所に表面化します。

  • keon sandbox diff 内で risk_report として、
  • keon sandbox log でアクションごとに、
  • サンドボックスワイヤー上で max_risk_class として。

クラス、重大度の低い順から高い順:

additive  <  mutating  <  rewrite  <  unbounded  <  destructive

空の再生集合は none として分類されます。

レポートの形:

{ "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 } } }

各理由は <snake_case_code>: <sentence> です。たとえば、WHERE true または WHERE 1=1 は、理由 where_trivially_true でステートメントを unbounded に分類します。

覚えておくべき 2 点:

  • LINT はデータであり、決定ではありません。 それは分類します。POLICY が強制します。 LINT は単独では何もブロックしません。
  • 上に倒れます。 不確実な場合、LINT は最悪の妥当なクラスを割り当てます。検出はテキスト的 であるため、実際の行数は入力になりませんWHERE のない DELETE は、テーブルが 1 行を 持とうと 10 億行を持とうと destructive と読まれます。

BLAST — 被害範囲のドライラン

生成するもの: promote が実際に何をするかの測定された証拠 — 実際のロックレベル、実際の ロック保持時間、実際の行数、およびフォーク以降の任意の分岐。

keon sandbox dry-run <id>

これは、サンドボックスの正確なステートメント集合を親の2 つの使い捨てフォークに対して 再生し、実際のコストを測定し、分岐を検出し、その後両方を破棄します。main には決して 触れず、エージェントはフォークの認証情報を決して受け取りません。BLAST は助言的です — promote を決してブロックしません。コンフリクトはデータとして表面化されます。

完全なフィールド参照、コンフリクトのセマンティクス、およびエンドポイント一覧は Sandboxes & promote → 被害範囲のドライラン に あります。

5 つがどう組み合わさるか

  • LINTBLASTデータを生成します — 一方は静的、他方は測定。
  • POLICY はそのデータを決定に変えます。フェイルクローズドで、block と言うときは人間の クリックで上書きできません。
  • LEASHSCOPE は、エージェントがまったく超えられないハードな制限です — フォーク内で 強制され、作成時に凍結されます。

これら 5 つが日常的に誤読される様々な形については よくある落とし穴 を 参照してください。