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 · 安全必须分层,且每一层都不可靠

Claude Code Hermes ───────────────────── ───────────────────── 工具白名单 ←→ 工具集按信任投放 权限规则匹配 ←→ 12 条红线 绕过免疫检查(1d-1g) ←→ 红线不可豁免 沙箱 ←→ 7 种执行环境 钩子(外部可拦截) ←→ 审批钩子(插件可拦截)

两边都明确承认「规则层拦不住一切」。

Hermes 的审计报告直接写明红线不能替代沙箱。Claude Code 把绕过免疫检查放在模式判断之前,等于承认「用户可能开了一个让规则层失效的模式」。

结论:不要指望任何单层防御。攻击面是相乘的,防御也必须是相乘的。

共识 4 · 默认值必须是「失败即关闭」

「fail-closed」的意思是:当系统不确定的时候,选择拒绝,而不是允许。

Claude CodeHermes
规则解析失败 → 视为不匹配(不放行)注入检测不确定 → 中止整个定时任务
工具未在白名单 → 拒绝记忆检查点 v2 → 失败即中止(v1 是尽力而为)
找不到对应的权限规则 → 询问用户凭据不可用 → 跳过,不猜测

Hermes 那个 v1 → v2 的演进特别说明问题:早期版本的记忆检查点是「尽力而为」(失败了就跳过,继续跑)。后来加了一个显式的版本号 PRE_COMPRESS_CHECKPOINT_API_VERSION = 2,v2 的语义是「失败就中止压缩」。这是从 fail-open 走向 fail-closed 的一次明确修正。

共识 5 · 用硬限制消灭一整类问题

Claude CodeHermes
工具结果字符预算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 CodeHermes
错误恢复状态机 —— 带命名 transition 字段,每条路径有幂等守卫EnvironmentConnectionError —— 把「基础设施故障」和「命令执行失败」分开
三级 413 瀑布 —— 上下文过长的分级处理凭据池按 HTTP 状态码决定冷却时长:429 短冷却、401 长冷却、5xx 极短冷却
withhold 机制 —— 可恢复的错误在恢复手段用尽前不暴露给外部_failure_streak_nudge —— 连续失败才告警,偶发失败不打扰

核心结论:「重试」不是一个动作,是一族策略。

用统一的重试间隔,要么对短暂故障太慢(浪费可用时间),要么对永久故障太急(无意义地刷屏)。

决定重试策略的,永远是「这个错误是什么类型」,而不是「重试了几次」。

共识 8 · 认领类操作必须有租约

两边都独立遇到并解决了同一个分布式问题:

Claude CodeHermes
孤儿 tool_use 处理 —— 每一条中止路径都必须合成一个 tool_result,否则下一次请求会被 API 拒绝(400)kanban_heartbeat —— 认领任务后要持续心跳,超时自动释放
墓碑(tombstone)机制 —— 标记已取消的工具try_register_running_job —— 防止定时任务堆叠,且注册记录必须带过期时间
同一个问题的两种外衣: Claude Code:「我声明了要调用一个工具,但中途取消了」 → 必须补一个结果,否则协议不完整 Hermes: 「我认领了一个任务,但进程死了」 → 必须能自动释放,否则任务永久丢失 ★ 本质相同:任何「先声明、后完成」的两段式操作, 都必须处理「声明了但没完成」这个中间态。 而处理方式只有两种:补偿(合成结果)或超时(自动释放)。

2.9 八条共识的一页速记

1. 上下文是最稀缺的资源 —— 常驻的每个 token 都要有理由
2. 渐进式披露 —— 目录常驻,内容按需,描述质量决定一切
3. 安全分层 —— 每一层都不可靠,只有相乘才够用
4. 失败即关闭 —— 不确定时选择拒绝
5. 硬限制 —— 用一个常量消灭一整类问题,好过智能地处理它
6. 能力查询 —— 抽象「做什么」,查询「能做什么」,永不判断「你是谁」
7. 错误分类 —— 重试策略由错误类型决定,不由次数决定
8. 认领带租约 —— 两段式操作必须处理「声明了但没完成」

这八条是本系列文章里最可迁移的部分。

它们不依赖于你用 Python 还是 TypeScript,不依赖于你做的是编程助手还是客服机器人,也不依赖于你用哪家模型。它们来自「智能体」这个形态本身的物理约束。

面试里如果被问到「你怎么设计一个智能体系统」,把这八条讲清楚,比背出任何一个具体实现都有说服力。

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 CodeHow it shows up in Hermes
The five-rung management ladderPluggable context engine (a 490-line interface)
Tool search (never list every tool)Toolsets served by trust level
Subagents get isolated contextDelegation partitions context
Skill/memory catalog resident, contents on demandSkill/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

Claude Code Hermes ───────────────────── ───────────────────── Tool allowlist ←→ Toolsets served by trust level Permission rule matching ←→ 12 red lines Bypass-immunity checks (1d-1g) ←→ Red lines cannot be exempted Sandbox ←→ 7 execution environments Hooks (externally interceptable) ←→ Approval hooks (plugin-interceptable)

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 CodeHermes
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 → refuseMemory checkpoint v2 → failure aborts (v1 was best-effort)
No matching permission rule found → ask the userCredential 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 CodeHermes
Character budget for tool resultsMAX_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 CodeHermes
Error-recovery state machine — with a named transition field and an idempotency guard on every pathEnvironmentConnectionError — separates “infrastructure failure” from “command execution failure”
Three-tier 413 waterfall — graduated handling of context-too-longThe 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 CodeHermes
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 canceledtry_register_running_job — prevents scheduled tasks from piling up, and every registration record must carry an expiry
The same problem in two different costumes: Claude Code: "I declared a tool call, then canceled it midway" → must synthesize a result, or the protocol is incomplete Hermes: "I claimed a task, then the process died" → must auto-release, or the task is lost forever ★ Same essence: any two-phase "declare first, complete later" operation has to handle the in-between state of "declared but never completed." And there are only two ways to handle it: compensate (synthesize a result) or time out (auto-release).

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.