OpenClaw 原理:一条消息如何变成一次智能回复

OpenClaw 原理:一条消息如何变成一次智能回复

本文基于 OpenClaw 源码与 learn 目录下的学习文档整理,旨在用一篇文章讲清「一条用户消息如何穿越通道、路由、Agent、系统提示、工具与记忆,最终变成模型给出的回复」。

 


 

一、OpenClaw 是什么

OpenClaw 是一个多通道 AI 网关:用户通过 TG、Signal、Slack、Discord、WhatsApp 等渠道发消息,OpenClaw 统一路由、调用大模型、执行工具(读文件、跑命令、发消息、搜记忆、开浏览器等),再把回复原路发回对应渠道。

核心价值在于:不是手写 if-else 决定「该做什么」,而是通过系统提示 + 工具 + ReAct 循环,让模型自己决定「先搜记忆、再读文件、再执行命令」。我们负责提供能力和执行,模型负责推理和选择。

 


 

二、整体架构:从消息入站到回复发出

2.1 七层流水线

一条用户消息的典型路径可以概括为七步:

1. 通道层:用户在某渠道(如 TG)发消息 → 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 提供,二者通过系统提示和工具协议衔接,形成一条从用户消息到智能回复的完整流水线。

 
posted @ 2026-03-05 18:01  阿狸哥哥  阅读(868)  评论(0)    收藏  举报