这一章把四个核心问题拿出来,看两个系统各自怎么答。每一节的结构都是:问题是什么 → 两种解法 → 差异的根源。
大语言模型有一个「上下文窗口」—— 一次能看多少文字,是有上限的。对话越长,占用越多,直到装不下。而且每一轮都要把全部历史重新发一遍,所以长上下文不只是「会满」,还是「每一轮都在烧钱」。
五级的关键在于每一级的信息损失和执行代价都比上一级大。系统总是从最轻的手段开始,只有不够才升级。
此外还有一个 Anthropic 私有能力 cache_edits:在服务端删除缓存里的某些内容,而本地消息列表完全不动。这让「压缩」可以做到不破坏提示词缓存的前缀 —— 这是外部开发者拿不到的能力。
class ContextEngine(ABC): # 490 行的抽象基类
def should_compress(...) # 抽象方法:现在该压缩了吗
def compress(...) # 抽象方法:怎么压缩
def select_context(...) # 抽象方法:这一轮发哪些消息
Hermes 不规定压缩策略,它规定的是「压缩器要长什么样」。内置一个叫 compressor 的默认实现,用户可以在配置里换成自己的。
而且它把动作拆成了两个正交的动词:
| 做什么 | |
|---|---|
select_context()选择 | 每一轮都调用。决定「这一轮往模型发哪些消息」。不修改存储的历史 |
compress()压缩 | 只在需要时调用。真正改写存储的历史,是不可逆的 |
源码里记录了一个真实的误用:有人为了让自己的引擎能每轮都介入,把 should_compress() 写死返回 True。结果是每一轮都真的执行一次不可逆压缩 —— 他想要的是「每轮选择」,用的却是「每轮销毁」。
这个故事说明:当一个接口被误用时,往往不是用户笨,而是接口没有把「读」和「写」分开。
| Claude Code | Hermes | |
|---|---|---|
| 形态 | 固定的五级阶梯,写死在系统里 | 一个抽象基类,策略可替换 |
| 可换吗 | 不能。用户只能调参数 | 能。换掉整个引擎 |
| 为什么 | 只服务一种工作负载(编程),可以针对它做到极致优化;而且能用私有的 cache_edits | 要服务未知的、多样的工作负载,无法预先知道最优策略 |
| 代价 | 换不了。你的场景如果不适合这五级,没有出路 | 抽象层本身的成本 —— 490 行接口定义,还要保证任何实现都不破坏系统不变量 |
智能体会执行命令、改文件、发网络请求。怎么防止它做出不可挽回的破坏?
每一次工具调用都走一条 10 步的判定链。最重要的设计是「绕过免疫」:
「绕过免疫」的意思是:有些检查不接受任何形式的豁免。
用户可以打开「不要再问我了」模式来跳过确认框,但跳不过 1d-1g 这几步。设计上把它们放在模式判断之前,就是为了让「跳过模式」这个开关在物理上够不着它们。
这比「在跳过逻辑里写 if 排除掉几项」更可靠 —— 后者依赖于每次改代码的人都记得维护那个排除列表。
第一层是 12 条硬编码红线(HARDLINE_PATTERNS),配合一套相当精密的解析:
| 机制 | 解决的问题 |
|---|---|
_CMDPOS 命令位置锚定 | 只有出现在命令位置的 rm 才算命令。--title "rm -rf /" 里的不算 |
| 引号屏蔽 | 引号里的内容是数据不是命令 —— 但 $() 是例外,它在双引号里仍会执行 |
| Shell 载体识别 | bash -c "..."、ssh host "..." 里面的内容要递归检查 |
| 去混淆 | r''m、r\m、$'\x72m' 都会被还原成 rm |
第二层是执行环境:7 种后端(本机 / Docker / Modal / Vercel / Daytona / Singularity / SSH)。这是唯一真正的硬边界 —— 前面所有规则都是「猜测这条命令危不危险」,只有隔离是「就算危险也出不去」。
必须诚实说明:2026 年 4 月的第三方审计(约 36.4 万行代码)发现 Hermes 有 4 个「严重」、9 个「高」级别的架构问题,头号问题是默认后端是本机、无沙箱。也就是说默认安装等于给模型一个完整权限的终端。
那 5,802 行红线不能替代隔离。它拦的是「一眼看去就是灾难」的命令。
| Claude Code | Hermes | |
|---|---|---|
| 主要手段 | 问人 —— 决策链的终点是弹确认框 | 规则 + 隔离 —— 因为常常没人可问 |
| 模式数量 | 6 种权限模式,用户按场景切换 | 按 Profile / 触发源配置 |
| 不可豁免的部分 | 绕过免疫的 1d-1g 步 | 12 条红线 |
| 隔离 | 有沙箱能力,但主要靠权限层 | 7 种可选环境,但默认不隔离 |
这是「用户在不在场」这条主线最直接的体现。
Claude Code 可以把最难的判断交给人 —— 因为人就在那儿。
Hermes 必须自己判断 —— 所以它需要 5,802 行规则去逼近人的判断力,而这必然做不到,所以还需要隔离层兜底。
| 形态 | 特点 |
|---|---|
| 普通子智能体 | 全新的上下文,独立执行一个任务 |
| 分叉(fork) | 继承父智能体的完整上下文。用了 4 个技巧做到与父级字节级一致的 API 请求前缀,从而共享提示词缓存 |
| 工作流 | 确定性的编排脚本,决定谁在什么时候跑 |
那 4 个分叉缓存技巧值得单独说:为了让子智能体的请求前缀和父级逐字节相同,系统必须保证系统提示词、工具定义、工具顺序、消息序列化方式全都完全一致。任何一个字节不同,整个缓存就失效,成本翻数倍。
MAX_DEPTH = 1 # 只允许一层
_DEFAULT_MAX_CONCURRENT_CHILDREN = 10 # 最多 10 个并发
_RECENT_SUBAGENTS_CAP = 200 # 历史记录上限
加上运行时控制:interrupt_subagent(中断)/ steer_subagent(注入指令)/ set_spawn_paused(停止派生但让现有的跑完)。
另一种模式是看板:多个对等的智能体共享一块任务板,各自认领。配合 kanban_heartbeat 心跳 —— 认领了但死掉的任务会自动回到待认领。
| Claude Code | Hermes | |
|---|---|---|
| 核心关注 | 成本 —— 怎么让子智能体也命中缓存 | 控制 —— 怎么让 10 个无人看管的子智能体不失控 |
| 深度 | 有分叉,层级由工作流脚本决定 | 硬限制一层 |
| 运行中干预 | 用户 Ctrl-C | 三个专门的接口 |
| 对等协作 | 无 | 看板模式 |
| Claude Code | Hermes | |
|---|---|---|
| 扩展点 | 技能 · 插件 · MCP · 15 种钩子事件 · 记忆目录 | 技能 · 插件 · MCP · 抽象基类矩阵 |
| 抽象基类 | 较少 —— 大部分能力是内置的 | 大量 —— 平台 / 记忆 / 上下文 / 模型 / 环境 / 定时,全都是可替换的 |
| 编译期 | 89 个特性开关 + 死代码消除。关掉的功能物理上不进二进制文件 | 运行时加载,无编译期 |
| 技能数量 | 随版本内置 | 81 个,跨 15 个类别 |
这是两个系统独立得出的相同结论,也是本文最值得记住的一条。
目录常驻,内容按需。81 个技能全展开是 6 万 token;只放描述目录是 5 KB。模型看到目录,判断需要哪个,再去读那一个的完整内容。
两个系统在技能、工具、MCP、记忆四个地方都用了这个模式。这不是巧合 —— 它是「上下文有限且昂贵」这个物理约束的必然产物。
| 维度 | Claude Code | Hermes |
|---|---|---|
| 目标形态 | 单一场景做到极致 | 任意场景都能跑 |
| 上下文 | 固定五级阶梯 + 私有缓存编辑 | 可插拔引擎(ABC) |
| 权限 | 10 步级联 + 绕过免疫 | 12 条红线 + 7 种环境 |
| 多智能体 | 分叉 + 缓存共享 | 委派(深度≤1)+ 看板 |
| 扩展 | 钩子事件 + 编译期开关 | 抽象基类矩阵 |
| 入口 | 终端(4 种启动形式) | 22 平台 + CLI + webhook + 定时 |
| 最大投入 | 人机界面(输入框 347 KB) | 自主运行(委派 5,071 行) |
| 失败时 | 用户看到并处理 | 事件系统 + 告警 + 自动重试 |
| 共同点 | 渐进式披露 · 分层安全 · 上下文是最稀缺资源 · fail-closed 默认 | |