comparison-with-claude-code.md 24 KB

Agent 架构对比:Cyber Agent vs Claude Code

对比 Cyber Agent(本项目)与 Claude Code v2.1.88(非官方重建,仅供研究)的架构设计异同,目标是找到可借鉴的设计思路,以及两者各自的取舍。


总览

维度 Cyber Agent Claude Code
语言/运行时 Python / asyncio TypeScript / Bun + Node.js
定位 嵌入式 Agent 框架,供程序集成调用 面向开发者的交互式 CLI 工具
LLM 接入 多 Provider(OpenRouter / Gemini / Anthropic 等) 仅 Claude API
持久化 文件系统(JSONL + JSON),消息树结构 内存中维护会话,JSONL 转录为副产品
多 Agent 递归子 Trace,独立持久化,可跨设备 Coordinator-Worker,内存中,不持久化
计划管理 原生 GoalTree(结构化、持久化、周期注入) 无原生计划结构(TodoWriteTool 扁平备忘录)
记忆层次 三层(Trace / Knowledge / Skill),Agent 主动写入 二层(CLAUDE.md / 历史压缩),人工维护
权限系统 预设白/黑名单(工具级) 规则引擎(参数级 glob 匹配 + 交互审批)
可观测性 REST + WebSocket,结构化事件流 终端 UI + OpenTelemetry,无外部 API
UI 层 无终端 UI 自研 Ink 终端框架(251KB)

核心结构类比

Cyber Agent              Claude Code
───────────────────      ───────────────────────
Trace                ↔   QueryEngine          (执行容器)
AgentRunner.loop     ↔   queryLoop            (while true 循环)
FileSystemTraceStore ↔   recordTranscript     (持久化)
GoalTree             ↔   TodoWriteTool        (计划管理,差距显著)
KnowHub + Reflect    ↔   CLAUDE.md            (跨任务记忆,差距显著)

一、执行容器:Trace vs QueryEngine

两者都是"单次执行会话"的容器,跨 turn 维护消息历史和统计数据。

维度 Trace QueryEngine
生命周期 持久化到文件系统 内存中,session 结束消失
消息结构 消息树(parent_sequence,支持 Rewind) 线性数组 Message[]
显式状态 running / completed / failed / stopped
计划结构 GoalTree(持久化,周期注入)
唯一标识 trace_id(UUID,可跨设备引用) 无专用 ID
子执行单元 所有子 Agent 都是 Trace,结构统一 AgentTool 各自独立,无统一模型
记忆去重 head_sequence 管理主路径,消息树自动过滤 loadedNestedMemoryPaths 防止 CLAUDE.md 重复注入

loadedNestedMemoryPathshead_sequence 解决了相似的问题(防止重复处理),但层次完全不同:前者是内容级去重,后者是结构级的消息主路径管理。

核心差异:Cyber Agent 把 Agent 执行视为持久化的、可被外部观测的任务;Claude Code 更偏向交互式会话,持久化是副产品而非一等公民。


二、Agent 循环:AgentRunner.loop vs queryLoop

两者都是 while true → 调 LLM → 执行工具 → 继续 的循环,但在五个维度上有实质差异。

2.1 循环状态的管理方式

queryLoop 用一个整体替换State 对象管理跨迭代状态,所有 continue 点都是 state = { ...newState }

// queryLoop 的每个 continue 点——必须声明完整的下一轮状态
state = {
  messages: [...messagesForQuery, ...assistantMessages, recoveryMessage],
  maxOutputTokensRecoveryCount: maxOutputTokensRecoveryCount + 1,
  hasAttemptedReactiveCompact,   // 显式保留
  maxOutputTokensOverride: undefined,  // 显式重置
  pendingToolUseSummary: undefined,    // 显式重置
  stopHookActive,
  turnCount,
  transition: { reason: 'max_output_tokens_recovery', attempt: 2 },
}
continue

