从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案

Claude Code 是 Anthropic 推出的终端 AI 编程助手。与普通的聊天式 AI 不同,它直接在终端里工作,能够读取代码、执行命令、修改文件、管理 Git 操作。阅读其源码后,可以从 Agent 循环、上下文工程、提示词工程和多 Agent 协同几个维度梳理出它的设计脉络。

整体架构

Claude Code 的核心是一个典型的 ReAct Agent 架构,入口是 query() 函数,它内部委托给 queryLoop() ------ 一个通过 while(true) 无限循环驱动的 AsyncGenerator。每一次循环就是一个 turn,完成从上下文准备、模型调用、响应处理到工具执行的完整闭环。

整体架构可以概括为以下几个层次:

下面按几个核心维度逐一拆解。

Agent 循环:queryLoop 的运转机制

设计思想

Claude Code 的 Agent 循环采用了 AsyncGenerator + while(true) 的组合。选择 AsyncGenerator 而非简单 async function 的原因很直接:每一轮循环都会向 UI 层 yield 流式事件,调用方可以在循环执行过程中实时渲染模型输出和工具执行进度。如果用普通 async function,只能等整个 turn 结束后才返回结果,用户体验会断掉。

状态管理上,Claude Code 没有把状态分散在各个局部变量里,而是定义了一个 State 对象,在每次 continue 时整体替换:

typescript 复制代码
type State = {
  messages: Message[]
  toolUseContext: ToolUseContext
  autoCompactTracking: AutoCompactTrackingState
  maxOutputTokensRecoveryCount: number
  hasAttemptedReactiveCompact: boolean
  maxOutputTokensOverride: number | undefined
  pendingToolUseSummary: Promise<ToolUseSummaryMessage | null> | undefined
  stopHookActive: boolean | undefined
  turnCount: number
  transition: Continue | undefined    // 记录上一次循环继续的原因
}

这种设计的优势在于:continue 站点只需要构造一个新的 State 对象然后赋值 + continue,不需要分别更新 9 个变量。同时 transition 字段记录了上一次循环继续的原因,让后续迭代能够基于前一次决策做判断(比如判断上一次是否已经做过 collapse_drain_retry,避免重复操作)。

实现过程

循环的执行流程如下:

第一步:循环前预启动(只执行一次)

startRelevantMemoryPrefetch()startSkillDiscoveryPrefetch() 在循环外启动。这两个操作返回 Promise 句柄,内部异步执行,不阻塞主流程。后续 turn 中通过检查 settledAt 字段判断是否就绪,就绪了就消费。

第二步:上下文准备

每轮 turn 进入后,按顺序执行:

  1. applyToolResultBudget() --- 工具结果瘦身,把超大的 tool_result 替换为摘要
  2. snipCompactIfNeeded() --- 历史裁剪,移除过旧的消息
  3. microcompact() --- 微压缩,清理过期的 tool result
  4. applyCollapsesIfNeeded() --- 上下文折叠,分段生成摘要
  5. autocompact() --- 自动压缩,最后的兜底策略
  6. calculateTokenWarningState() --- 检查是否触及硬上限

这些步骤不是互斥的。snip 在 microcompact 之前执行,因为两者都可能运行。collapse 在 autocompact 之前执行,因为如果折叠能把上下文压到 autocompact 阈值以下,autocompact 就变成 no-op,从而保留更细粒度的上下文。

第三步:模型调用

通过 callModel() 将组装好的消息发给 API。流式返回的消息逐条 yield 给 UI,同时收集到 assistantMessages 数组中。遇到 tool_use 块就记录到 toolUseBlocks,设置 needsFollowUp = true

第四步:工具执行

needsFollowUp 为 true 时进入工具执行阶段,通过 runTools()StreamingToolExecutor 执行工具调用。

第五步:附件注入

工具执行完毕后,调用 getAttachmentMessages() 注入各类附件:文件变更通知、任务通知、memory 预取结果、skill 预取结果。

第六步:决策

如果没有 follow-up(模型没有请求工具),进入决策层:执行 Stop Hooks、检查 Token Budget、判断 Max Turns,然后决定是否继续。

核心循环的伪代码:

plain 复制代码
async function* queryLoop(params):
  state = initialState(params)
  pendingMemoryPrefetch = startRelevantMemoryPrefetch(state.messages)

  while true:
    // --- 上下文准备 ---
    pendingSkillPrefetch = startSkillDiscoveryPrefetch(messages)
    messagesForQuery = applyToolResultBudget(messages)
    messagesForQuery = snipCompactIfNeeded(messagesForQuery)
    messagesForQuery = microcompact(messagesForQuery)
    messagesForQuery = applyCollapsesIfNeeded(messagesForQuery)
    messagesForQuery, compactionResult = autocompact(messagesForQuery)

    // --- 硬上限拦截 ---
    if isAtBlockingLimit(messagesForQuery):
      yield APIErrorMessage
      return { reason: 'blocking_limit' }

    // --- 模型调用 ---
    for await message in callModel(messagesForQuery, systemPrompt, tools):
      yield message
      if message.type == 'assistant':
        assistantMessages.push(message)
        toolUseBlocks.extend(extractToolUseBlocks(message))

    // --- 工具执行 ---
    if toolUseBlocks:
      for update in runTools(toolUseBlocks):
        yield update.message
        toolResults.push(update.message)

    // --- 附件注入 ---
    for attachment in getAttachmentMessages(...):
      yield attachment
      toolResults.push(attachment)

    // 消费 memory prefetch(如果已就绪)
    if pendingMemoryPrefetch.settledAt and not consumed:
      for mem in pendingMemoryPrefetch.promise:
        yield createAttachmentMessage(mem)
        toolResults.push(mem)

    // 消费 skill prefetch
    for skill in collectSkillDiscoveryPrefetch(pendingSkillPrefetch):
      yield createAttachmentMessage(skill)
      toolResults.push(skill)

    // --- 决策 ---
    if not needsFollowUp:
      // 处理 413 恢复、max_output_tokens 恢复
      // 执行 Stop Hooks
      // 检查 Token Budget / Max Turns
      if shouldContinue:
        state = newStateWithContinueReason(...)
        continue
      return { reason: 'completed' }

    // 有 follow-up,构建下一轮 state
    state = {
      messages: [...messagesForQuery, ...assistantMessages, ...toolResults],
      turnCount: turnCount + 1,
      ...
    }

流式 fallback 与错误恢复

