硬核拆解 OpenClaw 源码:1000 行代码复刻 AI Agent 核心架构
拆解 OpenClaw 源码:1000 行代码复刻 AI Agent 核心架构
为什么写这篇文章
"没有记忆的 AI 只是函数映射,有记忆 + 主动唤醒的 AI,才是会演化的'生命系统'"
OpenClaw 简直是当下所有做 AI Agent 的创业公司应该参照的最佳实践。
Github源码: openclaw-mini
过去一年见过太多 Agent 项目,打开源码一看,核心就是个 while 循环:
while tool_calls:
response = llm.generate(messages)
for tool in tools:
result = tool.execute()
messages.append(result)
没记忆,没上下文管理,没主动性。用户问一句答一句,关掉窗口什么都没了。本质还是个聊天框,只是多了几个工具调用。
OpenClaw 不一样。它的架构里藏着几个被忽视的设计:
- 长期记忆:跨会话的信息持久化
- 按需上下文:不是把所有东西塞给 LLM,而是分层加载
- 上下文压缩:对话太长时自动摘要,支持无限对话
- 主动唤醒:Agent 能自己检查任务、主动推进工作
这些加在一起,才让 Agent 从"工具"变成"劳动力"。
我把这套架构提炼成 1000 行左右的极简版本,叫 openclaw-mini。目的是让更多人能学到这些设计,应用到自己的项目里。
核心观点:被动聊天框 vs 真正的 Agent
先说结论。
一个没有长期记忆的 AI,只是一个瞬间的函数映射:输入 → 输出,然后遗忘。
一个有记忆、有上下文管理、有主动唤醒能力的 AI,才是一个动态演化的系统。它能记住之前做过什么,能根据项目规范调整行为,能在你不说话的时候继续推进任务。
市面上 99% 的所谓智能体,本质还是被动触发的聊天框。"主动唤醒"才是从"工具"进化到"劳动力"的分水岭。不解决自主性,再多的技能封装也只是玩具。
OpenClaw 的设计解决了这些问题。下面逐个拆解。
架构总览
┌──────────────────────────────────────────────────────────────┐
│ 5 大核心子系统 │
├──────────────────────────────────────────────────────────────┤
│ SessionManager │ 会话持久化,JSONL 格式 │
│ MemoryManager │ 长期记忆,跨会话检索 │
│ ContextLoader │ 按需加载上下文 │
│ SkillManager │ 可扩展技能,声明式定义 │
│ HeartbeatManager │ 主动唤醒,事件驱动调度 │
├──────────────────────────────────────────────────────────────┤
│ 基础设施 │
│ session-key 会话路由隔离 │
│ command-queue 请求串行化 │
│ tool-policy 工具沙箱控制 │
│ agent-events 事件发布订阅(可观测性) │
│ context/pruning 上下文裁剪 │
│ context/compaction 历史压缩摘要 │
└──────────────────────────────────────────────────────────────┘
1. Session Manager - 会话持久化
问题:Agent 重启后如何恢复对话?
常见做法是 JSON 存会话。问题:写入要先读全量再写回;文件写一半崩了,整个文件废掉。
OpenClaw 方案:JSONL 格式,每行一条消息。
{"role":"user","content":"读取 src/index.ts","timestamp":1706860800000}
{"role":"assistant","content":[{"type":"tool_use","name":"read",...}],"timestamp":1706860801000}
追加写入 O(1),坏一行不影响其他行。tail -f 能实时监控。
设计启示:持久化不只是"存起来"。要考虑写入性能、容错、可观测。
2. Memory Manager - 长期记忆
问题:如何让 Agent 记住跨会话的信息?
很多项目的做法:存向量库,每次对话前检索一遍塞进 prompt。
OpenClaw 方案(src/memory/manager.ts):
- SQLite-vec 做向量语义搜索
- BM25 做关键词搜索
- 混合排序
更关键的是,记忆不是被动注入,而是工具化调用:
// 不是这样(每次都塞)
systemPrompt += await memory.search(userMessage);
// 而是这样(给 LLM 一个工具,让它自己决定什么时候查)
tools: [{ name: "memory_search", description: "搜索历史记忆", ... }]
LLM 自己判断什么时候需要查记忆。不是每次都注入一堆可能无关的内容。
设计启示:记忆系统的关键不是"存了多少",而是"什么时候用、怎么用"。被动注入会污染上下文,工具化调用让 LLM 按需获取。
3. Context Loader - 按需上下文
问题:如何注入项目级规范而不污染每次对话?
很多项目的做法:把 README、代码库、所有配置全塞给 LLM。结果 token 爆炸,LLM 反而迷失在噪音里。
OpenClaw 方案:文件化的上下文结构,分层按需加载。
AGENTS.md # Agent 行为规范
SOUL.md # 人格设定
TOOLS.md # 工具使用说明
IDENTITY.md # 身份定义
USER.md # 用户偏好
HEARTBEAT.md # 待办任务
MEMORY.md # 记忆补充
不是把所有文件都塞进去:
- 主 Agent 加载完整上下文
- 子代理只允许
AGENTS.md + TOOLS.md,避免污染
- 超长文件按 head + tail 截断并加标记
设计启示:上下文管理的核心是"减法"而不是"加法"。塞得越多,LLM 越难聚焦。
3.1 Pruning - 裁剪
工具返回结果经常很长。读一个大文件,几万字符。全塞进去会爆。
OpenClaw 的做法:soft trim,保留头尾各 1500 字符,中间截断。
function softTrimToolResult(content: string): string {
if (content.length <= 4000) return content;
const head = content.slice(0, 1500);
const tail = content.slice(-1500);
return `${head}\n...\n${tail}\n[Trimmed: ${content.length} chars]`;
}
LLM 通常能从头尾推断完整内容。
3.2 Compaction - 压缩
对话长了之后,历史消息会超出上下文窗口。怎么办?
OpenClaw 的做法:当历史消息超过 75% 上下文窗口时,触发摘要压缩。
async function compactHistoryIfNeeded(params) {
const totalTokens = estimateMessagesTokens(params.messages);
if (totalTokens <= params.contextWindowTokens * 0.75) {
return { compacted: false };
}
// 对旧消息生成摘要
const summary = await buildCompactionSummary({
messages: oldMessages,
client: params.client,
});
// 摘要替换原始消息
return { summary, keptMessages: recentMessages };
}
摘要作为特殊消息插入对话开头:
【历史摘要】
用户请求实现一个 TODO 应用。已完成:创建项目结构、实现增删改查。
待解决:截止日期提醒功能。
这样 Agent 能继续之前的工作,支持无限长对话。
设计启示:上下文管理是三层机制——加载、裁剪、压缩。缺一不可。
4. Skills Manager - 可扩展技能
问题:如何让用户自定义 Agent 能力?
传统做法:
if (message.includes("/review")) { ... }
else if (message.includes("/refactor")) { ... }
加一个技能要改代码。
OpenClaw 方案:声明式定义,技能是一个 Markdown 文件。
---
id: code-review
name: 代码审查
triggers:
- /review
- 帮我看看这段代码
---
## 审查清单
1. 安全问题:SQL 注入、XSS
2. 性能问题:N+1 查询
3. 代码质量:命名、重复代码
技能文件放目录里,热加载,用户可以自己写。匹配到触发词后,Markdown 内容注入 system prompt。
设计启示:技能系统的核心是"声明式 + 热加载"。让用户能扩展,而不是改代码。
5. Heartbeat Manager - 主动唤醒
这是最被低估的设计。
问题:大多数 Agent 是被动的。用户不说话,它就不动。
OpenClaw 方案:Heartbeat 机制,让 Agent 能主动做事。
架构分两层:
┌─────────────────────────────────────────────────────────────┐
│ HeartbeatWake (请求合并层) │
│ │
│ 多来源触发: │
│ interval (定时器) / cron (任务完成) / exec (命令完成) │
│ ↓ │
│ 250ms 合并窗口 │
│ ↓ │
│ if running: 排队 else: 执行 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ HeartbeatRunner (调度层) │
│ │
│ 1. 活跃时间窗口检查 (避免半夜打扰) │
│ 2. 解析 HEARTBEAT.md 待办任务 │
│ 3. 空内容检测 (无任务时跳过 API 调用) │
│ 4. 执行回调 │
│ 5. 24h 内重复消息抑制 │
│ 6. setTimeout 精确调度下一次 │
└─────────────────────────────────────────────────────────────┘
Agent 可以:
- 定期检查 HEARTBEAT.md 里的待办
- 后台命令跑完主动汇报
- 在设定的时间窗口内主动推进任务
关键设计决策:
| 设计点 | 为什么 |
|--------|--------|
| setTimeout 而非 setInterval | 避免累积误差,精确调度 |
| 250ms 合并窗口 | 防止多事件同时触发 |
| 双重缓冲 | 运行中的请求不丢失 |
| 活跃时间窗口 | 避免半夜打扰用户 |
| 重复抑制 | 24h 内相同消息不重复发送 |
设计启示:主动唤醒是 Agent 和聊天框的分水岭。没有自主性,再多的技能封装也只是等人喂食的宠物。
闭环:记忆 + 主动唤醒
这两个系统加在一起,构成了一个闭环反馈:
┌──────────────────┐
│ Heartbeat │
│ 主动唤醒 │
└────────┬─────────┘
│ 定期检查任务
▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Memory │ ←──→ │ Agent │ ←──→ │ Context │
│ 长期记忆 │ │ Loop │ │ 上下文 │
└──────────────┘ └──────────────┘ └──────────────┘
↑ │ │
│ ▼ │
│ ┌──────────────┐ │
└───────────── │ Session │ ─────────────┘
│ 会话持久化 │
└──────────────┘
- Memory 让 Agent 能记住过去
- Context 让 Agent 能理解当前项目
- Session 让 Agent 能恢复对话
- Heartbeat 让 Agent 能主动行动
这才是一个完整的 Agent 架构。
源码映射
| mini 版本 | OpenClaw 源码 |
|-----------|---------------|
| agent.ts | src/agents/pi-embedded-runner/run.ts |
| session.ts | src/agents/session-manager.ts |
| memory.ts | src/memory/manager.ts |
| context/loader.ts | src/agents/bootstrap-files.ts |
| context/pruning.ts | src/agents/pi-extensions/context-pruning/pruner.ts |
| context/compaction.ts | src/agents/compaction.ts |
| skills.ts | src/agents/skills/ |
| heartbeat.ts | src/infra/heartbeat-runner.ts + heartbeat-wake.ts |
项目结构
openclaw-mini/
├── src/
│ ├── agent.ts # 主循环
│ ├── agent-events.ts # 事件系统
│ ├── session.ts # 会话持久化
│ ├── memory.ts # 长期记忆
│ ├── skills.ts # 技能系统
│ ├── heartbeat.ts # 主动唤醒
│ ├── context/
│ │ ├── loader.ts # 上下文加载
│ │ ├── bootstrap.ts # 启动文件
│ │ ├── pruning.ts # 裁剪
│ │ └── compaction.ts # 压缩
│ └── tools/ # 内置工具
└── README.md # 详细文档
代码全中文注释,每个设计决策都解释了"为什么"。
使用
git clone https://github.com/voocel/openclaw-mini
cd openclaw-mini
pnpm install
export ANTHROPIC_API_KEY=sk-xxx
pnpm dev
写在最后
2026 年了,还在写套壳聊天框?
OpenClaw 的设计值得每个做 Agent 的人学习:
- 会话持久化 - JSONL 格式,追加写入
- 长期记忆 - 工具化调用,按需检索
- 上下文管理 - 加载、裁剪、压缩三层机制
- 技能扩展 - 声明式定义,用户可自定义
- 主动唤醒 - 从工具到劳动力的分水岭
这些不是锦上添花,是 Agent 架构的基础设施。
把这套设计学会,去构建一个真正的 AI Agent,而不是基于 web2.0 思维的 copilot。
代码在 GitHub,欢迎讨论。