十大步骤、四个反共识:万字拆解 OpenClaw 源码

0 评论 186 浏览 1 收藏 49 分钟

当OpenClaw成为AI圈的现象级产品,其背后的技术架构却鲜为人知。本文通过万字源码解析,首次完整揭示了一条消息从接收、路由到Agent执行的十个关键步骤。从网关启动到模型调用,从工具管理到流式回复,深度拆解这款AI助手的工程实现逻辑与设计哲学。

自从上次聊 OpenClaw 到现在,也过去2周左右了。(OpenClaw火了,但现在还不是企业All In的时候)

这期间,这只红色小龙虾火得一塌糊涂。打开公众号和小红书,满屏都是“OpenClaw 保姆级部署教程”、“如何搭建你的AI军团”、“必备的 Top 10 Skill 推荐”……

说实话,这些内容看多了,都有点让人应激了。大家都在教你怎么使用它,却很少有人讲讲它是怎么运行起来的。

我是个喜欢探究本质的人,信奉第一性原理。随着对 OpenClaw 越用越深,我开始好奇一件事:当我们在各种IM工具上给 OpenClaw 发了一句“你好”时,屏幕背后到底发生了什么?它是如何把这句简单的问候,变成大模型能理解的指令?它又是如何调用工具、组织上下文,最后把答案吐回给我的?

带着这份疑惑,我花了2周时间,在 AI 的帮助下,做了一件枯燥、耗时,但却很有意义的事:把 OpenClaw 的源码顺着执行链路,从头读了一遍。

最后我发现,一条消息从进入 OpenClaw 到返回结果,总的来讲可以分为 10 个步骤。而且里面有几个地方,和很多人想象的完全不一样。于是也就有了这篇文章及其背后的思考。

在读源码的过程中,我发现这套执行链路很像一家餐厅接待客户的流程。所以下面我就以用户从 WhatsApp 发出一条消息为例,带你看看从顾客进店到菜品上桌,究竟经历了哪 10 个关键步骤:

第一步:餐厅开门,先让整个系统进入营业状态

OpenClaw把自己定位成一个本地优先的 Gateway(网关),所谓“网关”,可以理解为一道接待顾客的大门,因此所有流程的开始,也就和大模型无关,而是从 Gateway 启动出发的。

OpenClaw 本质上不是个聊天框,而是一个本地控制平台。它先把自己的 Gateway 跑起来,建立统一的会话、频道、工具、事件分发能力,然后不同渠道的消息才能往这里汇总。网关的启动过程如下图:

这段代码位于:auto-reply/monitor.ts,动作就是执行 monitorWebChannel 这个函数。

这就像餐厅经理早晨打开大门,检查各种设备是否正常,再接通前台、后厨、点单系统和出餐系统,点亮招牌,让 OpenClaw 处于营业状态,随时准备接客。没有这一层,后面所有智能都无从谈起。

第二步:服务员接单,把原始消息翻译成系统能处理的格式

当一条 WhatsApp 消息进来时,入口并不是直接把这段文本转发给大模型,而是构建了一个消息监听与接入层,先把平台原始消息捕获住。

在源码中,这一层主要由 inbound/monitor.ts 负责。系统会先建立 WhatsApp 的实时连接,并监听消息事件,一旦有新的消息到达,OpenClaw 就会启动一整套前台接待流程。如下图所示:

具体来说,这个过程大致会经历几个连续步骤:

首先是消息去重和合并。在真实环境里,IM 平台经常会出现重复推送或者高频消息事件,如果系统直接处理,很容易导致机器人重复回复。所以 OpenClaw 会先做一次消息去重,并对短时间内的连续消息进行合并,避免后续流程被噪声干扰。

接下来是权限和合法性检查。系统会判断这条消息是否来自允许的用户或群组,是否满足触发规则。如果不符合规则,这条消息会直接被过滤掉,不会进入后面的 Agent 流程。

这就像餐厅前台会先确认顾客是不是本店允许接待的客人。

如果消息中包含附件,例如图片或文件,系统还会下载媒体文件并保存到本地工作目录中。这样后续 Agent 在推理时,就可以访问这些文件,比如分析图片、读取文档等。

随后,系统会标记消息为已读,避免客户端继续推送同一条消息。

当这些基础处理完成后,OpenClaw 会把来自不同 IM 平台的原始消息,统一封装成标准结构。

