KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

02 · 逐维对比:同一件事的五种做法 — keel 龙骨

九个槽位是静态坐标系,这一章挑其中信息量最大的六个维度做纵深对比:每个维度先摆出五家的做法,再回答「为什么会有这种分歧」与「分歧背后的同一道题是什么」。五个维度全部满足一个入选标准——五家在这一点上至少存在三种真正不同的做法,而不是同一做法的五个命名。凡两家的差异只是参数而非机制,会在正文里点破。五家中四家有源码级证据(出自四门解析课),E 平台 为定性对比、无公开源码级证据,逐处标注。

九个槽位是静态坐标系,这一章挑其中信息量最大的六个维度做纵深对比:每个维度先摆出五家的做法,再回答「为什么会有这种分歧」与「分歧背后的同一道题是什么」。五个维度全部满足一个入选标准——五家在这一点上至少存在三种真正不同的做法,而不是同一做法的五个命名。凡两家的差异只是参数而非机制,会在正文里点破。五家中四家有源码级证据(出自四门解析课),E 平台 为定性对比、无公开源码级证据,逐处标注。

一、停止条件:显式枚举 vs 无上限 vs 预算制

家 机制 形状
Claude Code 显式枚举 10 个终止 reason + 7 个继续 transition 写进类型;maxTurns、MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3 都只是枚举里的成员
DeepSeek (dsh) 无上限 循环无步数计数器,停止 = concludesTurn / preStep reject / abort / error 四机制;「循环长度由任务语义决定」
Hermes 预算制 max_iterations 90 × IterationBudget.remaining × 宽限一轮;refund() 让程序化调用不占预算
OpenClaw 语义边界 无 maxTurns;48h 墙钟超时 + 六种循环检测器(warning 10 / critical 20)+ critical 二次命中跨轮累积后硬终止
E 平台 协作式取消 stop_triggered → RunCancelledEvent,终止权在调用方;未见轮次上限(定性)

分歧的根源是对「截断成本」的估值不同。轮次上限是廉价兜底,代价是合法长任务被腰斩——dsh 明确把预算交给 guard/timeout-policy、压缩与 provider 的 maxTokens,OpenClaw 为此付出六个检测器与 30 条调用历史的成本,换来「终止时能归因到哪个检测器、重复了几次」。Claude Code 反其道而行:它假设每一轮都值得被测试断言,所以把「为什么结束」做成字面量联合类型,stop_hook_blocking 那条注释(保留 hasAttemptedReactiveCompact 以免无限循环)证明枚举本身就是安全机制。Hermes 的 IterationBudget 是唯一带跨会话语义的预算——父 agent 与子 agent 各自计数、总额可以超过父的上限。

三条可迁移的判断:其一,去掉计数器就必须把「卡住」做成可判定问题(OpenClaw 的检测器 + Hermes 的 guardrail 熔断都是这个定律的产物);其二,停止原因要么进类型系统(Claude Code)要么进诊断串(Hermes),绝不应该是裸布尔;其三,「宽限一轮」(Hermes)与「二次命中才终止」(OpenClaw)都是在给启发式边界留缓冲。

怎么选:任务以「轮」为单位且每轮都值得审计,选显式枚举;任务可能任意长且截断代价高,选语义边界 + 检测器;多 agent 共享算力(父子 agent 各自预算)选预算制;终止权在用户侧的产品形态,选协作式取消。四者可以组合——Hermes 就是预算制之上再叠 guardrail 熔断。

二、事件与状态:生成器回调 vs append-only 事件流 vs Redis Stream

家 机制 关键性质
Claude Code async generator + 显式 State yield* 委托让生命周期信号有唯一位置;状态是循环内可变对象,持久化另走 JSONL
DeepSeek (dsh) append-only 事件流(event log) 日志是唯一真相,消息历史是派生结果;surface 投影 + replace 表达压缩;fork/resume/replay 同一条流
Hermes 19 个回调注入 无事件总线,内核在固定点位 if callback is not None 后调用;持久化走 SQLite,与事件流无关
OpenClaw AgentEvent 十种联合 + EventStream 内核产出事件流,渠道侧两次转译(embedded-agent-subscribe → Gateway broadcaster)
E 平台 Redis Stream 存流式事件 事件带 offset,客户端断线后从上次 offset 续传;HTTP 与执行解耦使重放不依赖进程存活(定性)

