仙kisenon
Agent-Safe Change Control

沙箱与提升

安全地将 AI 编码智能体对准你的生产数据库——被捕获、被观测、通过一道闸门提升,且可逆。

Agent-Safe Change Control 的一部分。本文所述的 keon sandbox 和 keon ledger 命令以及控制台 Sandboxes 视图都 已上线——需要较新的 keon(cli-v0.1.46+)。

Kisenon 让你把一个数据库交给编码智能体(Claude Code、Cursor 或你自己的), 任其自由更改——而不必冒险危及生产环境。沙箱是 你分支的一份每次运行的 fork,配有受限凭据、一份实时操作日志, 以及一个服务端的提升步骤。本页覆盖完整的循环:捕获 → 工作 → 提升 → 落地(可逆地)→ 证明。

Why it's safe

两项保证,二者皆为确定性的——信任路径中没有 LLM 介入:

  • 智能体无法触及生产环境。 它以受限的、 非超级用户的凭据连接到一份 fork。它绝不持有能 写入 main 的凭据。提升在服务端运行,并且只应用那些 已在沙箱中通过验证的更改。
  • 你能看清它究竟做了什么。 每一条语句都被归因并 流式推送到一个只读控制台——是真实的 SQL,而非摘要。

The loop

keon sandbox run \
  --migrate "alembic upgrade head" \
  --verify  "pytest tests/db"
# → green/red verdict + schema diff + a sandbox you can inspect

keon sandbox promote <id>   # cp applies the validated changes to main

智能体始终掌控着这个循环;正是那道边界使其变得安全。

Durable capture

操作日志是提升的事实来源,因此它必须完整。 每个沙箱携带一个 capture_state —— ok 或 lost。如果捕获曾经 受损,状态会翻转为 lost 并 保持 在那里:一个 lost 沙箱 无法提升(409 sandbox_capture_lost),并且绝不会悄悄应用 部分变更。没有自动修复——丢弃它,并在一个 全新的沙箱中重新运行该工作。keon sandbox get / list 会显示捕获健康状况。

Promote: self-serve or human-approved

每个项目选择一种 promote_mode:

模式由谁提交到 main
self(默认)智能体在其检查通过后自行提升——在其边界之内。
human智能体提议;由 owner/admin 审查 diff + 操作日志后点击 Approve。

在 human 项目中,keon sandbox promote 不会让任何变更落地:沙箱停在 awaiting_approval,响应中带有 next_step: "approve",以及一个说明要运行的命令和谁可以运行它的 approve_hint。退出码确保脚本不会把这种搁置误读为已落地的变更:

退出码含义
0已落地到 main。
1失败——没有任何变更落地。
3搁置于 awaiting_approval——尚未有任何变更落地。stderr 会输出一个 sandbox_awaiting_approval 信封;需要由 owner/admin 运行 keon sandbox approve <id>。

verdict(keon sandbox verdict <id> green|red)只用于把关之后的提升或批准,因此仅在沙箱处于 active 或 awaiting_approval 时才被接受。处于其他任何状态时,它返回 409 sandbox_verdict_not_accepted。

Blast-radius dry-run

在你提升之前,你可以请求平台 测量 提升会做什么 ——真实的锁级别、真实的持锁时长、真实的行数—— 而不是从语句文本去猜测:

keon sandbox dry-run <id>

平台将沙箱的确切语句集对父分支的两个短期存活的 一次性 fork 进行重放——一个在父分支的当前 HEAD(在那里测量锁、 时长和行数),一个在沙箱的 fork 点(分歧 基线)——然后销毁两者。它绝不触及 main,且 智能体绝不收到对任一 fork 的凭据。该试运行是 建议性的: 它产出报告,绝不阻断提升。

它还回答了一个静态 diff 无法回答的问题:现实是否在 智能体脚下移动了? 任何在父分支 HEAD 处报错、或影响与 fork 点不同行数的语句都会被暴露为一个 冲突 —— 例如 一条 UPDATE ... WHERE id = 2,其目标行在 fork 之后已在 main 上被删除。

已完成的报告携带:

字段含义
statements[].lock_level该语句在提升事务中新获取的最强锁(例如表重写时的 AccessExclusiveLock)。
statements[].lock_wait_est_ms在 head fork 上测量到的执行时间——该锁在 main 上被持有时长的下限。
statements[].rows_measured在 head fork 上受影响的行数(DDL 为 0)。
statements[].divergence当语句在 HEAD 处的行为与 fork 时不同时,为 {kind:"error"} 或 {kind:"row_count"}。
rollup.conflicts每一处分歧,外加一个 baseline_unavailable 标记(如果基线通道无法运行)——因此一道以"无冲突"为条件的闸门在测量降级时会 失败即关闭。
rollup.max_lock_level / rollup.duration_ms_total聚合的锁强度与总重放时间。
capture_advanced / parent_head_lsn陈旧性信号:该报告是一个快照,重新运行成本很低。

端点是 POST /v1/sandboxes/{id}/dry-run(启动一次异步运行,202) 和 GET /v1/sandboxes/{id}/dry-run(最新记录)。一次失败的试运行 携带一个机器可读的 error_code (capture_fence_failed、incomplete_capture、fork_failed、 compute_unreachable、replay_infra_failed、timeout)。沙箱详情页上的 Blast radius 面板在控制台中渲染同一份报告。

Undo a promote

提升是可逆的。在 cp 将验证过的语句应用到 main 之前,它锚定父分支提升前的确切状态(一个 LSN)。一条 命令即可将分支回滚到那个锚点:

keon sandbox undo <id>   # restore the parent branch to its pre-promote state

父分支的连接字符串不变——客户端重新连接到同一 端点。撤销是一个 owner/admin 操作;智能体能力密钥会被拒绝 (403 scope_insufficient),因为一次撤销会丢弃锚点之后落到分支上的 任何内容。

keon sandbox get <id> 携带一个 undo 对象——undoable、restored_lsn、 undone_at,以及在它无法运行时的 blocked_reason (superseded_by_later_promote、already_undone)。撤销只针对 最近的 一次提升,并在锚点消失后以 409 拒绝(sandbox_not_promoted、 undo_anchor_missing、undo_anchor_invalidated)。 只要锚点有效且在你的存储保留期内,它就保持可用 ——没有单独的倒计时。

撤销按 LSN 恢复:它回滚锚点之后的 一切,而 不只是被提升的变更——包括直接写入和该分支上后来的 智能体会话。参见 Common pitfalls。

Emitted migrations

每一次落地的提升都会发出一份确定性的 up/down SQL 迁移——没有 LLM, 从捕获的模式变更生成,并在提供之前于一次性 fork 上经过 往返验证。获取它:

keon sandbox migration <id> --out ./migrations --wait

该制品携带 up 和 down SQL(files[],每个带一个 role)、coverage、 down_fidelity、任何 uncovered_seqs、一个 validation 结果,以及一个 sha256。 该制品 仅含 schema——它从不携带数据写入——coverage 说明它代表了提升的多少内容:

coverage含义
full捕获到的每一项变更都在制品中。
partial部分捕获到的 DDL 未被 schema diff 覆盖;这些语句按原样追加在末尾,并列在 uncovered_seqs 中。
schema_full每一项 schema 变更都已覆盖,但该提升还执行了制品不携带的 DML(INSERT / UPDATE / DELETE)。data_omitted: true 和 omitted_data_seqs 指明了这些写入。

down_fidelity 取值为 full(down 完全撤销 up)、structural(down 恢复结构而非数据—— 在数据被省略或 up 具有破坏性时,报告的是它而不是 full)、partial(未覆盖的语句没有生成的 down)或 none。要撤销一次提升的数据写入,请使用 keon sandbox undo,而不是 down 脚本。

终态是 validated、 validation_failed、emission_failed 和 no_schema_changes。v1 发出 sql;GET /v1/sandboxes/{id}/migration(以及 .../migration/files/{role}) 提供它。发出在提升落地之后运行,绝不阻断或回滚 它——一次纯数据的提升只会报告 no_schema_changes。

Signed ledger

每一次提升和撤销都会向一个每项目的账本追加一条签名的、哈希链接的 记录——一份对所变更内容的可离线验证的证明。验证不需要 网络,且不信任 Kisenon:

keon ledger list --project <id>          # the chain (reports chain_ok)
keon ledger export <id> --out att.json   # one attestation document
keon ledger verify att.json              # offline; exit 0 iff valid
keon ledger keys --save                  # pin the signing keys you trust

keon ledger verify 检查 Ed25519 签名和哈希链,并 报告精确的失败(signature_invalid、statement_chain_mismatch、 seq_gap、key_untrusted、chain_broken、…);信任是三态的 (trusted / untrusted / unverified)。路由:GET /v1/sandboxes/{id}/ledger、GET /v1/projects/{projectId}/ledger、GET /v1/ledger/keys。

如果 cp 未配置签名密钥,提升仍会成功但是 未签名的(ledger_enabled: false)——签名不是无条件的。

What a sandbox is not

  • 不是代码沙箱——它限定的是你的数据库,而非智能体的进程。
  • 不是 LLM 评审——捕获、重放与审查都是逐字节确定性的。

Try it

在 kisenon.com 试用,并查看 快速开始。

沙箱与提升 · Kisenon