【系列:手搓自主 AI Agent:Hermes 架构原理剖析 · 第 7 篇】

安全审批与子 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 开始工作:

  1. 先查两层缓存:session 内已批准 → 放行;永久 allowlist 里的 pattern → 放行
  2. 打印 *** DANGEROUS COMMAND DETECTED ***,展示命令和描述
  3. 提示用户选择四选项之一

四个选项对应不同的放行策略:

  • 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 错

  1. 想做通用权限系统------先把最危险的终端管住
  2. 只检测 rm------SQL DROP、mkfs、kill -9 -1、curl|sh、fork bomb 都要拦
  3. 不做 session 缓存------每次 pip install 都弹确认,用户会关掉系统
  4. 不处理 Unicode 绕过------全角字符轻松骗过正则
  5. 把审批逻辑放核心循环------应该在工具 handler

子 Agent 5 错

  1. 让子 Agent 也能委派------递归失控
  2. 把子 Agent 完整 messages 带回父------失去隔离意义
  3. 给子 Agent 单独 budget------3 并行 = 270 次
  4. 让子 Agent 改记忆------并行写冲突
  5. 不做进度回传------用户以为挂了

小结与预告

s09s10 解决的是 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.mddocs/zh/s10-subagent-delegation.md(审批流程、委派设计详解)

📥 源码获取 :如需本系列全部源码,请在以下链接克隆:

https://gitcode.com/ganxin7932508/learn-hermes-agent.git

相关推荐
梦想很大很大1 小时前
如果有一个本地优先的 Workflow 工具,你们团队会愿意用吗?
python·agent·workflow
啾啾Fun1 小时前
【AI原生组织】6-AI Native团队组建与基础设施搭建
人工智能·chatgpt·ai-native·ai agent·ai原生组织·人机混编
阿图灵1 小时前
Agentic AI 架构入门(九):Agent 通信协议全景——ACP/A2A/AG-UI/MCP
人工智能·ui·架构·ai agent·智能体·mcp·agentic ai
用户8356290780511 小时前
Python 自动化 Word 文本框处理:创建、定位、填充内容与管理
后端·python
数据知道3 小时前
反序列化漏洞:Java、PHP、Python 三条线各讲透
java·网络·python·安全·网络安全·php
努力的小Qin3 小时前
梯度下降如何实现参数优化:从线性回归到 Sigmoid 分类
人工智能·python·神经网络
strength_zhou20133 小时前
Python检查MongoDB索引列中的字段是否存在
python·mongodb
zander2583 小时前
LeetCode 739:每日温度——为什么单调栈要持续弹出
开发语言·python·算法
for_ever_love__4 小时前
python基础语法学习: 文件操作
python·学习