最后,这条标准化后的消息会被放入系统的防抖队列(debounce queue)中,防止用户短时间内连续发很多条消息,然后触发回调,前台接待员把客人的信息登记完之后,会对餐厅内部喊一句:“这桌客人登记好了,可以安排入座了!”这句话就是回调。

在 OpenClaw 中,这个回调会把整理好的消息交给 Gateway,让后面的 Agent 系统继续处理。

这一步的本质,就是把外部世界的一条混乱消息,变成系统内部的一条标准事件。

第三步:领班分单,判断这条消息该交给哪个Agent

如果说第二步是餐厅前台完成了顾客登记,那么第三步就像是领班开始分单,同样是客人点菜,有的送去大堂,有的送去包间,有的送去后厨冷菜间。

当消息经过前一层的整理后,系统内部已经拿到一个标准化对象 WebInboundMessage。接下来这条消息会进入 OpenClaw 的路由模块,在源码中主要由 auto-reply/monitor/on-message.ts 负责。

这一层的任务并不是处理消息内容,而是根据渠道、账户、会话、群组规则,把消息路由到不同 agent 的独立 workspace 和独立 session中。如下图所示:

很多人理解 Agent 系统时,会以为所有消息都会送到同一个 AI,然后由它自己决定怎么处理。但 OpenClaw 的设计更像一个餐厅里的调度系统,不同类型的任务会被分配给不同的厨师。

在代码层面,消息进入这一层后,首先会通过 createWebOnMessageHandler 创建一个消息处理入口。这个入口会接收刚刚生成的 WebInboundMessage 对象,同时重新读取当前的系统配置。之所以要重新读取,是因为 OpenClaw 的配置是可以动态调整的,例如 Agent 的绑定关系、权限规则等,系统必须确保使用的是最新配置。

接下来系统会解析消息的发送方向。它会判断这条消息是来自用户私聊,还是来自群聊,并进一步确定发送者的 ID。例如在群聊中,机器人通常不会对所有消息都响应,而是需要判断是否被 @、是否满足触发规则等;而在私聊场景里,机器人通常默认可以直接处理。

在确认消息来源后,系统会进入真正的Agent 路由决策阶段。这一阶段会读取 openclaw.json 配置文件中的 bindings 设置,并通过 resolveAgentRoute 函数判断应该由哪个 Agent 来处理当前消息。这里的逻辑有点像餐厅领班根据订单类型分配厨师。如果客人点的是海鲜,就送到海鲜档口;如果是烧烤,就送到烧烤师傅那里。OpenClaw 也是一样,不同渠道、不同用户、不同类型的消息,都可能绑定到不同 Agent。

当 Agent 路由确定之后,系统还会为当前会话生成一个历史记录的 key,也就是 buildGroupHistoryKey。这个 key 的作用,是把当前聊天和历史对话关联起来。因为在很多情况下,Agent 的回答并不是只依赖当前这一句话,而是要结合之前的上下文。例如用户刚刚问过一个问题,下一句话可能只是简单补充一句“那第二种方案呢?”,如果没有会话记录,系统根本无法理解这句话的含义。

随后系统还会做一些额外检查。例如在某些配置模式下,会判断消息是不是来自“同一台设备”(Same-phone 模式),避免机器人自己触发自己发送的消息,形成循环回复。同时系统也会进行回声检测,通过 echoTracker 机制过滤掉刚刚自己发送的消息,避免系统重复处理这些同步事件。

当这些基础判断完成后,系统会进一步区分当前消息是群聊消息还是私聊消息。如果是群聊,系统会先构建群聊上下文,然后更新对应的路由记录,并检查权限规则,例如机器人是否被允许在这个群里回复、是否被 @、是否满足触发条件等。如果是私聊,则会对用户手机号进行标准化处理,用统一格式来识别用户身份。

接下来,系统还会判断这条消息是否需要广播处理。即是否要让多个 Agent 同时参与处理。例如有些系统会让信息检索 Agent和总结 Agent同时参与工作。如果满足广播条件,系统就会让所有参与的 Agent 分别执行一次 processForRoute。如果不需要广播,那么系统只会调用一次路由处理逻辑。

最终,当路由判断全部完成后,这条消息才会进入真正的处理函数 processMessage,传递给下一层处理。

第四步:帮厨备菜,做准备工作

第三步是确定客人该交给哪位厨师,第四步则是厨师正式接单,在开火前把所有食材和工具准备好。

