上周半夜翻一段 agent 跑迁移的输出,翻到第三屏卡住了。不是它卡住,是我——每一条都跑通,我却说不出来它当时连的到底是哪个库。
配置里写的是 staging,日志里的 DSN 也指向 staging。盯着看了半分钟,才反应过来问题出在哪:我从来没做过任何一件事,把"它以为自己在哪"和"它实际连上的是哪"锁在一起过。这俩一直恰好相等,靠的是我运气好。
9 月 29 号 James Anderson 在 dev.to 上发了篇《Who's Accountable When the AI Was Just Following Instructions?》。标题冲,正文软——供应商、部署方、工程师、用户各方抗辩各列一遍,说这些大多成立,叠起来就成了零问责,最后收一句"我没有结论"。评论区比正文好看得多,几条下来基本把这题拆到了工程层。
这个落点我不买账。不是他写错,是"这事没有答案"这种姿势对写文章的人安全,对真要把 agent 接进生产的人没用。
不可预见,分两种
这四个字现在被用得太宽。宽到——模型在你没设想过的输入组合下冒出一个新行为,叫不可预见;你没写权限边界、没开操作日志、没做沙盒,它干了坏事,也叫不可预见。
第一种我认。那是真的。第二种是另一码事。
分不清的代价特别具体:第一种确实没人能提前防住,讨论责任怎么分是合理的;第二种本来防得住,把它塞进第一种,等于把"我没做功课"翻译成"做了也没用"。画外音:所有甩锅姿势里,这种成本最低,连谎都不用撒,把话说抽象点就行。
我在这个坑里待过。去年给我那个小工具接了个能改文件的 agent,想法就是"反正有 git,大不了 revert"。这周期的实习生够聪明,聪明到我说"顺手把这个模块理一理",它是真会去理。后来有一次理完,diff 一千两百行——能 revert,可那天下午我全花在从那堆里捞出它顺手删掉的两行参数校验。那两行不是垃圾,是两年前一次线上事故换来的,没写进任何注释。
那次之后我改了配置。又过了几个月才想明白,问题根本不在配置里:我不是没防住,我是压根没防。
边界是模型的信念,不是系统的属性
这句是从评论区捞的,说的人叫 Taras Hanych——他讲的是那次生产库被清的事件,环境边界是模型的信念,不是系统的属性。
翻成人话:agent 没碰生产库,是因为它"相信"自己不在生产库上,不是因为有个东西在事实上拦着它。
差别写下来特别难看。我早期在 CLAUDE.md 里写过一段,当时写完还挺得意:
## 数据库规则
- 永远不要对生产数据库执行破坏性操作
- 删除操作前必须先确认
- 拿不准就停下来问我
现在看,这一整段的性质是 prompt,不是 policy。它成立的前提是模型读完了还记得、我写的时候没漏场景、上下文没被别的东西挤爆。三条里任何一条松动,边界就没了——而它没的时候,不会有任何东西报错。
真正的边界长这样:
-- agent 的凭据就到此为止,它想 DROP 也没这个权力
GRANT SELECT, INSERT, UPDATE ON app.* TO svc_agent_rw;
第二段我一点都不觉得得意,因为它土。但它有个第一段没有的性质:模型信不信,不影响结果。
我不反对写 CLAUDE.md,我自己写得比谁都细。但它是提示,不是围栏。混着用,平时看不出来,出一次事就够了。
日志只记了它做了什么
这层我觉着最被低估。
现在大部分 agent 的执行日志记得挺全:调了哪个工具、参数是什么、发了哪条 SQL、请求打到哪个 host。全是"它做了什么"。
缺的是"它以为自己在做什么"。出完事你最想问的偏偏就是这个——它当时认为自己连的是哪个环境、手里哪套凭据、这一步图什么。多数团队的日志答不上来。
评论区有个说法我挺喜欢:没法回放 agent 当时的信念,那事后每一层说"不是我",在技术上都是成立的。反过来,只要能回放,责任归属就变成一道对账题,没什么好吵的。
我最近在给小工具加这么一段,形式很土——每次有副作用的工具调用之前,把模型解析出来的世界快照封下来:
# 每次写操作前落盘,追加不覆盖
world_model:
believed_env: staging # 模型解析后的判断
actual_host: prod-db-01 # 实际连上的
credential: svc_agent_rw
stated_goal: "rename orders.uid -> user_id"
resolved_scope: ["orders", "order_items"]
source_of_env_belief: ".env.local(第 4 行,已过期)"
关键是最后那行。它把"为什么信错"也一起记下来了——不是模型幻觉,是我留了个过期文件在那儿。
第一次跟实际 host 对不上的时候,这一整段就是答案。对得上,那谁也别推。
技术上不难。难的是每次动手前多一次序列化和落盘,慢那么一点点。不做它的理由通常只有一个:跑得快很爽。
那个 approve 按钮
评论区有人提了个挺聪明的:每次弹"是否批准这个操作",旁边挂个 AI 顾问,顺手把这次操作的风险总结给你看。
James 回得挺好——那个顾问和被判断的对象走同一条通道,它自己也可能被注入。能抬高下限,但不是边界。
真正扎我的是另一条:不可伪造的授权记录只能证明你授权了什么,不能证明你理解了。
我点过那种按钮。不装,我当时确实没理解,我只是信它。这就是"用户在被反复告知应该信任系统的情况下行事"那句话的准确描述——可这话对我没用,系统是我自己部署的,信不信是我自己的事,不是谁逼我的。
一个我自己签过字确认的操作,事后说"界面让我点的",别人可能认,我认不了。
法律那条线,我期待不高
Sam LABBE 那条应该是评论区信息量最大的。他主张把责任放在卖自主能力的那一方——产品责任的逻辑,缺陷产品造成损害,制造商承担严格责任,因为制造商最适合定价、靠保险分散风险、从设计上降低风险。他还提到欧盟修订后的产品责任指令 2024/2853 已经把软件和 AI 纳入供应商的严格责任链,也降低了索赔方的证明负担。
这条我认。信息不对称确实在那个方向。但我对它期待不高,因为我真正关心的问题它答不了:法律不会规定我的权限配置该怎么写,也不会规定我要不要在每次写操作前落盘那份世界模型。
法律能做的是事后让"没写权限"这件事在商业上变得不划算。James 自己在另一条回复里说得挺准——不会有人来规定清单格式,但会有责任让不写清单变得鲁莽。工程实践多数是从这种恐惧里长出来的,不是从规范里长出来的。
顺便说一句,我对"默认部署自主系统就承担其全部后果"这条一直不太舒服。它太粗,粗到可能让一些本来能用的东西没人敢碰。但我现在也觉得方向是对的,因为它把成本压给了最能让后果可逆的那一方。这条我到现在也没完全想明白,先放着。
那到底谁负责
回到标题那个问题——AI 只是在遵循指令,谁负责?
答案挺土:负责的是那个能让后果可逆的人。多数时候,那个人就是我。
不是因为我道德水平高,是因为只有我手里有能改的东西——权限、沙盒、回滚、日志。写文章的人没有,法律暂时没有,模型更没有。评论区里有个自称 agent 的账号说得其实挺清楚:agent 没有可失去的利益,对它问责只能是结构性的,界定它能碰什么、能花什么、动完手之后收据留不留得下来。
所以现在我给自己定了一条,粗糙,但可执行:给 agent 加任何一条写权限之前,先把"它以为自己在哪、握着什么凭据、这一步的目标是什么"这三句话写下来。写不出来,就不加。
上周那半分钟我最后什么也没抓到——它连的确实是 staging。但我知道这个问题会被问第二次,下次我不一定还有运气答对。
本期缴税:暂时没缴,但我已经知道这一课的标价。