这是四个真正不同的答案,不是同一答案的四种写法。判断一家属于哪条路线,就看一个问题:进程崩溃后,恢复时读的是什么——读事件日志并 fold(dsh)、读 transcript 重放(Claude Code)、读数据库(Hermes)、还是什么都不读、让客户端重连拉流(E 平台)。Claude Code 与 Hermes 的「状态优先」路线里,事件只是过程的投影,恢复靠重放 transcript 或读库;dsh 的「日志优先」路线里状态是日志的 fold(requestHeader() 就是增量 fold),压缩因此只是一条新事件而非历史改写;OpenClaw 居中——内核合同是事件流,但 OpenClaw 侧又维护独立的 transcript 与写者校验(expectedWriterRunId 防被抢占的旧 run 提交陈旧数据)。E 平台 的 Redis Stream 把「事件」同时当传输与持久层,offset 续播解决了四家都要面对的问题:客户端断线后如何无损接回流——Claude Code 的答案是进程内 buffer,dsh 的答案是整条重放,OpenClaw 的答案是渠道重发。

怎么选:需要审计与回放合规,日志优先(dsh)是唯一一次投入三份回报的路线;产品形态是长连接观看,传输优先(E 平台 式 Stream);单机交互工具,状态优先最省——但要接受「恢复 = 重读全文」的成本,并像 OpenClaw 一样给续跑加前置校验。

三、工具安全:规则链 vs 分层收紧 vs 审批制 vs 操作系统边界

家 第一道闸 最终兜底
Claude Code 四级规则链:deny 优先、ask 次之、工具自检、allow 最后;1e/1f/1g 三步连 bypass 模式也绕不过 沙箱(可选,failIfUnavailable 决定是否硬门禁)
DeepSeek (dsh) 沙箱三后端 fail-closed,无沙箱即拒绝执行(SandboxUnavailableError 是唯一出口) 拒绝方言归一 + runner 失败识别,区分「约束生效」与「约束失效」
Hermes 审批链 manual/smart/off,决策 once/session/always/deny 判词「唯一安全边界是操作系统」,进程内组件自认「不是边界」
OpenClaw 四层求交只能收紧:Profile × allow/deny × 会话权限模式 × exec 审批 safe-bin 校验 argv 不只是 bin 名;两阶段审批有持久身份
E 平台 平台统一审批 + workspace scope 决定工具面 服务端队列与租户隔离(定性)

四家的分歧在「信任放在哪一层」。Claude Code 信任用户显式写下的规则(所以规则链否决权优先、policy 来源只读);dsh 信任操作系统原语(bwrap/landlock/seatbelt 直接 syscall,所以敢 fail-closed);OpenClaw 信任分层求交的数学性质(任何一层误配都不会意外放宽);Hermes 最彻底——它公开宣布进程内的一切(审批门、脱敏、扫描、白名单)都「不是边界」,只承认终端后端隔离与整进程包裹两种 OS 级姿态。值得注意的是收敛点:三家独立到达了「沙箱开着不等于放行」——Claude Code 的沙箱自动放行要求四个条件同时成立、dsh 的 ConfinedArgv 在类型层堵死「返回原 argv」、Hermes 的 terminal-backend isolation 明确列出 code-execution 与 MCP 子进程不覆盖的范围。

怎么选:按「谁来配置安全」倒推。用户手写规则且愿意为每条规则负责——规则链;运维统一交付、模型不可信——fail-closed 沙箱;给他人开权限、误配不可接受——四层求交;对抗性环境——以上全要,再套 OS 级隔离。共同底线只有一条:任何「放行」判断的失败方向必须是拒绝。

四、上下文压缩:五级流水线 vs 区间替换 vs safeguard vs 阈值触发

家 机制 压缩的代价承担者
Claude Code 五级各司其职、顺序固定(结果预算→snip→micro→collapse→auto);摘要由 fork 的模型调用生成(maxTurns: 1、skipCacheWrite: true) 一次额外 API 往返 + 「同一会话两次 resume 可能得到不同上下文」的固有不确定性
DeepSeek (dsh) 区间替换:往日志写一条 replace 事件遮蔽旧节点,摘要包成一条 user/message(surface 只允许五类消息事件改写) 「这是压缩产物」只能经 source 反查;替换 payload 随被遮蔽区间线性增长
Hermes 压缩失败是退出原因(compaction_handoff_not_actionable);辅助模型走 default_aux_model(注释自认「hardcoded id in source, so it rots」) 辅助模型默认值的腐烂风险
OpenClaw 两档 default / safeguard;配了 compaction provider 直接提升为 safeguard;硬要求保留 tool-call/toolResult 配对切割 可插拔上下文引擎是单槽位,组合能力受限
E 平台 压缩阈值约 75%,越线触发;阈值对多租户统一(定性) 阈值不可按租户调(推断)

