安全审批与子 Agent 委派:让 Agent 敢干活、不乱来
导读: Agent 能自主干活了,但 rm -rf /、DROP TABLE 这些命令一旦由模型自主执行,后果不堪设想。Hermes 用 15 条危险模式正则 + 两层缓存审批,把终端命令锁进笼子;再用子 Agent 委派机制,让复杂任务拆解到独立上下文里并行推进。本文拆解 s09 与 s10 两个核心模块,讲透"敢干活"与"不乱来"的工程平衡。
模型想做的,不能直接变成系统真的做的
你的 Agent 学会了调用工具、读写文件、执行终端命令。它很能干,但也很危险------如果模型在推理中突然决定执行 rm -rf / 或者 DROP TABLE users;,系统真的照做吗?
在 Hermes 架构里,答案是:不会。
因为阶段 2 引入了两个关键模块:s09_permission_system.py(33694 字节)负责危险命令审批,s10_subagent_delegation.py(38908 字节)负责子 Agent 委派。一个管"不乱来",一个管"敢干活"。
为什么只管终端命令?
先说一个反直觉的设计决策:Hermes 的权限系统不是 通用的 deny/allow/ask 三步管道。它只针对终端命令做模式匹配。
为什么?因为 read_file、web_search 这些工具,最坏情况是读到错误信息或搜到无关结果------可逆,可重试。但终端命令不一样:mkfs.ext4 /dev/sda 执行后,数据直接没了,没有任何撤销按钮。
在所有工具里,终端是唯一能造成不可逆损害的那个。所以权限系统的设计原则很明确:把最危险的管住,其他的放行。

15 条危险模式:覆盖六类破坏行为
s09 里定义了 DANGEROUS_PATTERNS 列表,共 15 条正则,预编译后用于每次终端命令检测:
python
DANGEROUS_PATTERNS = [
(r"rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|.*--no-preserve-root)", "Recursive/force file deletion"),
(r"rm\s+-[a-zA-Z]*r", "Recursive file deletion"),
(r"mkfs\.", "Filesystem format"),
(r"dd\s+if=", "Raw disk write"),
(r">\s*/dev/sd[a-z]", "Direct device write"),
(r"chmod\s+(-R\s+)?777", "World-writable permissions"),
(r"chown\s+-R\s+", "Recursive ownership change"),
(r"shutdown|reboot|poweroff|init\s+[06]", "System shutdown/reboot"),
(r"kill\s+-9\s+(-1|1\b)", "Kill all processes"),
(r":\(\)\s*\{\s*:\|\s*:\s*&\s*\}\s*;", "Fork bomb"),
(r"DROP\s+(TABLE|DATABASE|INDEX)", "SQL destructive operation"),
(r"TRUNCATE\s+TABLE", "SQL truncate"),
(r"DELETE\s+FROM\s+\w+\s*;?\s*$", "SQL delete without WHERE"),
(r"curl\s+.*\|\s*(bash|sh|zsh)", "Pipe remote script to shell"),
(r"wget\s+.*\|\s*(bash|sh|zsh)", "Pipe remote script to shell"),
]
注意两个细节:预编译 ------列表定义后立即 re.compile,避免每次命令都重新编译正则;IGNORECASE ------匹配时忽略大小写,防止 RM -RF 绕过。
15 条模式覆盖六类破坏行为:文件删除(rm -rf)、磁盘操作(mkfs/dd/设备写入)、SQL 破坏(DROP/TRUNCATE/DELETE 无 WHERE)、系统服务(shutdown/reboot)、进程管理(kill -9 -1)、远程脚本执行(curl|sh)。

