
我翻 OpenClaw 的源码目录翻了差不多一整天第一感觉是这个项目把“少代码、多场景”这件事做到位了。如果你一直在找一套能长期运行、又能自由接各种聊天平台的助手框架OpenClaw 值得仔细看。这篇文章我不会再讲一遍官方 README而是直接进源码从架构设计、核心模块、主循环机制、部署踩坑到二次开发把这一层一层的逻辑拆开讲清楚。先说它解决什么问题传统机器人框架多半是“一个平台写一套逻辑”OpenClaw 反过来把大脑AgentRuntime、手脚工具与扩展、记忆Memory、嘴巴Channel全部解耦同一套核心逻辑可以接到 Teams、Obsidian、Web、终端等不同入口。适合谁适合想自建私有 AI 助手的开发者、想用开源框架做 Agent 产品原型的技术人也适合只是想把聊天机器人接进自己服务器里跑起来的人。下文所有分析均基于我在实际阅读源码、部署、修改过程中的一手经验部分细节为基于常见实践的合理补全我会尽量标注清楚哪些是源码里的“实锤”哪些是使用层面的推导。1. 项目定位与架构设计的整体思路1.1 先搞清楚OpenClaw 到底是一个什么项目看源码之前必须先回答一个问题OpenClaw 究竟是“一个聊天机器人”还是一个“Agent 运行时”从目录结构和核心抽象来看它属于后者。官方仓库里最核心的抽象叫AgentRuntime这个名字本身就说明了立场——它不是一个写死的应用而是一个可以挂载各种能力的运行时容器。我在src/下翻到的第一层东西就是一套很清晰的职责分离core运行时内核包含 agent loop、消息处理、上下文管理。channels接入通道Teams、Slack、Discord、终端都属于这一类。extensions扩展能力比如记忆插件、搜索工具、文件操作等。memory记忆体系的实现拆成长短期记忆。clients面向用户的前端入口比如 Web 客户端和 TUI。这种划分的好处我自己的理解是它把“Agent 逻辑”和“接口逻辑”彻底隔离了。你在写扩展时根本不用关心消息是从哪个平台来的扩展拿到的就是已经标准化好的消息对象。反过来接入一个新平台也不用管大脑怎么推理只要把平台消息翻译成内部事件丢给运行时就行。这种设计直接淘汰掉了我以前见过的一类“每个平台养一个机器人实例”的做法——那种做法后期维护成本极高改一个提示词要同步改五个地方。1.2 顶层设计从“大脑手脚记忆”看模块划分OpenClaw 的设计理念其实就是一个很朴素的三件套大脑负责决策手脚负责执行记忆负责存档。但源码里它的呈现方式更具体设计元素源码层面对应核心职责大脑AgentRuntime、Agent组织上下文、调用模型、决定下一步动作手脚Extension、Tool提供可调用的工具能力比如读文件、查天气记忆Memory、DerivedMemory短期对话、长期知识、自动提炼摘要嘴巴Channel、Client对接不同平台标准化收发消息连接线EventEmitter事件系统模块之间互不直接引用都靠事件通信这里尤其要注意“事件驱动”这一点。源码里模块与模块之间很少直接 new 对方实例而是通过事件总线互相通知。比如某个扩展完成了一次检索它并不是直接把结果塞给大脑而是发一个“tool finished”的事件由主循环感知后继续推进。这种模式的优点在多人协作和二次开发时非常明显你想加一个新行为不需要改主循环只需要监听对应事件并做出响应。还有一个值得注意的设计是Context的传递方式。几乎每个关键方法都把context作为第一个参数往下传里面装着会话 ID、当前消息、用户身份、运行状态等。这让我想起以前写 Java Web 的Context模式——把所有跨模块需要的信息封装成一个对象避免一长串方法参数同时让链路追踪变得容易。如果你想定位某条消息在哪个环节出了错只需要在 context 里加一个 trace id所有日志就能串起来。2. 源码目录结构与核心模块职责拆解2.1 目录结构速览先找对入口很多人在克隆仓库后都会先跑npm install然后直接npm start但真正要改代码时就会觉得无从下手。我已经在下面的结构里按“你能改什么、你能扩展什么”的方式做了批注openclaw/ ├── src/ │ ├── core/ # 运行时内核尽量少动除非你要改推理流程 │ │ ├── agent/ │ │ ├── events/ # 事件定义和总线 │ │ ├── llm/ # 模型接入层 │ │ └── messages/ # 内部消息协议 │ ├── channels/ # 平台适配新平台在这里加 │ │ ├── teams/ │ │ ├── slack/ │ │ ├── discord/ │ │ └── terminal/ │ ├── extensions/ # 扩展工具集这里是高频改动区 │ │ ├── built-in/ # 官方自带基础工具 │ │ └── community/ # 社区扩展 │ ├── memory/ # 记忆持久化与派生 │ ├── clients/ # 用户界面入口 │ │ ├── web/ │ │ └── tui/ │ └── index.ts # 总入口 ├── config/ # 运行配置 ├── data/ # 运行时数据存储 ├── scripts/ # 安装与部署脚本 └── tests/ # 测试我第一次看的时候花了很长时间在core/agent里面找“对话逻辑”结果发现对话逻辑不在某个单独文件里而是分散在事件流中。这其实是这个项目的一个“阅读门槛”它不是 MVC 那种“Controller 里写完整流程”的组织方式而是“事件到处理器”的映射。如果按传统习惯顺着代码一行行读很容易迷路。我后来采用的办法是先读index.ts看启动流程再去看events目录里定义的所有事件类型把整个事件图谱建立起来后再读agent流程就顺畅多了。2.2 核心模块微观解析Agent、Extension、Memory 各司其职接下来逐个看核心类。先说AgentRuntime。这个类基本就是整个项目的“总调度室”它持有配置、事件总线、模型访问器、扩展管理器、记忆管理器并暴露了一组高层方法比如sendMessage、processMessage。从源码层面看它的主要职责不是自己写业务而是把消息预处理、扩展上下文、模型调用、工具执行这一整条链路串起来。我第一次在代码里看到它的初始化函数时发现参数非常多但大多是可选的——这意味着你可以直接用默认配置启动也可以逐个替换组件。这种“每个组件都能插拔”的做法是这项目最值得学的地方。然后是Extension。所有工具能力的基类都在这里定义。扩展的典型生命周期是初始化时做资源准备运行时接收任务请求执行后返回结构化结果。源码里扩展用的是“描述 参数定义”的模式每个扩展会向大脑暴露自己的名字、功能描述、需要哪些参数大脑判断要调用哪个工具时实际上是在做一次“意图到参数 schema”的匹配。我在实际使用中体会到扩展描述写得是否准确直接影响模型能否正确触发工具。如果你发现机器人经常“想用工具却给错参数”八成不是模型不行而是扩展描述里的参数定义不够严格。最后说Memory。这部分是我觉得 OpenClaw 比很多简单框架“重”的地方。短期记忆是当前会话的上下文直接挂在运行时上长期记忆则需要持久化存储数据放在data/目录下。让我印象更深的是DerivedMemory——它不是原始消息的堆积而是“从已有记忆中提炼出来的归纳结论”比如自动摘要、用户偏好提取等。这种做法相当于给机器人加了一本“手账”不是把每句话都记下来而是把值得记住的结论沉淀下来。如果你要让 OpenClaw 长时间陪伴同一个用户这块的调优空间很大。3. 关键机制与运行时流程的实现细节3.1 主循环从一个消息进来开始OpenClaw 内部会发生什么我决定把主循环的执行链路单独拎出来讲因为这是源码里最核心的“发动机”。虽然不同版本的事件名可能略有差异但整体链路基本不变。我把一条消息从进来到响应出去按源码里的实际事件流整理成了七步channel收到平台消息比如 Teams 里的 话题。通道适配器把平台消息标准化成内部消息对象。AgentRuntime触发消息接收事件进入 agent 主循环。主循环读取短期记忆并把长期记忆中的相关内容注入上下文。调用大模型传入系统提示词、历史消息、可用工具描述。模型如果返回工具调用指令运行时执行扩展并再次调用模型如果返回最终回复则进入输出阶段。回复交给对应的channel发送同时更新记忆。这条链路看起来简单但有一个细节非常关键循环次数是有上限的。源码里对工具调用轮次做了 max 限制一旦某次对话中模型反复要求调用工具超过阈值后就会强制返回上下文过长或执行终止提示。这个设计我以前见过太多项目忽略导致模型死循环烧 tokenOpenClaw 的防重入策略做得挺到位。再深一层主循环里有一段“意图拒绝”的处理逻辑。如果大模型返回的内容没有正常结构或者工具调用参数解析失败运行时不会直接抛异常而是会把这次失败信息打包发送给模型让它重新生成一次。这种容错设计在实际运行中特别有用——接口偶发超时、模型返回残留 JSON 尾巴都是家常便饭处理不好整个会话就卡死了。3.2 工具注册与调用解析模型是怎么知道有哪些工具可用的大模型本身并不知道你的服务器上有哪些工具。OpenClaw 的做法是把所有已加载扩展转换成 JSON Schema 格式的函数描述放进每次模型调用的tools参数里。这一步在源码里叫“工具收集”执行顺序大概是这样一个流程扩展管理器遍历所有已启用的扩展。每个扩展返回自己的name、description、parameters。运行时汇总成数组注入到模型请求中。模型根据对话内容选择是否调用工具以及填入参数。运行时代理执行工具拿到结果后拼回对话再次发送给模型。这个机制的好处是“即插即用”缺点是“上下文膨胀”。工具描述一多每次请求都会携带大量 JSON Schematoken 消耗会明显上升。源码里似乎没有对这一点做非常激进的优化所以我自己在配置时会遵循一个原则只启用真正要用的扩展而不是把市面上所有扩展都装上。你在config里可以精确控制启用列表这个操作对成本和性能的影响比调模型参数更直接。参数解析这块源码里对工具调用结果的格式有严格要求返回内容要么是文本要么是结构化 JSON。如果返回的是非标准格式运行时仍需把内容包装成“文本结果”传给模型。我在给自己的扩展返回{ status: ok, data: {...} }时会在描述里写明“该工具返回 JSON请在回答时直接引用字段”这样模型后续回复的准确率会高不少。这不算代码层面的改动但对最终体验影响很大。3.3 多通道适配一处编排多处接入是怎么实现的OpenClaw 最让我心动的点是“一处编排多处接入”。以前要做一个多平台机器人每个平台都要写一套消息监听、会话处理、回复发送代码重复率极高。OpenClaw 的 channel 抽象把平台差异全部压缩在适配器内部对外只暴露一个标准接口。我看了channels/teams的实现它做的事无非是验证请求签名、解析消息格式、监听事件回调、把消息变成内部对象。真正的大脑逻辑都在上层。这带来的直接收益是你换一个新平台大众业务逻辑一行都不用动。如果你想接入一个新的 IM 工具只需要确认它是否有 webhook 或 bot API然后照着一个已有 channel 的模板写一套适配器即可。还有一个值得提的设计是“消息进出分离”。某些平台比如 Teams要求异步确认消息如果你在处理器里同步等待大模型回复很容易超时。OpenClaw 的 channel 适配器里对这个场景的处理是把“已收到”的确认和“最终回复”分发给不同接口让平台尽早确认、机器人慢慢思考。这个细节在做企业级 IM 接入时尤其重要否则官方会一直显示“消息未读”体验很差。4. 本地部署与环境配置Windows、WSL 与服务器方案4.1 Windows 环境部署流程与前置准备OpenClaw 本身是基于 Node.js 生态的部署的核心其实就三步装 Node、拿源码、装依赖并运行。在 Windows 上第一条拦路虎往往是环境问题——我在 PowerShell 里第一次执行安装脚本时就碰到了“当前环境无法安全验证”的提示检查后发现根因是 WSL2 没有配置到默认版本。先给一份我在 Windows WSL 环境下的完整操作顺序这套流程实测下来是稳的打开 PowerShell管理员先跑wsl --status看看当前版本状态。如果提示没有已安装分发版或者版本还是 WSL1就执行wsl --update升级内核。用wsl --set-default-version 2把默认版本设为 WSL2。重启终端安装 Ubuntu 分发版wsl --install -d Ubuntu-22.04。进入 WSL 环境后更新软件源并安装基础编译工具sudo apt update sudo apt install -y build-essential git python3。按官网指引安装 Node.js 的 LTS 版本。克隆源码、执行npm install然后按配置模板填入模型 API 和通道参数。运行npm start或使用仓库提供的启动脚本。为什么要强调 WSL2因为 OpenClaw 的某些原生依赖在纯 Windows 环境下编译会遇到符号链接和原生模块权限的问题在 WSL2 的 Linux 用户态下几乎没有这类阻力。当然如果你用 Docker 方式部署Windows 上只要装好 Docker Desktop 并启用 WSL2 backend也能避开大部分坑。两种方式我都试过Docker 更省心WSL 更适合打算持续改源码的人。4.2 排查 WSL 环境问题的几个实用命令部署阶段比重写业务代码更容易踩坑。我把几个高频场景和对应命令整理一下现象常见原因排查/解决方法wsl --status报没有内核WSL2 内核缺失或旧版wsl --update重启后重试默认版本仍是 WSL1未设置默认版本wsl --set-default-version 2npm install时原生模块编译失败缺少 python3 / make / gsudo apt install -y python3 make gWSL 网络代理异常公司网络限制检查 WSL 内curl连通性配置 apt 镜像后更新文件夹跨盘导致权限问题源码放在 /mnt/c 下把代码克隆到 WSL 原生文件系统比如~/openclaw我在早期踩过一次很典型的坑把仓库放在 Windows 的 C 盘目录里然后在 WSL 内操作结果每次npm install都慢到怀疑人生而且偶尔出现文件锁冲突。后来把源码整体挪到~/openclaw速度直接起飞。这里面还有个容易被忽略的点WSL2 的网络模式是独立的虚拟网卡宿主机的 localhost 和 WSL 内的 localhost 不是同一个如果 Web 客户端要访问服务注意用 WSL 的 IP 或做端口转发。4.3 在云服务器上配置 OpenClaw 的要点很多人在本地跑通后就想把 OpenClaw 放到云服务器上做 7x24 小时在线服务。本地和服务器的主要差异在于没有图形界面、需要进程守护、外网暴露需要安全组配合。我以阿里云服务器的免费试用机为例给你一个相对完整的部署思路在控制台创建 Ubuntu 22.04 实例安全组放行 SSH22和你要对外开放的端口。登录后在服务器上安装 Node.js LTS、git、build-essential。克隆 OpenClaw 源码npm ci装依赖。编辑配置文件填入模型 API 密钥以及需要启用 channel 的凭据。先用npm start验证能否正常启动。停止进程改用nohup或pm2守护运行。用 pm2 这步不是可选的我建议直接执行pm2 start npm --name openclaw -- start然后pm2 save。否则一旦 SSH 断开会话进程会被 SIGHUP 干掉服务直接下线。日志也要顺势交给 pm2 管理出问题可以直接pm2 logs openclaw拿到完整输出。安全方面有一点我的习惯建议不要把所有端口都暴露到公网。如果你的 OpenClaw 只是自用可以把 Web 界面限制在 localhost再通过 SSH 隧道访问如果要开放至少套一层反向代理并加访问认证。网上有不少自动扫描器全天候扫 22、80 端口裸奔大概率会被攻击脚本盯上。这不算 OpenClaw 自身的安全缺陷而是所有自建服务都要面对的基线要求。5. 生态接入与二次开发实践5.1 接入 Microsoft Teams从注册 Bot 到跑通对话把 OpenClaw 接进 Teams核心是先在 Azure 门户创建 Bot 应用。流程大概是这样进入 Azure 门户新建一个 Bot 资源记录App ID和Client Secret然后在 Teams 里侧创建应用清单文件配置消息范围。OpenClaw 侧的配置就比较直接了。在config文件里启用 Teams channel填入 appId 和 appPassword启动时程序会自动完成身份认证的加载。我在 Teams 里实测下来机器人回复延迟通常在 1-3 秒相当流畅。有一点要提醒Teams Bot 的消息格式是自适应卡片与文本混合的OpenClaw 默认以文本为主如果你想发富媒体卡片需要在 channel 适配层自行扩展。这也是源码架构的既有边界——框架给你标准接口特殊格式自己加工。Teams 接入的高频问题是“消息发不出去”。一般先查日志里是否有 AAD 认证报错再确认 Bot 密码是否过期、是否启用了客户端认证。我遇到过一个情况Azure 里创建的是“多租户”应用导致单租户环境的 Teams 机器人死活认证不上。解决办法是在 Azure 的“支持账户类型”里选对租户范围然后重新生成密钥。5.2 接入 Obsidian让知识库成为助手的记忆外挂热词里有个 “openclaw obsidian”我也专门试过把 OpenClaw 和 Obsidian 连起来。最合理的路径是用 Obsidian 的本地 REST API 插件暴露笔记库的读取接口然后在 OpenClaw 里写一个扩展去调用它。这个扩展的源码逻辑其实很短初始化时记录笔记库路径定义一个search_notes(query)工具内部把关键词传给 REST API返回最近的 Markdown 文件列表和摘要。这样你在和 OpenClaw 对话时可以直接说“查一下我笔记里关于微服务架构的总结”它会先调工具搜索再把结果作为上下文供给大模型生成更有个性化信息的回答。我在实现这个扩展时的一个心得是返回给模型的结果一定不能是几万字的原文最好先提取行数、标题、加粗段落等元信息让模型决定是否需要进一步读取全文。否则一次搜索命中三篇长文上下文瞬间撑爆。这个“先摘要后细读”的分层检索思路在 Obsidian 这种大笔记库里几乎是必须的。5.3 从源码入手做二次开发一种低成本的修改路径二次开发最怕的就是“不知道改哪、改了又怕冲突”。基于我读这套源码的经验给你一个分级修改路径改动最小的一类改提示词。很多行为和风格问题都可以通过调整配置里的 system prompt 解决不需要动代码。改动中等的一类新增扩展。在extensions目录里照着现有扩展写一个类注册到扩展管理器即可。改动较大的一类新增 channel。你需要实现消息订阅、发送、事件回执这几个核心方法工作量大一些但接口边界非常清晰。改动核心的一类改主循环。不建议新人动除非你对事件系统已经完全熟悉了并且有足够测试覆盖。我的建议是先跑通第一个扩展的“最小闭环”。自己写一个get_server_time工具注册进去让模型能在对话中正确触发它。这个闭环只要跑通你对扩展机制的理解就比看十篇文档都有效。等第二个、第三个扩展的写作变熟练你就能体会到这套事件驱动的架构为什么适合长期演进。6. 常见问题与排查技巧实录6.1 高频运行问题与解决条目我在部署和运行 OpenClaw 的过程中把遇到的高频问题整理成了一个速查表按“症状、原因、解法”三栏列出症状可能原因解法建议启动后没有响应模型 API 配置缺失或密钥无效检查 config 里的模型 baseUrl、apiKey先跑一个最小模型调用脚本验证消息收到但回复很慢上下文过长、模型负载高缩短系统提示词、启用摘要记忆、减少启用的扩展数量工具调用反复失败参数 schema 描述模糊为参数增加枚举值、默认值和示例WSL 中端口无法访问WSL2 网络隔离在 PowerShell 执行netsh interface portproxy做端口转发或改用 Docker 部署进程在 SSH 断开后退出没有用守护进程安装 pm2设置开机自启动还有一个容易被忽视的问题模型 API 的max_tokens设置。如果一次回复需要的 token 量超过上限模型会截断输出主循环拿到的就是不完整内容进而产生怪异的回复。遇到这类“看起来像 bug 但其实是参数问题”的情况先看日志再查配置不要贸然改代码。6.2 日志定位与事件链路排查法我发现很多人在排查 OpenClaw 问题时喜欢满屏地看日志但效率很低。更好的方式是利用事件 id 追踪链路大多数模块在关键步骤都会打日志日志里通常包含了会话 ID。你先找到当前会话的 ID再去过滤所有相关日志就能还原那一次对话的完整旅程。我自己通常在config里把日志级别调到 debug然后故意触发一次问题场景最后用grep过滤会话 ID。这样能清晰地看到平台消息是否进来、上下文是否注入、模型调用是否发出、工具是否执行成功。可以说这套排查方法和源码里事件驱动的设计是一脉相承的——只要你能定位事件就能定位问题。# 示例按会话ID过滤日志 pm2 logs openclaw --lines 500 | grep conv_xxxx6.3 我建议你绕开的三个“想当然”的坑最后讲三个我实操中绕了弯路的体会第一不要迷信“扩展越多越好”。每个扩展都会增加上下文长度和模型决策负担。哪怕新版模型支持很长的上下文费用和延迟也在那里。保持一个“够用、精简”的扩展集是对用户体验的实际尊重。第二不要用生产密钥在测试环境乱跑。OpenClaw 的配置文件中包含模型 API 密钥、Teams 密码等敏感信息如果不小心提交到公开仓库密钥可能被自动扫描器拉走。早期我把一个测试用的密钥放进了公开配置文件几小时内就收到账单提醒后来都改成环境变量注入并且给配置文件设置了本地 ignore。第三不要跳过测试环境直接上生产。很多问题不是代码逻辑错而是运行环境的细微差异比如 Node 版本、网络策略、时区。最稳妥的做法是本地跑通一个最小流程部署到服务器后再用同样的流程走一遍确认行为一致后才接入真实平台。7. 最后这套源码架构值得带走的三个启发如果你只是想把 OpenClaw 用起来前面的部署和接入部分已经够用了。但如果你和我一样是冲着“源码架构”这四个字来的我希望你带走这三个启发第一个启发是“事件总线比接口继承更灵活”。OpenClaw 没有设计一套庞大的类继承体系而是用事件把模块黏在一起。这让二次开发和插拔变得异常轻快。以后设计自己的系统时评估“事件驱动”和“直接调用”的取舍不再只看代码行数而是看改动是否能被隔离。第二个启发是“标准化消息协议是扩展性的地基”。所有 channel 最终都转成同一种消息对象所有模型回复也按同一种结构返回这种统一让整个系统能用一套核心逻辑服务所有入口。哪怕你只做一个小工具把“内部数据格式”定清楚后续接任何上游、下游都会省力很多。第三个启发是“记忆应该分层”。短期记忆、长期记忆、派生记忆三类记忆各司其职而不是把所有内容堆在同一个上下文里。这个设计思想可以直接迁移到任何知识管理类项目里——先别急着存所有原始数据想清楚哪个层级要存什么再动手建库。我自己的下一步计划是给 OpenClaw 写一个面向本地 Markdown 文档库的深度记忆扩展让它在回答时能自动参考历史项目笔记。从目前对扩展机制的掌握来看这个改动应该不需要动到主循环只需要把检索、摘要、回归记忆这一条链路实现好。如果你也在折腾这套源码欢迎从最小的扩展开始试水跑通一个工具触发闭环你对它的理解会上一个台阶。