OpenClaw 源码拆解:一个开源 Coding Agent 的架构全景
OpenClaw 是目前最活跃的开源 AI 助手项目之一,面试中被高频问到"你看过源码吗"。本文基于对 OpenClaw 源码的完整阅读,拆解其核心架构------从 Agent 循环、工具系统、上下文管理、记忆体系到插件机制,逐模块分析设计决策和工程取舍。
项目概览
OpenClaw 是一个基于 TypeScript 的 AI 个人助手框架,采用 pnpm monorepo 结构。它不只是一个 Coding Agent,更是一个支持 25+ 消息通道(Telegram、Discord、Slack、飞书等)、93 个扩展插件、53 个内置 Skill 的通用 Agent 平台。
先看整体架构:
插件生态
记忆体系
上下文运行时
Agent 核心
用户层
CLI 终端
Web UI
消息通道
Telegram/Discord/Slack...
Agentic Loop
pi-embedded-runner
System Prompt
构建器
工具系统
Tool Catalog + 多层策略
ContextEngine
(可插拔)
Compaction
分块摘要 + 三级降级
SessionManager
会话历史
memory_search / memory_get
Dreaming 做梦机制
短期→长期自动提升
LanceDB 向量存储
SOUL.md / MEMORY.md
Provider 插件
Anthropic/OpenAI/Ollama...
Channel 插件
25+ 通道
Skill 系统
53 个内置技能
MCP 工具
一、Agentic Loop:不只是 ReAct 循环
大多数介绍 Agent 的文章把 Agentic Loop 简化为"Think → Act → Observe → Repeat"。OpenClaw 的实现远比这复杂------它在经典 ReAct 循环外面包了一层弹性重试循环,处理真实生产环境中的各种故障。
双层循环架构
内层:ReAct 循环 runEmbeddedAttempt()
外层:弹性重试循环 runEmbeddedPiAgent()
是
否
成功
上下文溢出
超时
认证失败
速率限制
计费错误
接收用户消息
调用 runEmbeddedAttempt()
检查结果
上下文溢出?
→ 自动压缩,最多3次
超时?
→ token使用率>65%则先压缩
认证失败?
→ 轮转 auth profile
速率限制?
→ profile轮转或模型降级
计费错误?
→ 触发 FailoverError
成功返回
初始化:沙箱、Skills、工具集、System Prompt
LLM 流式推理
工具调用?
执行工具
结果回灌
最终回复
外层循环的核心逻辑在 run.ts(1441 行),内层在 attempt.ts(2072 行)。几个值得注意的设计:
1. Auth Profile 轮转
OpenClaw 支持配置多个 API Key,当一个 Key 被限流或认证失败时,自动切换到下一个。这在个人使用中不常见,但在团队或多通道场景下很实用。
2. 上下文溢出的智能处理
不是简单地报错,而是检测到溢出后自动触发 compaction(压缩),然后重新尝试。最多压缩 3 次,每次都能腾出更多空间。
3. 超时时的启发式判断
超时不一定意味着失败。如果 token 使用率已经超过 65%,说明可能是上下文太长导致推理变慢,此时先压缩再重试,而不是直接放弃。
二、工具系统:多层策略管道
工具目录
OpenClaw 的工具分为 11 个类别:
| 类别 | 核心工具 | 说明 |
|---|---|---|
| Files | read, write, edit, apply_patch |
文件操作 |
| Runtime | exec, process, code_execution |
命令执行 |
| Web | web_search, web_fetch |
网络访问 |
| Memory | memory_search, memory_get |
记忆检索 |
| Sessions | sessions_list, sessions_spawn, subagents |
多会话/子Agent |
| UI | browser, canvas |
界面交互 |
| Messaging | message |
跨通道消息 |
| Automation | cron, gateway |
定时任务/网关 |
| Nodes | - | 节点管理 |
| Agents | - | Agent 管理 |
| Media | image, image_generate, tts |
多媒体 |
多层策略过滤
工具不是一股脑全给模型的。OpenClaw 设计了一条六层策略管道,逐层过滤出当前场景应该暴露的工具:
全量工具集
Profile 策略
minimal/coding/messaging/full
Provider 策略
按 LLM 提供商过滤
Agent 策略
按 Agent 类型过滤
Group 策略
群聊场景过滤
Sandbox 策略
沙箱模式限制
Subagent 策略
子Agent权限收窄
最终工具集
四种 Profile 预设:
minimal:只有session_status------用于状态检查场景coding:文件、运行时、Web、记忆、会话、自动化、媒体messaging:会话和消息工具------用于纯聊天场景full:所有工具
为什么这样设计? 工具数量直接影响模型的工具选择准确率。研究表明,当可用工具超过 15-20 个时,模型选错工具的概率显著上升。通过策略管道,不同场景下模型看到的工具集是精简且相关的。
MCP 工具集成
除了内置工具,OpenClaw 还通过 materializeBundleMcpToolsForRun 加载 MCP 工具。MCP(Model Context Protocol)是 Anthropic 提出的开放协议,让外部工具通过标准接口接入 Agent。OpenClaw 的 MCP 集成意味着任何实现了 MCP Server 的工具都可以被 OpenClaw 调用。
三、上下文管理:可插拔引擎 + 分块摘要压缩
ContextEngine 接口
这是 OpenClaw 上下文管理的核心抽象。通过定义一套标准接口,允许不同的上下文管理策略以插件形式接入:
ContextEngine 生命周期
正常
超限
bootstrap()
初始化会话状态
ingest()
逐条消息摄入
assemble()
在 token 预算内组装上下文
afterTurn()
turn 后处理
maintain()
转录维护
compact()
压缩上下文
ingestBatch()
批量摄入完成的 turn
接口中有几个精妙的设计:
1. assemble() 返回 systemPromptAddition
引擎不仅组装消息历史,还可以向 System Prompt 注入额外指令。这意味着上下文引擎可以根据当前对话状态,动态调整模型的行为指导。
2. maintain() 的安全重写机制
引擎可以通过 runtimeContext.rewriteTranscriptEntries() 安全地重写会话转录。注意是"安全重写"------引擎决定"改什么",运行时负责"怎么改",避免引擎直接操作底层存储导致数据不一致。
3. ownsCompaction 标志
引擎可以声明自己管理压缩(ownsCompaction: true),此时外层循环不会自动触发压缩,完全由引擎内部决定压缩时机和策略。
压缩算法:三级降级
当上下文超限时,OpenClaw 的压缩算法(compaction.ts,531 行)采用三级降级策略:
是
否
是
否
上下文超限
第一级:完整摘要
分块 → 逐块摘要 → 合并
每块3次重试
成功?
第二级:排除超大消息
过滤掉占窗口50%+的单条消息
只摘要小消息
附加 [Large xxx (~NNK tokens) omitted]
成功?
第三级:纯文本降级
返回 'Context contained N messages
(M oversized). Summary unavailable.'
压缩完成
几个关键的工程细节:
自适应分块
不是固定大小切分,而是根据平均消息大小动态调整:
当平均消息大小 > 上下文窗口的 10% 时:
reduction = min(avgRatio × 2, BASE_CHUNK_RATIO - MIN_CHUNK_RATIO)
chunkRatio = max(0.15, 0.4 - reduction)
消息越大,分块越小,避免单个分块超出模型处理能力。
标识符保留策略
这是我在源码中看到的一个非常实用的设计:摘要时严格保留 UUID、哈希、API Key、URL、文件路径等不可重构的标识符。因为标识符一旦被摘要改写(比如把一个 UUID 缩写成"之前提到的那个 ID"),后续所有引用都会断链。
20% 安全边际
SAFETY_MARGIN = 1.2------token 估算使用 chars/4 的启发式方法,但这会低估多字节字符(中文、emoji)和特殊 token 的实际消耗。20% 的安全缓冲确保分块不会意外超限。
合并摘要时的信息保留清单
多段摘要合并为最终摘要时,明确要求保留:
- 活跃任务状态和批处理进度
- 用户的最后请求
- 关键决策及其理由
- TODO 和开放问题
- 承诺的后续行动
这不是随便写的 Prompt,而是从大量真实对话中提炼出的"压缩时最容易丢失但最影响后续对话质量的信息类型"。
四、记忆体系:从"做梦"到长期记忆
OpenClaw 的记忆系统是我认为最有创意的模块。它不只是"存储和检索",而是构建了一个记忆生命周期管理系统。
存储层
| 层级 | 存储方式 | 说明 |
|---|---|---|
| 瞬时 | 会话上下文 | 当前对话中的工具结果,可被压缩回收 |
| 短期 | 召回追踪记录 | 每次 memory_search 的查询和命中结果 |
| 长期 | MEMORY.md / memory/*.md |
文件级持久化记忆 |
| 人格 | SOUL.md |
用户定义的 Agent 人格和行为偏好 |
| 向量 | LanceDB(可选) | 语义向量索引,支持高效相似搜索 |
memory_search:强制语义召回
memory_search 工具的描述是 "Mandatory recall step" ------在回答关于过去工作、决策、日期、人物、偏好或 TODO 的问题之前,Agent 被强制要求先搜索记忆。这不是建议,是指令。
执行流程:
召回追踪器 记忆后端 memory_search Agent 召回追踪器 记忆后端 memory_search Agent 记录 query + 命中结果 用于后续"做梦"提升决策 search("上次讨论的部署方案") 语义搜索 MEMORY.md + memory/*.md 返回匹配片段(path + lines + score) 按 maxInjectedChars 裁剪结果 装饰引用信息(citations) 返回结果 异步记录召回(fire-and-forget)
最后一步是关键:异步记录召回,永远不阻塞主流程。这些召回记录是"做梦"机制的数据来源。
Dreaming:模拟人类睡眠记忆巩固
这是 OpenClaw 记忆系统最有创意的设计------模拟人类"做梦"过程,在空闲时段自动将高频/高权重的短期记忆提升为长期记忆。
做梦(cron 定时触发)
日常使用
数据积累
memory_search 被调用
异步记录召回
query + 命中结果 + 分数
凌晨 3:00 触发
修复无效条目和过期锁
按权重排序候选记忆
score × recallCount × uniqueQueries
写入 MEMORY.md
三种"做梦"预设:
| 预设 | 触发频率 | 最低分数 | 最少召回次数 | 最少唯一查询数 | 适用场景 |
|---|---|---|---|---|---|
core |
每天凌晨3点 | 默认 | 默认 | 默认 | 日常使用 |
deep |
每12小时 | 0.8 | 3 | 3 | 中度使用 |
rem |
每6小时 | 0.85 | 4 | 3 | 高频使用 |
提升条件的设计逻辑:
不是"被搜索到就提升",而是要同时满足三个条件:
- 分数够高(minScore):语义匹配质量好
- 被召回次数够多(minRecallCount):不是偶然命中
- 被不同查询召回过(minUniqueQueries):不是同一个问题反复问
第三个条件最精妙------它区分了"用户反复问同一个问题"(可能是系统没回答好)和"这个知识在多个不同场景下都有用"(真正值得长期记住的信息)。
SOUL.md:Agent 的人格文件
如果工作空间根目录存在 SOUL.md,OpenClaw 会在 System Prompt 中注入指令,让 Agent 采用其中定义的人格和语气。这不是简单的"角色扮演 Prompt",而是作为 Project Context 的一部分注入,与项目上下文同级。
五、System Prompt:高度结构化的 25+ 章节
OpenClaw 的 System Prompt 构建器(system-prompt.ts,764 行)生成的 Prompt 包含 25+ 个结构化章节。这不是一段自然语言,而是一份精心组织的"操作手册":
动态层(不缓存)
用户层(用户级缓存)
稳定层(全局缓存)
身份声明
Tooling - 工具列表
Tool Call Style
Safety - 安全规则
Skills - 技能目录
Memory - 记忆使用指南
Workspace
Documentation
Authorized Senders
Current Date & Time
Workspace Files - 注入文件
Project Context
AGENTS.md / SOUL.md / TOOLS.md
CACHE_BOUNDARY
Anthropic 缓存分界线
Group Chat Context
Subagent Context
Heartbeats
Runtime - 运行时信息
缓存边界设计
[CACHE_BOUNDARY] 标记是专门为 Anthropic 的 Prompt Cache 设计的。标记之上的内容变化频率低,可以被缓存复用;标记之下的内容(群聊上下文、子Agent上下文、运行时信息)每次可能不同,不缓存。
这个设计直接影响成本------Anthropic 的缓存命中 token 成本是未命中的 1/10。如果 System Prompt 占输入 token 的 60%(在工具多的场景下很常见),合理的缓存边界能降低约 50% 的输入成本。
三种 Prompt 模式
| 模式 | 包含内容 | 使用场景 |
|---|---|---|
full |
全部 25+ 章节 | 主 Agent |
minimal |
去掉 Memory/Skills/Docs/Silent Replies | 子 Agent(减少 token 消耗) |
none |
仅身份声明一行 | 极简场景 |
子 Agent 使用 minimal 模式是一个务实的取舍:子 Agent 通常执行特定任务,不需要完整的技能目录和记忆系统,精简 Prompt 可以把更多窗口留给任务本身。
六、插件系统:三层扩展机制
OpenClaw 的可扩展性通过三层机制实现:
1. Plugin(插件)
93 个扩展插件,通过 definePluginEntry 注册:
注册 API
插件类型
Provider 插件(40+)
Anthropic / OpenAI / Google
Ollama / DeepSeek / Groq...
Channel 插件(20+)
Telegram / Discord / Slack
飞书 / QQ / WhatsApp...
Tool 插件
Tavily / Firecrawl / Browser
Memory 插件
memory-core / memory-lancedb
UI 插件
diffs / canvas
registerTool()
registerCli()
registerHook()
registerMemoryRuntime()
2. Skill(技能)
53 个内置 Skill,每个 Skill 是一个包含 SKILL.md 的目录。Skill 的加载有严格的优先级(低到高):
extra dirs--- 配置的额外目录openclaw-bundled--- 内置skills/目录openclaw-managed---~/.openclaw/skills/agents-skills-personal---~/.agents/skills/agents-skills-project--- 项目.agents/skills/openclaw-workspace--- 项目skills/
高优先级覆盖低优先级的同名 Skill。项目级 Skill 优先级最高,意味着每个项目可以定制自己的技能集。
Skill 的加载方式 :Skill 目录和描述以 XML 格式注入 System Prompt 的 <available_skills> 标签。Agent 在回答前扫描匹配的 Skill,然后用 read 工具加载对应的 SKILL.md 获取完整指令。
这是一种渐进式加载策略------不是把所有 Skill 的完整内容都塞进 System Prompt(那样会撑爆窗口),而是先给目录,按需加载。
3. MCP(Model Context Protocol)
通过 materializeBundleMcpToolsForRun 加载 MCP 工具。任何实现了 MCP Server 的外部服务都可以被 OpenClaw 作为工具调用,无需编写插件代码。
七、消息规范化与转录修复
流式事件处理
Agent 的流式响应不是简单的文本拼接。OpenClaw 处理以下事件类型:
| 事件 | 处理逻辑 |
|---|---|
text_delta |
增量文本累积 |
text_end |
最终文本处理 + 清洗 |
tool_call_start/end |
工具调用追踪和去重 |
message_end |
消息完成 |
compaction_start/end |
压缩事件 |
文本清洗管线
原始 LLM 输出
stripBlockTags()
移除 think/final 标签
stripDowngradedToolCallText()
移除降级的工具调用文本
解析回复指令
reply_to_current / reply_to:id
提取媒体 URL
干净的回复文本
转录修复
session-transcript-repair.ts 处理一个微妙但重要的问题:tool_use 和 tool_result 的配对修复。
在正常流程中,每个 tool_use 消息都应该有对应的 tool_result。但在上下文压缩、会话恢复、异常中断等场景下,可能出现:
- 孤立的
tool_result(对应的tool_use被压缩掉了) - 缺失的
tool_result(工具执行中断)
OpenClaw 在每次组装上下文前都会运行修复,确保 API 收到的消息序列是格式正确的。
八、多通道架构:一个 Agent 服务所有平台
通道抽象
OpenClaw 支持 25+ 消息通道,通过统一的通道注册表管理:
通道能力
通道注册表
消息路由
Session Key 解析
Account ID
DM / Group / Thread
getActivePluginRegistry().channels
markdownCapable
是否支持 Markdown
inlineButtons
内联按钮
reactions
表情反应
threadCreate
线程创建
approvals
审批机制
每个通道声明自己的能力(是否支持 Markdown、内联按钮、表情反应等),Agent 的回复格式会根据通道能力自动适配。比如在 Telegram 上可以用 Markdown 格式化代码块,在 SMS 上就只能发纯文本。
统一消息发送
message 工具提供统一的跨通道消息发送接口,支持:
send:发送消息thread-create:创建线程react:添加表情反应
消息自动路由到源通道,Agent 不需要关心底层是 Telegram 还是 Discord。
九、设计哲学总结
读完 OpenClaw 的源码,我总结出它的几个核心设计哲学:
1. 可插拔优先于深度优化
ContextEngine、Provider、Channel、Memory 全部采用注册表 + 接口模式。这意味着任何模块都可以被替换,但也意味着默认实现可能不如深度定制的方案(比如 Claude Code 的上下文管理)。这是广度 vs 深度的取舍。
2. 弹性优先于性能
外层循环的 auth 轮转、模型降级、自动压缩、三级降级摘要------到处都是 fallback。这反映了一个在 25+ 通道上运行的系统的现实需求:不能因为一个 API Key 过期就整个系统挂掉。
3. 渐进式加载优先于全量注入
Skill 只注入目录不注入全文、记忆只在需要时搜索不全量注入、工具按策略过滤不全量暴露。这是在有限上下文窗口下的务实选择。
4. 记忆是一等公民
"做梦"机制、强制召回、短期到长期的自动提升------OpenClaw 把记忆当作核心特性来建设,而不是事后加的功能。这跟大多数 Coding Agent(包括 Codex CLI)形成鲜明对比。
与 Claude Code 的差异
| 维度 | OpenClaw | Claude Code |
|---|---|---|
| 定位 | 通用 AI 助手(25+ 通道) | 专注编码(终端原生) |
| 上下文管理 | 可插拔接口,实现深度有限 | 工业级精密管理(三路缓存、四级压缩) |
| 记忆 | 做梦机制 + LanceDB 向量搜索 | 五层记忆 + AutoDream + 语义召回 |
| 工具 | 多层策略过滤 | 按语义分类(只读并行/写入独占) |
| 扩展性 | 93 个插件 + 53 个 Skill + MCP | 相对封闭 |
| 核心优势 | 广度(通道 × 插件 × 通道) | 深度(上下文管理精度) |
写在最后
OpenClaw 的源码质量在开源 Agent 项目中属于上乘。它不是一个"能跑就行"的 demo,而是一个经过深思熟虑的生产级系统。特别值得学习的几个点:
- 多层策略管道:工具过滤不是一个 if-else,而是一条可组合的策略链
- 做梦机制:把"记忆管理"从被动存取升级为主动整理,这个思路可以迁移到任何 Agent 系统
- 标识符保留策略:压缩时保留 UUID/URL 等不可重构信息,这个细节在其他系统中很少见
- 弹性重试循环:把 auth 轮转、模型降级、自动压缩统一在外层循环中处理,比分散在各处的 try-catch 优雅得多
如果你正在做自己的 Agent 系统,OpenClaw 的源码值得通读一遍------不是为了复制它的实现,而是理解它在每个设计决策点上的取舍逻辑。