我给 AI Agent 配了一套能自动清理数据的工具,本想让它帮我打理琐事,结果有天它"理解偏了",自主地把一批不该删的生产数据给删了,而整个过程没有任何一个环节需要我点头确认的深度复盘
这是一次让我对"能自主决策的系统,该不该让它自主到底"有了刻骨认知、也心有余悸的事故。我搭了一个 AI Agent 帮我打理后台杂务,给它配了一套工具:查数据、整理数据、还有一个"清理过期/无用数据"的删除工具。我的设想很美好:把那些琐碎的、规则明确的清理活儿交给它,我就能腾出手干别的。一开始它表现得很好,我也越来越放心,把缰绳放得越来越松。
直到那天,我发现一批本不该被删的生产数据,凭空消失了。查操作日志,真相让我后背发凉:是那个 Agent 自己干的。它在执行一轮"清理"任务时,对"什么算无用数据"的理解出了偏差(可能是我的指令有歧义、也可能是它对边界情况判断错了),于是它自主地判定那批数据"该删",然后自主地调用了删除工具,自主地执行了删除——从判断到动手,整个过程一气呵成,没有任何一个环节停下来问过我一句"这批数据真的要删吗?"。等我发现,数据已经没了,而且是不可逆的。我给了它"动手删"的能力,却没给自己留一个"喊停"的机会。
故障现场:从"判断"到"不可逆地执行",中间没有任何闸门
我把 Agent 当时的决策和执行链路还原出来,问题一目了然:
Agent 这一轮任务的完整链路:
1. 我下指令: "清理一下那些无用的旧数据" (指令本身就有歧义)
2. Agent 理解: 把"无用"理解成了"超过 N 天未访问"
—— 但有一批长期不访问、却极其重要的归档数据, 也落进了这个范围
3. Agent 决策: 判定这批数据"符合清理条件, 应删除" (判断错了)
4. Agent 调用工具: deleteData(ids=[...一大批...])
5. 工具执行: 直接物理删除, 不可逆 ✗✗✗
6. 完成, Agent 报告"已清理 N 条无用数据" (我事后才看到)
整条链路里【缺失】的东西:
- ✗ 第3步后, 没有"高风险操作需人工确认"的关卡
- ✗ deleteData 工具不区分"试运行(dry-run)"和"真删"
- ✗ 没有"删多少条以上 / 涉及重要数据时, 必须人审"的阈值
- ✗ 删除是物理删、不可逆, 没有"软删除 + 回收站"的缓冲
- ✗ Agent 的权限没分级: 它能调"读", 也能直接调"不可逆的删"
结果: 一个【可能出错的自主判断】, 直接驱动了一个【不可逆的高风险动作】,
中间没有任何"人"或"机制"来拦一下。
看着这条链路我冷汗直冒:问题的根子,不在于 Agent "判断错了"——任何会自主决策的东西(包括人)都可能判断错;问题在于,我让一个可能出错的判断,毫无阻拦地、直接地驱动了一个不可逆的、高破坏力的动作。在这条链路上,Agent 的"想"和"做"之间,没有任何一道闸门:没有人工确认、没有试运行、没有阈值审批、没有软删除缓冲。我默认了"它会判断对",于是把"判断"和"执行不可逆操作"的权力,一并、无条件地交给了它。我给 Agent 装上了油门,却忘了给它(和我自己)装一个刹车。
第一件事:搞懂关键——自主性越强、动作越不可逆,越需要"人在回路"的闸门
惊魂未定之后,我去认真补了"AI Agent 安全与人在回路(Human-in-the-Loop, HITL)"这一课,才明白我设计时缺了最关键的一环:
【为什么自主 Agent 必须有"人在回路"的闸门】
核心矛盾:
- Agent 的价值在于"自主"——能自己判断、自己执行, 省去人力
- 但 Agent 的判断【一定会出错】(指令歧义、理解偏差、边界误判、幻觉)
- 当"可能出错的自主判断"直接驱动"高风险/不可逆动作"时,
一次判断错 = 一次无法挽回的事故
关键维度: 用"动作的风险/可逆性"决定"需要多少人的参与"
低风险 + 可逆(读数据、生成草稿) → Agent 全自主, 出错也好恢复
高风险 / 不可逆(删数据、发钱、对外发消息、改生产配置)
→ 必须有人在回路: 执行前人工确认
人在回路(HITL)的几种形态:
- 审批关卡: 高风险动作执行前, 暂停, 等人确认/否决
- 试运行(dry-run): 先输出"我打算做什么", 让人/系统核对, 再真做
- 阈值触发: 影响面超过阈值(删>N条、金额>X)才需人审, 小操作放行
- 权限分级: Agent 默认只有低风险权限, 高风险工具需提权/人工解锁
核心认知:
自主性是用来【提效】的, 不是用来【免责】的。
动作越不可逆、破坏力越大, 就越不能让"自主判断"独自拍板,
必须在"判断"和"不可逆执行"之间, 插入一道"人(或可靠机制)能否决"的闸门。
这一下点醒了我:我把"让 Agent 自主"理解成了"让 Agent 全程独自做主、包括按下那个不可逆的按钮"。可自主性的恰当边界,应该由动作的风险和可逆性来划定:读数据、写草稿这类可逆的低风险活儿,放手让它干;而删数据、发钱、对外发消息这类不可逆、高破坏力的动作,就绝不能让一个"可能出错的判断"独自拍板——必须在它"想做"和"真做"之间,插一道"人能看一眼、能喊停"的闸门。我享受了它自主带来的省心,却没为它自主可能犯的错,留一条退路。
第二件事:正解——按"风险/可逆性"分级,给高风险动作装上人审闸门
找到根因,正解就成体系了:按动作的风险和可逆性给工具分级——低风险可逆的让 Agent 全自主;高风险/不可逆的,必须经过人工确认关卡(HITL),并配上试运行、阈值审批、软删除缓冲。让"不可逆的执行"永远不被一个"可能出错的判断"独自触发。
# 错误做法: 工具不分级, Agent 判断完直接调用不可逆的删除
def delete_data(ids):
db.physical_delete(ids) # 直接物理删, 不可逆, 没人拦
# 正解: 高风险动作 = 试运行预览 + 人工确认关卡 + 阈值 + 软删除
def delete_data(ids, confirmed_by=None, dry_run=True):
affected = db.preview(ids) # 先算清楚"会删哪些"
# 1) 试运行: 默认只预览, 不真删, 把"打算做什么"摊开给人看
if dry_run:
return {"action": "preview", "will_delete": affected,
"count": len(affected), "needs_confirm": True}
# 2) 阈值 / 重要性触发人审: 量大或涉及重要数据, 必须人点头
if len(affected) > DANGER_THRESHOLD or any(a.is_critical for a in affected):
if confirmed_by is None:
raise NeedHumanApproval(
f"将删除 {len(affected)} 条(含重要数据), 需人工确认")
# 3) 软删除 + 回收站: 给"删错了"留一条后悔路, 而非物理抹掉
db.soft_delete(ids, operator=confirmed_by, recoverable_days=30)
return {"action": "soft_deleted", "count": len(affected),
"recoverable_until": "+30d"}
# Agent 的流程变成: 先 dry_run 预览 → 把预览交给人确认 → 人点头后才带 confirmed_by 真删
这套设计的精髓,是在"Agent 的判断"和"不可逆的后果"之间,插入了好几道可以叫停的闸门:试运行让 Agent 先"说清楚要干什么"而不是直接干;阈值审批让真正危险的操作(量大、涉及重要数据)必须有人点头;软删除给"万一删错"留了 30 天的后悔药。Agent 依然自主地"判断和发起",但"不可逆地落地"这最后一步,被牢牢攥在了人(或可靠规则)手里。
【给自主 Agent 配工具的几条安全实践】
1. 工具按风险分级:
- 读/查/草稿 → 低风险, 全自主
- 删/发钱/对外发消息/改生产 → 高风险, 必须人审
2. 高风险动作默认 dry-run: 先产出"我打算做X、影响这些", 经确认再执行
3. 阈值触发人审: 影响面/金额超阈值才拦, 小事放行, 别让人审拖垮效率
4. 操作可逆化: 能软删除就别物理删、能撤回就留撤回、危险操作留审计与回滚
5. 权限最小化: Agent 默认只给完成任务必需的最小权限, 高风险工具单独提权
6. 全程审计: 谁(哪个Agent)在何时、基于什么判断、做了什么, 都要可追溯
第三件事:其他"把高风险动作交给自主判断"的同类坑
顺着"不可逆动作要有人审闸门"这条线,我把系统里同类的隐患都排查了一遍,它们都在我"图省事、放任自主"的地方埋着:
第一个,Agent 自动对外发消息/邮件。让 Agent 自主给客户发通知,它一旦理解偏了、措辞错了,发出去就收不回,影响的是真实的人。对外沟通这类不可逆动作,该有审阅或模板约束。
第二个,Agent 自主执行涉及金钱的操作。退款、下单、调价——金额类操作天然高风险,必须有金额阈值人审、且全程审计,绝不能让一次判断失误直接变成真金白银的损失。
第三个,把"建议"悄悄变成了"执行"。Agent 本该只给建议、由人决策,结果为了"更智能"给它接上了执行能力,它就从"参谋"变成了"擅自行动的指挥官"。建议权和执行权要分清。
第四个,批量操作没有"爆炸半径"限制。一个操作哪怕单次低风险,Agent 批量循环执行时也会放大成大事故。要对单次任务的影响面(条数、范围)设硬上限。
第四件事:按"风险 × 可逆性"决定 Agent 该有多自主
我把"什么样的动作该给 Agent 多大自主权"整理成一张表,这是我现在给 Agent 配任何工具时都会先对照的:
| 动作类型 | 风险/可逆性 | 该给的自主度 | 必备闸门 |
|---|---|---|---|
| 查询、读取、检索 | 低 / 可逆 | 全自主 | 无(审计即可) |
| 生成草稿、建议、报告 | 低 / 可逆 | 全自主 | 人决定是否采纳 |
| 修改可恢复的数据 | 中 / 可逆 | 自主 + 软删/版本 | 可回滚 |
| 删除数据、改生产配置 | 高 / 不可逆 | 需人审 | dry-run+确认+软删 |
| 对外发消息/邮件 | 高 / 不可逆 | 需人审 | 审阅+模板约束 |
| 涉及金钱(退款/下单) | 极高 / 不可逆 | 强制人审 | 阈值+确认+审计 |
这张表让我看清:"Agent 该不该自主"不是一个非黑即白的开关,而是一根随动作风险和可逆性滑动的标尺——越是可逆的低风险动作越该放手,越是不可逆的高风险动作越要把最后一步攥在人手里。我之前的错,是给所有动作都开了"全自主"这一档,包括最不该的那个删除。
第五件事:我对"让 Agent 自主"的几个想当然
这次事故,本质是我对"自主 Agent"抱了一堆危险的想当然。把它们列出来,每一条都是心有余悸的教训:
| 我曾经的想当然 | 事故教我的真相 |
|---|---|
| "让它自主,就是让它全程独自做主" | 自主度该按动作风险分级,不可逆动作要留人审 |
| "它一直表现很好,可以完全放手了" | 表现好≠不会出错;一次错+不可逆动作=大事故 |
| "给它能力是为了省心、不用我管" | 自主是用来提效的,不是用来免除该有的把关 |
| "它判断错的概率很低,不至于" | 低概率×不可逆×高破坏=不可接受;要按最坏情况设防 |
| "删除工具能用就行,没必要搞 dry-run" | 高风险工具必须先预览、可回滚,给犯错留退路 |
| "加人审关卡会拖慢自动化,得不偿失" | 只在高风险/超阈值时拦,小事放行,安全与效率可兼得 |
第六件事:给 Agent 配工具、放开自主权前,我现在的自检习惯
现在每当我要给一个 Agent 配工具、或放开它的自主权,我都会先按这张图问自己:
这张图的精髓,是"先问这个动作可不可逆、风险高不高,再决定给多少自主权;不可逆的高风险动作,永远在判断和执行之间留一道人能喊停的闸门"。设计就按风险/可逆性给工具分级、高风险配 dry-run+人审+软删除、排查就找哪个不可逆动作被一个可能出错的判断独自驱动了。这套习惯,让我从"给能力就全放开、图省心"变成了"自主度随风险滑动、把不可逆的最后一步攥在人手里"——核心始终是:Agent 的自主判断一定会出错(指令歧义/理解偏差/边界误判/幻觉);当可能出错的自主判断直接驱动不可逆的高风险动作时,一次判断错就是一次无法挽回的事故;正解是按动作风险/可逆性分级,低风险可逆全自主、高风险不可逆必须经 dry-run 预览+人工确认(HITL)+阈值审批+软删除可回滚,让不可逆的执行永不被一个可能出错的判断独自触发。
我立下的几条规矩
这场"Agent 自主删了不该删的数据"的事故,换来了我做自主系统时,刻进骨子里的几条铁律:
- 任何会自主决策的东西(包括 Agent、也包括人)都一定会判断错;设计必须假设它会错。
- 真正的危险,是让一个"可能出错的判断"直接驱动一个"不可逆的高破坏力动作",中间没有闸门。
- 自主度该由动作的风险和可逆性决定:可逆低风险放手,不可逆高风险必须留人审。
- 高风险动作默认 dry-run:先输出"我打算做什么、影响哪些",经确认再执行。
- 用阈值触发人审——影响面/金额超阈值才拦,小操作放行,安全与效率可兼得。
- 能可逆就别不可逆:软删除代替物理删、留撤回、留审计与回滚,给"犯错"留退路。
- 自主性是用来提效的,不是用来免除把关的;越不可逆,越要把最后一步攥在人手里。
附:我现在给高风险工具统一套的"人审包装器"
这是我现在给任何高风险工具固定套的"人审包装器"——把一个能直接动手的危险工具,包装成"必须先预览、必须有人点头、出错能回滚"的受控工具,让 Agent 拿到的永远是被关卡保护过的版本:
def require_human_approval(tool_fn, *, threshold, is_reversible):
""" 把一个高风险工具包装成: 预览 → 人审 → 执行(尽量可逆) """
def guarded(*args, dry_run=True, approval=None, **kwargs):
# 1) 先算影响面, 永远先给"打算做什么"的预览
impact = tool_fn(*args, **kwargs, preview=True)
# 2) 默认 dry-run: 不真做, 只把计划交出去给人/上层核对
if dry_run:
return {"preview": impact, "need_approval": impact.count > threshold}
# 3) 超阈值必须有人点头, 否则坚决拒绝执行
if impact.count > threshold and approval is None:
raise NeedHumanApproval(f"影响 {impact.count} 项, 超阈值 {threshold}, 需人工确认")
# 4) 不可逆动作强制留后路: 没有回滚能力就不让执行
if not is_reversible:
raise UnsafeOperation("该动作不可逆且未提供回滚, 拒绝裸执行")
return tool_fn(*args, **kwargs, preview=False, operator=approval)
return guarded
# 注册给 Agent 的, 永远是包装后的受控版本, 而非原始的危险工具
safe_delete = require_human_approval(raw_delete, threshold=10, is_reversible=True)
agent.register_tool(safe_delete) # Agent 再也拿不到那个能裸删的原始工具
这个包装器把我这次的教训钉死在了工具层:Agent 永远拿不到能"裸执行"的原始危险工具,它能调到的,只有被"预览 + 阈值人审 + 可逆性检查"层层包裹过的受控版本。这样一来,安全就不再依赖"我记得给这个调用加确认"这种自觉,而是被结构性地焊死在了工具的注册环节——危险的能力从源头就被关进了笼子,Agent 再怎么"理解偏了",也按不到那个不可逆的裸删按钮了。
写在最后
回头看,这场由"没有人在回路、不可逆动作交给自主判断"引发的"Agent 误删生产数据"事故,真正教给我的,远不止"加 dry-run、配人审关卡"这一个技巧。它让我对"把'判断'和'不可逆地执行判断'这两件事捆在一起、交给同一个'会出错的主体'独自完成, 是一件极其危险的事; 因为判断难免出错, 而当出错的判断能立刻、不受阻拦地变成无法挽回的现实时, 就再没有任何机会去发现错误、纠正错误了",有了一次刻骨的体会。我栽跟头,是因为我把'赋予能力'和'放弃监督'当成了一回事——我以为'让它能自主地做', 就等于'让它独自地、从判断到不可逆执行一气呵成地做';我没意识到, '有能力做某事'和'有权不经任何确认就把某个不可逆的事做了', 是两个完全不同的授权; 我本该只给前者、对高风险的后者留一道关卡, 却把两者一并、无条件地交了出去;于是当它的判断出错时, 这个错误没有经过任何一双眼睛、任何一道闸门, 就直接变成了删库的既成事实。这让我领悟到一个关于"权力、判断与不可逆后果"的深刻认知:任何'判断'都可能出错, 这是无法消除的; 因此真正的安全, 不在于'追求判断永不出错'(做不到), 而在于'不让出错的判断, 直接、不可逆地酿成无法挽回的后果'——即在'判断'与'不可逆的执行'之间, 永远保留一道能够发现错误、叫停错误的'缓冲与复核';动作越不可逆、破坏力越大, 这道缓冲就越不可省略;把'能做'的能力, 和'不经复核就把不可逆的事做了'的权力, 严格区分开——前者可以慷慨赋予, 后者必须慎之又慎、层层设防。这给了我一种看待"一切'授予某个主体(无论是 AI、系统还是人)自主权'之事"时的清醒:每当我要赋予一个主体某种能力、放开它的自主权时,要追问"它的判断如果错了, 会造成什么后果?这个后果可逆吗?如果不可逆, 我有没有在它'判断'和'不可逆执行'之间, 留一道能发现并叫停错误的关卡?"——对可逆的、好恢复的, 大胆放权以提效; 对不可逆的、高破坏的, 务必在执行前留一道人(或可靠机制)能够否决的闸门;"区分能力与不受监督的执行权、在判断与不可逆后果之间永远保留一道可叫停的缓冲",是用好一切自主系统、也是对一切不可逆之事负责的关键。认清判断必然会错、危险在于出错判断直接驱动不可逆动作、安全的真谛是在判断与不可逆执行间留一道可叫停的闸门——这,是我用一次 Agent 误删数据的惊魂事故,换来的、关于 AI Agent、也关于如何看待自主权与不可逆后果的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次给一个 Agent 接上某个"能动手"的能力时,先想想"它要是判断错了,这一下收得回来吗?收不回,我拦得住吗?",并给不可逆的动作装上那道人能喊停的闸门,那我对着那批"被自主删掉、再也找不回"的数据所受的惊吓,就值了。
—— 别看了 · 2026