在源码中,这一层主要发生在 get-reply.ts。可以想象成厨师接到订单后,先确认几件事情:这桌客人是谁、以前来过没有、有没有忌口、今天厨房里有哪些食材、需要用哪个灶台。如下图所示:

在 OpenClaw 中,第一件事通过 resolveSessionAgentId 确定当前会话真正对应的 Agent,同时决定使用哪个 AI 模型。虽然在第三步已经做过一次路由,但那一步更多是确定应该由谁负责,而在这里系统会再次确认当前会话的上下文状态,并选择最终要使用的模型。例如有的 Agent 可能默认使用豆包 seed-1.8,有的可能使用本地模型,这些都会在这里被最终确定。

接下来系统会执行 ensureAgentWorkspace,为当前 Agent 准备一个独立的工作目录。OpenClaw 的 Agent 在运行时,经常要读写文件、下载附件、生成临时结果,甚至执行一些工具命令。因此系统会为每个 Agent 维护一个专属 workspace,用来存放这些运行时文件。就像帮厨把砧板、刀具和食材都摆到厨师面前,让厨师可以随时操作。

如果用户发的不是纯文本,而是图片、链接或者其他媒体内容,系统还会通过 applyMediaUnderstanding 和 applyLinkUnderstanding 对这些信息进行解析。让 Agent 在推理之前就对内容有个全面理解。

媒体内容解析完成后,还会通过 initSessionState 初始化当前会话状态。把历史对话记录加载进来。很多 AI 系统看起来像是记得你之前说过什么,背后的原理是系统在每次推理前都会把历史对话重新加载到上下文中。OpenClaw 在这一层就会完成这件事,为接下来真正的推理准备好完整的会话环境。

紧接着系统会执行 resolveReplyDirectives,解析用户消息中的一些特殊控制指令,如!reset、!model gpt-4、!verbose ,调整当前会话的运行方式(跳过会话,直接执行内置管理指令)。

随后系统会执行 handleInlineActions处理一些不需要 AI 推理就能完成的操作。例如某些简单命令、状态查询或者系统级操作,都可能在这里直接完成,而不需要调用大模型。

当这些准备工作全部完成之后,系统才会进入 runPreparedReply,把所有准备好的参数——包括会话上下文、模型选择、工具环境、用户消息等——全部打包,然后交给后面的流程。

换句话说,到第四步结束时,OpenClaw 仍然还没有真正开始思考。它只是完成了大量幕后准备工作:确认 Agent、准备工作空间、理解媒体内容、加载历史对话、解析控制命令,并整理好推理环境。

第五步:主厨调火

第五步就轮到主厨开始真正读菜单,并决定怎么做这道菜了。

这一步相对简单,发生在 get-reply-run.ts,核心函数就是 runPreparedReply,作用是把用户消息转换成完整的模型运行配置。如下图:

系统会通过 runPreparedReply 判断当前消息是否真的需要进入 AI 推理流程。例如某些系统指令、状态查询或者已经被前面流程处理过的消息,可能会在这里直接结束。

接下来把当前会话的信息整理成模型可以理解的提示词结构,还会根据用户消息中的一些特殊指令,对模型配置进行调整。例如用户可能在消息中加入类似 /think high 这样的标记,这意味着希望模型使用更高的推理强度;或者加入 /new 这样的标记,表示希望开启一个新的会话环境。系统会在这一层把这些信息解析出来,并转化为明确的模型运行参数。

除了这些显式指令之外,系统还会校验当前推理的思考等级和运行策略。不同任务可能需要不同程度的推理深度,例如简单问答和复杂任务规划,在模型配置上可能会有所区别。这些判断都会在这一阶段完成。

与此同时,系统还会处理当前会话中的消息队列。确保这些消息按照正确顺序进入推理流程,而不会互相打乱。

当这些工作全部完成后,runPreparedReply 会把所有信息整理成一个完整的参数包,传递给agent-runner.ts排队执行下一步

第六步:后厨排队,调度Agent,管理任务生命周期

在餐厅里,下单后并不会马上开始炒菜,而是先把订单放进厨房的排队系统里,确保厨师一单一单地处理,不会因为同时涌进很多订单而乱套。

在 OpenClaw 中,这一层主要由 agent-runner.ts 负责。它负责管理 Agent 运行的完整生命周期。很多人理解 AI 系统时,只关注模型怎么回答问题,但在真实系统里,如果没有一个稳定的运行调度层,AI 很容易出现并发混乱、重复执行或者上下文错乱的问题。具体执行流程如下图:

当消息进入这一层后,系统会调用 runReplyAgent把消息放进处理队列,并标记当前这条消息为“正在处理”。

与此同时,还会执行一个叫 runMemoryFlushIfNeeded 的操作,在必要的时候自动保存当前会话的记忆状态。因为在长时间对话过程中,系统需要不断叠加历史会话信息,如果不及时控制长度,就会撑爆上下文,因此需要在会话信息达到上下文窗口上限时进行自动压缩,持久化到记忆文件中。这对应的就是 OpenClaw 的记忆机制。

当这些准备完成之后,系统才会进入真正的 AI 调用流程,也就是 runAgentTurnWithFallback。从名字就能看出来,这一层不仅负责调用模型,还包含了一套失败重试机制。如果模型调用失败,例如网络问题或者服务异常,系统不会立刻终止,而是会尝试重新执行。这一点在生产环境里非常重要,因为 AI 服务并不总是稳定的。

当模型成功生成回复之后,系统会通过 buildReplyPayloads 把结果整理成适合当前渠道发送的格式。随后系统会记录本次运行的使用情况,通过 persistRunSessionUsage 保存当前调用的 token 数量、模型类型等信息。紧接着系统还会通过 estimateUsageCost 计算本次推理的大致成本。对于企业级系统来说,这一步非常重要,因为 AI 调用的成本通常与 token 使用量直接相关,如果不做记录和统计,很难进行后续的成本管理。

在这些数据记录完成后,系统还会触发 emitDiagnosticEvent 上报诊断信息。这些信息通常会被发送到监控系统,用来观察当前 Agent 的运行状态,例如是否出现异常、运行耗时是多少等等。这样一来,系统运维人员就可以随时了解 AI 系统的健康状况。

接下来还要通过 typing.markRunComplete()关闭“正在输入…”的提示,让用户知道回复已经准备好了。再执行 finalizeWithFollowup,把当前回复返回给用户,同时检查是否还有后续消息需要处理。如果队列中还有新的任务,系统会继续调用 runFollowupTurn,开始处理队列下一条消息。这样整个系统就形成了一个稳定的循环机制。

如果继续用餐厅来类比,这一步就像后厨有一个专门的排单系统。每一张菜单都会被挂在厨房的订单板上,厨师按照顺序一道一道地完成。菜做好之后,系统会记录今天用了多少食材、成本是多少,如果后面还有新的订单,厨房就继续处理下一张。

第七步:主厨就位,准备烹饪

前面的系统已经完成了排队、状态标记、记忆保存和调度准备,现在轮到真正负责执行这次任务的那一层开始接手。但要注意,这一步依然不是模型已经开始推理,而是做执行准备和容错布置。

这部分源码主要在 auto-reply/reply/agent-runner-execution.ts 中,核心入口来自上一步调用的runAgentTurnWithFallback。从这个函数名能猜到,这一层的核心任务,是在真正调用模型之前,先把整次 AI 执行包装成一个可重试、可切换、可恢复的运行过程。如果继续用餐厅来打比方,第七步是主厨终于走到灶台前,一手拿锅铲、一手拿灭火器,确保等会儿做菜时别出事。如下图中间部分所示:

具体来看,系统会先通过 crypto.randomUUID 给本次运行生成一个唯一标识,再通过 registerAgentRunContext 注册这次 AI 调用的上下文。你可以把它理解成后厨在正式开工前,先给这张订单贴上工单编号,并在后厨管理板上记一笔:这道菜是谁做、什么时候开始做、走的是哪条处理链路。这样做的好处,是后面一旦出问题,系统知道该去追哪一次运行,而不是一团乱账。

接着会进入一个循环执行框架,目的是在模型调用失败时,系统不会立刻放弃,而是会按照预设策略决定要不要重试、要不要降级、要不要换后端。

在真正选定执行路径之前,系统还会先做一次 normalizeStreamingText。这个动作可以理解成对输入输出环境做一轮清理,把一些无意义的跳标、静默内容、空文本或者不适合继续往后传递的内容先处理掉。像是主厨在点火前,先把台面擦干净,把锅里不该有的杂质清掉,保证真正开始执行时,灶台是干净的。

随后才会进入 runWithModelFallback。系统会先判断:这次是不是要走 CLI 模式。如果是 CLI 模式,就会进入 runCliAgent,把已经准备好的 prompt 交给外部 CLI 后端去执行,比如 Claude Code、Codex CLI、Gemini CLI 这种已经有自己 session 和工具体系的外部执行器。这种情况下,OpenClaw 更像是总控台,负责把任务转交给外部厨师。

如果不是 CLI 模式,那么系统就会走 runEmbeddedPiAgent,进入嵌入式 Agent 模式。这也是 OpenClaw 默认的主路径。这里会把前面几步准备好的会话、模型配置、运行参数继续往下传。注意,这里仍然是把主厨、锅、火、菜谱全部摆好,而不是最终的那一下下锅。

在这个过程中,系统还会随时检查返回结果里有没有异常信号,比如会话是不是坏了、上下文是不是溢出了、顺序有没有出错。如果发现问题,系统会在这里先决定要不要重置会话、要不要提示异常、要不要提前终止。如果发现这些基础条件不对,就先处理基础设施,而不是硬着头皮开炒。

最后,这一层还会统一捕获执行过程中的各类异常,并做分类处理。临时网络错误,可能会重试;会话损坏,可能会重建会话;上下文溢出,可能会清理后再继续;其他问题,则会转成一个可返回的提示结果。

等这些都走完之后,系统才会把一个标准化的结果对象封装出来,里面会说明这次准备之后要用哪个模型、是否触发了过滤、当前块 ID 是多少、是否已经完成这轮准备等信息。

第八步:按菜谱正式下锅,执行单次 AI 调用

前一步里,主厨已经完成了最后的开炒准备:确认走哪条执行路径、决定是不是用 CLI、是不是走嵌入式 Agent、这次运行有没有备用方案、出错后如何重试。换句话说,锅、火、食材、菜谱、备用灶都已经确认完毕。接下来,真正负责做菜的,就是 agents/pi-embedded-runner/run.ts 这一层的 runEmbeddedPiAgent。如下图所示:

runEmbeddedPiAgent会执行这么几项操作:

第一件事是 resolveSessionLane,确认当前这次调用应该遵守哪条排队规则,是会话级的顺序,还是更高一层的全局级顺序。你可以把它理解成后厨先确认,这道菜是这个包间自己的单独队列,还是全厨房统一调度的一部分。因为不同类型的任务,排队和资源占用规则可能不一样。

接着会执行 resolvedToolResultFormat,也就是根据当前消息来自哪个渠道,确定后面输出内容应该整理成什么格式。不同平台的消息能力不一样,有的适合纯文本,有的支持块状内容,有的对附件、引用、分段有不同要求。在真正调用模型前,就已经要想清楚:这道菜最后是装盘上桌,还是打包外卖,还是分成几小份依次端出去。

随后,系统会进入 resolveModel,把当前要使用的模型细节补齐,包括模型名称、上下文窗口、API 地址、调用方式、默认参数等。类似让厨师把用哪套灶具、哪种调味标准、哪口锅,全都落实清楚。

再往下,系统会找到这次调用可用的身份凭据。源码里的处理方式并不是只认一个固定 key,而是会遍历一组认证配置候选项,优先尝试第一个可用的 API Key。如果当前认证失败,就记录错误,再尝试切换到下一个认证配置。

认证通过后,系统会进入一个内部循环,这个循环会通过 runEmbeddedAttempt进行单次 AI 调用尝试,把 prompt、会话历史、工具环境、模型参数、认证信息、工作目录,全都交给底层 Agent runtime,正式向模型发起请求。

当然,这个单次调用并不是发出请求就结束了,调用完成后,系统会立刻解析结果,统计本次用了多少 token、有没有异常、工具调用有没有报错。就像记账单,把今天用了多少食材、花了多少成本、哪里出了问题一起记下来。

如果发现问题,比如上下文溢出、prompt 提交异常、AI API 返回错误,这些都会在这里被识别和分类。等都处理完后,系统会开始构建最终的回复内容,包括 payload、meta 信息、工具调用结果,以及当前 API Key 是否已经被验证为可用等状态。让上一层拿到的是一个已经整理过、可继续流转的标准结果对象。

最后一步,系统会执行 process.chdir(prevCwd),把工作目录恢复回去,避免影响后续调用。

第九步:真正开炒,模型开始流式生成

这一层对应的源码在 agents/pi-embedded-runner/run/attempt.ts,核心函数就是 runEmbeddedAttempt。

在这一层里,OpenClaw 不再只是准备环境,而是正式进入 Agent 循环,并真正向大模型发起请求。如下图所示:

在进入这一步后,系统首先会准备完整的运行环境。函数一开始会通过 resolveUserPath 和 resolveSandboxContext 确定当前 Agent 的工作目录以及沙箱环境,确保后面读取文件、执行命令或者生成临时文件等操作,都在一个受控的 workspace 中进行。

接下来系统会加载当前 Agent 可用的技能与上下文文件,例如 loadWorkspaceSkillEntries 和 resolveBootstrapContextForRun。这些步骤会把当前项目中的各种能力加载进来,比如技能文件、上下文说明文档或者一些初始化配置。对于 Agent 来说,这些内容就像是一本厚厚的厨房手册,里面写着这家餐厅的做菜规则、特色菜谱以及各种操作说明。

随后系统会执行 createOpenClawCodingTools。这一步会创建 Agent 可用的所有工具,例如读取文件、写入文件、搜索代码、执行命令等等。图右侧的工具列表就是这一层准备出来的。

值得注意的是,这些工具有一部分是 OpenClaw 自己实现的,例如 exec、process、apply_patch 等;也有很多是来自第三方资源包,比如 Pi 的 pi-coding-agent 提供的 read、write、edit、glob、grep 等工具。也就是说,当我们看到 Agent 能读代码、改文件或者执行命令时,其实背后是多个工具系统共同提供能力,而不是 OpenClaw 单独完成的。

当工具环境准备完成后,系统进入 buildEmbeddedSystemPrompt,以及后续的 buildAgentSystemPrompt生成系统提示词。这一层会读取大量运行时信息,并把这些内容拼接成一段非常长的 system prompt(系统提示词)。比如当前的工作目录、运行环境、用户时间和时区、当前可用工具、系统规则、上下文文件以及推理配置等等。所有这些信息都会被写进系统提示词中,然后一起发给模型。

这一步其实是 OpenClaw 能表现得“很聪明”的一个核心原因。因为模型在开始推理之前,已经拿到了非常完整的上下文环境。比如它不仅知道用户刚刚说了什么,还知道当前项目里有哪些文件、有哪些工具可以用、当前系统允许它做什么、不允许它做什么。换句话说,最后传给模型的东西,绝不是“用户一句话 + 一个角色提示词”这么简单,而是一整包运行时环境。就像主厨不是拿到“炒个青椒肉丝”就开始做,而是同时知道今天厨房里有哪些原料、哪些灶能用、客人有什么忌口、后厨有哪些规矩、这桌是不是会员、上道菜吃到哪一步。