这带来三个好处:

  1. 消灭"隐式携带"bug:每个 continue 点必须声明每个字段,想沿用旧值也要显式写出来,不可能因为"忘了重置"而静默出错。
  2. 每个 continue 路径自描述:不需要追踪前面的赋值历史,任意找一个 continue 块就能知道下一轮的完整状态。
  3. transition 字段:零成本可观测性:记录每次 continue 的原因(源码注释明说是专为测试设计的):
transition: { reason: 'next_turn' }
transition: { reason: 'max_output_tokens_recovery', attempt: 2 }
transition: { reason: 'reactive_compact_retry' }
transition: { reason: 'stop_hook_blocking' }
transition: { reason: 'collapse_drain_retry' }

测试可以直接断言 state.transition.reason === 'reactive_compact_retry',而不必解析消息内容推断"压缩是否发生了"。这个字段也是天然的 debug 信息:任何时候 dump 出 state 就能知道循环目前处于哪个恢复阶段。

注意:state.messages 不是 API payload。实际发给 API 的是 prependUserContext(messagesForQuery, userContext),其中 messagesForQuerystate.messages 经过多层变换(compact 边界过滤 → 工具结果截断 → microcompact → autocompact)后的结果。State 本身不落盘,只有 queryLoop yield 出来的 Message 才被 QueryEngine 记录到 transcript。

AgentRunner 当前直接在迭代内修改变量,没有这个整体替换的抽象。

2.2 工具执行时机(最大差异)

queryLoop 有 StreamingToolExecutor(feature gate),工具在模型还在流式输出期间就并发开始执行:

模型流式输出:  [tool1 完整] ── [tool2 完整] ── [tool3 完整] ── 流结束
StreamingExecutor: └→ 立即开始执行  └→ 立即开始执行  └→ 立即开始执行
                        ↓ 执行完立即 yield 结果,不等流结束

工具执行时间被完全"藏"在模型输出时间里。AgentRunner 等 LLM response 完整后才执行工具。

2.3 循环内多层恢复路径

queryLoop 内置了多条恢复路径,全部通过 state = next; continue循环内动态触发:

prompt_too_long → 1. context_collapse drain(轻量,清空 staged collapses)
               → 2. reactive compact(重新压缩,LLM 调用)
               → 失败 → 暴露错误,return

max_output_tokens → 1. 升级到 64k(maxOutputTokensOverride,一次机会)
                 → 2. 多轮文本恢复(最多 3 次,注入 meta 消息)
                 → 3. 耗尽 → 暴露错误,return

模型降级(FallbackTriggeredError)→ 切换 fallbackModel,丢弃已有输出,重试

stop hook blocking → 注入 hook 错误消息,continue

AgentRunner 的恢复(_heal_orphaned_tool_calls)发生在进入循环之前_build_history 阶段),不是循环内的动态恢复。

2.4 周期性上下文注入

AgentRunner 主动、周期性注入 GoalTree + Collaborators:

if iteration % 10 == 0:
    inject_context(goal_tree, collaborators)

queryLoop 的记忆是被动消费的:startRelevantMemoryPrefetch 在每轮开头触发异步预取,settled 且本轮尚未消费时才注入为 attachment。没有强制周期,也没有计划结构注入。

2.5 Hooks:外部介入点

这是两个系统差距最大的维度之一。

Cyber Agent context_hooks:每 10 轮调用一次,返回 markdown 字符串注入为上下文。功能是只读注入,无法阻断或修改任何事物。

CC hooks:覆盖 25+ 事件,三种执行模式(shell / HTTP / TypeScript 回调),每个事件有专用的双向契约:

维度 Cyber Agent context_hooks CC hooks
事件数量 1(周期注入) 25+(全生命周期)
工具生命周期 PreToolUse / PostToolUse / PostToolUseFailure / PermissionDenied
loop 控制 Stop hook block → stopHookActive=true → 强制 continue
修改输入/输出 PreToolUse 改 toolInput;PostToolUse 改 MCP 输出
阻断执行 PreToolUse deny;PostToolUse preventContinuation
执行模式 Python 函数 shell / HTTP / TypeScript callback / postSampling
会话级注册 随 AgentRunner 传入 addFunctionHook(),Map-based,O(1),高并发友好