模型调用阶段有一个 streaming fallback 机制。如果流式调用过程中触发了 FallbackTriggeredError(通常是模型负载过高),系统会:

  1. 清空当前轮收集的所有 assistantMessages 和 toolResults
  2. 将模型切换到 fallbackModel
  3. 丢弃 StreamingToolExecutor 的 pending 结果,重建新的 executor
  4. 重新发起完整请求

fallback 触发时,还会 yield 一个 tombstone 消息给 UI 层,让前端移除上次流式调用产生的不完整内容。

Agent 架构细节

工具结果预算控制

设计思想

工具调用的返回结果(比如 BashTool 执行 ls -la / 的输出、FileReadTool 读取一个大文件的内容)可能非常庞大。如果不加限制地全部塞进上下文,几次工具调用就能吃光整个 token 预算。

Claude Code 的设计思路是:不直接丢弃原始内容,而是替换为摘要,同时在摘要里保留原始内容的文件路径。模型如果觉得摘要不够,可以主动通过 Read 工具去读取全文。这本质上是一种 lazy loading 策略。

实现过程

applyToolResultBudget() 的执行过程分四步:

  1. collectCandidatesByMessage --- 按 API 消息序列分组,把同一轮流式返回的多个 tool_result 候选收集到一起共享预算
  2. partitionByPriorDecision --- 对每组候选按历史决策分区:
    • mustReapply:之前已经被替换成摘要的结果,直接用缓存的 preview 重放,不需要重新决策
    • frozen:之前已经见过但模型已经读取过的结果,标记为冻结,不能再改动。改动会破坏 prompt cache 的字节匹配
    • fresh:首次见到的结果,可以做替换决策
  3. selectFreshToReplace --- 如果 frozen + fresh 的总大小超过 200KB 阈值,从 fresh 中选最大的先替换。选择策略是贪心的:按大小降序排列,从最大的开始替换,直到总大小降到阈值以下
  4. persistToolResult + buildLargeToolResultMessage --- 被选中的结果先写入磁盘文件,然后生成 preview 字符串替换原始内容。preview 中会包含完整内容的文件路径
plain 复制代码
applyToolResultBudget(messages, contentReplacementState)
  │
  ├─ state === undefined → 直接返回(feature 关闭)
  │
  └─ enforceToolResultBudget()
       │
       ├─ 1. collectCandidatesByMessage()
       │     按 API 消息序列分组,同一序列的 tool_result 共享预算
       │
       ├─ 2. 对每组消息执行:
       │     ├─ partitionByPriorDecision()
       │     │   ├─ mustReapply: 之前替换过 → 复用缓存的 preview
       │     │   ├─ frozen: 之前见过但模型已读取 → 冻结不动
       │     │   └─ fresh: 首次见到 → 进入替换决策
       │     │
       │     ├─ frozen + fresh 总量 > 200KB?
       │     │   ├─ YES → selectFreshToReplace()
       │     │   │         按大小降序,从最大的开始替换
       │     │   └─ NO  → 全部标记为 seen,跳过
       │     │
       │     └─ 被选中的结果:
       │          ├─ persistToolResult() → 写磁盘
       │          └─ buildLargeToolResultMessage() → 生成 preview
       │
       ├─ 3. replaceToolResultContents()
       │     用 preview 字符串替换原始内容
       │
       └─ 4. 返回 { messages, newlyReplaced }

缓存持久化(contentReplacementState)是关键设计。因为下一轮循环时,同一批 tool_result 会再次经过 applyToolResultBudget(),系统需要知道哪些已经被替换过(mustReapply),哪些是模型读取过的(frozen)。这个状态只在 agentIdrepl_main_thread 的 querySource 下才会持久化到磁盘,确保 resume 会话能恢复状态。

多级压缩策略

设计思想

Claude Code 的压缩体系不是一刀切的,而是按影响范围和成本分了四个层次。设计的核心原则是:能用便宜操作的就不用贵的,能保留细粒度上下文的就不做全局摘要。

四个层次从轻量到重量排列:

  1. snipCompact --- 裁剪旧消息,直接删除,零成本
  2. microcompact --- 清理过期的 tool result,利用 cache_edits API 保持 KVCache
  3. collapse --- 分段折叠,每段生成独立摘要,保留部分结构
  4. autocompact --- 全局压缩,生成整个对话的摘要替换历史,成本最高

实现过程

microcompact 的处理逻辑:

  1. 检查距离上一次 AI 消息的时间是否超过阈值
  2. 如果超时,直接清空过期的 tool result
  3. 如果启用了 CACHED_MICROCOMPACT,走 cachedMicrocompactPath
    • 依赖 Claude Code 的 cache_edits API
    • 将压缩操作作为缓存编辑提交给服务端
    • 服务端返回实际的 cache_deleted_input_tokens
    • 客户端用实际值替换预估的 token 删除数
    • 边界消息(boundary message)延迟到 API 响应后才 yield,确保使用真实的 token 数据

**为什么使用 ****cache_edits**会又块又节省 token?

要理解这一点,需要先了解 Anthropic API 的 prompt caching 机制。当请求中包含 cache_control: { type: 'ephemeral' } 标记时,API 服务端会缓存该标记位置之前所有消息的 KVCache(Key-Value Cache,即 transformer 模型中 attention 计算产生的中间状态)。下一轮请求如果前缀字节完全一致,服务端可以直接复用缓存的 KV,不需要重新 prefill,从而显著降低延迟和 token 消耗。

问题是:当我们要删除某些旧的 tool result 时,如果直接修改消息内容,整个前缀的字节序列就变了,KVCache 全部失效,服务端必须重新 prefill 全部内容。这就是传统压缩的代价------你节省了上下文 token 数量,但浪费了 cache 命中率。

cache_edits API 解决了这个矛盾。它的工作方式是:

  1. 客户端在请求体中额外附带一个 cache_edits 块,声明要删除哪些内容
  2. 服务端收到请求后,先匹配前缀 KVCache(此时前缀未变,cache 命中),然后再执行删除操作
  3. 删除操作是在 cache 命中之后进行的,所以不会影响前缀的 cache 命中率

autocompact 的处理逻辑:

  1. 守卫检查:DISABLE_COMPACT 开启则跳过
  2. 熔断器检查:连续失败次数达到阈值则跳过
  3. shouldAutoCompact() 判断是否需要压缩
  4. 优先尝试 trySessionMemoryCompaction() --- 将对话信息提取到 session memory 中,然后删除旧消息
  5. 如果 session memory 方式不可用,兜底执行 compactConversation() --- 生成整个对话的摘要替换全部历史
  6. 失败时递增失败计数,触发熔断

