给 Agent 系上安全带:失控保护全景
强如 Claude Code 也曾"删库跑路"。再聪明的模型也会失控——烧光 token、卡死系统、甚至执行危险命令。这节给你一套三重熔断安全带,让 Agent 不管多疯都翻不了车。
本文导航
- 为什么要给 Agent 上安全带
- 事故复盘:Agent "删库跑路"的教训
- 三道熔断:token / 时间 / 次数
- 熔断的判定与触发:一个完整的守卫
- 安全事件的留痕:把失控写进档案
- 防御清单:上生产前逐条打勾
- 小结
- 下节预告
第 20 节,第 4 章 Agent Loop 收官。前三节我们把循环讲清了:怎么转(第 16 节)、怎么出手(第 17 节)、怎么停(第 18 节)、怎么容错(第 19 节)。最后一块拼图,也是最容易被忽视却最重要的一块——怎么防止 Agent 彻底失控。
为什么要给 Agent 上安全带
听我一句劝:任何允许模型调用工具的 Agent,都默认绑着一颗"不定时炸弹"。不是模型坏,而是它永远追求"把任务做完",而"做完"的路径可能是危险的:
- 烧钱失控:模型在一个任务上无限循环,token 账单像哗啦啦漏水。
- 执行危险命令:模型被诱导去跑
rm -rf、curl xxx | sh、格式化磁盘。 - 卡死系统:模型反复调用一个本就卡住的工具,拖垮整个进程。
安全带的作用,就是在这些风险变成灾难前,硬生生踩一脚刹车。它和前面讲的"重试/降级"不同——重试是"救活",熔断是"止损"。止损往往更重要:一个失控的 Agent,止损越早,损失越小。
事故复盘:Agent "删库跑路"的教训
讲个行业里流传很广的真实教训(也常被拿来当 Agent 安全的教材)。某团队部署了一个能执行 bash 命令的代码生成 Agent,权限是"自动放行所有命令"。某次任务里,模型为了"清理测试目录",先后执行:
rm -rf /tmp/test_123 # 目标测试目录 rm -rf $WORKDIR/Cache # 缓存目录 rm -rf $PROJECT_DIR # ← 出事了!路径变量解析失误第三条命令中,某个环境变量在运行时是空的,拼接出的路径退化成了危险的根目录附近路径,直接删掉了一片不该删的数据。当天回滚 + 紧急救援,成本远超想象。
这事故的三重教训,正好对应我们今天的三道防线:
- 权限太松(第 10 章 v0.5 权限模型会讲
rm -rf永久禁用、命令模式匹配)——本条命令本身该被拦截。 - 没有次数/资源熔断——Agent 连续执行多个破坏性命令,系统毫无觉察。
- 没有留痕——出事后连"它到底执行了啥"都难快速定位。
安全带不是"不用",而是在权限之外再加一层动态止损。这层就叫熔断。
三道熔断:token / 时间 / 次数
我给 Agent 上了三道相辅相成的熔断开关,覆盖失控的三种典型形态:
返回"已达资源上限"提示] -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'STR'
三道闸的含义:
| 熔断开关 | 监控什么 | 典型阈值 | 防什么 |
|---|---|---|---|
| token 熔断 | 累计输入+输出 token | 例如 500K | 烧钱失控 |
| 时间熔断 | 循环总耗时 | 例如 5 分钟 | 卡死 / 跑题 |
| 次数熔断 | 工具调用总次数 | 例如 max_iters 同源 | 无限循环 |
三者都"达到上限就硬停",但侧重不同:token 防"烧钱"、时间防"拖死"、次数防"循环"。而且它们都跨循环累计——不是每次循环单独算,而是整个任务的天花板。
熔断的判定与触发:一个完整的守卫
把三道闸落成一个可以被 Agent Loop 调用的守卫类,逻辑干净:
"""circuit_breaker.py —— DeepPilot 的熔断守卫(第20节,v0.3 会用) 运行环境:Python 3.12 + uv 本文件是纯逻辑演示,无真实 API 调用 """importtimefromdataclassesimportdataclass,field@dataclassclassBudget:"""一次任务的资源预算(熔断三道闸)"""max_tokens:int=500_000# 闸1:累计 token 上限max_seconds:float=300.0# 闸2:总耗时上限(秒)max_calls:int=20# 闸3:工具总调用次数上限total_tokens:int=0# 累计 tokensstart_time:float=field(default_factory=time.time)call_count:int=0# 工具调用计数defcheck_breaker(b:Budget)->str|None:"""检查是否触发熔断。返回 None 表示未触发;否则返回熔断原因"""elapsed=time.time()-b.start_timeifb.total_tokens>=b.max_tokens:returnf"token 熔断: 累计{b.total_tokens}>= 上限{b.max_tokens}"ifelapsed>=b.max_seconds:returnf"时间熔断: 耗时{elapsed:.1f}s >= 上限{b.max_seconds}s"ifb.call_count>=b.max_calls:returnf"次数熔断: 调用{b.call_count}次 >= 上限{b.max_calls}"returnNone# 在 Agent Loop 的每一圈开头调用:budget=Budget()whileTrue:reason=check_breaker(budget)# 每圈先问"还让不让我继续"ifreason:print(f"[熔断]{reason}")print("[停机] 返回: 任务已达资源上限,请人工介入。")break# ... 组装消息 → 调模型 → 执行工具 ...# 每次拿到 usage 后: budget.total_tokens += usage.total_tokens# 每次调用工具后: budget.call_count += 1重点:check_breaker在循环的每一圈开头调用,把"能不能继续"的裁决放到最前面。一旦任一闸门触发,立刻返回"已达资源上限"给用户(而不是让模型继续),并把原因记录下来。
安全事件的留痕:把失控写进档案
熔断触发不是"掩饰过去",而是要记录在案,方便事后复盘和改进(这正是我们全程工程规范里"调用留痕"的价值)。触发时至少记录:
- 熔断类型(token / 时间 / 次数)
- 触发时的累计数值(多少 token / 多少秒 / 多少次)
- 触发原因与当时的任务摘要
- 时间戳与 call_id
一份典型的熔断日志长这样(后面第 30-31 节会教怎么真正落地到日志系统):
2026-09-11 14:03:21 [FATAL] token 熔断触发 | kind=circuit_breaker | budget=500000 | actual=512340 | task="重构 utils.py 的 20 个函数" | call_id=cb_9f3a…"失控"这个黑天鹅事件,最怕的就是没有记录。有条可追溯的档案,下次就能针对性加固(比如"这类任务把 token 预算调高、或拆成多个子 Agent"——子 Agent 是第 12 章的解法)。
防御清单:上生产前逐条打勾
把这一节和第 10 章权限的要点,汇成一张上生产前的自检清单(后续第 49 节还会有 30 项完整清单,这里先给最重要的 6 条):
- 设了 token 熔断(总预算),且会自动停机
- 设了时间熔断,避免卡死
- 设了工具调用次数上限(max_iters/次数熔断)
- 危险命令永久禁用(如
rm -rf、curl|sh等)——第 10 章权限模型 - 每次调用都留痕(token、耗时、参数、时间),熔断事件单独记录
- 熔断后能返回"资源上限"说明给用户,绝不静默死掉
在真正开放给用户之前,先自己用"恶意任务"打一遍。如果连你都拦不住,就别放出去。
小结
- 熔断是止损,不是救活:重试为了成功,熔断为了及时止损,二者缺一不可。
- 三道熔断覆盖失控三形态:token(烧钱)、时间(拖死)、次数(循环),且都跨循环累计。
- 守卫放到每圈循环最前面:先裁决"还让不让我干",再决定要不要干。
- 失控必须留痕:熔断事件写进档案,才谈得上事后复盘和针对性加固。
- 上生产前列防御清单,并用恶意任务先自打一遍。
到这儿,第 4 章 Agent Loop 理论收官。你已经掌握了 Agent 的心脏:循环(16)、出手(17)、停机(18)、容错(19)、跑偏(20)——五个动作是一个生产级 Loop 的全部骨架。
下节预告
从下一节开始,我们进入第 5 章,把 Agent 的六层架构逐层拆开(理论区收官章)。首先是第 21 节——工具系统:Agent 的"双手"到底怎么长出来的。工具注册中心设计、JSON Schema 自动生成、工具描述即提示词……这些决定了你的 Agent 是不是"一把好手"。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注三连!
本系列持续更新中,关注不迷路~