三个最重要的 hook 事件在 loop 上下文里:

  • PreToolUse:在权限检查之前调用,可 allow/deny/ask + 修改 toolInput。允许外部系统做"无人值守的审批代理",比交互弹窗更适合自主运行场景。
  • Stop hook:每轮结束时调用,若返回 block,则 state.transition = 'stop_hook_blocking',loop 强制再 continue 一轮。这是"外部系统驱动 Agent 继续工作"的接入点。
  • UserPromptSubmit:用户消息进队列时调用,可注入 additionalContext 或改写消息本体。比 context_hooks 的定时注入更精确(每次用户输入时触发,而非每 10 轮)。

2.6 工具摘要的异步隐藏

queryLoop 在工具执行完毕后 fire-and-forget 一个 Haiku 摘要任务,不等它,Promise 存入 nextPendingToolUseSummary,下一轮迭代开头才 await:

// 工具执行完毕后立即触发(不阻塞当前轮)
nextPendingToolUseSummary = generateToolUseSummary({...})

// 下一轮迭代开头取结果——此时 Haiku ~1s 早已完成
const summary = await pendingToolUseSummary

Haiku 摘要的延迟被下一轮 5-30s 的 API 调用时间完全吸收。AgentRunner 没有对应机制。


三、持久化与消息结构

Cyber Agent:消息树(非破坏性)

每条 Message 有 sequence(全局递增)和 parent_sequence,构成树结构:

正常执行:   1 → 2 → 3 → 4 → 5
Rewind 到3:             3 → 6 → 7    (4,5 自动脱离主路径)
压缩 1-3:   8(summary,parent=None) → 6 → 7
侧分支:     5 → 6(reflection) → 7   (不在主路径)
             5 → 8(summary, 主路径)

trace.head_sequence 始终指向主路径头,build_llm_messages 沿链回溯,侧分支自动被过滤。所有历史永久保留,Rewind 是非破坏性操作。

Claude Code:线性历史(有损压缩)

消息以数组形式存在内存中,转录文件是 append-only JSONL。没有树结构,压缩是破坏性的(原始消息不再恢复)。QueryEngine 维护完整的 mutableMessages,queryLoop 内的 state.messages 在压缩后被替换为压缩后版本。

关键差异:Cyber Agent 是非破坏性时间旅行;Claude Code 是有损折叠。代价是 Cyber Agent 存储开销更高,查询需要沿链回溯。


四、计划管理

Cyber Agent:原生 GoalTree

GoalTree 是一等公民,有独立数据模型(Goal)、专用工具(goal)、持久化(goal.json)和周期注入机制:

1. [in_progress] 分析代码架构
   1.1. [completed] 读取项目结构
   1.2. [in_progress] 分析核心模块
        1.2.1. [pending] 分析 runner.py
        1.2.2. [pending] 分析 tool_registry.py
  • Goal 与 Message 关联(msg.goal_id),压缩以 Goal 为粒度
  • 每 10 轮自动注入,模型始终知道自己在做什么
  • 支持 focus / done(附 summary)/ abandon(附原因)

Claude Code:TodoWriteTool(扁平备忘录)

扁平的待办列表,markdown 文本,格式由模型自行决定。无层级结构,无与消息的关联,不跨 session 持久化,不参与压缩粒度决策。

结论:这是两个系统差距最大的维度之一。GoalTree 是架构级方案,TodoWriteTool 是临时补丁。


五、记忆系统

Cyber Agent:三层记忆,Agent 主动写入

Layer 3: Skills(技能库)
  Markdown 文件,领域知识和操作规范,启动时注入 system prompt
        ▲ 归纳
Layer 2: Knowledge(知识库)
  数据库 + 向量索引(KnowHub),语义检索按需注入
        ▲ 提取(Reflect 机制)
Layer 1: Trace(任务状态)
  当前任务的工作记忆:Messages + GoalTree
  • Reflect:执行完成后触发侧分支 Agent,分析历史,调用 knowledge_save 结构化入库
  • 跨任务学习:知识从一个 Trace 提取后,下一个 Trace 可语义检索复用

Claude Code:二层记忆,人工维护

Layer 2: CLAUDE.md(持久指令)
  项目/用户级 Markdown,自动发现(目录树遍历),注入 system prompt

Layer 1: Session 历史(工作记忆)
  当前对话历史(内存),超长时自动压缩折叠

跨 session 保留的内容只有写入 CLAUDE.md 的部分,Agent 本身不会主动提炼知识。

记忆组织轴的根本差异

| | Claude Code | Cyber Agent | |--|-------------|-------------| | 组织轴 | 人为定义的实体(用户、项目) | 执行本身涌现的语义主题 | | 边界划定者 | 人(目录结构、项目边界) | Agent(type + tags + scopes) | | 谁来写记忆 | 人(或由 Claude 辅助人写 CLAUDE.md) | Agent 自身(Reflect 触发后主动入库) |

Claude Code 的记忆是静态附加的上下文,边界由人预先划定。Cyber Agent 的记忆可以从执行本身涌现,围绕"自主执行的任务"或"跨任务的全局线索"(如某类 bug 的修复模式)自然积累。


六、上下文压缩

Cyber Agent:两级压缩 + 侧分支(非破坏性)

Level 1(Goal 完成压缩,零 LLM 成本):以 Goal 为粒度,completed goal 的消息折叠为 long_term_memory 摘要,确定性,无额外 API 调用。

Level 2(LLM 侧分支压缩)POST /compact 触发,在 Trace 末尾创建 branch_type="compression" 侧分支,压缩结果作为 summary 消息插入主路径。原始消息保留在侧分支,可审计。

控制参数:RunConfig(goal_compression="none"|"on_complete"|"on_overflow")

Claude Code:多层被动触发(有损)

每轮迭代前依次经过:HISTORY_SNIPmicrocompactCONTEXT_COLLAPSEautocompact(token 接近阈值时触发)。触发后用摘要模型折叠历史,原始消息丢失。prompt_too_long 错误时进入 reactive compact(最后手段)。

方面 Cyber Agent Claude Code
压缩粒度 Goal 级(语义边界) 全量历史折叠
压缩成本 Level 1 零成本;Level 2 LLM 成本 始终有 LLM 成本
压缩可逆性 ✅ 侧分支,原始消息保留 ❌ 有损折叠,原始丢失
触发时机 主动(on_complete / on_overflow) 被动(超限后)+ 手动

七、多 Agent 协作

Cyber Agent:递归 Trace + 跨设备协议