执行顺序也很讲究。collapse 在 autocompact 之前执行,因为 collapse 是一个 read-time 的 view projection,不会真正删除消息。如果 collapse 能把上下文压到 autocompact 阈值以下,autocompact 就变成 no-op,从而保留更细粒度的上下文而不是一个粗粒度的摘要。

这些压缩机制都通过 bun:bundlefeature() 函数进行编译期树摇。外部构建可以完全剔除未启用的功能代码,减小包体积。


设计思想

当上下文超出模型限制,API 返回 413 (prompt too long) 错误时,Claude Code 不是简单报错退出,而是设计了渐进式的恢复路径。核心思路是:先做便宜的恢复(保留更多上下文),不行再做重量级恢复。

实现过程

恢复流程分两层:

  1. recoverFromOverflow() --- 折叠释放
    • 将所有 staged 的 context collapses 提交
    • 这是一个相对便宜的操作,只释放之前已经计算好的折叠摘要
    • 保留了细粒度的上下文结构
    • 如果没有 staged collapses 或已经执行过(通过 transition.reason === 'collapse_drain_retry' 判断),则跳过
  2. tryReactiveCompact() --- 反应式压缩
    • 当折叠释放不够用时触发
    • 做一次完整的摘要压缩,替换历史消息
    • hasAttemptedReactiveCompact 守卫,防止重复执行
    • 如果 oversized 的是媒体内容(图片/PDF),跳过 collapse drain 直接走这层,因为 collapse 不处理图片

max_output_tokens 错误也有类似的恢复路径:

  1. 首先尝试 escalation:如果使用了 capped 8k 默认值,将 maxOutputTokensOverride 提升到 64K,用同样的消息重试
  2. 如果仍然超出,进入 multi-turn recovery:注入一条 meta 消息告诉模型 "Output token limit hit. Resume directly --- no apology, no recap",最多重试 3 次
  3. 3 次重试后用 withheld 的错误消息终止循环

上下文工程:精细化控制每一点 token

Memory 预取机制

设计思想

Memory 系统的设计目标很明确:让 Agent 能在跨会话中积累知识并复用。但注入太多记忆会浪费 token,注入不相关的记忆会干扰模型判断。所以需要在检索的准确性和注入的成本之间做平衡。

实现过程

startRelevantMemoryPrefetch() 在循环外启动,返回一个 MemoryPrefetch 句柄。整个流程包括:

  1. 功能开关检查 --- 判断 isAutoMemoryEnabled() 和对应的 feature flag
  2. 提取用户消息 --- 获取最后一条真实用户消息,跳过 isMeta 标记的系统注入消息
  3. 词长过滤 --- 单词数少于 2 个的查询直接跳过,不浪费检索资源
  4. 累计熔断 --- 检查会话累计已注入字节是否 >= 60KB,达到上限后停止预取
  5. 异步搜索 --- 启动不阻塞主流程的后台检索

检索过程的具体实现:

  1. 扫描 memory 目录,读取所有文件的 header
  2. 用 Sonnet 模型的 sideQuery 做相关性评分,选出最多 5 个相关记忆
  3. 读取每个记忆的内容,单文件上限 4KB,超出截断

消费阶段在 getAttachmentMessages() 中进行:

  1. 检查 pendingMemoryPrefetch.settledAt !== null 判断是否就绪
  2. 检查 consumedOnIteration === -1 避免重复消费
  3. 通过 filterDuplicateMemoryAttachments() 过滤已读取过的文件(基于 readFileState 判断)
  4. 将过滤后的记忆转为 AttachmentMessage 注入
plain 复制代码
startRelevantMemoryPrefetch(messages, toolUseContext)
  │
  ├─ 1. isAutoMemoryEnabled() + feature flag?
  │     └─ NO → 返回 null
  │
  ├─ 2. 提取最后一条非 isMeta 用户消息
  │     └─ 单词数 < 2? → 返回 null
  │
  ├─ 3. 会话累计注入字节 >= 60KB?
  │     └─ YES → 返回 null
  │
  ├─ 4. 启动异步搜索(fire-and-forget)
  │     └─ getRelevantMemoryAttachments()
  │           ├─ 扫描 memory 目录,读取文件 header
  │           ├─ Sonnet sideQuery 评分 → 最多选 5 个
  │           └─ 读取内容(单文件 4KB 上限)
  │
  └─ 5. 返回 MemoryPrefetch 句柄
        后续 turn 中通过 settledAt 和 consumedOnIteration 判断消费时机

这套设计有几个值得注意的细节。已读取过的文件通过 readFileState 过滤,这是 cumulative 的状态,跨 turn 累加,确保同一份记忆不会被重复注入。Sonnet 筛选保证了选出的 5 个文件与当前上下文的相关度最高。每个文件只读取 4KB,这个量级大致只够做开头摘要,至于是否读取全文留给后续工具决策。

Skill 发现预取

设计思想

Skill 是 Claude Code 中自定义的命令集合(类似 .claude/commands/ 目录下的命令)。如果每次 turn 都阻塞式搜索哪些 skill 适用,生产数据显示 97% 的情况下搜不到匹配项,白白浪费时间。所以设计为异步预取。

实现过程

startSkillDiscoveryPrefetch() 每轮 turn 启动一次(与 Memory prefetch 不同,skill prefetch 是每轮都启动的,因为它依赖 findWritePivot 守卫来判断是否是非 write 迭代)。

发现搜索在模型流式返回和工具执行的窗口期内运行,等到工具执行结束后与 memory prefetch 结果一起消费。只有 turn-0 的用户输入 discovery 才会阻塞在 userInputAttachments 中,因为那是唯一一个没有前置工作可以隐藏等待时间的信号。

Command Queue

设计思想

在多 Agent 场景下,子 Agent 完成任务后需要通知父 Agent,用户也可以在中途发送新的消息。Claude Code 用一个进程全局的 CommandQueue 单例来管理这些异步消息。

关键设计是消息的作用域隔离。队列是进程全局的,Coordinator 和所有 in-process 子 Agent 共享同一个队列。每个 Agent 在循环中只 drain 属于自己的消息:

  • 主线程(agentId === undefined):获取所有非 slash-command 的通知
  • 子 Agent:只获取 mode === 'task-notification'agentId 匹配自己的通知
  • 用户提示(mode: 'prompt')始终只发送给主线程,子 Agent 永远不会看到用户的 prompt 流

实现过程

query.ts 的每轮 turn 中,工具执行后、附件注入前,会从队列中取出消息:

plain 复制代码
const sleepRan = toolUseBlocks.some(b => b.name === SLEEP_TOOL_NAME)
const queuedCommandsSnapshot = getCommandsByMaxPriority(
  sleepRan ? 'later' : 'next'
).filter(cmd => {
  if (isSlashCommand(cmd)) return false       // slash command 不走这个路径
  if (isMainThread) return cmd.agentId === undefined  // 主线程拿所有通知
  return cmd.mode === 'task-notification' && cmd.agentId === currentAgentId
})

Sleep 工具在这里起调度作用。如果本轮执行了 Sleep,队列优先级切换到 later,否则用 next。这使得不同类型的任务通知可以有不同的调度优先级。

提示词工程

系统提示词的分层构建

设计思想

系统提示词不是单一的字符串,而是由多层组件拼装而成。这种设计让不同来源的配置可以独立演进,互不干扰。

实现过程

构建分三个阶段:

第一阶段:获取基础组件fetchSystemPromptParts()

  1. defaultSystemPrompt --- 默认系统提示,定义 Agent 的基本行为
  2. userContext --- 来自 getUserContext(),包含 CLAUDE.md 内容和当前日期
  3. systemContext --- 来自 getSystemContext(),包含 git 状态(分支、近期提交、用户等)

getUserContextgetSystemContext 都用 memoize 缓存,在对话开始时计算一次,整个对话周期复用。

第二阶段:在 query.ts 中拼接

plain 复制代码
const systemPrompt = asSystemPrompt([
  ...(customPrompt ? [customPrompt] : defaultSystemPrompt),
  ...(memoryMechanicsPrompt ? [memoryMechanicsPrompt] : []),
  ...(appendSystemPrompt ? [appendSystemPrompt] : []),
])

自定义 prompt 存在时替换默认系统提示。memory mechanics prompt 和 append system prompt 作为可选层追加。

第三阶段:在 claude.ts 的 queryModel 中最终组装

plain 复制代码
systemPrompt = asSystemPrompt([
  getAttributionHeader(fingerprint),        // 归属头
  getCLISyspromptPrefix(...),              // CLI 前缀
  ...systemPrompt,                          // 上面传下来的系统提示词
  ...(advisorModel ? [ADVISOR_TOOL_INSTRUCTIONS] : []),
  ...(injectChromeHere ? [CHROME_TOOL_SEARCH_INSTRUCTIONS] : []),
])

上下文注入

消息最终发给模型时,通过 prependUserContext(messagesForQuery, userContext) 将用户上下文插入到消息数组的最前面。系统上下文通过 appendSystemContext(systemPrompt, systemContext) 追加到系统提示词的末尾。

这种 prepend/append 的设计保证了用户上下文(CLAUDE.md 等)出现在消息序列的最前面,离用户输入最近,注意力权重最高。而系统上下文(git 状态等)放在系统提示词最后,作为背景信息。

工具 Schema 构建

工具 schema 的构建流程:

  1. filteredTools --- 按当前权限和可用性过滤工具列表
  2. toolToAPISchema --- 将每个工具的定义转换为 API 可接受的 schema 格式
  3. toolSchemas + extraToolSchemas --- 合并基础工具 schema 和额外工具 schema
  4. allTools --- 最终发送给模型的完整工具列表

在 Coordinator 模式下,Coordinator 只能看到 4 个工具(Agent、SendMessage、TaskStop、SyntheticOutput),而 Worker 能看到完整的工具池。这个过滤在 toolUseContext.options.tools 中完成,由 coordinatorMode 的配置决定。

多 Agent 架构

Coordinator 模式:编排者与工作者

设计思想

Coordinator 模式是最经典的多 Agent 架构,核心思想是角色分离:Coordinator 负责理解和规划,Worker 负责执行和探索。Coordinator 被刻意限制只能使用 4 个工具,防止它越俎代庖去做具体的代码工作。

工作流分为四个阶段:Research → Synthesis → Implementation → Verification,支持并行研究。

Coordinator 的工具限制

Coordinator 只能使用以下 4 个工具:

  1. Agent --- 启动新的 Worker
  2. SendMessage --- 继续已有的 Worker(复用其上下文)
  3. TaskStop --- 停止运行中的 Worker
  4. SyntheticOutput --- 合成输出

在 Coordinator 模式下,模型参数被忽略,不允许自选模型。Worker 使用默认模型进行实质性任务。

AgentTool 的调用逻辑

AgentTool.call() 的执行流程:

  1. 参数解析和前置校验
    • Coordinator 模式下忽略 model 参数
    • 检查 team_name 参数
  2. 路由分发
    • 如果指定了 team_name,走 spawnTeammate 路径,直接进入 team 模式
    • 如果没有指定 subagent_type 且 FORK_SUBAGENT 开启,fork 子 Agent(禁止递归 fork)
    • 否则在已定义的 agent 列表中查找匹配的 agent
  3. Agent 配置解析
    • 等待 MCP 服务器连接
    • 解析模型并记录
    • 解析 isolation 参数(worktree 或 remote)
    • 构建 system prompt 和 prompt messages:
      • fork 的 Agent 继承父 Agent 的 system prompt、上下文、tool result 和指令
      • 普通 Agent 重新组装,只传递一条单独的简单 user message
    • 组装 runAgentParams 对象
  4. 异步/同步 Agent 执行
    • 异步 Agent:注册到后台,调用 runWithAgentContext,启用每 30 秒生成进度摘要,返回 async_launched 状态
    • 同步 Agent:保留中途异步化的能力
plain 复制代码
AgentTool.call()
  │
  ├─ 1. 参数解析 & 前置校验
  │     ├─ Coordinator 模式 → 忽略 model 参数
  │     └─ 检查 team_name
  │
  ├─ 2. 路由分发
  │     ├─ team_name 存在 → spawnTeammate → return
  │     ├─ 无 subagent_type + FORK_SUBAGENT → forkSubagent(禁止递归)
  │     └─ 有 subagent_type → 查找匹配的 agent 定义
  │
  ├─ 3. Agent 配置解析
  │     ├─ 等待 MCP 服务器连接
  │     ├─ 解析模型
  │     ├─ 解析 isolation(worktree / remote)
  │     └─ 构建 system prompt 和 messages
  │          ├─ fork: 继承父 Agent 的完整上下文
  │          └─ 普通: 单条 user message
  │
  ├─ 4. 执行
  │     ├─ 异步: runAsyncAgentLifecycle() → fire-and-forget
  │     │       后台执行,实时更新 UI 进度
  │     │       stream 结束后 → finalizeAgentTool()
  │     │                    → completeAsyncAgent()
  │     │                    → enqueueAgentNotification()
  │     └─ 同步: 阻塞等待结果
  │
  └─ 5. 返回 { status: 'async_launched', agentId }

异步 Agent 的通知机制

异步 Agent 通过以下流程与父 Agent 通信:

  1. runAsyncAgentLifecycle() 在后台执行,通过 for await (msg of runAgent()) 逐条消费 Agent 输出
  2. 实时更新 UI 进度(updateAsyncAgentProgress()
  3. Stream 结束后,finalizeAgentTool() 提取运行结果
  4. completeAsyncAgent() 标记任务状态为 completed
  5. enqueueAgentNotification() 构造 XML 通知推入全局 commandQueue

父 Agent 在下一个 turn 开始时从队列中 drain 这些通知,作为 AttachmentMessage 注入。模型看到 <task-notification> XML 后处理结果并决定下一步。

plain 复制代码
AgentTool.call()
  │
  +--[异步] void runAsyncAgentLifecycle()   ←── fire-and-forget
  |    │
  |    +-- for await (msg of runAgent())    ←── 后台执行,逐条消费
  |    |     updateAsyncAgentProgress()     ←── 实时更新 UI 进度
  |    │
  |    +-- finalizeAgentTool()              ←── stream 结束
  |    +-- completeAsyncAgent()             ←── task.status = 'completed'
  |    +-- enqueueAgentNotification()       ←── 构造 XML
  |         │
  |         +-- enqueuePendingNotification()  ←── 推入全局 commandQueue
  |              │
  |              +-- notifySubscribers()      ←── 唤醒订阅者
  │
  +--[立即] return { status: 'async_launched', agentId }

                    ... 父 Agent 继续做其他事 ...

父 Agent 下一个 turn:
  query.ts 主循环
  │
  +-- getCommandsByMaxPriority()            ←── 从 commandQueue drain
  +-- getQueuedCommandAttachments()         ←── 转为 attachment
  +-- yield AttachmentMessage               ←── 作为 user message 注入
  │
  Claude 看到 <task-notification> XML       ←── 处理结果,决定下一步

Coordinator 的提示词设计

Coordinator 的系统提示词有一个非常关键的设计:它被明确告知 "Workers can't see your conversation",所以每个 Worker prompt 必须是自包含的。这防止了 Coordinator 写出 "基于我们刚才讨论的内容" 这种依赖对话历史的无效 prompt。

Research 完成后,Coordinator 被要求自己做 Synthesis ------ 不是简单地把研究结果转发给下一个 Worker,而是自己理解后发现,写出具体的文件路径、行号和变更内容。提示词中给出了明确的反例和正例:

plain 复制代码
// 反例 ------ 懒惰委托
AgentTool({ prompt: "Based on your findings, fix the auth bug" })

// 正例 ------ 自己理解后合成规格
AgentTool({ prompt: "Fix the null pointer in src/auth/validate.ts:42.
  The user field on Session (src/auth/types.ts:15) is undefined when
  sessions expire. Add a null check before user.id access." })

Fork Subagent 模式:上下文继承与并行扇出

设计思想

Fork 模式的核心价值是上下文继承。与 Coordinator 模式下 Worker 获得独立 prompt 不同,Fork 子 Agent 继承父 Agent 的完整对话历史。这在需要并行探索同一个代码库的不同角度时特别有用。

实现过程

为了最大化 prompt cache 命中率,所有 Fork 子 Agent 的 API 请求前缀必须是字节级一致的。buildForkedMessages() 的实现很巧妙:

  1. 保留完整的父 assistant 消息(包含所有 tool_use 块)
  2. 为每个 tool_use 块生成统一的占位 tool_result(FORK_PLACEHOLDER_RESULT = "Fork started --- processing in background"
  3. 附加每个子 Agent 独有的指令
plain 复制代码
buildForkedMessages(directive, assistantMessage):
  │
  ├─ fullAssistantMessage = clone(assistantMessage)
  │   保留所有 tool_use 块、thinking、text
  │
  ├─ toolUseBlocks = extract all tool_use blocks
  │
  ├─ toolResultBlocks = toolUseBlocks.map(block => {
  │     type: 'tool_result',
  │     tool_use_id: block.id,
  │     content: FORK_PLACEHOLDER_RESULT  // 所有子 Agent 完全一致
  │   })
  │
  └─ return [
       fullAssistantMessage,              // 完全一致
       createUserMessage([
         ...toolResultBlocks,             // 完全一致
         { text: buildChildMessage(directive) }  // 每个子 Agent 不同
       ])
     ]

这样构造的结果是:所有 Fork 子 Agent 的 API 请求只有最后一个 text block 不同,前面的消息内容完全一致,最大化 prompt cache 命中。

Fork 有一个重要限制:禁止递归 fork。源码中通过 isInForkChild() 检测消息中是否存在 FORK_BOILERPLATE_TAG 来实现。Fork 子 Agent 的 system prompt 中也有明确规则:"Your system prompt says 'default to forking.' IGNORE IT --- that's for the parent. You ARE the fork. Do NOT spawn sub-agents."

Fork 子 Agent 还通过 permissionMode: 'bubble' 让权限提示冒泡到父终端,通过 model: 'inherit' 保持与父 Agent 相同的模型选择,确保上下文长度一致。

Swarm / Team 模式:多队友协作

设计思想

Swarm 模式引入了真正的团队概念。与 Coordinator 模式下 Coordinator 通过 Agent tool 直接管理 Worker 不同,Swarm 模式中有一个 Team Leader 和多个 Teammate,队友之间可以通过任务列表自主 claim 任务。

Team = TaskList。创建团队的同时创建对应的任务列表,任务所有权通过 TaskUpdateowner 参数管理。队友的主循环是:findAvailableTask()tryClaimNextTask()runAgent()sendIdleNotification()

实现过程

团队创建

TeamCreateTool 的执行流程:

  1. 检查是否已经在团队中(一个 Leader 只能管理一个团队)
  2. 生成唯一的 team name(如果名称冲突则生成新的 word slug)
  3. 生成确定性的 Lead Agent ID:formatAgentId(TEAM_LEAD_NAME, teamName)
  4. 读取当前模型的配置(处理 session model、settings、CLI 覆盖)
  5. 写入 Team File(.claude/teams/{team_name}/team.json
  6. 重置并创建对应的 Task List 目录(确保任务编号从 1 开始)
  7. 注册团队名称到 Leader 上下文,更新 AppState

队友初始化

队友(teammate)启动时通过 initializeTeammateHooks() 注册 Stop hook:

  1. 读取 team file,获取 Leader 的 Agent ID
  2. 查找 Leader 的名字
  3. 如果当前 Agent 就是 Leader,跳过(Leader 不需要给自己发 idle 通知)
  4. 注册 Stop hook:
    • 当 Agent 的 turn 结束时触发
    • 标记自己不活跃(setMemberActive(teamName, agentName, false)
    • 创建 idle_notification 写入 Leader 的邮箱

跨进程通信:文件邮箱系统

Swarm 模式最有趣的设计是跨进程通信。队友可以是同进程(in-process teammate),也可以是独立进程(tmux pane 或 iTerm2 pane),所以需要一种通用的通信机制。

Claude Code 选择了基于文件的邮箱系统:

  • 每个队友的收件箱路径:.claude/teams/{team_name}/inboxes/{agent_name}.json
  • 写操作通过 writeToMailbox() 完成,使用 proper-lockfile 防止并发写入冲突
  • 锁重试配置:最多 10 次,退避时间 5ms-100ms
plain 复制代码
writeToMailbox(recipientName, message, teamName):
  │
  ├─ ensureInboxDir(teamName)
  │
  ├─ inboxPath = .claude/teams/{team}/inboxes/{agent}.json
  ├─ lockPath = inboxPath + .lock
  │
  ├─ 确保收件箱文件存在(flag: 'wx',已存在则忽略)
  │
  ├─ lockfile.lock(inboxPath, { retries: 10, minTimeout: 5, maxTimeout: 100 })
  │
  ├─ readMailbox() → messages(获取锁后重新读取最新状态)
  │
  ├─ messages.push({ ...message, read: false })
  │
  ├─ writeFile(inboxPath, JSON.stringify(messages))
  │
  └─ release lock

Team Leader 的轮询消费

Team Leader 通过 useInboxPoller 每 1 秒轮询自己的邮箱:

plain 复制代码
useInboxPoller(poll):
  │
  ├─ 每 1000ms 执行一次 poll()
  │
  ├─ poll():
  │     ├─ agentName = getAgentNameToPoll(appState)
  │     │     跳过 in-process teammate(有自己的轮询机制)
  │     │     Teammate 返回 getAgentName()
  │     │     Team Lead 返回 teamContext.teammates[leadAgentId].name
  │     │
  │     ├─ unread = readUnreadMessages(agentName, teamName)
  │     │
  │     ├─ 按消息类型分类:
  │     │     ├─ permissionRequests → 路由到 ToolUseConfirmQueue
  │     │     ├─ permissionResponses → 处理权限回调
  │     │     ├─ sandboxPermissionRequests → workerSandboxPermissions 队列
  │     │     ├─ sandboxPermissionResponses → 处理沙箱回调
  │     │     ├─ shutdownRequests → 传递给 UI 组件
  │     │     ├─ shutdownApprovals → 关闭 pane + 清理团队状态
  │     │     ├─ teamPermissionUpdates → 应用权限更新
  │     │     ├─ modeSetRequests → 设置队友模式
  │     │     ├─ planApprovalRequests → 自动审批并写回
  │     │     └─ regularMessages → 常规消息
  │     │
  │     ├─ 如果没有 regularMessages → markRead() + return
  │     │
  │     ├─ 格式化 regularMessages 为 XML:
  │     │     <teammate_message teammate_id="..." color="..." summary="...">
  │     │       {message text}
  │     │     </teammate_message>
  │     │
  │     ├─ 如果 idle(!isLoading && !focusedInputDialog):
  │     │     onSubmitTeammateMessage(formatted) → 立即作为新 turn 提交
  │     │
  │     └─ 如果 busy:
  │           消息入队 AppState.inbox → 等空闲时投递
  │
  └─ 空闲时交付 pending messages(useEffect)
        格式化 → onSubmitTeammateMessage → 清除已提交的消息

邮箱系统的协议消息

邮箱不只传递简单文本,还承载了多种结构化协议消息,通过 JSON 序列化后以 type 字段区分:

  1. PermissionRequest / PermissionResponse --- 工具权限请求/响应。Teammate 需要执行需要审批的工具时,写入 PermissionRequest 到 Leader 的邮箱。Leader 通过 UI 审批后,写回 PermissionResponse
  2. SandboxPermissionRequest / SandboxPermissionResponse --- 沙箱网络访问权限。当沙箱运行时检测到非允许主机的网络连接时触发
  3. PlanApprovalRequest / PlanApprovalResponse --- 计划审批。Teammate 在 plan 模式下完成计划后,请求 Leader 审批
  4. ShutdownRequest / ShutdownApproved / ShutdownRejected --- 关闭请求。Leader 发送 shutdown_request 给队友,队友可以批准或拒绝
  5. TeamPermissionUpdate --- 团队权限广播。Leader 修改权限后广播给所有队友
  6. ModeSetRequest --- 模式设置。Leader 修改队友的 permission mode
  7. IdleNotification --- 空闲通知。队友 turn 结束时通过 Stop hook 写入

与 OpenClaw 的设计对比

Claude Code 和 OpenClaw 都是 Agent 驱动的产品,但阅读两者的源码会发现,它们在 Agent 循环、上下文管理和多 Agent 通信上的设计思路有显著差异。这些差异不是技术能力的差距,而是产品定位不同导致的架构分叉。关于 OpenClaw 的具体介绍可以看:OpenClaw架构与实现解析

定位差异:专用工具 vs 通用平台

Claude Code 是 Anthropic 自家的产品,深度绑定 Claude API。它的设计目标是把一个 Agent 做到极致------极致的上下文控制、极致的 cache 利用、极致的单用户体验。所有压缩策略都围绕 cache_edits API 设计,所有消息格式都围绕 Anthropic 的 message schema 构建。

OpenClaw 是一个通用 Agent 平台,需要对接所有主流厂商(Anthropic、OpenAI、Google、Ollama 等),支持多种消息通道(Discord、Telegram、WhatsApp、Slack、Web UI),并且允许插件注入自定义的上下文管理逻辑和安全守卫。它的设计目标是可扩展性和多厂商适配,而非对单一 API 的深度优化。

Agent 实现的差异:手写工具 vs 框架包装

Claude Code:手写完整的 Agent 循环

Claude Code 的 queryLoop() 从零手写了整个 Agent 循环。query.ts 里 1700 多行代码完整实现了:上下文准备管线(6 层压缩)、模型调用、工具执行、附件注入、停止决策、错误恢复。没有任何外部 Agent 框架参与。

这背后的原因是 Claude Code 的定位------它是一个工具,不是一个平台。Anthropic 控制从 API 到客户端的整个栈,有动机也有能力对每个环节做深度优化。手写循环让他们可以在精确的位置插入精确的逻辑:在 snipCompactmicrocompact 之间插入 applyCollapses,在工具执行后注入 getAttachmentMessages,在 413 错误时走 recoverFromOverflow 而不是直接报错。每一行代码都为 Claude API 服务。

OpenClaw:包装 PI 框架,提供平台能力

OpenClaw 的 Agent 核心不是自己写的,而是复用了 @mariozechner/pi-coding-agent 库(简称 PI 框架)。它的职责不是实现 Agent 循环,而是围绕 PI 框架做包装和适配:

  1. 多厂商 API 适配层runEmbeddedPiAgent() 内部通过 streamSimple(来自 @mariozechner/pi-ai)统一调用不同厂商的 API。Claude、OpenAI、Google、Ollama 的流式接口各不相同,PI 框架把它们统一成一致的 AsyncGenerator 接口。OpenClaw 在此基础上再做模型选择、鉴权、重试和降级
  2. SessionManager 生命周期管理 :PI 框架的 SessionManager 负责管理对话历史、工具注册、消息流转。OpenClaw 负责创建、持久化、预热 SessionManager,处理 session 文件锁、transcript 修复、compaction 重试
  3. 插件和扩展点 :OpenClaw 在 PI 框架外围构建了完整的插件体系------before_prompt_buildbefore_agent_start 钩子,ContextEngine 可插拔接口,安全守卫注入。这些都不是 PI 框架提供的,而是 OpenClaw 作为平台的核心价值
plain 复制代码
Claude Code 的 Agent 层次:
  ┌─────────────────────────────────┐
  │  queryLoop() (手写, 1700+ 行)   │  ← 完整实现
  │  ├── 上下文管线 (6层)            │
  │  ├── callModel()                │  ← 直接调 Anthropic API
  │  ├── runTools()                 │
  │  ├── getAttachmentMessages()    │
  │  └── 决策层 (stop hooks)        │
  └─────────────────────────────────┘
       直接面向 Claude API


OpenClaw 的 Agent 层次:
  ┌──────────────────────────────────┐
  │  OpenClaw 平台层                  │  ← 包装 + 适配
  │  ├── 多厂商 API 适配              │
  │  ├── SessionManager 生命周期      │
  │  ├── 插件体系 (钩子/ContextEngine)│
  │  ├── 多通道路由 (Discord/Slack..) │
  │  └── Gateway 事件总线             │
  ├──────────────────────────────────┤
  │  PI 框架 (@mariozechner/...)     │  ← Agent 核心
  │  ├── SessionManager              │
  │  ├── streamSimple (统一 API 调用) │
  │  └── 工具注册/消息流转            │
  └──────────────────────────────────┘
       面向所有厂商 API

本质差异 :Claude Code 的 Agent 就是它自己------整个 queryLoop 就是 Agent 循环。OpenClaw 的 Agent 是 PI 框架------OpenClaw 提供的是平台能力:多厂商适配、插件扩展、多通道路由、Gateway 调度。Claude Code 是"工具",把一件事做到极致;OpenClaw 是"平台",让不同的 Agent 实现和插件都能跑起来。

上下文管理的差异

Claude Code:极致精细的内置管线

Claude Code 的上下文管理完全内置,包含 6 层递进管线:

applyToolResultBudgetsnipCompactmicrocompactapplyCollapsesautocompact → 硬上限拦截。

每一层都有精确的阈值控制和状态跟踪。

关键特点:

  1. 深度适配 Claude API 的 cache_edits,利用 KVCache 保持能力
  2. 工具结果分三类(mustReapply/frozen/fresh),每类处理策略不同
  3. 200KB 工具结果预算、4KB memory 截断、60KB 累计熔断
  4. 413 错误两层渐进恢复

OpenClaw:插件化的 ContextEngine 接口

OpenClaw 通过 ContextEngine 接口实现上下文管理的可插拔:

typescript 复制代码
interface ContextEngine {
  assemble(params): AssembleResult    // 组装上下文
  compact(params): CompactResult      // 压缩
  ingest(params): IngestResult        // 摄入新信息
  maintenance(params): ContextEngineMaintenanceResult  // 维护
}

用户可以注册自定义的 ContextEngine 实现,替换默认的压缩和组装逻辑。插件还可以通过 before_prompt_buildbefore_agent_start 钩子注入上下文或修改消息。

关键特点:

  1. 多厂商适配 :不同 API 有不同的上下文限制和错误模式。OpenClaw 的 pi-embedded-helpers 中有专门的 guard 逻辑:isContextOverflowErrorisCompactionFailureErrorisBillingErrorMessage,针对不同厂商返回不同的错误分类
  2. 安全守卫resolveContextWindowGuard 检查上下文窗口是否低于安全阈值(CONTEXT_WINDOW_HARD_MIN_TOKENS),防止在极端情况下发起请求
  3. 插件注入:安全插件可以通过钩子在 prompt 构建前插入指令,比如注入 rate limit 提示、安全警告等
  4. Bootstrap 注入bootstrap-budget 管理启动时的上下文注入量,根据剩余预算决定注入多少 bootstrap 文件内容
plain 复制代码
OpenClaw 上下文组装流程:
  │
  ├─ 1. 加载 Session 历史
  │     limitHistoryTurns(sessionKey) → 根据通道类型限制 turn 数
  │
  ├─ 2. Bootstrap 注入
  │     resolveBootstrapContextForRun() → 按预算截断
  │
  ├─ 3. ContextEngine.assemble()
  │     可插拔的实现
  │     默认: LegacyContextEngine
  │     自定义: 用户注册的插件引擎
  │
  ├─ 4. 插件钩子: before_prompt_build
  │     可修改 prompt 和 messages
  │
  ├─ 5. 插件钩子: before_agent_start
  │     可做最终修改
  │
  ├─ 6. Context Window Guard
  │     evaluateContextWindowGuard() → 低于 MIN 则拒绝
  │
  └─ 7. 发起请求
       多厂商 API 适配层统一调用

核心差异总结:Claude Code 的上下文管理是"深度优化"------针对单一 API 做到极致。OpenClaw 的上下文管理是"广度适配"------用接口抽象让不同厂商和插件都能工作。Claude Code 选择深度是因为 Anthropic 控制整个栈,可以做字节级优化;OpenClaw 选择广度是因为它面对的是碎片化的 AI 生态。

多 Agent 通信的差异

在异步 Agent 通信方面,两者的协调模式有一个共同点:Coordinator/主 Agent 启动子 Agent 后都继续做自己的事,子 Agent 完成后通过某种机制把结果送回给父 Agent。但它们的"turn 结束"语义和通信通道截然不同。

Claude Code:循环不结束,轮询消费

Claude Code 的 Coordinator 调用 Agent tool 启动异步 Worker 后,queryLoop 继续运行------它不会 yield 或暂停。Worker 完成后通过 enqueueAgentNotification() 把 XML 通知推入全局 CommandQueue,Coordinator 在下一轮 turn 的开头通过 getCommandsByMaxPriority() 从队列中取出通知,作为 AttachmentMessage 注入。

团队模式下,队友(Teammate)可能运行在不同进程(tmux pane / iTerm2 pane),通过文件邮箱系统通信:写入 .claude/teams/{team}/inboxes/{agent}.json,Leader 每 1 秒轮询一次。

OpenClaw:session_yield 让出,Gateway 事件唤醒

OpenClaw 的主 Agent 调用 sessions_yield 后,当前 turn 直接结束queueSessionsYieldInterruptMessage() 通过 agent.steer() 注入一个自定义中断消息,persistSessionsYieldContextMessage() 写入 yield 原因到 session 上下文。整个 Agent 进入等待状态,不再消耗资源。

子 Agent(或外部事件)完成后,通过 Gateway 的事件系统(emitAgentEvent)通知父 Agent。Gateway 检测到父 Agent 处于 yield 等待状态,将其唤醒并注入子 Agent 的结果作为下一条消息,触发新一轮 turn。

通信通道的本质区别

Claude Code 的文件邮箱系统是"同机优先"的设计。它假设所有 Agent 运行在同一台机器上,通过共享文件系统通信就够了。这在实际使用中是合理的------Claude Code 的目标用户是单机开发者,Team 模式最多扩展到几个 tmux pane。文件系统通信的好处是零依赖:不需要启动额外的服务,不需要网络连接,开箱即用。

OpenClaw 的 Gateway 是"分布式优先"的设计。Gateway 是一个独立的 WebSocket 服务,所有 Agent 实例(无论是同进程、同机不同进程、还是远程机器)都通过 Gateway 通信。这使得 OpenClaw 可以部署在云服务器上,Agent 可以分布在不同的节点。代价是必须维护 Gateway 基础设施,增加了系统复杂度。

相同点

两者在异步 Agent 的核心思想上是一致的:父 Agent 启动子 Agent 后不阻塞等待,子 Agent 完成后通过某种通知机制把结果送回。这都体现了"非阻塞优先"的设计理念。两者的 CommandQueue 和 Gateway 事件总线在功能上是等价的------都是解耦生产者和消费者的消息通道。

不同点的根源

这些差异都可以追溯到产品定位。Claude Code 是一个精心打磨的单机编程工具,Anthropic 控制整个技术栈,可以做深度优化。OpenClaw 是一个通用的 Agent 平台,需要适配多厂商、多通道、多部署场景,必须在灵活性和深度优化之间做取舍。Claude Code 选择了深度,OpenClaw 选择了广度。两者没有对错之分,只是各自服务于不同的用户群体和使用场景。

总结

Claude Code 的架构设计中,有几个贯穿始终的设计原则:

非阻塞优先。Memory 预取、Skill 发现、工具执行总结都是异步启动、按需消费的模式。主流程绝不等待可能没有结果的后台操作。97% 的 skill 搜索命中率意味着阻塞式搜索几乎总是浪费时间。

精细化控制。从 tool result 的三类分区(mustReapply/frozen/fresh),到 memory 文件的 4KB 截断和已读过滤,到 60KB 的累计熔断,到 fork 子 Agent 的字节级 cache 对齐,每个环节都在精打细算 token 和 cache 的使用。

Agent 间通信解耦。Coordinator 模式的 XML 通知、Fork 模式的继承上下文、Swarm 模式的文件邮箱,都保持了 Agent 间的松耦合。每个 Agent 不需要知道对方是谁,只需要按照协议读写。文件邮箱系统通过 JSON 类型区分、文件锁序列化、轮询投递,实现了跨进程的可靠消息传递。

一些个人见解-关于Claude Code的"遥遥领先"

Claude Code的代码泄漏让我们得以窥见其内部运作的精妙,但这并不意味着它就是AI编程领域的唯一真理。Claude Code的设计优秀,更多体现的是Anthropic在特定技术路径上的深耕,而非AI编程助手的终极形态。

这恰恰是我们在评估AI工具时最需要警惕的认知陷阱。今天也有不少人因为看到Claude Code使用体验不错,Agent设计又用优秀之处,就对 Claude Code 大吹特吹,甚至贬低其他的 AI Coding 工具。但实际上也没有几个人会去分析它的代码和设计,也不会同时深度使用多个 AI Coding 工具。就像几年前新能源汽车爆发时,很多人不认识几个汽车零件,就盲目吹捧某个品牌"天下第一"一样。

真正的智慧在于理解差异的本质。Prompt工程的差异决定了AI如何理解任务,Agent架构的差异决定了AI如何执行任务,而模型差异只是底层的能力支撑。对于开发者而言,与其纠结于哪个工具"更好",不如从原理出发,理解不同设计选择背后的权衡,然后在实际使用中验证这些权衡是否适合自己的需求。

多用、多总结、多学习,这才是AI时代技术人的正确打开方式。无论是Claude Code的精工细作,还是OpenClaw的宏大架构,都值得我们深入研究和实践。因为最终,定义我们生产力的不是某个工具,而是我们驾驭工具的能力。

相关推荐
Jagger_3 小时前
周末和AI肝了两天,终于知道:为什么要把AI当做实习生
前端
weixin_456164833 小时前
vue3 子组件向父组件传参
前端·vue.js
沉鱼.443 小时前
第十二届题目
java·前端·算法
Setsuna_F_Seiei3 小时前
CocosCreator 游戏开发 - 多维度状态机架构设计与实现
前端·cocos creator·游戏开发
努力的小郑3 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Bigger3 小时前
CodeWalkers:让 AI 助手化身桌面宠物,陪你敲代码的赛博伙伴!
前端·app·ai编程
Victor3564 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3564 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁4 小时前
单线程 Redis 的高性能之道
redis·后端