OpenClaw 原理:一条消息如何变成一次智能回复
OpenClaw 原理:一条消息如何变成一次智能回复
本文基于 OpenClaw 源码与 learn 目录下的学习文档整理,旨在用一篇文章讲清「一条用户消息如何穿越通道、路由、Agent、系统提示、工具与记忆,最终变成模型给出的回复」。
一、OpenClaw 是什么
OpenClaw 是一个多通道 AI 网关:用户通过 Telegram、Signal、Slack、Discord、WhatsApp 等渠道发消息,OpenClaw 统一路由、调用大模型、执行工具(读文件、跑命令、发消息、搜记忆、开浏览器等),再把回复原路发回对应渠道。
核心价值在于:不是手写 if-else 决定「该做什么」,而是通过系统提示 + 工具 + ReAct 循环,让模型自己决定「先搜记忆、再读文件、再执行命令」。我们负责提供能力和执行,模型负责推理和选择。
二、整体架构:从消息入站到回复发出
2.1 七层流水线
一条用户消息的典型路径可以概括为七步:
1. 通道层:用户在某渠道(如 Telegram)发消息 → Channel 收到事件 → resolveAgentRoute 根据 channel、accountId、peer 等解析出 agentId、sessionKey → 组装 MsgContext(Body、SessionKey、To、Surface 等)。
2. 派发入口:dispatchInboundMessage → finalizeInboundContext → dispatchReplyFromConfig,统一处理去重、ACL、指令解析。
3. 取回复配置:getReplyFromConfig 根据 ctx 解析 agent、model、技能、记忆等配置,准备「跑一轮 Agent」所需的参数。
4. Agent 执行层:runReplyAgent → runAgentTurnWithFallback → runWithModelFallback 选 provider/model → runEmbeddedPiAgent。
5. Embedded 一次 Run:runEmbeddedAttempt 里完成:建 tools(exec、read、write、message、memory_search、browser 等)→ createAgentSession(带 systemPrompt、tools)→ 加载并清洗历史 messages → 调 activeSession.prompt(用户消息)。
6. 模型 + 工具循环:session.prompt 内部(pi-agent SDK)反复「发请求 → 收回复 → 若有 tool_calls 则执行工具、把结果追加进 messages → 再发」,直到模型不再调工具、只输出最终文本。
7. 回写与发送:attempt 返回 payloads → 原路回到 dispatchReplyFromConfig → routeReply / deliver → 用户看到回复。
一句话:用户发消息 → Channel 收事件、路由、组 MsgContext → dispatch → getReply → runEmbeddedPiAgent → runEmbeddedAttempt 里建 tools + session + prompt(用户消息) → SDK 内 ReAct 循环直到得到最终文本 → routeReply / deliver → 用户看到回复。
2.2 会话与路由
- sessionKey:当前这条消息所属的「会话」唯一标识,可按渠道、账号、peer(人/群)细分,用于持久化、历史、限流。
- mainSessionKey:该 agent 的「主会话」固定 key,不区分渠道和人,用于心跳、Cron、健康检查、主会话 UI。
- 配置 session.dmScope 可控制私聊是否每人一会话(per-peer)或全共用一个主会话(main)。
三、核心:系统提示 + 工具 + ReAct
3.1 系统提示是「行为说明书」
OpenClaw 在每次 run 前会拼好一整段系统提示,告诉模型:
- 身份:你是 OpenClaw 里的个人助理。
- 工具:当前有哪些工具(read、write、exec、message、memory_search、browser、cron、sessions_send 等)、名字大小写敏感、何时该用哪个。
- Skills:有哪些技能(name、description、location),若有一个适用就先 read 其 SKILL.md 再执行。
- 记忆:回答「偏好、过往、人、待办」前,先 memory_search,再用 memory_get 按需拉行。
- 规则:安全约束、Tool Call 风格(默认不叙述常规调用)、Silent Reply、Heartbeat 等。
- 运行环境:Workspace 路径、Runtime(agent、channel、model、thinking)、当前时间等。
系统提示一次设置、同轮复用:同一条用户消息内的多次模型请求(ReAct 循环)都用同一份 system prompt。
3.2 工具是「能力的实现」
OpenClaw 通过 createOpenClawCodingTools 生成一整套工具,每个工具有 name、description、parameters、execute。模型在回复里发出 tool_calls: [{ name: "read", arguments: { path: "foo.txt" } }],我们按 name 找到对应 tool,调用 execute(toolCallId, args),把返回值作为 tool result 追加进对话。
我们做的:定义有哪些工具、参数、执行逻辑;模型说「调 exec」,我们就真的跑 exec。
模型做的:根据用户问题和上下文,自己决定要不要搜记忆、读哪个文件、执行哪条命令、给谁发消息;每步看到结果后再决定继续调工具还是直接回答。
3.3 ReAct:推理 → 行动 → 观察 → 再推理
ReAct 循环不在 OpenClaw 仓库里实现,而在依赖 @mariozechner/pi-coding-agent 中。我们只提供三样「零件」:
1. streamFn:每次要请求模型时,SDK 调 streamFn(model, context, options)。我们把 context(system、messages、tools)转成对应 API 的请求体,发 HTTP/WS,解析响应流,返回 { type: "done", reason, message }。
2. tools:SDK 收到 tool_calls 后,按 name 查工具并 execute(toolCallId, params),把结果当 tool result 追加进 messages。
3. sessionManager:负责消息持久化;我们用 installSessionToolResultGuard 包装 appendMessage,做 tool_calls 与 tool result 的配对、过滤、占位等。
因此:同一条用户消息下,可能发生多次「发请求 → 收回复 → 若有 tool_calls 则执行并追加 → 再发请求」,直到模型返回 stop_reason: "end_turn"、只有最终文本为止。
四、技能:不是模型内置,是「先读文档再执行」
技能不是模型的内置功能,而是一份放在仓库里的 SKILL.md。系统提示里只给出技能的名单(name、description、location);模型若判断某技能适用,就用 read 工具去读该技能的 SKILL.md,再按文档里的步骤调其他工具(exec、message 等)执行。
举例:用户说「帮我用 1password 登录」。系统提示里有「1password | Set up and use 1Password CLI… | ~/workspace/skills/1password/SKILL.md」。模型判断适用 → 调 read("skills/1password/SKILL.md") → 拿到文档全文 → 按文档里的 Workflow(op –version、tmux、op signin…)再发 exec 等 tool_calls。
技能可从多目录加载(workspace/skills、managed、bundled、extra 等),经 filterSkillEntries(config、skillFilter、eligibility)过滤后,用 formatSkillsForPrompt 拼成一段 <available_skills> 块,塞进系统提示的 ## Skills (mandatory) 段。
五、记忆:文件是来源,SQLite 是索引
5.1 两层内容 + 一层实现
- 持久化记忆文件:MEMORY.md 和 memory/*.md(如 memory/2026-01-15.md),人类或模型用 write/edit 显式写入,是记忆的权威来源。
- 会话转录(可选):历史对话的导出文本,配置 sources: ["memory", "sessions"] 时可被索引,用于「从过往对话里回忆」。
- 索引实现:Builtin 用 SQLite(files、chunks、embedding、FTS)做向量和关键词检索;QMD 用外部 qmd 命令。memory_search 查的是索引(SQLite 的 chunks),返回的 snippet 直接来自 chunks.text,不读文件;memory_get 才从磁盘读 MEMORY.md / memory/*.md。
5.2 记忆怎么和回复关联
系统提示里有一段 ## Memory Recall:回答「偏好、过往、人、待办」前,先 memory_search,再用 memory_get。用户问「我喜欢什么?」→ 模型判断属「偏好」→ 调 memory_search({ query: "user preferences likes" }) → 我们执行并返回 results(path、行号、snippet)→ 模型在下一轮看到 tool result,生成「根据之前的记录,你喜欢…」的回复。记忆不自动注入,是模型先调工具拿到片段,再写进自己的回复里。
5.3 存储何时触发
- 写记忆文件:① 预压缩记忆冲刷(会话快压缩时,提示模型把该保留的写进 memory/YYYY-MM-DD.md);② /new 或 /reset 时 session-memory 钩子写 memory/YYYY-MM-DD-slug.md。
- 更新索引:watch(文件变更 + debounce)、onSearch(memory_search 时若脏则 sync)、onSessionStart(首搜时 sync)、interval(定时 sync)、会话转录变更(标记脏后参与下次 sync)。
六、一次完整交互示例
以「我喜欢吃什么?」为例:
第 1 轮请求:system(身份 + 工具 + Memory Recall + Runtime)+ user(我喜欢吃什么?)+ tools(memory_search、memory_get、read…)。
第 1 轮响应:assistant 带 tool_use(memory_search, query="user preferences likes food"),stop_reason: "tool_use"。
第 2 轮请求:在上轮基础上追加 assistant(含 tool_use)+ user(含 tool_result:memory_search 返回的 results JSON)。
第 2 轮响应:assistant 的 text(「根据之前的记录,你喜欢吃水果…」),stop_reason: "end_turn"。
我们取第 2 轮响应的 content[0].text 作为最终回复,通过 routeReply / deliver 发回用户。
七、小结
OpenClaw 的原理可以概括为:
1. 消息流:Channel 收事件 → 路由得到 agentId、sessionKey → dispatch → getReply → runEmbeddedPiAgent → runEmbeddedAttempt。
2. Agent 核心:为这一轮准备好「系统提示 + 历史 + 工具实现」,调 session.prompt(用户消息),由 pi-agent 在内部跑 ReAct 循环。
3. 系统提示:决定「模型是谁、能做什么、何时该用哪项能力」;Skills 和 Memory Recall 两段直接驱动「先读 SKILL.md」和「先 memory_search 再答偏好」。
4. 工具:模型通过 tool_calls 点名;我们按 name 执行并回报结果;技能 = 一份 SKILL.md,模型用 read 读后再按文档调其他工具。
5. 记忆:文件是来源,SQLite 是索引;memory_search 查索引返回 snippet,memory_get 读文件;存储靠 flush 和 session-memory 钩子,索引靠 watch / onSearch / interval 等 sync 触发。
整体是模型决策 + 我们执行的设计:思考与选动作由模型完成,能力与执行由 OpenClaw 提供,二者通过系统提示和工具协议衔接,形成一条从用户消息到智能回复的完整流水线。

浙公网安备 33010602011771号