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

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

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

 


 

一、OpenClaw 是什么

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

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

 


 

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

2.1 七层流水线

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

1. 通道层:用户在某渠道(如 Telegram)发消息 → Channel 收到事件 resolveAgentRoute 根据 channelaccountIdpeer 等解析出 agentIdsessionKey 组装 MsgContextBodySessionKeyToSurface 等)。

2. 派发入口dispatchInboundMessage finalizeInboundContext dispatchReplyFromConfig,统一处理去重、ACL、指令解析。

3. 取回复配置getReplyFromConfig 根据 ctx 解析 agentmodel、技能、记忆等配置,准备「跑一轮 Agent」所需的参数。

4. Agent 执行层runReplyAgent runAgentTurnWithFallback runWithModelFallback 选 provider/model → runEmbeddedPiAgent

5. Embedded 一次 RunrunEmbeddedAttempt 里完成:建 toolsexecreadwritemessagememory_searchbrowser 等) createAgentSession(带 systemPrompttools)→ 加载并清洗历史 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 里的个人助理。
  • 工具:当前有哪些工具(readwriteexecmessagememory_searchbrowsercronsessions_send 等)、名字大小写敏感、何时该用哪个。
  • Skills:有哪些技能(namedescriptionlocation),若有一个适用就先 read 其 SKILL.md 再执行。
  • 记忆:回答「偏好、过往、人、待办」前,先 memory_search,再用 memory_get 按需拉行。
  • 规则:安全约束、Tool Call 风格(默认不叙述常规调用)、Silent ReplyHeartbeat 等。
  • 运行环境Workspace 路径、Runtimeagentchannelmodelthinking)、当前时间等。

系统提示一次设置、同轮复用:同一条用户消息内的多次模型请求(ReAct 循环)都用同一份 system prompt

3.2 工具是「能力的实现」

OpenClaw 通过 createOpenClawCodingTools 生成一整套工具,每个工具有 namedescriptionparametersexecute。模型在回复里发出 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)。我们把 contextsystemmessagestools)转成对应 API 的请求体,发 HTTP/WS,解析响应流,返回 { type: "done", reason, message }

2. toolsSDK 收到 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。系统提示里只给出技能的名单namedescriptionlocation);模型若判断某技能适用,就 read 工具去读该技能的 SKILL.md,再按文档里的步骤调其他工具(execmessage 等)执行。

举例:用户说「帮我用 1password 登录」。系统提示里有「1password | Set up and use 1Password CLI… | ~/workspace/skills/1password/SKILL.md」。模型判断适用 调 read("skills/1password/SKILL.md") 拿到文档全文 按文档里的 Workflowop –versiontmuxop signin…)再发 exec 等 tool_calls

技能可从多目录加载(workspace/skillsmanagedbundledextra 等),经 filterSkillEntriesconfigskillFiltereligibility)过滤后,用 formatSkillsForPrompt 拼成一段 <available_skills> 块,塞进系统提示的 ## Skills (mandatory) 段。

 


 

五、记忆:文件是来源,SQLite 是索引

5.1 两层内容 + 一层实现

  • 持久化记忆文件MEMORY.md 和 memory/*.md(如 memory/2026-01-15.md),人类或模型用 write/edit 显式写入,是记忆的权威来源
  • 会话转录(可选):历史对话的导出文本,配置 sources: ["memory", "sessions"] 时可被索引,用于「从过往对话里回忆」。
  • 索引实现Builtin 用 SQLitefileschunksembeddingFTS)做向量和关键词检索;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" }) 我们执行并返回 resultspath、行号、snippet)→ 模型在下一轮看到 tool result,生成「根据之前的记录,你喜欢…」的回复。记忆不自动注入,是模型先调工具拿到片段,再写进自己的回复里。

5.3 存储何时触发

  • 写记忆文件 预压缩记忆冲刷(会话快压缩时,提示模型把该保留的写进 memory/YYYY-MM-DD.md);② /new 或 /reset 时 session-memory 钩子写 memory/YYYY-MM-DD-slug.md
  • 更新索引watch(文件变更 + debounce)、onSearchmemory_search 时若脏则 sync)、onSessionStart(首搜时 sync)、interval(定时 sync)、会话转录变更(标记脏后参与下次 sync)。

 


 

六、一次完整交互示例

以「我喜欢吃什么?」为例:

 1 轮请求system(身份 + 工具 + Memory Recall + Runtime+ user(我喜欢吃什么?)+ toolsmemory_searchmemory_getread…)。
 1 轮响应assistant 带 tool_use(memory_search, query="user preferences likes food"),stop_reason: "tool_use"

 2 轮请求:在上轮基础上追加 assistant(含 tool_use+ user(含 tool_resultmemory_search 返回的 results JSON)。
 2 轮响应assistant 的 text(「根据之前的记录,你喜欢吃水果…」),stop_reason: "end_turn"

我们取第 2 轮响应的 content[0].text 作为最终回复,通过 routeReply / deliver 发回用户。

 


 

七、小结

OpenClaw 的原理可以概括为:

1. 消息流Channel 收事件 路由得到 agentIdsessionKey → 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 查索引返回 snippetmemory_get 读文件;存储靠 flush 和 session-memory 钩子,索引靠 watch / onSearch / interval 等 sync 触发。

整体是模型决策 + 我们执行的设计:思考与选动作由模型完成,能力与执行由 OpenClaw 提供,二者通过系统提示和工具协议衔接,形成一条从用户消息到智能回复的完整流水线。

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