两层缓存:session 内存 + 磁盘持久化
如果每次执行 pip install 都弹确认框,用户大概率会直接关掉系统。所以 s09 实现了两层缓存:
python
_session_approved: set[int] = set() # 本次进程内:pattern_index 已被 session 批准
_ALLOWLIST_FILE = HERMES_HOME / "allowlist.json"
_permanent_allowlist: set[str] = _load_allowlist() # 磁盘持久化
第一层是进程内的 session_approved 集合,存的是 pattern 索引------本次运行中批准过一次,后续直接放行。第二层是磁盘上的 allowlist.json,存的是 pattern 字符串------永久生效,下次启动还在。
审批流程:四选项 once / session / always / deny
当 detect_dangerous_command 命中危险模式后,approve_command 开始工作:
- 先查两层缓存:session 内已批准 → 放行;永久 allowlist 里的 pattern → 放行
- 打印
*** DANGEROUS COMMAND DETECTED ***,展示命令和描述 - 提示用户选择四选项之一
四个选项对应不同的放行策略:
- o(once):只放行这一次,下次再遇到同样命令继续拦截
- s(session) :本次进程内放行,记入
session_approved集合 - a(always) :永久放行,写入
allowlist.json持久化 - d(deny):拒绝执行,命令直接丢弃
这个设计很务实:pip install 这种高频但安全的操作,用户选一次 session 就够了;rm -rf 这种高危操作,永远选 deny。
审批逻辑在工具 handler,不在核心循环
这是 s09 最容易被忽视的架构决策:审批逻辑放在 run_terminal 工具 handler 里,核心循环完全无感知。
python
def run_terminal(command):
detect_dangerous_command(command) # 检测
approve_command(command) # 审批
execute(command) # 执行
核心循环只负责"模型决定调工具",不关心工具内部怎么处理安全。好处是权限系统可以独立演进,不影响循环逻辑;未来可以给不同工具挂不同的安全策略,互不干扰。
三个独特设计:反绕过 / smart approval / 超时
s09 还有三个值得学习的细节:
Unicode 反绕过 。全角字符 rm -rf 能骗过普通正则,但 s09 在匹配前会做 Unicode 规范化,把全角字符转成半角再匹配。
smart approval。对于低风险命令,会启动一个辅助 LLM 评估,自动放行而不打扰用户。注意:这是可选配置,默认关闭。
审批超时默认拒绝。如果用户长时间不响应,默认拒绝执行------安全优先。
过渡:安全了,但任务复杂了怎么办?
权限系统解决了"不乱来",但下一个问题来了:任务太复杂,单个上下文装不下。
比如让 Agent "分析这个项目并写一份优化报告"------需要读代码、查文档、跑测试、对比方案。所有中间过程全堆在同一个 messages 里,上下文迅速膨胀;而且两个不相关的子任务(比如"读配置文件"和"跑性能测试")会互相污染推理质量。
解法是子 Agent 委派:把局部任务放进独立上下文里做,做完只把必要结果带回来。

子 Agent 是什么?
子 Agent 是父 Agent 临时创建的、拥有独立 messages 的 Agent 实例。执行完返回结果后销毁。
关键点:它和父 Agent 共享同一代码路径 ------都是 run_conversation。区别在于三点:独立 messages(上下文隔离)、工具受限、消耗父 Agent 的 iteration budget。
三个硬限制:blocked tools / budget / 深度
s10 用三个硬限制防止子 Agent 失控:
python
DELEGATE_BLOCKED_TOOLS = {"delegate_task", "memory", "skill_manage"}
MAX_CHILD_ITERATIONS = 15
DELEGATE_BLOCKED_TOOLS:子 Agent 不能递归委派、不能改记忆、不能改技能。子 Agent 是一次性的,不应该产生持久副作用。
MAX_CHILD_ITERATIONS = 15:子 Agent 给更少的预算。父 Agent 可能有 90 次迭代,子 Agent 最多 15 次,防止子任务无限循环。
深度限制 2 层:父 → 子,子不能再派子。递归委派是失控的根源。