主 Agent(Trace A)
    ├── agent(task="研究X")           → 子 Trace B(独立持久化,可单独续跑)
    │    └── agent(task="分析子模块") → 子 Trace C(递归)
    └── agent(agent_url="https://remote/", task="...")
         → 远程 Trace(agent://remote/xxx,跨设备)
  • explore 模式task: List[str] 触发 asyncio.gather() 并行多 Agent
  • evaluate 工具:专用评估 Agent,从 GoalTree 自动注入评估标准
  • 续跑子 Agentagent(continue_from=sub_trace_id) 可续跑被中断的子 Agent

Claude Code:Coordinator-Worker(内存中)

Coordinator Agent
    ├── AgentTool(subagent_type="worker") → Worker 1(内存,非持久化)
    ├── AgentTool(subagent_type="worker") → Worker 2
    └── SendMessageTool(to=agent_id)      → 向活跃 Worker 发消息

Worker 以 <task-notification> XML 上报状态,通过 SendMessageTool 持续对话。

方面 Cyber Agent Claude Code
子 Agent 持久化 ✅ 独立 Trace ❌ 内存中
续跑子 Agent continue_from SendMessageTool
跨设备协作 agent_url + 远程 Trace ID ❌ 不支持
并行执行 ✅ explore 模式 ✅ 多 Worker 并行
评估反馈循环 ✅ evaluate 工具 ❌ 无原生评估工具

八、工具系统

工具定义

Cyber Agent(装饰器,轻量):

@tool(description="执行 shell 命令")
async def bash(command: str, ctx: ToolContext) -> ToolResult:
    return ToolResult(
        output=result.stdout,
        long_term_memory=f"Ran: {command}",  # 压缩后保留的摘要
    )

Claude Code(接口,功能丰富): 每个工具实现约 15 个方法:calldescriptioncheckPermissionsisReadOnlyisConcurrencySafeprompt(注入 system prompt 的说明)、renderToolResultMessage(UI 渲染)等。接口更重,但权限检查、并发安全标记、进度报告、UI 渲染都内置其中。

ToolResult 双层记忆(Cyber Agent 特有)

ToolResult(
    output="<10K tokens 完整内容>",      # 第一次给 LLM 完整内容
    long_term_memory="提取了 10000 字符", # 之后只看摘要
    include_output_only_once=True
)

大输出只在首次注入完整内容,后续轮次自动替换为摘要,有效节约 context window。Claude Code 没有对应机制。

Bash 安全(Claude Code 更精细)

Cyber Agent 通过 AgentPreset.denied_tools 黑名单控制,粒度是整个工具。

Claude Code 有独立的 102KB 安全分析器(bashSecurity.ts):

  • 命令语义分析(参数级,不只是工具名匹配)
  • 路径白名单(工作目录 + 额外允许目录)
  • 破坏性命令检测(rm -rf, dd, mkfs 等)
  • 模式匹配规则:"Bash(git *)" 自动允许所有 git 命令

九、权限系统

Cyber Agent:静态预设(工具级)

AgentPreset(
    allowed_tools=["read", "glob", "grep"],
    denied_tools=["write", "edit", "bash"],
)

工具级粒度,静态配置,无动态审批。适合离线/批量 Agent 场景。

Claude Code:规则引擎(参数级)+ 交互审批

  • 规则格式:"Bash(git *)" 支持 glob 模式匹配到参数级
  • 三种规则:alwaysAllow / alwaysDeny / alwaysAsk
  • 权限模式:default(交互)/ auto(ML 分类器)/ bypass
  • 规则自动学习:用户选择"始终允许"后自动写入配置,构建个性化规则库
  • 企业远程策略:通过 managed settings 下发权限规则

适合面向开发者的交互式场景。


十、可观测性

Cyber Agent:结构化事件流(服务视角)

  • WebSocket /api/traces/{id}/watch:实时推送所有 Message 事件
  • REST API:查询 Trace、Messages(main_path / all)、GoalTree
  • GoalTree 统计:每个 Goal 有 self_statscumulative_stats(token、cost、duration)
  • 全历史可查:包括 rewind 旧路径、侧分支

Claude Code:终端 UI + 遥测(工具视角)

  • Ink 终端 UI:实时 spinner、CoordinatorTaskPanel
  • OpenTelemetry:结构化日志(懒加载)
  • 无外部 API:不暴露 HTTP 接口

关键差异:Cyber Agent 从架构上是服务,天然可集成监控;Claude Code 是工具,可观测性服务于操作者自己。


总结:差异与互补

Cyber Agent 的架构优势

优势 价值
消息树 + Rewind 非破坏性时间旅行,支持回溯,全历史可审计
GoalTree 结构化计划,与消息关联,驱动细粒度压缩
Knowledge + Reflect 跨任务知识积累,Agent 主动从经验中学习
记忆组织轴 围绕执行语义积累,不依赖人为划定边界
evaluate 工具 内置评估-反馈循环,Agent 可自我校正
ToolResult 双层记忆 精细的 context 节约机制
跨设备 Agent 协作 分布式 Agent 网络,协议级支持
Context Injection Hooks 灵活的外部事件注入(A2A IM、监控告警等)
服务化 API REST + WebSocket,天然可集成、可观测

值得从 Claude Code 借鉴的设计

设计 价值
Loop State 整体替换 + transition 字段 消灭"隐式携带"bug;transition 记录每次 continue 的原因,可用于测试断言和 debug,扩展后也可通过 WebSocket 推出去实现循环级可观测性
流式工具并发执行 工具在模型流式输出期间就开始执行,延迟藏在模型输出时间里
循环内多层恢复路径 prompt_too_long / max_tokens / 模型降级等错误在循环内动态恢复,无需重启
工具摘要异步隐藏 Haiku 摘要在工具执行后 fire-and-forget,延迟藏在下一轮 API 调用时间里
Bash 语义级安全分析 参数级权限控制,远比工具级黑名单精准,生产环境自主运行时重要
ToolSearch 延迟加载 工具数量大时按需加载描述,节约 system prompt token
权限规则自动学习 "始终允许"自动写入配置,构建个性化规则库
工具生命周期 hooks(PreToolUse / PostToolUse) 在工具执行前后插入外部逻辑:审批代理(PreToolUse allow/deny)、MCP 输出改写(PostToolUse updatedMCPToolOutput)、失败后通知(PostToolUseFailure)。Cyber Agent 目前对工具执行无介入点
Stop hook(loop 控制) 每轮结束时调用,可返回 block 强制 loop 再 continue 一轮(transition='stop_hook_blocking')。这是"外部事件驱动 Agent 继续"的最直接接入点
UserPromptSubmit hook 用户消息进队列时触发,可注入 additionalContext 或改写消息。比 context_hooks 定时注入更精确,也更适合做"收到外部信号时注入上下文"的触发器
工具结果自动存档(toolResultStorage) 每个工具声明 maxResultSizeChars,超限时自动写入磁盘临时文件,API 收到 preview(前 2000 字节)+ 文件路径,模型可按需用 FileReadTool 读回完整内容。与 Cyber Agent 的 long_term_memory(手动摘要、原始内容丢弃)互补:这是零开发者工作量的兜底层,防止意外大输出撑爆 context,且原始内容可恢复

待办:值得引入的改进

优先级 改进项 说明
★★★ Loop State 整体替换 + transition 字段 将 AgentRunner loop 的跨迭代变量收拢为一个整体替换的 State 对象;每个 continue 点声明完整下一轮状态,消灭"忘记重置某字段"的隐患。新增 transition 字段记录每次 continue 的原因(tool_use / compressed / healed_orphan 等),可直接用于测试断言,也可通过 WebSocket 推出去作为循环级可观测事件
★★★ 工具结果自动存档 每个工具声明 max_result_size(字符数),超限时自动写入临时文件,给模型 preview(前 N 字节)+ 文件路径,模型可用 read 工具读回完整内容。与现有 long_term_memory(开发者手写精炼摘要)互补:这是零开发者工作量的兜底层,防止 bash 大输出、webfetch 整页 HTML 等意外撑爆 context
★★☆ 工具并发执行 单次 LLM 响应包含多个 tool_use 时,用 asyncio.gather() 并行执行,而非顺序执行。对"同时读取多个文件"等场景延迟改善明显
★★☆ 工具摘要异步隐藏 工具执行完毕后 fire-and-forget 启动摘要生成(用小模型),不阻塞当前轮,下一轮迭代开头取结果。摘要延迟藏在下一次 LLM 调用时间里,对用户零感知
★★☆ 工具生命周期 hooks ToolRegistry / AgentRunner 层面引入 pre_tool_use / post_tool_use / post_tool_use_failure 三个事件。最高价值用例:① pre_tool_use 实现无人值守审批代理(尤其对 bash 危险命令);② post_tool_use_failure 触发外部通知或重试策略;③ post_tool_use 对特定 MCP 工具的输出做后处理
★★☆ Stop hook + Loop 控制接入点 在每轮 loop 结束时(得到 stop_reason='end_turn')触发一个 hook,如果返回 block 则注入消息并 continue。这让外部系统(监控、测试框架)可以在运行时"插入"下一步指令,而无需重新发起整个对话
★☆☆ 循环内多层恢复路径 目前 _heal_orphaned_tool_calls 在进入循环前执行。可考虑将更多恢复逻辑移入循环内(context 超限时自动触发压缩并 continue,而非报错退出)

对比基于 Cyber Agent 文档(architecture.md)与 Claude Code v2.1.88 npm 包 source map 分析(非官方重建,仅供研究),生成日期:2026-04-02。