共同的不变量是压缩不能产生对模型不合法的历史:Claude Code 用 getMessagesAfterCompactBoundary 每轮重建视图,dsh 用 sourceEventSeqs 强制覆盖全部被遮蔽节点,OpenClaw 直接把「保留配对切割」写成硬要求。最大的分歧在「谁做摘要」:Claude Code 与 Hermes 用模型(前者 fork 同一模型命中缓存,后者专门的 aux model),dsh 与 OpenClaw 允许压缩不带模型语义(dsh 的摘要也是事件,OpenClaw 的 safeguard 是引擎行为)。E 平台 的 75% 阈值是五家里唯一公开的数字触发线——Claude Code 的对应物是「有效窗口 − 13k」的 buffer 常量组,只是前者对外、后者藏在内置常量里。

怎么选:会话要跨 resume 复现,模型做摘要的不确定性是硬伤,选事件式压缩(dsh);追求摘要质量与「压缩后像新会话」的体验,模型摘要值得那次 API 往返(Claude Code);多租户平台需要一条可解释的触发线,公开阈值并把它做成配置(E 平台 思路),同时接受阈值不可按租户细调(推断)。无论哪种,压缩产物的同构性(摘要必须长得像普通消息)是省不掉的共识。

五、扩展边界:vendored vs 响应式插件 vs bundled 同边界

家 扩展形态 边界如何强制
Claude Code vendored Ink fork + 编译期 feature() 裁剪 + 27 hook 事件 + MCP 无运行时插件总线;扩展即编译产物的一部分
DeepSeek (dsh) vendored Cordis(Proxy context / fiber / inject epoch)+ 54 类 307 包 + YAML 补丁装配 epoch 指纹驱动挂卸:依赖换了提供者(uid 变化)也要整插件重启
Hermes plugin.yaml + entry-points 懒发现,36 provider + 22 渠道 注册表拒绝静默覆盖(需 override + operator 开关);安装前有 guard 扫描
OpenClaw bundled 与第三方守同一条 plugin-sdk 边界 + 双重声明 extensions/AGENTS.md 禁止深导入 src/**,且「ALL bundled plugins must move to modern SDK seams in the same change」
E 平台 平台内置注册 + DynamicTool 惰性加载 边界由平台运行时持有(定性)

「vendored」在 Claude Code 与 dsh 是同一个词、两种动机:Ink 被 fork 是为了 UI 渲染的完全控制权,Cordis 被引入是为了「上游行为成为可 review 的代码」。真正的对照是 Hermes 与 OpenClaw——前者把扩展做成发现问题(跑一次才知道有什么),后者把扩展做成声明问题(manifest 优先,发现、配置校验、setup 提示都不执行插件代码就算得出来)。OpenClaw 的「bundled 也守第三方边界」是五家里最强的自约束:上百个内置插件任何一次核心重构都要同步迁移 SDK seam,换来的是这条门永远是被热身过的。

怎么选:扩展者是不可信第三方——必须声明式 + 签名 + 扫描;扩展者是自己团队——vendored 或响应式插件都行,但要选一种「上游升级必然被看见」的机制(dsh 的 Drift Gate 把上游漂移变成编译错误,是这类的标杆);扩展者既有第三方又有自己——OpenClaw 的「同一条边界」是唯一能防止双标腐化的答案。

六、反直觉设计:八处「看起来错、其实对」

1. dsh 在 abort 时为没跑的工具补合成错误结果(TOOL_ABORTED_BEFORE_DISPATCH)。
模型发了 5 个 tool-call、日志只有 3 个结果,重建历史就是断的。伪造结果的代价是「日志里出现模型没见过的错误」,收益是 replay 永远合法。区分「用户取消」(必须伪造)与「调度器故障」(不伪造)的那条注释,是把语义想透的标志。

2. Claude Code 的工具池「内置在前 + 保序去重、同名内置胜出」。
按名字扁平排序更直观,但服务端在最后一个前缀匹配的内置工具后放 cache breakpoint,MCP 工具一旦插进内置中间,下游所有 cache key 全部失效。这是「为了一个全局优化牺牲局部直觉」的典型,也是五家里最隐蔽的缓存耦合。

3. OpenClaw 的循环阈值硬编码、不可被策略覆盖。
TOOL_LOOP_WARNING_THRESHOLD = 10 的注释直说:不允许覆盖,否则准入路径与检测路径会漂移。与 OpenClaw 其余「一切可配置」的气质相反,恰好说明作者分得清「偏好」与「安全不变量」。

4. Hermes 的复盘 prompt 要求「大多数会话至少产出一条技能更新」。
这听起来会制造低价值技能——事实也如此。但闭环的价值密度靠下游 curator 恢复:只 archive 不 delete、只动 agent 自建技能、never-used 有宽限期。先让闭环转起来,再靠治理层兜底,比「等高质量的产出规则想清楚再上线」工程上正确得多。

5. Claude Code 的 dontAsk 语义是「不问就直接拒绝」,不是「不问就直接允许」。
实现放在最外层收口处(把 ask 强转 deny),注释说这样前面任何早返回都绕不过它。名字的直觉方向与语义相反,恰恰是因为作者按「失败方向」而非「字面意思」命名:权限系统的默认动作必须 fail-closed。

6. Hermes 的 YOLO 模式在模块导入期冻结(_YOLO_MODE_FROZEN)。
如果每次调用都读环境变量,中途改 env 会让一次已授权的运行突然失去授权。把配置快照在导入期,牺牲运行时可调性,换授权语义的稳定性。

7. OpenClaw 的 ack 策略默认 manual。
自动回「已收到」在群聊里是噪音,且各渠道的 ack 手段差异极大(加表情、回帖、标记已读)。默认关闭让「什么都不做」成为合法实现,只逼真正需要的渠道显式打开——默认值方向由「哪个错误更吵」决定,不是由「哪个实现更省事」决定。

8. dsh 的事件兼容机制 ignorable 默认缺席(即必需)。
忘记标 ignorable → 旧读方拒绝整个 session(可见、可修);错标 → 静默丢一个影响重建的事件(不可见、难查)。两害相权取前者——兼容机制的默认方向永远应该选「过度拒绝」而不是「静默降级」。

代码地图

机制 位置 要点
显式 State 与 10+7 枚举 claude-code-harness/course/02-the-agent-loop.md 终止/继续原因全部字面量化,transition 为可测试性而生
工具池保序去重 claude-code-harness/course/03-tool-runtime.md 内置在前 + uniqBy 保序,防 cache breakpoint 击穿
dontAsk 收口语义 claude-code-harness/course/04-permission-and-safety.md ask 强转 deny 放最外层,早返回绕不过
五级压缩与递归护栏 claude-code-harness/course/05-context-management.md 顺序固定;querySource 为 compact/session_memory 直接跳过
无 maxSteps 四机制 deepseek-harness/course/03-agent-loop.md concludesTurn / reject / abort / error;max-tokens 粘性
abort 补合成结果 deepseek-harness/course/03-agent-loop.md appendSkippedToolCall 成对写 tool/call + tool/result
append-only 事件流 deepseek-harness/course/04-session-event-log.md 日志唯一真相;ignorable 默认必需;surface 五类事件
沙箱 fail-closed deepseek-harness/course/06-sandbox-and-providers.md SandboxUnavailableError 唯一出口;拒绝方言按后端拆分
90 轮预算与宽限 hermes-harness/course/02-conversation-loop.md 三变量条件;_budget_grace_call 进入即消费
回调注入式事件 hermes-harness/course/02-conversation-loop.md 19 个回调;无总线,异常由宿主各自兜
审批链与 hardline hermes-harness/course/03-tools-and-approval.md hardline 永不可绕;YOLO 导入期冻结
自改进闭环 hermes-harness/course/04-skills-and-memory.md 复盘 fork 白名单;curator 只 archive;usage 遥测
循环检测与硬终止 openclaw-harness/course/02-agent-loop-and-guardrails.md 六检测器;critical 二次命中;阈值不可覆盖
四层策略求交 openclaw-harness/course/03-tools-and-exec-safety.md 每层只能收紧;safe-bin 校验 argv
两级结果截断 openclaw-harness/course/03-tools-and-exec-safety.md 单条 8000 字符 + 上下文预算两级职责不同
压缩 safeguard openclaw-harness/course/05-plugins-memory-and-deploy.md 配 provider 直接提升;保留 tool-call/toolResult 配对
E 平台 事实来源 无公开源码(内部平台) Redis Stream offset 续播 / 协作式取消 / 75% 阈值均为定性
ack 默认 manual openclaw-harness/course/04-channels-and-routing.md 默认值方向由「哪个错误更吵」决定
ignorable 兼容机制 deepseek-harness/course/04-session-event-log.md 默认必需;不对称错误取向写进注释
Drift Gate deepseek-harness/course/06-sandbox-and-providers.md 上游联合类型变化 → 编译失败
并发分批与顺序提交 claude-code-harness/course/03-tool-runtime.md partitionToolCalls 分批;写工具串行
顺序提交游标 deepseek-harness/course/03-agent-loop.md slots + committed;model order 不可跳
lane 与写者校验 openclaw-harness/course/02-agent-loop-and-guardrails.md per-session lane 串行;expectedWriterRunId 防陈旧提交
webhook 安全清单 hermes-harness/course/03-tools-and-approval.md 不可信事件默认只给 4 个低危工具
两阶段审批 openclaw-harness/course/03-tools-and-exec-safety.md 先注册再解析;请求有持久身份
相关记忆槽位 claude-code-harness/course/05-context-management.md 每轮 5 条;compact 天然重置注入状态

关键取舍

六个维度里,「停止条件」与「事件状态」的分歧最深,因为它们各自绑定一个不可妥协的前提。
停止条件绑定任务长度假设,事件状态绑定「谁拥有真相」。其余四个维度(工具安全、压缩、扩展、反直觉)五家的收敛度反而更高——都能找到共同的不变量。做技术选型时优先对齐前两个维度,后四个通常是可适配的。

「反直觉设计」的共同结构是:在局部看起来是 bug 或坏味道,在全局是一个不变量的执行点。
伪造工具结果保 replay、保序去重保 cache、硬编码阈值保一致性、鼓励性 prompt 保闭环运转、反直觉命名保 fail-closed。识别这类设计的办法是问「去掉它会破坏哪条可陈述的性质」——答不出来说明它真的只是坏味道。

进程内防护的「不是边界」宣言(Hermes)与 OpenClaw 的四层求交并不矛盾。
Hermes 否定的是进程内组件的对抗性边界地位,OpenClaw 收紧的是误配传播。前者面向攻击者,后者面向操作失误;两家的公共结论是:真正的硬边界要么在 OS,要么在数学性质(求交),不会在某个聪明的启发式里。

E 平台 的 Redis Stream 路线提示了「事件流」的第三种消费者。
四家解析课的事件流都服务于「重建 agent 状态」,E 平台 的流首先服务于「客户端观看」。这解释了它为什么需要 offset 续播而不需要 ignorable(推断)——观看型消费者断线重连只关心「从哪继续」,不关心「旧版本事件能否被新代码理解」。

压缩维度的对比揭示了一个行业级默认共识:压缩产物必须与普通消息同构。
dsh 把摘要包成 user/message、Claude Code 把边界做成真实消息、OpenClaw 要求配对切割——三家都不为「压缩」发明新的消息类型。代价是「这是压缩产物」要靠旁路反查,收益是 provider 侧零特判。

每个维度的「怎么选」都指向同一个前置问题:先答「谁在用、谁在配、谁在攻击」。
停止条件取决于任务由谁发起,事件状态取决于谁在观看,安全取决于谁来配置,扩展取决于谁来写插件,压缩取决于会话要不要复现。六个维度没有一个是纯技术题——把使用者画像先钉住,维度选择大多会自动收敛。

自测题

  1. OpenClaw 的「critical 二次命中才终止」与 Hermes 的「宽限一轮」都是给启发式边界留缓冲。请分析两者的缓冲对象分别是什么(检测器 vs 预算),以及为什么 Claude Code 不需要类似机制(提示:它的恢复路径有显式计数器)。
  2. dsh 的 surface replace 与 Claude Code 的 compact boundary 都不改写历史。请比较两者的「恢复成本」:从日志重建视图时,谁需要 fold 更多事件?contentGeneration 缓存在其中起什么作用?
  3. 把 Claude Code 的 23 项 Bash 检查与 OpenClaw 的 safe-bin argv 校验对比:两者都是「参数级」白/黑名单。请指出哪一个是黑名单思路、哪一个是白名单思路,并解释为什么白名单的维护成本被描述为「参数级资产」。
  4. 五家的工具并发机制里,有两家把「顺序」作为核心不变量(model order 提交、contextModifier 延迟提交)。请说明这两种顺序各自服务什么(replay/缓存 vs 上下文一致性),以及为什么 Hermes 的 8 worker 不需要等价机制(推断)。
  5. 假设要给 dsh 补上 E 平台 式的「客户端 offset 断线续播」,最小改动是什么?需要动事件 schema(如加 offset 字段)还是只动读路径?seq 连续性保证在其中扮演什么角色。

进入 keel 阅读