build_child_agent:不继承父的 SOUL/MEMORY
看真实代码,build_child_agent 的设计很精细:
python
DELEGATE_BLOCKED_TOOLS = {"delegate_task", "memory", "skill_manage"}
def build_child_agent(goal, context, toolsets):
# 从父 registry 同一个工具池取,按黑名单过滤
child_tools = []
for tool_def in registry.get_definitions(toolsets):
function_name = tool_def["function"]["name"]
if function_name not in DELEGATE_BLOCKED_TOOLS:
child_tools.append(tool_def)
# 子 agent system prompt 一次性、任务专属;不继承父的 SOUL/MEMORY
child_prompt = (
"You are a focused sub-agent. "
"Complete the assigned task and report results.\n"
"Do NOT delegate further. "
"Do NOT modify memory or skills.\n\n"
f"# Task\n{goal}\n\n"
)
if context:
child_prompt += f"# Context\n{context}\n\n"
child_messages = [{"role": "user", "content": goal}]
return {
"system_prompt": child_prompt,
"messages": child_messages,
"tools": child_tools,
}
注意:子 Agent 不继承父的 SOUL/MEMORY ,system prompt 是任务专属的。而且双保险------prompt 层明确说"不要委派、不要改记忆",代码层用 DELEGATE_BLOCKED_TOOLS 拦截。
budget 共享:从父的预算里扣
这是最容易做错的地方。很多实现会给子 Agent 单独预算------3 个并行子 Agent 各 90 次,总共 270 次,直接失控。
Hermes 的做法是:子 Agent 从父 Agent 的 iteration budget 里扣。子 Agent 不是"另外给 15 次",而是消耗父的预算。这样总迭代次数可控,不会因为委派而无限膨胀。
进度回传与心跳机制
子 Agent 执行时间可能很长,用户会以为系统挂了。s10 用两个机制解决:
进度回传 :通过 tool_progress_callback 观察者模式,闭包捕获父的 spinner,子 Agent 每执行一步就更新父的进度显示。
心跳机制 :后台线程每 30 秒更新 _last_activity_ts,骗过 Gateway 的不活动检测------防止父 Agent 等子 Agent 时被当卡死杀掉。
这两个机制是"敢干活"的保障:用户能看到进度,系统不会误杀。
两章避坑:10 个初学者错误精选
权限系统 5 错:
- 想做通用权限系统------先把最危险的终端管住
- 只检测 rm------SQL DROP、mkfs、kill -9 -1、curl|sh、fork bomb 都要拦
- 不做 session 缓存------每次 pip install 都弹确认,用户会关掉系统
- 不处理 Unicode 绕过------全角字符轻松骗过正则
- 把审批逻辑放核心循环------应该在工具 handler
子 Agent 5 错:
- 让子 Agent 也能委派------递归失控
- 把子 Agent 完整 messages 带回父------失去隔离意义
- 给子 Agent 单独 budget------3 并行 = 270 次
- 让子 Agent 改记忆------并行写冲突
- 不做进度回传------用户以为挂了
小结与预告
s09 和 s10 解决的是 Agent 工程化的两个核心问题:安全边界 (不乱来)和任务分解(敢干活)。前者的关键是"只管最危险的终端命令 + 两层缓存 + 工具 handler 内审批";后者的关键是"独立上下文 + 受限工具 + 共享 budget + 深度限制"。
下一篇(第 8 篇)将拆解配置系统优先级链------当 CLI 参数、环境变量、配置文件、默认值同时存在时,谁说了算?
你在设计 Agent 权限系统时,是选择"白名单放行"还是"黑名单拦截"?欢迎在评论区聊聊你的取舍。
参考文献
- Hermes Agent 教学仓库:
agents/s09_permission_system.py(本文代码素材,33694 字节真实可运行) - Hermes Agent 教学仓库:
agents/s10_subagent_delegation.py(本文代码素材,38908 字节真实可运行) - Hermes Agent 教学仓库:
docs/zh/s09-permission-system.md与docs/zh/s10-subagent-delegation.md(审批流程、委派设计详解)
📥 源码获取 :如需本系列全部源码,请在以下链接克隆: