全文目录Contents
- 0 · What Problem Each System Is Solving
- 1 · Mechanism by Mechanism
- 2 · The Consensus They Reached Independently
- Consensus 1 · Context Is the Scarcest Resource
- Consensus 2 · Progressive Disclosure
- Consensus 3 · Safety Must Be Layered, and No Layer Is Reliable
- Consensus 4 · The Default Must Be “Fail Closed”
- Consensus 5 · Kill a Whole Class of Problems With a Hard Limit
- Consensus 6 · Abstract “What to Do,” Query “What Can You Do,” Never Ask “Who Are You”
- Consensus 7 · Errors Must Be Classified, Because Different Kinds Need Different Strategies
- Consensus 8 · Claim-Type Operations Need a Lease
- 2.9 The Eight Points on One Page
- 3 · The Cost of Every Choice
- 4 · How to Choose When You Build Your Own
0 · 两个系统在解什么题
前两篇文章分别讲了 Claude Code 和 Hermes 各自的架构。这一篇只做一件事:把它们放在一起,看同一个问题的两种答案。
先声明一件重要的事,避免误读整篇文章。
本文不评判「谁更好」。这两个系统解的不是同一道题。把它们的设计选择直接对比,就像比较一辆越野车和一辆城市通勤车的离地间隙 —— 数字上有高低,但那个高低本身没有意义,有意义的是「为什么在各自的用途下,这个数字是对的」。
这篇文章要提取的是那个「为什么」。因为在你自己的项目里,你要做的也是同样的推导:先想清楚约束,再选设计。
0.1 两句话的定位
| Claude Code | Hermes | |
|---|---|---|
| 是什么 | 一个命令行编程助手。你在终端里跑它,它帮你读代码、改代码、跑测试 | 一个可以住在任何地方的个人智能体。它在 22 个聊天平台上活着,也在定时任务里活着 |
| 一句话 | 把一件事(写代码)做到极致 | 把一个东西(智能体)放到所有地方 |
| 技术栈 | TypeScript,编译成单个可执行文件 | Python,插件化的运行时 |
| 开源状态 | 闭源(本文分析基于泄漏的源码) | 开源 |
| 用户在场 | 几乎总是在场 —— 你盯着终端 | 经常不在场 —— 半夜的定时任务、群里的自动回复 |
0.2 「用户在不在场」是所有分歧的源头
上表最后一行,是理解两个系统全部差异的钥匙。
这一条差异会向下传导,改变几乎每一层的设计。后面几章逐个展开,但你会发现绝大多数分歧最终都能回溯到这里。
0.3 从代码体量看重心
看两个系统各自最大的文件,能直接看出它们把力气花在哪:
| Claude Code 的重头戏 | Hermes 的重头戏 |
|---|---|
PromptInput.tsx —— 347 KB终端里的那个输入框 |
delegate_tool.py —— 5,071 行多智能体委派 |
| 上下文治理五级阶梯 工具结果预算 → 剪裁 → 微压缩 → 坍缩 → 自动压缩 |
审批红线 —— 5,802 行 命令危险性判定 |
| 权限系统的 10 步级联 含 4 步「绕过免疫」检查 |
scheduler.py —— 367 KB定时任务 |
| 提示词缓存优化 分叉子智能体做到字节级前缀一致 |
7 种执行环境 + 22 个平台适配器 抽象基类矩阵 |
一个输入框 347 KB,一个委派工具 5,071 行 —— 这两个数字放在一起,比任何架构图都能说明问题。
Claude Code 把最大的力气花在「人和智能体之间那 5 厘米」:输入体验、渲染性能、中断响应、状态展示。因为用户全程盯着这块屏幕。
Hermes 把最大的力气花在「智能体自己活下去」:怎么派活、怎么定时、怎么判断危险、怎么跨平台。因为没人在旁边看着。
0.4 本文的结构
| 章节 | 内容 |
|---|---|
| 第 1 章 | 机制对照 —— 同一个问题(上下文、权限、多智能体、扩展),两套解法逐条对比 |
| 第 2 章 | 共识 —— 两个团队独立地得出了哪些相同的结论。这些是最有价值的部分 |
| 第 3 章 | 代价 —— 每个选择放弃了什么。没有免费的设计 |
| 第 4 章 | 决策指南 —— 你自己做系统时,怎么在这两条路之间选 |
0.5 一个提醒:不要照抄
这两个系统的很多设计,在你的项目里可能是错的。
举例:Hermes 的 5,802 行红线规则、里面那个 _CMDPOS 命令位置锚定,还有 Claude Code 权限系统里的「影子规则检测」—— 这些都是被真实事故逼出来的。Hermes 源码的注释里就明确写了触发 _CMDPOS 的那次事故:一条 gh pr create --title "block rm -rf / spellings" 命令,因为标题里含有 rm -rf / 字样而被误拦 —— 那明明只是一个字符串参数,不是要执行的命令。
你的系统没有经历过那次事故,直接抄一个 5,802 行的规则库,得到的是五千行你不理解、不敢改、也不知道为什么存在的代码。
正确的做法是:理解每条规则回答的是什么问题,然后在你的系统里判断这个问题存不存在。
1 · 机制逐条对照
这一章把四个核心问题拿出来,看两个系统各自怎么答。每一节的结构都是:问题是什么 → 两种解法 → 差异的根源。
1.1 上下文治理
问题
大语言模型有一个「上下文窗口」—— 一次能看多少文字,是有上限的。对话越长,占用越多,直到装不下。而且每一轮都要把全部历史重新发一遍,所以长上下文不只是「会满」,还是「每一轮都在烧钱」。
Claude Code:五级阶梯
五级的关键在于每一级的信息损失和执行代价都比上一级大。系统总是从最轻的手段开始,只有不够才升级。
此外还有一个 Anthropic 私有能力 cache_edits:在服务端删除缓存里的某些内容,而本地消息列表完全不动。这让「压缩」可以做到不破坏提示词缓存的前缀 —— 这是外部开发者拿不到的能力。
Hermes:可插拔引擎
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 行接口定义,还要保证任何实现都不破坏系统不变量 |
1.2 权限与安全
问题
智能体会执行命令、改文件、发网络请求。怎么防止它做出不可挽回的破坏?
Claude Code:10 步级联决策
每一次工具调用都走一条 10 步的判定链。最重要的设计是「绕过免疫」:
「绕过免疫」的意思是:有些检查不接受任何形式的豁免。
用户可以打开「不要再问我了」模式来跳过确认框,但跳不过 1d-1g 这几步。设计上把它们放在模式判断之前,就是为了让「跳过模式」这个开关在物理上够不着它们。
这比「在跳过逻辑里写 if 排除掉几项」更可靠 —— 后者依赖于每次改代码的人都记得维护那个排除列表。
Hermes:红线 + 环境隔离
第一层是 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 行规则去逼近人的判断力,而这必然做不到,所以还需要隔离层兜底。
1.3 多智能体
Claude Code:三种形态
| 形态 | 特点 |
|---|---|
| 普通子智能体 | 全新的上下文,独立执行一个任务 |
| 分叉(fork) | 继承父智能体的完整上下文。用了 4 个技巧做到与父级字节级一致的 API 请求前缀,从而共享提示词缓存 |
| 工作流 | 确定性的编排脚本,决定谁在什么时候跑 |
那 4 个分叉缓存技巧值得单独说:为了让子智能体的请求前缀和父级逐字节相同,系统必须保证系统提示词、工具定义、工具顺序、消息序列化方式全都完全一致。任何一个字节不同,整个缓存就失效,成本翻数倍。
Hermes:委派 + 看板
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 | 三个专门的接口 |
| 对等协作 | 无 | 看板模式 |
1.4 扩展机制
| Claude Code | Hermes | |
|---|---|---|
| 扩展点 | 技能 · 插件 · MCP · 15 种钩子事件 · 记忆目录 | 技能 · 插件 · MCP · 抽象基类矩阵 |
| 抽象基类 | 较少 —— 大部分能力是内置的 | 大量 —— 平台 / 记忆 / 上下文 / 模型 / 环境 / 定时,全都是可替换的 |
| 编译期 | 89 个特性开关 + 死代码消除。关掉的功能物理上不进二进制文件 | 运行时加载,无编译期 |
| 技能数量 | 随版本内置 | 81 个,跨 15 个类别 |
两个都用的模式:渐进式披露
这是两个系统独立得出的相同结论,也是本文最值得记住的一条。
目录常驻,内容按需。81 个技能全展开是 6 万 token;只放描述目录是 5 KB。模型看到目录,判断需要哪个,再去读那一个的完整内容。
两个系统在技能、工具、MCP、记忆四个地方都用了这个模式。这不是巧合 —— 它是「上下文有限且昂贵」这个物理约束的必然产物。
1.5 一页速查表
| 维度 | Claude Code | Hermes |
|---|---|---|
| 目标形态 | 单一场景做到极致 | 任意场景都能跑 |
| 上下文 | 固定五级阶梯 + 私有缓存编辑 | 可插拔引擎(ABC) |
| 权限 | 10 步级联 + 绕过免疫 | 12 条红线 + 7 种环境 |
| 多智能体 | 分叉 + 缓存共享 | 委派(深度≤1)+ 看板 |
| 扩展 | 钩子事件 + 编译期开关 | 抽象基类矩阵 |
| 入口 | 终端(4 种启动形式) | 22 平台 + CLI + webhook + 定时 |
| 最大投入 | 人机界面(输入框 347 KB) | 自主运行(委派 5,071 行) |
| 失败时 | 用户看到并处理 | 事件系统 + 告警 + 自动重试 |
| 共同点 | 渐进式披露 · 分层安全 · 上下文是最稀缺资源 · fail-closed 默认 | |
2 · 独立得出的共识
上一章看的是差异。这一章看更有价值的东西:两个团队、两种语言、两套目标,独立地得出了哪些相同的结论。
为什么「共识」比「差异」更值得学?
差异往往来自具体约束 —— 换一个场景就不成立。
共识意味着这是被问题本身逼出来的,两条完全不同的路径最终收敛到同一个答案。这种结论在你的项目里大概率也成立。
下面 8 条,每一条都是你在设计任何智能体系统时应该默认采纳的。
共识 1 · 上下文是最稀缺的资源
不是算力,不是存储,不是网络。是模型一次能看多少字。
| Claude Code 的体现 | Hermes 的体现 |
|---|---|
| 五级治理阶梯 | 可插拔上下文引擎(490 行接口) |
| 工具搜索(不全量列工具) | 工具集按信任级别投放 |
| 子智能体隔离上下文 | 委派分区上下文 |
| 技能/记忆目录常驻,内容按需 | 技能/记忆目录常驻,内容按需 |
| 输出预算 + 剪裁 | _BoundedOutputCollector 40/60 头尾窗口 |
这条共识的深层含义:你的架构里每一个「往上下文里放东西」的决定,都是在花一笔会被每一轮重复收取的钱。
一个 500 token 的工具描述,在一个 40 轮的对话里被发送了 40 次 —— 20,000 token。而它可能一次都没被用到。
「常驻上下文的每一个 token 都要有理由。」
共识 2 · 渐进式披露
共识 1 的直接推论,但值得单独列出,因为它的应用范围超乎想象。
模式:目录常驻(廉价)+ 内容按需(昂贵但只在需要时付费)
Claude Code 用在:工具 · 技能 · MCP · 记忆目录 · 文件读取
Hermes 用在:工具集 · 技能(81 个)· 记忆预取 · 环境输出
而且两边都发现了同一个次级结论:「目录条目的描述质量决定一切」。因为那是模型唯一能看到的、用来判断「我需不需要展开这一项」的信息。
共识 3 · 安全必须分层,且每一层都不可靠
两边都明确承认「规则层拦不住一切」。
Hermes 的审计报告直接写明红线不能替代沙箱。Claude Code 把绕过免疫检查放在模式判断之前,等于承认「用户可能开了一个让规则层失效的模式」。
结论:不要指望任何单层防御。攻击面是相乘的,防御也必须是相乘的。
共识 4 · 默认值必须是「失败即关闭」
「fail-closed」的意思是:当系统不确定的时候,选择拒绝,而不是允许。
| Claude Code | Hermes |
|---|---|
| 规则解析失败 → 视为不匹配(不放行) | 注入检测不确定 → 中止整个定时任务 |
| 工具未在白名单 → 拒绝 | 记忆检查点 v2 → 失败即中止(v1 是尽力而为) |
| 找不到对应的权限规则 → 询问用户 | 凭据不可用 → 跳过,不猜测 |
Hermes 那个 v1 → v2 的演进特别说明问题:早期版本的记忆检查点是「尽力而为」(失败了就跳过,继续跑)。后来加了一个显式的版本号 PRE_COMPRESS_CHECKPOINT_API_VERSION = 2,v2 的语义是「失败就中止压缩」。这是从 fail-open 走向 fail-closed 的一次明确修正。
共识 5 · 用硬限制消灭一整类问题
| Claude Code | Hermes |
|---|---|
| 工具结果字符预算 | MAX_DEPTH = 1 |
| 并发工具执行的贪心分区 | _DEFAULT_MAX_CONCURRENT_CHILDREN = 10 |
| 思考块签名绑定模型 | _RECENT_SUBAGENTS_CAP = 200 |
| 兄弟中止控制器(两级取消域) | _is_descendant_of(max_hops=8) |
_TOOL_DEFS_CACHE_MAX = 8 |
注意 max_hops=8 这一项 —— 它是最能说明问题的一个。
既然 MAX_DEPTH = 1,理论上遍历父子关系最多只需要 1 跳。设成 8 是纯防御性的:万一数据里出现了环,这个上限保证函数一定会返回,而不是把进程挂死。
「即使不变量被破坏,程序也不能挂死」 —— 这是成熟系统和 demo 之间最明显的一条分界线。
共识 6 · 抽象「做什么」,查询「能做什么」,永不判断「你是谁」
两边都在多处独立使用了这个模式:
// Hermes 的平台适配器
adapter.supports_threads() 而不是 if platform == "slack"
adapter.supports_reactions()
adapter.supports_editing()
// Hermes 的模型供应商
provider.supports_prompt_caching() 而不是 if provider == "anthropic"
// Claude Code 的工具能力组
tool.isConcurrencySafe() 而不是 if toolName == "Read"
tool.isReadOnly()
为什么这个模式如此重要:
if platform == "slack" 这样的判断,每加一个平台就要改所有出现它的地方。而且你永远不知道漏了哪一处 —— 编译器不会告诉你。
adapter.supports_threads() 则把「新平台要回答哪些问题」变成了接口的一部分。加一个平台,你必须实现所有的能力查询方法,漏掉的会立刻报错。
这个模式可以直接搬到任何「一群做同一件事但能力不同的外部系统」的场景:支付渠道、短信通道、对象存储、推送服务。
共识 7 · 错误必须分类,因为不同类型要用不同策略
| Claude Code | Hermes |
|---|---|
错误恢复状态机 —— 带命名 transition 字段,每条路径有幂等守卫 | EnvironmentConnectionError —— 把「基础设施故障」和「命令执行失败」分开 |
| 三级 413 瀑布 —— 上下文过长的分级处理 | 凭据池按 HTTP 状态码决定冷却时长:429 短冷却、401 长冷却、5xx 极短冷却 |
| withhold 机制 —— 可恢复的错误在恢复手段用尽前不暴露给外部 | _failure_streak_nudge —— 连续失败才告警,偶发失败不打扰 |
核心结论:「重试」不是一个动作,是一族策略。
用统一的重试间隔,要么对短暂故障太慢(浪费可用时间),要么对永久故障太急(无意义地刷屏)。
决定重试策略的,永远是「这个错误是什么类型」,而不是「重试了几次」。
共识 8 · 认领类操作必须有租约
两边都独立遇到并解决了同一个分布式问题:
| Claude Code | Hermes |
|---|---|
孤儿 tool_use 处理 —— 每一条中止路径都必须合成一个 tool_result,否则下一次请求会被 API 拒绝(400) | kanban_heartbeat —— 认领任务后要持续心跳,超时自动释放 |
| 墓碑(tombstone)机制 —— 标记已取消的工具 | try_register_running_job —— 防止定时任务堆叠,且注册记录必须带过期时间 |
2.9 八条共识的一页速记
1. 上下文是最稀缺的资源 —— 常驻的每个 token 都要有理由
2. 渐进式披露 —— 目录常驻,内容按需,描述质量决定一切
3. 安全分层 —— 每一层都不可靠,只有相乘才够用
4. 失败即关闭 —— 不确定时选择拒绝
5. 硬限制 —— 用一个常量消灭一整类问题,好过智能地处理它
6. 能力查询 —— 抽象「做什么」,查询「能做什么」,永不判断「你是谁」
7. 错误分类 —— 重试策略由错误类型决定,不由次数决定
8. 认领带租约 —— 两段式操作必须处理「声明了但没完成」
这八条是本系列文章里最可迁移的部分。
它们不依赖于你用 Python 还是 TypeScript,不依赖于你做的是编程助手还是客服机器人,也不依赖于你用哪家模型。它们来自「智能体」这个形态本身的物理约束。
面试里如果被问到「你怎么设计一个智能体系统」,把这八条讲清楚,比背出任何一个具体实现都有说服力。
3 · 每个选择的代价
前两章讲了差异和共识。这一章讲一件更少被谈论的事:每一个设计选择放弃了什么。
为什么必须单独讲代价?
技术文章通常只讲「这个设计好在哪」。但在真实的架构决策里,你要判断的从来不是「这个设计好不好」,而是「它的代价我能不能承受」。
一个你付不起代价的好设计,等于一个坏设计。
3.1 Claude Code 的代价
代价 1 · 固定的上下文策略换不掉
| 得到 | 失去 |
|---|---|
五级阶梯针对编程场景做到了极致优化,配合私有的 cache_edits 能力,能在压缩的同时保住缓存前缀 | 如果你的工作负载不是编程,你没有出路。五级阶梯的每一级都对「什么信息可以丢」有隐含假设 —— 而那个假设是围绕代码工作流建立的 |
举个具体的:五级阶梯里的「剪裁」优先丢弃最老的工具结果。在编程场景这通常没问题(你读过的文件内容,需要时可以再读一遍)。但如果你的场景是长期的、有状态的对话(比如一个持续几周的项目管理助手),最老的信息可能恰恰是最重要的约束条件。
代价 2 · 编译期特性开关的双刃
89 个 feature() 开关 + 死代码消除
→ 关掉的功能【物理上不进二进制文件】
| 得到 | 失去 |
|---|---|
| 体积更小、攻击面更小、没有「关掉的功能还残留代码」的风险。而且有「排除字符串检查」这样的构建时验证,确保被禁用的功能真的没漏出来 | 用户不能在运行时开启任何东西。要一个不同的功能组合,就要重新构建一个二进制文件。 而且这套机制本身有复杂度成本 —— 源码里能看到「排除字符串检查」逼出了一个奇怪的回调注入( snipReplay),只为了绕过构建时校验 |
代价 3 · 单一入口形态
Claude Code 是终端工具。它有 4 种启动形式,但都在终端里。没有聊天平台集成、没有 webhook、没有定时任务。
这换来的是:整套交互设计可以假设「有一个 TTY、有一个人在看、支持 ANSI 转义序列、支持 Ctrl-C」。那 347 KB 的输入框组件之所以能存在,正是因为它只需要服务一种终端。
代价 4 · 闭源
这是最实际的一条:你不能在自己的产品里用它的实现。
本系列的分析基于泄漏的源码,性质是架构学习,不是可复用的代码资产。你能学到设计思路,但要自己重新实现。
而且其中一些关键能力(比如 cache_edits)是 Anthropic 的私有服务端能力,外部开发者根本拿不到。照着抄一个「压缩时不破坏缓存」的实现,是做不到的。
3.2 Hermes 的代价
代价 1 · 抽象层的重量
ContextEngine 抽象基类:490 行接口定义
内置 compressor 实现: 419 KB
→ 接口与实现的比例:490 行 : 419 KB
490 行接口不是「一堆方法签名」,它包含了大量必须遵守的契约说明:
· 生命周期是「不是每轮一次」
· select_context() 的输出必须在所有校验器之前
· 缓存不变式:你的实现不能破坏前缀
· usage 字典的版本演进规则
· protect_first_n 的语义在某个版本变过
一个第三方要写出一个正确的上下文引擎,需要读懂并遵守全部这些约定。这个门槛非常高 —— 高到实践中可能几乎没有人真的换掉默认实现。
那么这 490 行的抽象成本值不值?这是一个真实的、没有标准答案的问题。
代价 2 · 默认不安全
| 得到 | 失去 |
|---|---|
| 本机执行最快、最简单、没有文件同步问题。开箱即用 | 默认安装等于给模型一个完整权限的终端。审计报告的头号「严重」问题 而且这个风险是隐性的 —— 用户不会主动去改一个「已经能正常工作」的默认配置 |
「安全的默认值」和「好用的默认值」在这里直接冲突。
如果默认是 Docker:新用户第一次跑要等镜像下载,要处理文件同步,可能因为 Docker 没装而直接失败。大量用户会在第一次尝试时流失。
如果默认是本机:所有人都能立刻跑起来,代价是所有人默认处在无隔离状态。
这个权衡没有正确答案,只有明确的取舍。但至少应该在首次运行时明确告知用户当前处于无隔离模式 —— 让风险是「用户知情后接受的」,而不是「用户不知道的」。
代价 3 · 22 个平台的持续维护负担
相关的还有一条隐性成本:测试。22 个平台每一个都需要真实的测试账号、测试工作区、测试凭据。全量回归测试的成本极高,实践中很可能做不到。
代价 4 · Python 的分发问题
| Claude Code | Hermes |
|---|---|
| Bun 编译成单个可执行文件(296 MB)。下载即用,没有依赖问题 | Python 包。用户要有正确版本的 Python、要装依赖、要处理虚拟环境、要面对依赖冲突 |
296 MB 听起来很大,但它换来的是「一个文件,双击就跑」。而任何做过 Python 应用分发的人都知道,「在我机器上是好的」这句话有多常见。
3.3 两边共有的代价
共同代价 1 · 复杂度是不可逆的
Claude Code 的 10 步权限级联、Hermes 的 5,802 行红线 —— 这些复杂度一旦加进去,就再也拿不出来了。
因为每一条规则都对应一个曾经发生过的问题。删掉它,那个问题就会回来。而且删规则的人通常不知道它为什么存在(写它的人可能已经离职了)。
结论:加规则要慎重,因为你是在给系统加一笔永久负债。每一条规则最好都带上「它防的是什么」的注释 —— Hermes 那条记录了 gh pr create --title 事故的注释就是正面例子。
共同代价 2 · 对模型行为的强依赖
两个系统都在提示词里写了大量「你必须这样做」的约束:
- Claude Code 的「思考三定律」、压缩时的「不许调用任何工具」(穷举点名 + 说明后果)
- Hermes 的注入检测、技能描述的匹配
这些约束是「劝说」,不是「强制」。
模型可能不听。而且换一个模型版本,服从度就会变化 —— 一段在旧版本上 99% 有效的提示词,在新版本上可能只有 90% 有效,而那 10% 会变成线上事故。
Claude Code 的源码里能看到这个演进痕迹:那条「不许调用工具」的指令原来放在结尾,后来移到了开头,因为发现模型对开头指令的服从度明显更高。
这类依赖是智能体系统特有的技术债:它不在你的代码里,而在模型的权重里,你无法测试也无法控制。
共同代价 3 · 可观测性的成本
Claude Code 有 660 个事件类型、1,093 个埋点。这些代码本身要写、要维护、要在重构时同步更新。Hermes 有事件系统、投递台账、技能使用统计、执行历史。
但这个代价是必须付的 —— 因为智能体系统的失败往往是「行为不对」而不是「报错了」,没有埋点就完全无法排查。
3.4 代价对照总表
| 维度 | Claude Code 付出的 | Hermes 付出的 |
|---|---|---|
| 灵活性 | 上下文策略换不掉;只服务终端场景 | — |
| 复杂度 | 编译期开关体系(89 个 + 死代码消除 + 排除检查) | 抽象基类矩阵(6 类扩展点,每个都有完整契约) |
| 安全 | — | 默认无隔离(审计头号问题) |
| 维护 | — | 22 个平台的永久负债 |
| 分发 | — | Python 依赖地狱 |
| 可复用 | 闭源 + 依赖私有服务端能力 | — |
| 共有 | 复杂度不可逆 · 依赖模型服从度 · 可观测性成本 | |
看完这张表你会发现一件事:两边的代价几乎是互补的。
Claude Code 用「灵活性」换「极致优化和分发便利」。
Hermes 用「安全默认值和维护成本」换「无处不在和可替换」。
没有一个选择是免费的,也没有一个是错的 —— 只有「跟你的约束匹配」和「不匹配」。下一章讲怎么判断你的约束是什么。
4 · 你自己做系统时怎么选
这一章把前面所有内容转成可操作的判断。不是「学哪个」,而是「在你的约束下,哪个设计是对的」。
4.1 第一个问题:用户在不在场
这是分水岭。先回答它,后面一半的决策会自动确定。
4.2 第二个问题:工作负载是单一的还是多样的
| 单一工作负载 | 多样工作负载 |
|---|---|
| 例子:只做代码助手 / 只做客服 / 只做数据分析 | 例子:一个平台,上面跑各种各样的智能体 |
| 该做:把上下文策略、工具集、提示词全部针对这一种负载做死。深度优化 | 该做:抽象基类 + 可插拔。允许每种负载配自己的策略 |
| 不该做:过早抽象。你会为了一个永远不会有第二种实现的接口,付出永久的复杂度 | 不该做:把某一种负载的假设写进核心。它会在第二种负载出现时炸掉 |
最常见的错误是「预防性抽象」:还没有第二种实现,就先定义一个抽象基类。
代价:你的接口是凭想象设计的,而不是从两个真实实现里提炼的。等真的出现第二种实现时,你会发现接口不合适,然后要么改接口(破坏第一个实现),要么让第二个实现别扭地适配。
更好的做法:先写死。当出现第二个真实需求时,再从两个具体实现里提炼接口。那时你提炼出来的接口才是对的。
4.3 第三个问题:谁来扩展
| 扩展者 | 该提供的机制 | 不该提供的 |
|---|---|---|
| 只有你自己 | 直接改代码。也许加几个配置项 | 插件系统。你在给自己制造麻烦 |
| 你的团队 | 技能(Markdown) + 配置。零代码,人人可写 | 复杂的插件 API |
| 其他工程团队 | 工具注册 + 钩子 + MCP | 让他们能替换核心策略 |
| 不可信的第三方 | MCP(跨进程隔离) | 同进程插件。一个崩溃会拖垮你 |
记住 Hermes 第 9 章那个权限梯度:扩展的门槛应该和它能造成的破坏成正比。
技能(纯文本、人人可写、最多让智能体走错路)
→ MCP(跨进程、崩溃隔离、只能提供工具)
→ 插件(同进程、能挂钩子、但钩子只能追加不能替换)
→ 核心策略(能替换整个上下文,但单选,必须用户显式配置)
不要提供一个万能的插件接口让所有人都能做所有事。
4.4 无论选哪条路,都必须做的八件事
这是第 2 章八条共识的行动版本:
| # | 要做的事 | 具体动作 |
|---|---|---|
| 1 | 给上下文记账 | 算清楚:系统提示词多少 token、工具定义多少、每轮增长多少。任何常驻内容都要能说出它为什么值这个价 |
| 2 | 渐进式披露 | 工具、技能、文档、记忆 —— 全部改成「目录常驻 + 内容按需」。并且在描述质量上投入时间 |
| 3 | 至少两层安全 | 「能力收窄」(不给危险工具)+ 「执行隔离」(容器/沙箱)。规则匹配不算一层可靠的防御 |
| 4 | 默认失败即关闭 | 检查每一处 try/except:出异常时是放行还是拒绝?把所有「出错就跳过」改成「出错就拒绝」 |
| 5 | 加硬限制 | 委派深度、并发数、单次输出长度、遍历跳数、缓存条目数。每一个「理论上不会太大」的量都要有上限 |
| 6 | 能力查询而非身份判断 | 搜索代码里所有的 if xxx == "某个具体名字",改成 if xxx.supports_yyy() |
| 7 | 错误分类 | 至少分出三类:可重试的临时故障(短退避)/ 配置或凭据问题(长退避或告警)/ 逻辑错误(不重试,直接报告) |
| 8 | 两段式操作带租约 | 任何「先声明后完成」的操作(工具调用、任务认领、锁),都要处理「声明了但没完成」:补偿或超时 |
4.5 面试场景:怎么把这些讲出来
不要从「有一个循环,模型调工具,工具返回结果」开始讲。这是所有人都会说的,说明不了什么。
从约束开始讲:
「首先要确定两件事:用户在不在场,以及工作负载是单一还是多样。
用户在场,安全的终点可以是问人;不在场,就必须有自动决策规则、告警系统和执行隔离,因为出了问题没人会发现。
工作负载单一,就把策略写死做深度优化;多样,就需要抽象基类 —— 但要区分哪些是可叠加的能力,哪些是必须单选的策略。
然后是三个跑不掉的约束:上下文是最稀缺的资源,所以一切设计围绕渐进式披露;安全必须分层,因为每一层都不可靠;默认值必须是 fail-closed。」
| 被问到 | 可以举的例子 |
|---|---|
| 上下文管理 | 五级阶梯(每级的信息损失递增,从最轻的开始);select_context 与 compress 的正交(读 vs 写,那个误用故事) |
| 提示词缓存 | 前缀匹配,一个字节不同就全部失效;分叉子智能体为了共享缓存做到字节级一致 |
| 安全 | _CMDPOS 命令位置锚定和那次 gh pr create --title 误拦事故;绕过免疫检查放在模式判断之前 |
| 多智能体 | MAX_DEPTH = 1(用硬限制消灭指数爆炸);set_spawn_paused(区分「停止接受新工作」和「终止现有工作」) |
| 可靠性 | 孤儿 tool_use 必须在每条中止路径合成结果;try_register_running_job 防定时任务堆叠 |
| 重试 | 凭据池按 HTTP 状态码决定冷却时长(429 短、401 长、5xx 极短) |
4.6 最后:一个可以带走的判断框架
这七个问题不需要你记住任何一个具体实现。
它们是从 Claude Code 的 176,391 字分析和 Hermes 的 141,079 字分析里提炼出来的、真正可迁移的部分。
具体的实现会过时 —— 模型会变、API 会变、框架会变。但这些约束来自「智能体」这个形态本身,它们不会变。
0 · What Problem Each System Is Solving
The previous two articles covered the architecture of Claude Code and Hermes, each on its own. This one does exactly one thing: it puts them side by side and looks at two answers to the same question.
One important thing up front, so the rest of the article isn't misread.
This article does not judge which is “better.” These two systems are not solving the same problem. Comparing their design choices head-on is like comparing the ground clearance of an off-road truck and a city commuter car: one number is higher, but that by itself means nothing. What matters is “why, given what each one is for, that number is the right one.”
What this article is after is that “why.” Because in your own project you'll be running the same derivation: get clear on the constraints first, then pick the design.
0.1 Each in Two Sentences
| Claude Code | Hermes | |
|---|---|---|
| What it is | A command-line coding assistant. You run it in your terminal, and it reads code, edits code, and runs tests for you | A personal agent that can live anywhere. It lives on 22 chat platforms, and it also lives inside scheduled tasks |
| In one line | Do one thing (writing code) as well as it can possibly be done | Put one thing (an agent) in every place it could possibly be |
| Tech stack | TypeScript, compiled into a single executable | Python, a plugin-based runtime |
| Open source? | Closed (this analysis is based on leaked source) | Open |
| Is the user present? | Almost always — you're watching the terminal | Often not — a scheduled task at 3 a.m., an auto-reply in a group chat |
0.2 “Is the User Present?” Is Where Every Disagreement Starts
The last row of that table is the key to every difference between the two systems.
This one difference propagates downward and reshapes the design at almost every layer. The following chapters unpack it piece by piece, but you'll find that the vast majority of disagreements trace back to right here.
0.3 Where the Weight Is, by Code Volume
Look at the single largest file in each system and you can see directly where the effort went:
| Claude Code's heavy hitters | Hermes's heavy hitters |
|---|---|
PromptInput.tsx — 347 KBThe input box in the terminal |
delegate_tool.py — 5,071 linesMulti-agent delegation |
| The five-rung ladder of context management Tool-result budget → trimming → micro-compaction → collapse → auto-compaction |
Approval red lines — 5,802 lines Judging how dangerous a command is |
| The 10-step permission cascade Including 4 “bypass-immunity” checks |
scheduler.py — 367 KBScheduled tasks (cron) |
| Prompt-cache optimization Forked subagents keep a byte-identical prefix |
7 execution environments + 22 platform adapters A matrix of abstract base classes |
An input box at 347 KB, a delegation tool at 5,071 lines — those two numbers side by side say more than any architecture diagram.
Claude Code spends its biggest effort on “the two inches between the human and the agent”: input experience, rendering performance, interrupt handling, status display. Because the user is staring at that screen the whole time.
Hermes spends its biggest effort on “the agent keeping itself alive”: how to hand out work, how to run on a schedule, how to judge danger, how to span platforms. Because nobody is standing there watching.
0.4 How This Article Is Organized
| Chapter | Contents |
|---|---|
| Chapter 1 | Mechanism by mechanism — the same problems (context, permissions, multi-agent, extensibility), two sets of solutions compared one at a time |
| Chapter 2 | Consensus — which conclusions the two teams reached independently. This is the most valuable part |
| Chapter 3 | Cost — what each choice gave up. There is no free design |
| Chapter 4 | The decision — when you build your own system, how to choose between these two roads |
0.5 A Warning: Don't Copy
Many of the designs in these two systems may be wrong for your project.
For example: Hermes's 5,802 lines of red-line rules, the _CMDPOS command-position anchoring inside them, and the “shadow rule detection” in Claude Code's permission system — all of these were forced into existence by real incidents. A comment in the Hermes source spells out the incident that produced _CMDPOS: a gh pr create --title "block rm -rf / spellings" command got blocked by mistake because the title contained the text rm -rf / — when it was plainly just a string argument, not a command about to run.
Your system never lived through that incident. Copy a 5,802-line rulebook wholesale and what you get is five thousand lines you don't understand, don't dare change, and can't explain the existence of.
The right move is: understand what question each rule answers, then decide whether that question even exists in your system.
1 · Mechanism by Mechanism
This chapter takes four core problems and looks at how each system answers them. Every section follows the same shape: what the problem is → the two solutions → where the difference comes from.
1.1 Context Management
The problem
A large language model has a “context window” — there is a hard limit on how much text it can look at in one go. The longer the conversation, the more of it is used up, until nothing more fits. And every turn re-sends the entire history, so long context isn't just “it will fill up” — it's “every turn burns money.”
Claude Code: a five-rung ladder
The point of the five rungs is that each rung loses more information and costs more to run than the one before it. The system always starts with the gentlest tool and only escalates when that isn't enough.
On top of this there's a private Anthropic capability, cache_edits: delete specific content from the cache server-side while the local message list stays completely untouched. This lets “compaction” happen without breaking the prompt-cache prefix — a capability outside developers can't get.
Hermes: a pluggable engine
class ContextEngine(ABC): # a 490-line abstract base class
def should_compress(...) # abstract: is it time to compress?
def compress(...) # abstract: how to compress
def select_context(...) # abstract: which messages to send this turn
Hermes doesn't prescribe a compaction strategy; what it prescribes is “what a compressor has to look like.” It ships a default implementation called compressor, and you can swap in your own through config.
It also splits the action into two orthogonal verbs:
| What it does | |
|---|---|
select_context()Select | Called every turn. Decides “which messages go to the model this turn.” Does not modify the stored history |
compress()Compress | Called only when needed. Actually rewrites the stored history; irreversible |
The source records a real misuse: someone wanted their engine to step in every turn, so they hard-coded should_compress() to return True. The result was a genuine, irreversible compaction on every single turn — they wanted “select every turn” and got “destroy every turn.”
The lesson: when an interface gets misused, it's usually not that the user is dumb — it's that the interface didn't separate “read” from “write.”
Where the difference comes from
| Claude Code | Hermes | |
|---|---|---|
| Shape | A fixed five-rung ladder, hard-wired into the system | One abstract base class; the strategy is swappable |
| Swappable? | No. Users can only tune parameters | Yes. Replace the whole engine |
| Why | It serves one workload (coding), so it can be tuned to the limit for it — and it gets to use the private cache_edits | It has to serve unknown, varied workloads, so the optimal strategy can't be known in advance |
| Cost | Can't be swapped. If your scenario doesn't fit these five rungs, there's no way out | The cost of the abstraction itself — a 490-line interface definition, plus guaranteeing that no implementation can break the system's invariants |
1.2 Permissions and Safety
The problem
An agent runs commands, edits files, and makes network requests. How do you stop it from doing irreversible damage?
Claude Code: a 10-step cascade
Every tool call goes through a 10-step decision chain. The most important design element is “bypass immunity”:
“Bypass immunity” means: certain checks accept no exemption of any kind.
The user can turn on a “stop asking me” mode to skip the confirmation prompts, but they cannot skip steps 1d-1g. Those steps sit before the mode check by design, precisely so that the “skip” switch physically can't reach them.
That's more reliable than “add an if inside the skip logic to exclude a few items” — the latter depends on everyone who ever touches the code remembering to maintain that exclusion list.
Hermes: red lines + environment isolation
The first layer is 12 hard-coded red lines (HARDLINE_PATTERNS), backed by a fairly sophisticated parser:
| Mechanism | Problem it solves |
|---|---|
_CMDPOS command-position anchoring | Only when it sits in command position does rm count as a command. The one inside --title "rm -rf /" doesn't |
| Quote masking | What's inside quotes is data, not a command — except $(), which still executes inside double quotes |
| Shell-carrier detection | The contents of bash -c "..." and ssh host "..." get checked recursively |
| De-obfuscation | r''m, r\m, and $'\x72m' all get normalized back to rm |
The second layer is the execution environment: 7 backends (local / Docker / Modal / Vercel / Daytona / Singularity / SSH). This is the only true hard boundary — every rule above is “guessing whether this command is dangerous”; only isolation is “even if it is, it can't get out.”
In fairness, this has to be said: a third-party audit in April 2026 (roughly 364,000 lines of code) found Hermes had 4 “critical” and 9 “high” severity architectural issues, the number one being that the default backend is the local machine, with no sandbox. In other words, a default install hands the model a terminal with full permissions.
Those 5,802 lines of red lines are not a substitute for isolation. What they catch are the commands that are obviously a disaster at a glance.
Where the difference comes from
| Claude Code | Hermes | |
|---|---|---|
| Primary means | Ask a human — the decision chain ends in a confirmation prompt | Rules + isolation — because often there's nobody to ask |
| Number of modes | 6 permission modes; the user switches by scenario | Configured per profile / trigger source |
| The non-exemptable part | Bypass-immune steps 1d-1g | 12 red lines |
| Isolation | Has sandbox capability, but leans mainly on the permission layer | 7 optional environments, but no isolation by default |
This is the most direct expression of the “is the user present” theme.
Claude Code can hand the hardest judgment calls to a human — because the human is right there.
Hermes has to make the call itself — so it needs 5,802 lines of rules to approximate human judgment, which can never fully succeed, which is why it also needs an isolation layer as the backstop.
1.3 Multi-Agent
Claude Code: three forms
| Form | Characteristics |
|---|---|
| Plain subagent | A fresh context; runs one task independently |
| Fork | Inherits the parent agent's full context. Uses 4 tricks to produce an API request prefix byte-identical to the parent's, so it shares the prompt cache |
| Workflow | A deterministic orchestration script that decides who runs when |
Those 4 fork-caching tricks deserve a separate mention: for the subagent's request prefix to be identical to the parent's byte for byte, the system has to guarantee that the system prompt, tool definitions, tool ordering, and message serialization are all exactly the same. A single differing byte invalidates the whole cache and multiplies the cost several times over.
Hermes: delegation + kanban
MAX_DEPTH = 1 # only one level allowed
_DEFAULT_MAX_CONCURRENT_CHILDREN = 10 # at most 10 concurrent
_RECENT_SUBAGENTS_CAP = 200 # cap on the history record
Plus runtime controls: interrupt_subagent (interrupt) / steer_subagent (inject instructions) / set_spawn_paused (stop spawning but let the existing ones finish).
The other mode is kanban: several peer agents share one task board and each claims work from it. Paired with the kanban_heartbeat heartbeat — a task that was claimed and then died automatically goes back to unclaimed.
Where the difference comes from
| Claude Code | Hermes | |
|---|---|---|
| Core concern | Cost — how to get subagents to hit the cache too | Control — how to keep 10 unsupervised subagents from running wild |
| Depth | Has forks; the hierarchy is set by the workflow script | Hard-limited to one level |
| Mid-run intervention | The user hits Ctrl-C | Three dedicated interfaces |
| Peer collaboration | None | Kanban mode |
1.4 Extension Mechanisms
| Claude Code | Hermes | |
|---|---|---|
| Extension points | Skills · plugins · MCP · 15 hook events · memory directory | Skills · plugins · MCP · a matrix of abstract base classes |
| Abstract base classes | Few — most capabilities are built in | Many — platform / memory / context / model / environment / scheduling are all replaceable |
| Compile time | 89 feature flags + dead-code elimination. A disabled feature is physically absent from the binary | Loaded at runtime; there is no compile step |
| Number of skills | Built in, varies by release | 81, across 15 categories |
A pattern both use: progressive disclosure
This is a conclusion the two systems reached independently, and the single most worthwhile thing in this article to remember.
The catalog stays resident; the contents load on demand. All 81 skills fully expanded is 60,000 tokens; a catalog of descriptions alone is 5 KB. The model sees the catalog, decides which one it needs, and then reads that one in full.
Both systems use this pattern in four places: skills, tools, MCP, and memory. That's no coincidence — it's the inevitable product of a physical constraint: context is finite and expensive.
1.5 One-Page Cheat Sheet
| Dimension | Claude Code | Hermes |
|---|---|---|
| Target shape | One scenario, pushed to the limit | Runs in any scenario |
| Context | Fixed five-rung ladder + private cache edits | Pluggable engine (ABC) |
| Permissions | 10-step cascade + bypass immunity | 12 red lines + 7 environments |
| Multi-agent | Fork + shared cache | Delegation (depth ≤ 1) + kanban |
| Extension | Hook events + compile-time flags | Matrix of abstract base classes |
| Entry points | Terminal (4 launch forms) | 22 platforms + CLI + webhook + cron |
| Biggest investment | The human interface (a 347 KB input box) | Autonomous operation (5,071 lines of delegation) |
| On failure | The user sees it and deals with it | Event system + alerting + automatic retry |
| In common | Progressive disclosure · layered safety · context is the scarcest resource · fail-closed by default | |
2 · The Consensus They Reached Independently
The last chapter looked at differences. This one looks at something more valuable: two teams, two languages, two sets of goals — and the conclusions they arrived at independently that turned out to be the same.
Why is “consensus” worth more study than “difference”?
Differences usually come from specific constraints — change the scenario and they stop holding.
Consensus means the problem itself forced the answer: two completely different paths converged on the same place. That kind of conclusion very likely holds in your project too.
Each of the 8 points below is something you should adopt by default when designing any agent system.
Consensus 1 · Context Is the Scarcest Resource
Not compute, not storage, not network. How much text the model can look at in one go.
| How it shows up in Claude Code | How it shows up in Hermes |
|---|---|
| The five-rung management ladder | Pluggable context engine (a 490-line interface) |
| Tool search (never list every tool) | Toolsets served by trust level |
| Subagents get isolated context | Delegation partitions context |
| Skill/memory catalog resident, contents on demand | Skill/memory catalog resident, contents on demand |
| Output budget + snipping | _BoundedOutputCollector with a 40/60 head/tail window |
The deeper meaning of this point: every decision in your architecture to “put something into the context” is spending money that gets charged again on every single turn.
A 500-token tool description, in a 40-turn conversation, gets sent 40 times — 20,000 tokens. And it may never have been used once.
“Every token that lives permanently in the context needs a reason to be there.”
Consensus 2 · Progressive Disclosure
A direct corollary of Consensus 1, but worth listing on its own, because it applies far more widely than you'd expect.
Pattern: catalog resident (cheap) + contents on demand (expensive, but you pay only when needed)
Claude Code uses it for: tools · skills · MCP · memory directory · file reads
Hermes uses it for: toolsets · skills (81) · memory prefetch · environment output
And both sides found the same secondary conclusion: “the quality of a catalog entry's description decides everything.” Because that is the only information the model has for deciding “do I need to expand this one?”
Consensus 3 · Safety Must Be Layered, and No Layer Is Reliable
Both sides openly admit “the rules layer can't catch everything.”
Hermes's audit report states outright that red lines are no substitute for a sandbox. Claude Code puts the bypass-immunity checks ahead of the mode check, which amounts to admitting “the user may have turned on a mode that disables the rules layer.”
Conclusion: don't count on any single layer of defense. The attack surface multiplies, so the defenses have to multiply too.
Consensus 4 · The Default Must Be “Fail Closed”
“Fail-closed” means: when the system isn't sure, it refuses rather than allows.
| Claude Code | Hermes |
|---|---|
| Rule fails to parse → treated as no match (not allowed through) | Injection detection is uncertain → abort the entire scheduled task |
| Tool not on the allowlist → refuse | Memory checkpoint v2 → failure aborts (v1 was best-effort) |
| No matching permission rule found → ask the user | Credential unavailable → skip; don't guess |
The Hermes v1 → v2 evolution is especially telling: in early versions the memory checkpoint was “best effort” (if it failed, skip it and keep going). Later an explicit version number was added, PRE_COMPRESS_CHECKPOINT_API_VERSION = 2, and the v2 semantics are “if it fails, abort the compaction.” That is a deliberate correction from fail-open to fail-closed.
Consensus 5 · Kill a Whole Class of Problems With a Hard Limit
| Claude Code | Hermes |
|---|---|
| Character budget for tool results | MAX_DEPTH = 1 |
| Greedy partitioning of concurrent tool execution | _DEFAULT_MAX_CONCURRENT_CHILDREN = 10 |
| Thinking-block signatures bound to the model | _RECENT_SUBAGENTS_CAP = 200 |
| Sibling abort controller (two-level cancellation domains) | _is_descendant_of(max_hops=8) |
_TOOL_DEFS_CACHE_MAX = 8 |
Look at the max_hops=8 entry — it's the most telling one of the lot.
Given MAX_DEPTH = 1, walking the parent-child relationship should in theory take at most 1 hop. Setting it to 8 is purely defensive: if a cycle ever shows up in the data, this cap guarantees the function returns instead of hanging the process.
“Even if an invariant is violated, the program must not hang” — that is the clearest single line between a mature system and a demo.
Consensus 6 · Abstract “What to Do,” Query “What Can You Do,” Never Ask “Who Are You”
Both sides independently use this pattern in several places:
// Hermes platform adapters
adapter.supports_threads() instead of if platform == "slack"
adapter.supports_reactions()
adapter.supports_editing()
// Hermes model providers
provider.supports_prompt_caching() instead of if provider == "anthropic"
// Claude Code tool capability groups
tool.isConcurrencySafe() instead of if toolName == "Read"
tool.isReadOnly()
Why this pattern matters so much:
A check like if platform == "slack" has to be edited everywhere it appears each time you add a platform. And you never know which spot you missed — the compiler won't tell you.
adapter.supports_threads(), on the other hand, turns “which questions a new platform must answer” into part of the interface. Add a platform and you must implement every capability query; anything you skip fails immediately.
This pattern transfers directly to any “a group of external systems doing the same job with different capabilities” situation: payment providers, SMS gateways, object storage, push services.
Consensus 7 · Errors Must Be Classified, Because Different Kinds Need Different Strategies
| Claude Code | Hermes |
|---|---|
Error-recovery state machine — with a named transition field and an idempotency guard on every path | EnvironmentConnectionError — separates “infrastructure failure” from “command execution failure” |
| Three-tier 413 waterfall — graduated handling of context-too-long | The credential pool sets cooldown length by HTTP status code: short cooldown on 429, long on 401, very short on 5xx |
| The withhold mechanism — recoverable errors aren't exposed externally until every recovery option is exhausted | _failure_streak_nudge — alert only on consecutive failures; don't bother anyone over a one-off |
Core conclusion: “retry” is not an action; it's a family of strategies.
A single uniform retry interval is either too slow for transient failures (wasting available time) or too eager for permanent ones (spamming to no purpose).
What decides the retry strategy is always “what kind of error is this,” never “how many times have we retried.”
Consensus 8 · Claim-Type Operations Need a Lease
Both sides independently ran into, and solved, the same distributed-systems problem:
| Claude Code | Hermes |
|---|---|
Orphaned tool_use handling — every abort path must synthesize a tool_result, or the API rejects the next request (400) | kanban_heartbeat — after claiming a task you must keep heartbeating; on timeout it's released automatically |
| The tombstone mechanism — marks tools that were canceled | try_register_running_job — prevents scheduled tasks from piling up, and every registration record must carry an expiry |
2.9 The Eight Points on One Page
1. Context is the scarcest resource — every resident token needs a reason
2. Progressive disclosure — catalog resident, contents on demand, description quality decides everything
3. Layered safety — no layer is reliable; only the product of them is enough
4. Fail closed — when unsure, refuse
5. Hard limits — killing a whole class of problems with one constant beats handling it cleverly
6. Capability queries — abstract “what to do,” query “what can you do,” never ask “who are you”
7. Error classification — the retry strategy is set by error type, not by attempt count
8. Claims carry leases — two-phase operations must handle “declared but never completed”
These eight points are the most transferable part of this whole series.
They don't depend on whether you use Python or TypeScript, on whether you're building a coding assistant or a support bot, or on whose model you use. They come from the physical constraints of the “agent” form itself.
If an interviewer asks “how would you design an agent system,” laying out these eight clearly is more convincing than reciting any specific implementation.
3 · The Cost of Every Choice
The first two chapters covered differences and consensus. This one covers something that gets talked about far less: what each design choice gave up.
Why does cost need its own chapter?
Technical articles usually only explain “what's good about this design.” But in a real architecture decision, the question you're answering is never “is this design good,” it's “can I afford its cost.”
A good design whose cost you can't pay is the same thing as a bad design.
3.1 What Claude Code Pays
Cost 1 · A fixed context strategy you can't swap out
| Gained | Lost |
|---|---|
The five-rung ladder is tuned to the limit for the coding scenario, and with the private cache_edits capability it can compact while preserving the cache prefix | If your workload isn't coding, there's no way out. Every rung of the ladder carries an implicit assumption about “which information is safe to drop” — and that assumption was built around code workflows |
A concrete example: the “snip” rung drops the oldest tool results first. In a coding scenario that's usually fine (a file you already read can be read again when needed). But if your scenario is a long-lived, stateful conversation (say, a project-management assistant that runs for weeks), the oldest information may be exactly the most important constraint.
Cost 2 · The double edge of compile-time feature flags
89 feature() flags + dead-code elimination
→ a disabled feature is [physically absent from the binary]
| Gained | Lost |
|---|---|
| Smaller size, smaller attack surface, no risk of “the feature is off but its code is still lying around.” There are also build-time verifications like the “excluded-string check” to make sure disabled features really didn't leak through | Users can't turn anything on at runtime. A different combination of features means rebuilding the binary. And the mechanism has its own complexity cost — the source shows the “excluded-string check” forcing an odd callback injection ( snipReplay) purely to get around the build-time validation |
Cost 3 · A single form of entry
Claude Code is a terminal tool. It has 4 launch forms, but all of them live in the terminal. No chat-platform integrations, no webhooks, no scheduled tasks.
What that buys: the entire interaction design can assume “there is a TTY, a human is watching, ANSI escape sequences work, Ctrl-C works.” The 347 KB input-box component can exist precisely because it only has to serve one kind of terminal.
Cost 4 · Closed source
This is the most practical one: you can't use its implementation in your own product.
This series is based on leaked source; its nature is architecture study, not a reusable code asset. You can learn the design thinking, but you'll have to re-implement it yourself.
And some of the key capabilities (cache_edits, for one) are Anthropic's private server-side features that outside developers simply can't get. Copying an implementation of “compact without breaking the cache” is not something you can do.
3.2 What Hermes Pays
Cost 1 · The weight of the abstraction layer
ContextEngine abstract base class: 490 lines of interface definition
Built-in compressor implementation: 419 KB
→ Interface-to-implementation ratio: 490 lines : 419 KB
Those 490 lines of interface aren't “a pile of method signatures”; they hold a lot of contract text that must be obeyed:
· The lifecycle is “not once per turn”
· The output of select_context() must come before all validators
· The cache invariant: your implementation must not break the prefix
· The versioning rules for the usage dict
· The semantics of protect_first_n changed in some version
For a third party to write a correct context engine, they have to read and honor all of these conventions. That bar is very high — high enough that in practice almost nobody may actually swap out the default implementation.
So is the cost of those 490 lines of abstraction worth it? That's a real question with no standard answer.
Cost 2 · Insecure by default
| Gained | Lost |
|---|---|
| Local execution is the fastest and simplest, with no file-sync problems. Works out of the box | A default install hands the model a terminal with full permissions. The number one “critical” finding in the audit report And the risk is invisible — users don't go out of their way to change a default that “already works” |
“Safe defaults” and “usable defaults” collide head-on here.
If the default is Docker: a new user's first run waits on an image download, has to deal with file sync, and may fail outright because Docker isn't installed. A large share of users would leave on their first attempt.
If the default is local: everyone can run it immediately, at the price of everyone being unisolated by default.
There is no correct answer to this trade-off, only an explicit choice. But at minimum, the first run should tell the user plainly that they're in unisolated mode — so the risk is one “the user knew about and accepted,” not one “the user didn't know about.”
Cost 3 · The ongoing maintenance burden of 22 platforms
There's a related hidden cost: testing. Each of the 22 platforms needs a real test account, test workspace, and test credentials. Full regression testing is extremely expensive and very likely infeasible in practice.
Cost 4 · Python's distribution problem
| Claude Code | Hermes |
|---|---|
| Bun compiles it into a single executable (296 MB). Download and run; no dependency problems | A Python package. The user needs the right Python version, has to install dependencies, deal with virtual environments, and face dependency conflicts |
296 MB sounds big, but what it buys is “one file, double-click, it runs.” And anyone who has ever shipped a Python application knows how often you hear “it works on my machine.”
3.3 Costs Both Sides Share
Shared cost 1 · Complexity is irreversible
Claude Code's 10-step permission cascade, Hermes's 5,802 lines of red lines — once this complexity goes in, it never comes back out.
Because every rule corresponds to a problem that actually happened once. Delete it and that problem comes back. And the person deleting the rule usually doesn't know why it exists (the person who wrote it may have left).
Conclusion: add rules with care, because you're adding a permanent liability to the system. Ideally every rule carries a comment saying “what this guards against” — the Hermes comment recording the gh pr create --title incident is the positive example.
Shared cost 2 · Heavy dependence on model behavior
Both systems write lots of “you must do it this way” constraints into their prompts:
- Claude Code's “three laws of thinking,” and the “do not call any tool” rule during compaction (every tool named exhaustively, plus the consequences spelled out)
- Hermes's injection detection, and its skill-description matching
These constraints are “persuasion,” not “enforcement.”
The model may not listen. And switch model versions and compliance shifts — a prompt that worked 99% of the time on the old version may work 90% of the time on the new one, and that 10% becomes a production incident.
The Claude Code source shows the trail of this evolution: the “do not call tools” instruction originally sat at the end and was later moved to the beginning, because the model turned out to obey instructions at the start noticeably more.
This kind of dependence is technical debt unique to agent systems: it lives not in your code but in the model's weights, where you can neither test it nor control it.
Shared cost 3 · The cost of observability
Claude Code has 660 event types and 1,093 instrumentation points. That code has to be written, maintained, and kept in sync during refactors. Hermes has an event system, a delivery ledger, skill-usage statistics, and execution history.
But this is a cost that must be paid — because agent systems fail mostly by “behaving wrong” rather than “throwing an error,” and without instrumentation there is no way to investigate at all.
3.4 Cost Comparison Summary
| Dimension | What Claude Code pays | What Hermes pays |
|---|---|---|
| Flexibility | Context strategy can't be swapped; serves only the terminal scenario | — |
| Complexity | The compile-time flag system (89 flags + dead-code elimination + exclusion checks) | The abstract-base-class matrix (6 kinds of extension points, each with a full contract) |
| Safety | — | No isolation by default (the audit's number one finding) |
| Maintenance | — | The permanent liability of 22 platforms |
| Distribution | — | Python dependency hell |
| Reusability | Closed source + depends on private server-side capabilities | — |
| Shared | Irreversible complexity · dependence on model compliance · the cost of observability | |
Read this table and one thing jumps out: the two sides' costs are almost complementary.
Claude Code trades “flexibility” for “extreme optimization and easy distribution.”
Hermes trades “safe defaults and maintenance cost” for “being everywhere and being replaceable.”
No choice is free, and none is wrong — there is only “matches your constraints” and “doesn't.” The next chapter is about how to figure out what your constraints are.
4 · How to Choose When You Build Your Own
This chapter turns everything so far into decisions you can act on. Not “which one to learn from,” but “under your constraints, which design is right.”
4.1 The First Question: Is the User Present?
This is the dividing line. Answer it first and half the remaining decisions settle themselves.
4.2 The Second Question: Is the Workload Uniform or Varied?
| A single workload | Varied workloads |
|---|---|
| Examples: only a coding assistant / only customer support / only data analysis | Example: one platform running all kinds of agents |
| Do: take the context strategy, toolset, and prompts and hard-wire all of them for that one workload. Optimize deeply | Do: abstract base classes + pluggability. Let each workload bring its own strategy |
| Don't: abstract prematurely. You'll pay permanent complexity for an interface that will never have a second implementation | Don't: bake one workload's assumptions into the core. It blows up the moment a second workload shows up |
The most common mistake is “preventive abstraction”: defining an abstract base class before a second implementation exists.
The cost: your interface is designed from imagination, not distilled from two real implementations. When the second one actually arrives, you'll find the interface doesn't fit, and then you either change the interface (breaking the first implementation) or contort the second one to fit.
The better approach: hard-wire it first. When a second real need appears, distill the interface from the two concrete implementations. The interface you extract then is the one that's actually right.
4.3 The Third Question: Who Will Extend It?
| Extender | Mechanism to provide | Don't provide |
|---|---|---|
| Only you | Edit the code directly. Maybe add a few config options | A plugin system. You're making trouble for yourself |
| Your team | Skills (Markdown) + config. Zero code; anyone can write one | A complex plugin API |
| Other engineering teams | Tool registration + hooks + MCP | The ability to replace core strategies |
| Untrusted third parties | MCP (cross-process isolation) | In-process plugins. One crash takes you down with it |
Remember the permission gradient from Hermes Chapter 9: the barrier to extending should scale with the damage the extension can do.
Skills (plain text, anyone can write one, worst case the agent takes a wrong turn)
→ MCP (cross-process, crash-isolated, can only provide tools)
→ Plugins (in-process, can attach hooks, but hooks can only append, never replace)
→ Core strategies (can replace the entire context, but single-select and must be explicitly configured by the user)
Don't offer one universal plugin interface that lets everyone do everything.
4.4 Eight Things You Must Do Whichever Road You Take
This is the action version of the eight consensus points from Chapter 2:
| # | What to do | Concrete action |
|---|---|---|
| 1 | Account for context | Work out the numbers: how many tokens in the system prompt, how many in tool definitions, how much growth per turn. Anything resident must be able to justify its price |
| 2 | Progressive disclosure | Tools, skills, docs, memory — convert all of them to “catalog resident + contents on demand.” And put time into description quality |
| 3 | At least two layers of safety | “Capability narrowing” (don't hand out dangerous tools) + “execution isolation” (container/sandbox). Rule matching doesn't count as a reliable layer |
| 4 | Fail closed by default | Inspect every try/except: on exception, does it allow or refuse? Change every “on error, skip” to “on error, refuse” |
| 5 | Add hard limits | Delegation depth, concurrency, single-output length, traversal hops, cache entries. Every quantity that “shouldn't get too big in theory” needs a cap |
| 6 | Capability queries, not identity checks | Search the code for every if xxx == "some specific name" and change it to if xxx.supports_yyy() |
| 7 | Classify errors | At least three classes: retryable transient failures (short backoff) / config or credential problems (long backoff or alert) / logic errors (no retry; report immediately) |
| 8 | Two-phase operations carry leases | Any “declare first, complete later” operation (tool calls, task claims, locks) must handle “declared but never completed”: compensate or time out |
4.5 The Interview: How to Talk About All This
Don't open with “there's a loop, the model calls tools, the tools return results.” Everyone says that; it proves nothing.
Open with the constraints:
“First, two things need settling: whether the user is present, and whether the workload is uniform or varied.
If the user is present, safety can end in asking a human; if not, there have to be automatic decision rules, an alerting system, and execution isolation, because nobody will notice when something goes wrong.
If the workload is uniform, hard-wire the strategy and optimize deeply; if it's varied, you need abstract base classes — but distinguish the capabilities that can stack from the strategies that must be single-select.
Then come three constraints you can't escape: context is the scarcest resource, so everything is designed around progressive disclosure; safety must be layered, because no layer is reliable; and the default must be fail-closed.”
| If asked about | Examples you can give |
|---|---|
| Context management | The five-rung ladder (each rung loses more information; start with the gentlest); the orthogonality of select_context and compress (read vs. write, and the misuse story) |
| Prompt caching | Prefix matching — one differing byte invalidates all of it; forked subagents go byte-identical to share the cache |
| Safety | _CMDPOS command-position anchoring and the gh pr create --title false-block incident; bypass-immunity checks placed before the mode check |
| Multi-agent | MAX_DEPTH = 1 (a hard limit that kills exponential blowup); set_spawn_paused (separating “stop accepting new work” from “terminate existing work”) |
| Reliability | Orphaned tool_use must get a synthesized result on every abort path; try_register_running_job keeps scheduled tasks from piling up |
| Retries | The credential pool sets cooldown length by HTTP status code (short on 429, long on 401, very short on 5xx) |
4.6 Finally: A Decision Framework You Can Take With You
These seven questions don't require you to remember any specific implementation.
They are the genuinely transferable part, distilled from 176,391 characters of analysis of Claude Code and 141,079 characters of analysis of Hermes.
Specific implementations will go out of date — models change, APIs change, frameworks change. But these constraints come from the “agent” form itself, and they won't.