但这一点其实也是把双刃剑。因为在很多情况下,这个系统提示词会非常长。系统不仅会注入技能文档,还可能把项目中的一些说明文件直接加载进来,例如 README.md、ARCHITECTURE.md、SOUL.md 等文档,甚至还包括每日生成的记忆文件 memory/*.md。当这些内容一起进入上下文窗口时,模型虽然获得了更完整的环境信息,但同时也会消耗大量 token。而且如果上下文过长,反而可能导致模型的注意力被稀释,甚至触发上下文窗口溢出的问题。

构建好系统提示词后,系统会直接 import Pi 的 createAgentSession 创建一个 Agent 会话,并把这些工具、上下文和规则绑定到当前 session 中。随后通过 applySystemPromptOverrideToSession,把刚刚生成的 system prompt 固定在这次会话中。到这里为止,整个 Agent 的运行环境才算真正准备完成。

这一步里有一个非常重要、但很多人并不知道的事实:真正调用大模型的代码,其实并不在 OpenClaw 里,而是在一个第三方依赖包——Pi SDK 中完成的(这里依赖的包包括 pi-ai、pi-agent-core、pi-coding-agent、pi-tui)。

OpenClaw 在这里更像是厨房的调度系统,它负责准备好食材、环境和菜谱,然后把整套材料交给一个已经训练好的厨师团队去执行。而这个厨师团队,就是 Pi SDK。你在图中绿色部分看到的那一块,就是 Pi 负责的执行逻辑。

代码中会通过 activeSession.agent.streamFn 建立一个流式请求函数,然后调用 activeSession.prompt(effectivePrompt),真正向大模型发送请求。在这个过程中,Pi SDK 会负责管理整个 Agent 循环,包括模型生成文本、触发工具调用、接收工具结果、再继续生成下一轮内容等。

也就是说,OpenClaw 并不直接写 HTTP 请求去调用模型,而是通过 Pi 提供的 Agent Runtime 来完成真正的模型调用与循环推理。这个点特别重要,因为很多人会误以为 OpenClaw 的智能主循环完全是自己手写的,实际上它更像是在 Pi runtime 外面,套上了自己的渠道接入、工具注入、system prompt 拼装、会话管理和交付层。

最后,由于是流式生成,模型不会一次性返回完整答案,而是逐段输出内容。系统会在 session 建好之后,通过 subscribeEmbeddedPiSession 监听message_start、message_update、message_end、工具执行开始和结束、turn 开始结束、agent 开始结束、auto_compaction 等事件。然后在流式输出过程中,触发 onPartialReply、onBlockReply 这类回调,把模型刚生成的一段内容,尽快往前台渠道送。

你在聊天窗口看到的文字一点点往外蹦的打字机输出效果,就是后端已经搭好的事件订阅和块级流式分发机制。餐厅类比就是,厨师菜还没全部出完,传菜员已经在灶台边等着,炒好一盘先端一盘走,不等整桌全部齐活。

而如果模型触发了工具调用,系统也会执行对应工具,然后把工具返回结果再写回会话历史,继续下一轮推理。这种循环过程,就是典型的 Agent Loop。

第十步:传菜上桌,回复最终送达用户

如果说第九步是后厨完成了整道菜的烹饪过程,那么第十步就是把做好的菜端到客人面前。

这一层不再涉及模型推理,也不再涉及 Agent 的内部循环,而是负责把前面生成好的结果整理好,并通过对应的消息渠道发送回用户。

在源码中,这一步主要发生在 auto-reply/monitor/process-message.ts 中。这里的核心逻辑是 dispatchReplyWithBufferedBlockDispatcher:接收 AI 已经生成好的回复,然后把这些内容按合适的方式发送回聊天平台。如下图左上部分:

之所以需要这一层,是因为在第九步中,模型的输出是以流式的方式逐段返回。系统会不断接收到新的文本片段,或者工具调用结果,然后再逐步拼接成完整回复。因此在真正发送给用户之前,需要有一个统一的处理层,负责把这些碎片整理好,确保最终输出符合当前平台的消息格式。

整个过程通常从 replyResolver 开始,接收来自 Agent 运行层的结果,包括文本内容、工具调用信息、块 ID 等数据。然后系统会把这些内容转换成最终可发送的回复结构,例如如果消息需要分段发送,或者包含不同类型的内容块,这一步都会统一处理。

随后系统会调用 deliverWebReply,根据当前使用的渠道,比如 WhatsApp、Telegram 或其他 IM 平台,系统会调用对应的接口,把整理好的回复内容推送给用户。对于用户来说,这一步就是聊天窗口里出现的那段回复。

如果用餐厅来比喻,这一步就像服务员把后厨刚刚做好的菜端出来。客人并不会看到后厨的排队、备菜、翻锅、调味这些过程,只会看到一道已经完成的菜摆在桌上。而服务员的工作,就是确保这道菜准确地送到正确的桌子上。

同时,在流式回复的场景下,这个“传菜过程”也可能是分批完成的。就像有些餐厅会先上前菜,再上主菜,最后上甜点。系统在接收到模型生成的每一段内容后,都可以立即通过消息通道发送出去。因此用户在聊天窗口中看到的回复,往往是一段一段逐渐出现的,而不是等待很久之后才一次性显示完整答案。

当所有内容发送完成后,这一轮消息处理流程就正式结束了。整个系统也会回到监听状态,等待下一条用户消息的到来。至此,一条用户消息从进入 OpenClaw,到经过网关监听、消息接入、路由决策、会话初始化、任务排队、执行准备、Agent 推理,再到最终回复发送,就完成了完整的一次运行闭环。

整个过程其实就像是一家组织良好的厨房:前台接单、领班分单、后厨备菜、主厨掌勺、服务员传菜。每一层都有明确分工,而 OpenClaw 的代码结构,本质上就是把这套流程用软件的方式重新实现了一遍。

最终完整的十步流程如下图所示:(关注我的公众号,回复:龙虾源码,获取完整原图文件)

我对 OpenClaw 的四个更底层的认知

这次源码之旅,不仅让我搞懂了流程,更让我洞察到了几个 OpenClaw 设计上的“第一性原理”。这些细节,如果你只是看一些部署和使用教程,是绝对发现不了的:

1、OpenClaw 的核心竞争力,是它的上下文工程

这是我看完代码后最强烈的感受。

大家平时最爱讨论的是:装哪个模型、接哪些 Skill、能不能配十个龙虾专家。但从系统结构看,OpenClaw 真正最值钱的地方,其实是它怎么把用户消息嵌进一个完整运行时:路由到哪个 agent、关联哪段 session history、加载哪些 tools、拼上哪些 skills、带上哪些 runtime metadata、加入哪些 workspace/context files、遵守哪些 channel rules。

也正因为这样,它给人的感觉是更懂你,更懂场景、更理解你的意图,乃至懂得主动行动完成任务。这背后不是说AI具备了人的意识,也没到AGI时代提前到来那么夸张,而是靠充分的上下文装配实现了一种“智能增值”。

2、OpenClaw 其实是个“二道贩子”

很多人以为 OpenClaw 内部实现了复杂的模型调用逻辑。但看了源码才发现,在最核心的 attempt.ts 里,它其实是把球踢给了外部 SDK —— Pi。

OpenClaw 本身并不生产智能,它更像是一个极其出色的包工头。它负责把脏活累活(鉴权、文件读写、工具调用、上下文管理)都干完了,最后把整理得干干净净的数据喂给 SDK。

这让我意识到: 未来的 AI 应用开发,核心竞争力不在于你会调多少模型 API,而在于你能把信息的前后处理做到多极致。当然,也许未来更多Agent的能力会内嵌到模型中,但应用前后的准备工作也是必不可少的。

3、OpenClaw 为我们展示了什么叫“把提示词写漂亮”

很多人谈 Agent,还停留在设定角色、给几个规则,再加一点历史对话的水平。

但 OpenClaw 的 system prompt 做的事情,是把工具能力、调用风格、安全边界、Workspace、Sandbox、Docs、Skills、Memory、Reply tags、Voice 等都显式地翻译进 prompt 中。

这个思路很工程化,也很现实。因为大模型不是天然懂你,它只是被喂了一个尽可能完整的世界状态。

之前听过一个说法:智能的本质,是上下文工程的暴力美学。看了 OpenClaw 源码后,对这句话又有了更深层次的理解。

同时也给正在养虾的各位提个醒:大家在写 AGENTS.md、SOUL.md、IDENTITY.md、USER.md、各个技能的SKILL.md,以及其他自定义规则时,千万别写太长太啰嗦。你写的每一个字,都在消耗 Token,都在挤占大模型有限的注意力窗口。写得越杂,越会带来成本、延迟、注意力稀释和溢出风险。

这也是为什么在 OpenClaw 的源码中设计了大量 history limiting、auto-compaction、context window guard、context pruning 这些“内存管理与垃圾回收”系统,确保 AI 在有限的上下文窗口内能长期、稳定地运行,不会因为聊得太久把“脑子”撑爆。

4、OpenClaw 表面上是 AI 产品,底层更像一个本地消息操作系统

你如果只从用户界面看,会觉得它像一个会聊天、会调用工具的机器人。但从架构看,它更是一个本地优先的消息操作系统:前面接很多渠道,里面有统一控制平面,下面挂 agent runtime、tools、nodes、session store、skills、web UI、CLI、memory。

其实这个项目的 README 已经把这个意思写得很明显了:Gateway 才是 control plane,assistant 只是它呈现出来的产品形态。你真正该把它理解成什么?是一套把消息、工具、上下文和执行能力统一调度起来的AI操作系统。写在最后

这次我看 OpenClaw 源码最大的收获,是更清楚地了解到,一个真正能用的 Agent,不光要考虑模型能力,更要关注模型前的上下文工程有没有搭好,模型后那层执行与交付机制有没有接稳。

OpenClaw 能火,不只是因为它能接各种 IM 渠道,它有很多 Skill 能24小时不停干活儿。更重要的是,它把消息入口、Agent 运行、工具调用、会话持久化、失败恢复、流式交付这些本来分散,但又不可或缺的东西,串成了一条完整的链,给我们展示了如何用工程化的手段,去弥补当前大模型能力的不足。

所以,如果你真想学 OpenClaw,或者想借鉴它做自己的 Agent 系统,最值得研究的,可能不是“怎么装十个专家”,而是这条链路背后的工程方法。

因为那才是本质。

本文由人人都是产品经理作者【申悦】,微信公众号:【互联网悦读笔记】,原创/授权 发布于人人都是产品经理,未经许可,禁止转载。

题图来自Unsplash,基于 CC0 协议。

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
评论
评论请登录
  1. 目前还没评论,等你发挥!