源码逐层解剖 · OpenClaw v2026.6.11

OpenClaw 源码架构全解剖

OpenClaw 是一个跑在你自己设备上的「个人 AI 助手」:你在 WhatsApp / Telegram / Discord 等 20+ 个聊天软件里给它发消息,它在你家里的一台机器上思考、执行工具、再把回复发回来。本文基于 2026-07-06 的 main 分支源码(commit 83f0527),逐层拆解一条消息从聊天软件到 agent 再回到聊天软件的全过程。所有结论都标注 文件:行号,可直接对照源码阅读。读过《Claude Code 源码架构全解剖》的读者会发现两者的 agent 内核惊人地相似,而「外壳」完全不同——本文会不时对比两者。

src/ 5328 个源文件 + 3900 个测试 141 个 bundled 扩展 21 个 workspace 包 Gateway 默认端口 18789

00全景架构:一个 Gateway,众星拱月

先建立整体心智模型。Claude Code 是「终端里的一个进程」,OpenClaw 是「一台常驻服务器 + 一群客户端」。

OpenClaw 的核心是一个叫 Gateway(网关)的常驻进程:它监听一个 WebSocket 端口(默认 18789),所有东西——聊天渠道、网页控制台、终端 UI、手机 App、定时任务——都围着它转。你可以把它理解成「你私人的 AI 后端服务器」。产品口号也直说了:「The Gateway is just the control plane — the product is the assistant」(Gateway 只是控制平面,产品是助手本身,README.md)。

① 入口层openclaw.mjs · src/entry.ts · src/cli/run-main.ts

npm bin 启动器(纯 JS、零依赖、带快路径)→ TS 入口(respawn 自我重启机制)→ Commander 命令树(60+ 个子命令)

② Gateway 控制平面src/gateway/server.impl.ts(1869 行)

常驻守护进程:HTTP + WebSocket 服务器、自研 req/res/event 三帧协议(v4)、鉴权、配置热重载、渠道生命周期管理。launchd / systemd / schtasks 三平台可装成系统服务

↓ 入站消息 / ↑ 回复
③ 渠道层(Channels)extensions/telegram · whatsapp · discord …(141 个扩展)

每个聊天平台一个插件:收消息 → 落盘 spool(可靠性)→ 授权闸门(配对/白名单)→ 规范化成统一格式。回程负责格式转换与分块

↓ dispatch / ↑ 流式 block
④ 回复编排层src/channels/turn · src/auto-reply · src/routing

路由(这条消息归哪个 agent、哪个会话)、会话级并发控制(steer / 排队 / 打断)、回复分块流水线

↓ prompt / ↑ 事件流
⑤ Agent 内核packages/agent-core/src/agent-loop.ts · src/agents/

真正的 agentic while 循环:调模型 → 执行工具 → 回填结果 → 再调模型。源自 Mario Zechner 的 pi-mono,已 fork 进 monorepo 改名 @openclaw/agent-core

↓ HTTP/SSE
⑥ 模型层packages/ai(@openclaw/ai)· src/agents/model-fallback.ts

按「API 协议族」注册的流式适配器(Anthropic/OpenAI/Google/Mistral 官方 SDK)+ 认证 profile 轮换 + 五级失败恢复链

横向插入所有层
⑦ 扩展生态src/plugins · src/skills · src/hooks · src/cron · src/node-host

插件(能注册 30+ 种能力)、Skills(SKILL.md)、Hooks、MCP、定时任务(cron)、节点设备(手机/Mac 外设)

0.1 和 Claude Code 的三个根本差异

维度Claude CodeOpenClaw
进程模型用户敲 claude 起一个进程,退出即结束Gateway 常驻 24/7,装成系统服务(launchd/systemd),客户端来来去去
入口面一个终端(Ink 渲染的 REPL)20+ 聊天渠道 + WebUI + TUI + 手机 App + HTTP API,全部收敛到同一个 WS 协议
会话粒度一个目录一个会话按「agent × 渠道 × 聊天对象」路由出的 session key,一个 Gateway 可养多个 agent、并行服务多个群聊

相同点也很本质:两者的心脏都是「一个 while 循环 + 一张工具表」。OpenClaw 的 packages/agent-core/src/agent-loop.ts 与 Claude Code 的 query.ts 在结构上几乎是同构的(第 5 章详述)。

0.2 仓库地图

目录内容
src/核心:gateway、channels、auto-reply、agents、routing、config、plugins、cron、tui……(5328 个非测试 TS 文件)
packages/21 个 workspace 包:agent-core(主循环)、ai(provider 流式运行时)、gateway-protocol(WS 帧 schema)、plugin-sdk
extensions/141 个 bundled 插件:渠道(telegram/whatsapp/discord/imessage…)、模型 provider(anthropic/openai/ollama…)、能力(browser/canvas/memory-core…)
ui/Control UI:Vite + Lit 的单页应用,由 Gateway 直接托管
docs/ · skills/文档站源码;内置 skills

三个最重要的文件:如果只看三个文件,看 packages/agent-core/src/agent-loop.ts(主循环)、src/gateway/server.impl.ts(Gateway 怎么启动和接客)、src/agents/agent-tools.ts(工具怎么装配和被策略过滤)。

血统说明:OpenClaw 的 agent 内核源自 Mario Zechner 的 pi-mono(pi-ai / pi-agent-core),THIRD_PARTY_NOTICES.md 官方声明「Portions of OpenClaw were adapted from Pi / pi-mono」。历史上是外部 npm 依赖(CHANGELOG 可见从 @mariozechner/pi-ai 0.45.7 一路升级到 0.58.0),现已 vendor 进 monorepo 改名 @openclaw/agent-core + @openclaw/ai;只有终端 UI 库 @earendil-works/pi-tui 仍是外部依赖。代码里还留有化石:exec 工具至今读 PI_BASH_YIELD_MS 环境变量兜底(src/agents/bash-tools.exec.ts:1308)。

01一条消息的一生 ★

全文主线。以 Telegram 为例:从你在手机上按下发送,到回复出现在聊天窗口,共 14 站。每一站在后面章节都有放大详解。

1

Telegram 服务器 → 先落盘,再确认

Gateway 里的 Telegram 插件通过长轮询(getUpdates)或 webhook 收到 update。它做的第一件事不是处理,而是把原始 update 写进磁盘 spool(缓冲池):轮询模式下 spool 落盘成功才推进 offset;webhook 模式下 spool 写成功才回 200,失败就让 Telegram 重投。这保证进程崩溃也不丢消息(durable-before-ack,「先持久化再确认」)。

extensions/telegram/src/telegram-ingress-spool.ts:17 · polling-session.ts:610

2

spool 回放进 grammY

处理循环从 spool 认领一条 update(带 30 分钟租约,卡死可被别的进程接管),回放进 grammY(Telegram Bot 框架)的 bot.handleUpdate。处理成功打「墓碑」标记而不是删除——Telegram 可能重发同一条 update,删了会导致副作用重跑。失败则按退避策略重试,绝不吞错

polling-session.ts:610-648 · bot-processing-outcome.ts

3

授权闸门:你是谁,凭什么跟我说话

过滤掉 bot 自己的回声后,进入单一授权闸门(跑在一切缓存副作用之前):群消息查 groupPolicy(默认 allowlist 白名单);私聊查 dmPolicy(默认 pairing 配对——陌生人会收到一个 8 位配对码,等 bot 主人在终端批准,见第 7 章)。白名单用数字 sender ID 而不是 username(username 可以改,不可靠)。

bot-handlers.runtime.ts:3267 · src/security/dm-policy-shared.ts:170-231

4

入站缓冲:把碎片拼回一句话

三套缓冲同时工作:debounce(你连发三条短消息会在时间窗内合并成一条)、相册攒批(多张图按 media_group_id 等齐再处理)、长文分片拼接(Telegram 把 >4096 字符的长文拆成多条,按连续 message_id 拼回)。群里没 @ 到 bot 时甚至不下载媒体,省流量。

bot-handlers.runtime.ts:611-744(debounce)· :1005-1119(相册)· :1121-1197(分片)

5

回复链 + 群聊历史 → prompt 上下文

从本地 message-cache 拉出这条消息的 reply-to 祖先链和群聊近期历史(Bot API 拉不到任意历史,上下文只来自 OpenClaw 亲眼观察过的消息),组装成结构化的「Conversation context」,附在给模型的 prompt 里。

bot-handlers.runtime.ts:1456-1598 · :1274-1407

6

规范化 + mention 判定 + typing 提示

Telegram 特有字段被转换成内部统一格式 MsgContext(Body/From/ChatType/SessionKey…)。群里如果要求 @ 才响应(requireMention)而你没 @,消息在这里被丢弃(但仍记入历史当旁观上下文)。通过则立刻发 typing... 提示。

extensions/telegram/src/bot-message-context.ts · bot-message.ts:183-190

7

路由:哪个 agent、哪个会话

按配置里的 bindings[] 走 8 级优先匹配(精确聊天对象 > 话题继承父群 > 通配 > Discord 服务器+角色 > … > 默认 agent),选出 agentId;再生成 session key,比如群聊是 agent:main:telegram:group:-1001234:topic:42,而所有私聊默认折叠进同一个主会话 agent:main:main(第 4 章详表)。

src/routing/resolve-route.ts:611-818 · session-key.ts:222-274

8

Channel Turn 内核:五阶段准入

所有渠道共用同一个「回合内核」:ingest(原始→规范输入)→ classify(这事件能开一个 agent 回合吗)→ preflight(准入裁决:dispatch / 只旁观 / 已处理 / 丢弃)→ resolveTurndispatch。中间有 bot 对 bot 死循环熔断;先把入站消息记进会话,派发。

src/channels/turn/kernel.ts:676-838 · :523-641

9

会话级并发控制:同一会话一次只跑一个 run

如果这个会话的 agent 正在干活,新消息按配置四选一:steer(注入进行中的回合,「顺便帮我把标题改了」这种补话);排队(等当前 run 结束后逐条 drain);interrupt(打断重来);drop(心跳类直接丢)。这是多渠道产品必须有、而单终端的 Claude Code 不需要的一层。

src/auto-reply/reply/agent-runner.ts:1263-1349 · get-reply-run.ts:1232-1320

10

跑 agent:进入 while 循环

runEmbeddedAgent 启动嵌入式 agent run:组装 system prompt(工具清单 + AGENTS.md/SOUL.md 等工作区文件 + 记忆)、解析模型与认证 profile、然后进入 agent-loop.ts 的双层 while 循环:调模型 → 有 toolCall 就执行工具 → 结果回填 → 再调模型,直到模型不再要工具(第 5、6 章)。

src/agents/embedded-agent-runner/run.ts · packages/agent-core/src/agent-loop.ts:305-344

11

流式回流:block 而不是 token

模型的流式输出经三级订阅链冒泡回渠道层,但 OpenClaw 有一条铁律:对外不发 token 级增量(聊天软件不是终端,逐 token 编辑消息会刷爆 API 限速)。文本被切成语义 block,经保序流水线(promise 链 + 单块超时熔断 + 与 final 去重)交给渠道发送。

src/agents/embedded-agent-subscribe.ts:162 · src/auto-reply/reply/block-reply-pipeline.ts:106-363

12

工具执行(若模型要求)

工具默认并行执行(任一工具声明 sequential 则整批串行)。exec(shell)工具跑 10 秒没完自动转后台;危险命令触发两阶段审批——你会在聊天窗口里收到一条「Reply with: /approve abc123」的审批卡(第 6、7 章)。

agent-loop.ts:548-597 · src/agents/bash-tools.exec.ts

13

Telegram 侧发送:draft 流 + 分块 + 三级降级

流式预览用「sendMessage + editMessageText 就地编辑」让一条消息慢慢长大(progress 模式则是一个进度窗口,完成后塌缩成一行摘要)。最终回复按 4000 字符分块(代码围栏内不断行),Markdown 转 Telegram HTML,失败三级降级:富实体 → HTML → 纯文本。reply 引用挂第一块、按钮挂最后一块。

bot-message-dispatch.ts:1005-1125 · send.ts:705-954 · src/auto-reply/chunk.ts

14

收尾:reaction 收敛 + 转录镜像 + spool 墓碑

状态 reaction 从 👀(思考中)收敛为 ✅/❌;bot 自己的回复也记入 message-cache(下次别人 reply 它时有上下文);assistant 回复镜像写回会话转录(JSONL);最后给 spool 里那条 update 打上墓碑。这条消息的一生结束。

bot-message-dispatch.ts:818-855 · send.ts:902 · polling-session.ts:640

为什么比 Claude Code 的 12 站多了「前置可靠性」?终端里丢一次输入,用户重敲就行;聊天渠道是异步的,用户发完就锁屏了。所以 OpenClaw 在消息进 agent 之前垫了 spool 落盘、去重、授权、缓冲合并四层「消息队列基础设施」——这是全仓库工程含量最高的部分之一。

02启动流程:从 openclaw 命令到 Gateway 就绪

入口链:openclaw.mjs(纯 JS 启动器)→ dist/entry.js → runCli → Commander → startGatewayServer。中间夹着两类「自我重启」。

2.1 三级入口设计

npm 的 bin 指向 openclaw.mjs——一个刻意零依赖的纯 JS 启动器。它先卡 Node 版本门槛(≥22.19,推荐 24,不满足直接退出并提示 nvm,openclaw.mjs:11-15,46-59),然后跑一组快路径--version 不加载任何 TS 运行时、直接拼 package.json + git HEAD 输出(openclaw.mjs:63-65);裸 --help 优先输出构建期预计算好的帮助文本openclaw.mjs:725-752)——但如果配置文件里出现 plugins$include 字样就放弃快路径,因为插件可能注册新命令(openclaw.mjs:547-563)。都不命中才动态 import("./dist/entry.js")

openclaw.mjs:773-788
if (!waitingForCompileCacheRespawn) { if (!isHelpFastPathDisabled() && (await tryOutputBareRootHelp())) { // OK:预计算帮助,零运行时加载 } else if (!isHelpFastPathDisabled() && tryOutputPrecomputedCommandHelp()) { } else { await installProcessWarningFilter(); if (await tryImport("./dist/entry.js")) { } else if (await tryImport("./dist/entry.mjs")) { } else { throw new Error(await buildMissingEntryErrorMessage()); } } }

src/entry.ts:51-62 有个防御性守卫 isMainModule:entry.js 可能被打包工具当共享依赖 import,若非主模块则跳过全部入口副作用——否则会二次 runCli,起重复 Gateway、撞锁撞端口。

2.2 两类自我 respawn(重生)

「respawn」= 进程发现自己需要不同的 Node 启动参数,于是带着新参数重新 spawn 一个自己、把信号转发过去。OpenClaw 有两类:

  • compile-cache respawnsrc/entry.compile-cache.ts:148-180):Node 的模块编译缓存能显著加速冷启动,但在两种场景必须禁用并 respawn——源码 checkout(存在 .gitsrc/entry.ts)、Windows + Node 24.0-24.14(该组合在 ESM 加载期可能死锁entry.compile-cache.ts:15-17)。打包安装则启用版本化缓存目录(版本号 + 安装标记做 key,防升级后用旧字节码,entry.compile-cache.ts:100-124)。
  • CLI flag respawnsrc/entry.respawn.ts:75-159):Windows 注入 --stack-size=8192;探测到额外 TLS CA 时注入 NODE_EXTRA_CA_CERTS;压掉 ExperimentalWarning。逃生口 OPENCLAW_NO_RESPAWN

启动器对 respawn 出的子进程做信号监督:转发 SIGTERM/SIGINT 等,三级宽限各 1 秒(转发 → SIGTERM → SIGKILL → 父进程强退,openclaw.mjs:110-213)。

2.3 runCli:裸命令的智能分派

src/cli/run-main.ts:841-1357 是 CLI 主流程。最有意思的是裸 openclaw(不带任何子命令)的分派逻辑(run-main.ts:297-329):

  • 配置文件不存在(或只有壳)→ 进 onboarding 向导(引导你配 Gateway、渠道、模型);
  • 配置有效 → WS 探测本机/远端 Gateway 是否活着:活着就起 TUI 连上去,不活就 TUI local 模式;
  • 配置无效 → 起 Crestodian——一个专门的「诊断修复助手」agent,帮你把配置修好。

命令树规模:23 个核心命令(onboard / doctor / message / sessions / tasks…src/cli/program/core-command-descriptors.ts:10-117)+ 39 个子 CLI(gateway / daemon / nodes / skills / plugins / security…src/cli/program/subcli-descriptors.ts:10-194),全部懒注册openclaw gateway run 还有专用快路径——只注册这一条命令的裁剪版 Commander,跳过整棵命令树构建(run-main.ts:147-242)。

2.4 Gateway 启动 12 步

startGatewayServer(port = 18789)src/gateway/server.impl.ts:524)按 startupTrace 逐步推进,关键顺序:

1

环境规范化

state 目录 env 归一,把端口写进 OPENCLAW_GATEWAY_PORT 让 browser/canvas 等派生端口一致。

server.impl.ts:528-553

2

config.snapshot + auth

加载配置快照(可复用 CLI 预读、避免重复解析);解析鉴权——mode=token 但没配 token 时生成随机 token 并警告(只存内存,不落盘)。

server.impl.ts:577-632

3

plugins.bootstrap

构建插件 registry 与查表;channel 插件可带自己的 gateway 方法;未配置的 channel 插件延后加载。

server.impl.ts:674-721

4

runtime.config:bind 与 TLS

定 bindHost(默认 loopback 回环)、Control UI 开关、TLS。非回环绑定 + 无鉴权 = 直接拒绝启动(第 7 章)。

server.impl.ts:740-843

5

channelManager + runtime.state

创建渠道管理器(Telegram/Discord 等插件运行时);创建 HTTP server、WebSocketServer、客户端集合、广播器——注意此时只创建、还没 listen

server.impl.ts:852-919

6

ws-attach → http.listen

把带鉴权、限流、握手超时的 WS 连接处理器挂上,然后才真正绑端口。顺序很讲究:先能处理,再开门。

server.impl.ts:1553-1591

7

post-attach sidecars → ready

启动「边车」集:插件运行时、渠道连接、tailscale serve/funnel、update check。就绪前新 WS 连接会被以专用 close code 拒绝并提示重试。

server.impl.ts:1628-1734 · ws-connection.ts:436

8

配置热重载 + 兜底维护

watch 配置文件 + 订阅进程内写事件,diff 后按 reload plan 增量应用(渠道启停/cron 重建/插件 reload),必要时判定整体重启;启动快照晋升为 last-known-good 备份。250ms 后起维护定时器(健康检查、去重清理、media TTL)。

server.impl.ts:1736-1839

2.5 配置系统:一个 JSON5 文件 + 三板斧

配置文件默认 ~/.openclaw/openclaw.json(state 目录可用 OPENCLAW_STATE_DIR 覆盖,src/config/paths.ts:24-25,150)。加载管线(src/config/io.ts:1982-2067):JSON5 解析(允许注释和尾逗号)→ $include 指令(配置拆多文件)→ ${ENV} 环境变量替换(缺失的 env var 降级为警告而非致命——Gateway 允许带残缺 provider key 降级启动)→ 插件感知的 schema 校验。写入带冲突检测与重试;解析失败的快照标 invalid,Gateway 会进降级 / Crestodian 修复路径,且随时可从 last-known-good 恢复(src/config/config.ts:18-24)。

对比 Claude Code 的「五层 settings 合并」:OpenClaw 是单文件 + include模型——因为它的配置对象是「一台服务器」而不是「用户×项目×企业」的矩阵。

2.6 装成系统服务(daemon)

openclaw daemon install 把 Gateway 装成开机自启服务,三平台适配器统一聚合在 src/daemon/service.ts:8-53:macOS launchd(label ai.openclaw.gateway,plist 走 launchctl bootstrap 装载,src/daemon/launchd.ts:307-346)、Linux systemd 用户单元(~/.config/systemd/user/openclaw-gateway.servicesrc/daemon/systemd.ts:67)、Windows 计划任务(schtasks,任务名「OpenClaw Gateway」,src/daemon/constants.ts:4-17)。运行中的 Gateway 还支持 SIGUSR1 触发自我重启,带 restart-handoff 文件把重启前后的耗时追踪串起来(server.impl.ts:562-569,641)。

03Gateway 控制平面:自研 WS 协议

所有客户端——WebUI、TUI、手机 App、节点设备——说的都是同一门语言:跑在 WebSocket 上的 req / res / event 三帧协议(v4)。

3.1 帧模型:三种帧走天下

协议 schema 独立成包 packages/gateway-protocol/(用 TypeBox 定义,TypeBox 是一个「写 TS 即得 JSON Schema」的校验库)。当前协议版本 4(version.ts:2-6)。整个协议只有三种帧:

packages/gateway-protocol/src/schema/frames.ts:169-210
/** 客户端请求帧;method 决定用哪个 payload 校验器 */ export const RequestFrameSchema = Type.Object( { type: Type.Literal("req"), id: NonEmptyString, method: NonEmptyString, params: Type.Optional(Type.Unknown()) }, { additionalProperties: false }, ); /** 服务端响应帧,靠 id 与请求配对 */ export const ResponseFrameSchema = Type.Object( { type: Type.Literal("res"), id: NonEmptyString, ok: Type.Boolean(), payload: Type.Optional(Type.Unknown()), error: Type.Optional(ErrorShapeSchema) }, { additionalProperties: false }, ); /** 服务端事件帧(推送),带可选 seq 与状态版本号 */ export const EventFrameSchema = Type.Object( { type: Type.Literal("event"), event: NonEmptyString, payload: Type.Optional(Type.Unknown()), seq: Type.Optional(Type.Integer({ minimum: 0 })), stateVersion: Type.Optional(StateVersionSchema) }, { additionalProperties: false }, );

方法名如 chat.sendagents.listsessions.*config.*nodes.invoke,每个域一个实现文件(src/gateway/server-methods/)。这跟 Claude Code 的「SDK 控制协议走 stdin/stdout」是同一个思想的网络化版本。

3.2 握手:挑战 → connect → hello-ok

1

连接建立后服务端先发 connect.challenge 事件,带一个随机 nonce(一次性随机数,防重放攻击)。

src/gateway/server/ws-connection.ts:387-392

2

客户端第一帧必须是 req connect,携带:协议版本区间(协商用)、客户端身份(id/version/platform/mode)、角色与 scopes(node 设备走 role:"node")、设备身份签名(Ed25519 公钥 + 对 challenge nonce 的签名)、以及 auth(token / password / deviceToken / bootstrapToken…)。

message-handler.ts:720-728 · frames.ts:30-82

3

成功回 hello-ok:协商后的协议号、服务端信息、能力发现(methods[] / events[] 清单)、初始状态快照、可签发 deviceToken 供后续免密重连、payload 大小策略、插件的 Control UI tab 注入。

frames.ts:85-155 · message-handler.ts:2103

3.3 防护细节(值得抄的部分)

  • 预认证预算:未完成握手的连接占用一个有限预算池,握手完成才释放;握手还有超时定时器(ws-connection.ts:258-340)。
  • 预认证帧限长:认证前的帧大小上限远小于认证后(ws-connection.ts:26-29)——没验明正身之前,别想发大包。
  • 失败限速只烧「错误凭据」:给了 token 但错了才计入限速;没给凭据不计(防止把正常探测也锁死,src/gateway/auth.ts:399-408)。回环地址默认豁免。
  • 慢消费者熔断:socket 积压超 MAX_BUFFERED_BYTES 直接 close(1008, "slow consumer")(ws-connection.ts:362-379);认证后每 25 秒 ping 心跳。
  • 握手处理器懒加载:处理器模块加载期间入队最多 16 帧,超限断连——连「模块还在 import」这种窗口期都设了上限(ws-connection.ts:53,184-218)。

同一个 HTTP server 上还挂着:Control UI 静态资源、OpenAI 兼容的 chat-completions HTTP 端点(让任何支持 OpenAI API 的工具把 OpenClaw 当模型用)、回环 MCP server、插件 HTTP 路由(src/gateway/control-ui.ts / openai-compatible-http*.ts / mcp-http.ts;逐端点实现本文未核实)。

04多渠道接入架构

「渠道」= 一个聊天平台的接入插件。141 个 bundled 扩展里有 20+ 个渠道,它们只做传输(transport-only),所有共性逻辑都在核心层。

4.1 分层:插件只管收发,内核管准入

渠道消息的分层(以 Telegram 为例)
extensions/telegram/ 传输层:grammY 收发、spool 落盘、格式转换、分块(transport-only) ↓ ingest src/channels/turn/ 回合内核:ingest → classify → preflight → resolveTurn → dispatch ↓ dispatch src/auto-reply/ 回复编排:命令、hook、typing、会话并发控制、block 流水线 ↓ prompt src/agents/ agent 运行时(第 5 章)

回合内核 runChannelTurnsrc/channels/turn/kernel.ts:676-724)是所有渠道的共同必经之路:

src/channels/turn/kernel.ts:676-724(节选)
export async function runChannelTurn<TRaw, TDispatchResult>(params) { const input = await params.adapter.ingest(params.raw); // 原始 → 规范化输入 if (!input) { return { admission: { kind: "drop", reason: "ingest-null" }, dispatched: false }; } const eventClass = (await params.adapter.classify?.(input)) ?? DEFAULT_EVENT_CLASS; if (!eventClass.canStartAgentTurn) { return { admission: { kind: "handled" } }; } const preflight = normalizePreflight(await params.adapter.preflight?.(input, eventClass)); // admission ∈ dispatch | observeOnly | handled | drop;drop 时仍可记入群历史 const resolved = await params.adapter.resolveTurn(input, eventClass, preflight); // 之后 runPreparedChannelTurnCore:先 recordInboundSession,再 runDispatch() }

注意 observeOnly(只旁观)这个裁决:群里没 @ 你的消息不触发回复,但会被记进群历史——下次有人 @ 你时,agent 能看到之前的讨论。这是「群聊 AI」和「单聊 AI」的关键差异。

4.2 路由:bindings 8 级匹配 + Session Key

一个 Gateway 可以养多个 agent(比如「工作 agent」绑 Slack、「家庭 agent」绑 WhatsApp)。谁接谁的消息由配置 bindings[] 决定,按 8 级优先匹配(src/routing/resolve-route.ts:736-817):精确聊天对象 → 话题继承父群 → 通配(group:*)→ Discord 服务器+角色 → Discord 服务器 → Slack team → 账号 → 渠道 → 默认 agent。命中即产出 {agentId, sessionKey},带 LRU 路由缓存(上限 4000 key)。

Session key 决定「哪些消息共享同一段记忆」,真实格式(src/routing/session-key.ts:222-274):

场景Session Key 格式
agent 主会话agent:main:main
私聊(默认 dmScope=main)折叠进主会话——你在 Telegram 和 WhatsApp 私聊它是同一段记忆
私聊(per-channel-peer)agent:main:telegram:direct:<peerId>(按渠道×联系人拆开)
群聊agent:main:telegram:group:-1001234567890
群话题(forum topic)agent:main:telegram:group:<chatId>:topic:<threadId>
跨渠道同一个人identityLinks 可把多渠道 peerId 映射到统一身份,折叠成一个会话(session-key.ts:276-320

默认值有安全含义:dmScope 默认 main 意味着所有获准私聊的人共享 agent 主会话——A 说的话 B 可能看到。所以 openclaw security audit 有一条专门的 finding:dm.scope_main_multiuser(多人共用主会话告警,第 7 章)。

4.3 会话级并发:steer / followup / interrupt / drop

同一 session 同时只跑一个 agent run(src/auto-reply/reply/get-reply-run.ts:1232-1320)。run 进行中来了新消息,按队列模式四选一,其中 steer(转向)最有意思——把新消息注入进行中的回合,模型在下一轮工具调用间隙就能看到:

src/auto-reply/reply/agent-runner.ts:1263-1282(steer 注入)
if (effectiveShouldSteer && isActive) { const steerSessionId = (sessionKey ? replyRunRegistry.resolveSessionId(sessionKey) : undefined) ?? followupRun.run.sessionId; const steerOutcome = await queueEmbeddedAgentMessageWithOutcomeAsync( steerSessionId, followupRun.prompt, { steeringMode: "all", ... }, ); if (steerOutcome.queued) { await touchActiveSessionEntry(); typing.cleanup(); return undefined; // 本轮不再起新 run }

排队(followup)模式则把整个 run 描述入 session 队列、按 message-id 去重,当前 run 清场后逐条 drain(agent-runner.ts:1316-1349)。渠道层还有第二重串行化:Telegram 按 chat/topic 生成 lane key,spool 按 lane 串行 drain,只读命令(/status)可绕行(extensions/telegram/src/sequential-key.ts:150)。

4.4 出站:流式 draft、分块、降级

  • 流式三模式partial/block——「sendMessage + editMessageText 就地编辑」让一条预览消息慢慢长大(首条有 30 字符起送门槛,改善推送通知体验);progress——单个进度窗口渲染 🧠 思考 / 💬 评论 / 工具行,final 送达后塌缩成一行摘要;off——只发最终回复(bot-message-dispatch.ts:1005-1125)。
  • 保序流水线:block 经 promise 链保证出站顺序,单块发送超时则整体熔断丢弃后续块——宁可少发,不可乱序;final 与已流式内容重复时被去重压掉(block-reply-pipeline.ts:158-224,340-360)。
  • 分块与格式:Telegram 上限 4000 字符(留余量 <4096),代码围栏内不断行;Markdown → Telegram HTML,三级降级:富实体 400 → HTML;HTML 解析 400 → 纯文本(send.ts:815-826,954 · src/auto-reply/chunk.ts:1-33)。多块时 reply 引用挂第一块、inline 键盘挂最后一块。
  • 限速:bot-token 级共享节流器 + 尊重 Telegram 的 retry_after

渠道插件的「同构降级」纪律:流式和非流式两条发送漏斗必须共享同一批错误谓词与降级链(extensions/telegram/CLAUDE.md Send funnel parity)——否则会出现「流式能发、final 报错」的分裂行为。这类不变量直接写在渠道目录的 CLAUDE.md 里,是 AI 辅助开发时代「把守则放在代码旁边」的实践。

05Agentic 主循环:@openclaw/agent-core

整个产品的心脏。和 Claude Code 的 query.ts 一样,是一个「调模型 → 执行工具 → 回填 → 再调」的 while 循环——只是这里是双层的。

5.1 九层调用栈:从渠道消息到 while 循环

调用关系(自顶向下)
渠道 dispatch └─ runEmbeddedAgent() [src/agents/embedded-agent-runner/run.ts, 4294 行] 命令排队/模型与 auth 解析/failover/compaction 编排 └─ runEmbeddedAttemptWithBackend() [run/attempt.ts, 5916 行] 单次 provider 尝试的全部编排 └─ createAgentSession() [sessions/sdk.ts:406] new Agent(...) + new AgentSession(...) └─ session.prompt() [sessions/agent-session.ts:1145] 命令展开/steer 或 followUp 分流 └─ agent.prompt() [packages/agent-core/src/agent.ts:374] └─ runAgentLoop() [packages/agent-core/src/agent-loop.ts:172] ★真·主循环

分工与 Claude Code 的 QueryEngine / queryLoop 二分如出一辙:外层管「会话生命周期」(转录落盘、锁、failover、压缩),内层 agent-loop.ts 只管「干活」。内核包 packages/agent-core 依赖极简——只有 @openclaw/ai 和 typebox(packages/agent-core/package.json:106-109),可以脱离 OpenClaw 单独复用。

5.2 双层 while:内层跑工具,外层接后续

packages/agent-core/src/agent-loop.ts:305-344(节选)
// Outer loop: continues when queued follow-up messages arrive after agent would stop while (true) { let hasMoreToolCalls = true; // Inner loop: process tool calls and steering messages while (hasMoreToolCalls || pendingMessages.length > 0) { if (await stopIfAborted()) { return; } ... // 注入 pending 消息(steering 补话在下一次模型响应前生效) if (pendingMessages.length > 0) { for (const message of pendingMessages) { await emit({ type: "message_start", message }); await emit({ type: "message_end", message }); currentContext.messages.push(message); } } // 流式拿一条 assistant 消息 const message = await streamAssistantResponse( currentContext, config, signal, emit, streamFn, runtime, ); // 之后:过滤 toolCall 块 → executeToolCalls → 结果 push 回 context → turn_end } // 内层停了;followUp 队列非空 → 回填 pendingMessages,continue 外层 }

内层循环条件 hasMoreToolCalls 就是 Claude Code 的 needsFollowUp——模型不再要工具即停,没有任何计划器。外层多出的一圈是为聊天场景准备的:agent 刚要收工时用户又发来一条消息(followUp 队列),不需要重新起 run,直接续循环。

两条消息队列的语义(agent.ts:155-190):steering(转向)每个 turn 末尾 drain,打断式补话;followUp(后续)只在 agent 本该停下时 drain。abort 时会补一条 stopReason:"aborted" 的 assistant 消息再收尾(agent-loop.ts:281-303)——保证转录永远不会停在裸 toolCall 上,否则续跑和压缩都会坏档。

5.3 模型调用:streamFn 依赖注入

内核不直接认识任何厂商——它只调一个被注入的 streamFn

packages/agent-core/src/agent-loop.ts:446-480(节选)
async function streamAssistantResponse(context, config, signal, emit, streamFn, runtime) { let messages = context.messages; if (config.transformContext) { // 压缩/剪枝的挂点(第 9 章) messages = await config.transformContext(messages, signal); } const llmMessages = await config.convertToLlm(messages); const llmContext: Context = { systemPrompt: context.systemPrompt, messages: llmMessages, tools: context.tools }; const streamFunction = resolveAgentCoreStreamFn(runtime, streamFn); // 每次调用都重解析 API key —— 应对会过期的 OAuth token const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey; const response = await streamFunction(config.model, llmContext, { ...config, apiKey: resolvedApiKey, signal }); // for-await provider 事件:text_delta / thinking_* / toolcall_* / done / error

嵌入层在 src/agents/sessions/sdk.ts:406-432 注入真实实现:streamFn 内部先向模型注册表要凭据和 header,再调 @openclaw/aistreamSimple。这个依赖注入设计让 agent-core 可以被单测、被 CLI 复用、甚至换整个模型层。

5.4 System Prompt 组装:一个「人格」是怎么拼出来的

真正的渲染器是 src/agents/system-prompt.ts:680buildAgentSystemPrompt(1425 行)。按稳定前缀顺序输出这些节:身份行("You are a personal assistant running inside OpenClaw.")→ ## Tooling 工具清单 → 子 agent 委派指引 → ## Tool Call Style(含 /approve 规则)→ ## Safety## Skills## Memory → 时间与授权发送者 → Project Context(工作区文件注入)## Heartbeats

工作区文件是 OpenClaw 的「人格文件系统」,注入顺序硬编码(system-prompt.ts:69-81):

src/agents/system-prompt.ts:69-81
const CONTEXT_FILE_ORDER = new Map<string, number>([ ["agents.md", 10], // 操作守则(≈ Claude Code 的 CLAUDE.md) ["soul.md", 20], // 人格/语气 —— OpenClaw 特色 ["identity.md", 30], // 我是谁 ["user.md", 40], // 用户是谁 ["tools.md", 50], // 工具使用指导(不改变可用性) ["bootstrap.md", 60], // 首次开机仪式,setup 完成后不再注入 ["memory.md", 70], // 长期记忆精华 ]); const DYNAMIC_CONTEXT_FILE_BASENAMES = new Set(["heartbeat.md"]);

缓存策略与 Claude Code 的 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 同思路:稳定前缀按内容 sha256 做 LRU 缓存(上限 64 条,system-prompt.ts:121-141),动态内容(heartbeat、当前时间)放缓存边界之下,保 provider 端 prompt cache 命中。文件按 dev:ino:size:mtime 身份缓存、变了才重读,单文件上限 2MB(src/agents/workspace.ts:62-107)。子 agent 只注入 AGENTS+TOOLS(workspace.ts:1126);还有个动人的细节——workspace 目录 24 小时内有存活证明却突然消失时,抛 WorkspaceVanishedError 拒绝重新播种 BOOTSTRAP.md:防止把一个老 agent 当新生儿重新「洗脑」workspace.ts:217-234)。

5.5 流式事件的三级订阅链

  1. Agent.subscribe()agent.ts:278-283):内核事件流 agent_start / turn_start / message_update / tool_execution_* / turn_end / agent_end
  2. AgentSession 订阅 Agent(sessions/agent-session.ts:439),翻译成会话事件并驱动转录落盘。
  3. 嵌入层 subscribeEmbeddedAgentSessionsrc/agents/embedded-agent-subscribe.ts:1313)挂上一束回调(onBlockReply / onPartialReply / onReasoningStream / onToolResult…),内部做 <think> 标签剥离、markdown 围栏安全断句、block 分块,然后进第 4 章的渠道流水线。

06工具系统

与 Claude Code 的差异一句话:schema 从 Zod 换成 TypeBox、权限从「弹窗问用户」换成「多层策略过滤 + 聊天内审批」,其余惊人一致。

6.1 AgentTool 接口(TypeBox schema)

packages/agent-core/src/types.ts:459-487(节选)
export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = unknown> extends Tool<TParameters> { label: string; // UI 显示名 hideFromChannelProgress?: boolean; // 不在聊天进度窗里刷存在感 prepareArguments?: (args: unknown) => Static<TParameters>; /** 执行。失败要 throw,而不是把错误编码进 content */ execute: ( toolCallId: string, params: Static<TParameters>, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<TDetails>, // 进度回调 → tool_execution_update 事件 ) => Promise<AgentToolResult<TDetails>>; executionMode?: ToolExecutionMode; // "sequential" | "parallel" 单工具覆写 }

参数校验失败不炸循环——转成 errorKind:"argument-validation" 的错误 tool result 回给模型自我修正(agent-loop.ts:922-932),和 Claude Code 的 <tool_use_error> 反馈闭环同一思想。执行默认并行,任一工具声明 sequential 则整批串行(agent-loop.ts:548-597)——对比 Claude Code 的「只读并发、写串行」二分法,这里把并发决定权下放给了单个工具。

6.2 装配管线:六路来源 + 十一层策略

createOpenClawCodingToolssrc/agents/agent-tools.ts:384-1195)文件头注释即总纲:「Assembles core, shell, channel, OpenClaw, plugin, and Tool Search tools, then applies sandbox, profile, provider, sender, group, and sub-agent policy.」六路工具来源合流后过 applyToolPolicyPipeline

src/agents/agent-tools.ts:1088-1127(节选)
const subagentFiltered = applyToolPolicyPipeline({ tools: toolsForModelProvider, steps: [ ...buildDefaultToolPolicyPipelineSteps({ profilePolicy, profile, globalPolicy, globalProviderPolicy, agentPolicy, agentProviderPolicy, groupPolicy, senderPolicy, agentId, ... }), { policy: sandboxToolPolicy, label: "sandbox tools.allow", ... }, { policy: subagentPolicy, label: "subagent tools.allow", ... }, { policy: inheritedToolPolicy, label: "inherited tools", ... }, ], });

层级依次是:profile → provider profile → 全局 → 全局×provider → agent → agent×provider → 发送者 → 沙箱 → 子 agent → 父会话继承。「群」和「发送者」两层是聊天产品特有的:同一个 agent 在公开群里可以只留只读工具,对 owner 私聊才放开 exec。被 deny 的工具在模型看到之前就被剔除。收尾还有三道包裹:schema 兼容清洗(OpenAI 拒绝 root union、Gemini 要剥约束关键字而 Anthropic 要保留,agent-tools.ts:1139-1148)→ before/after_tool_call 插件钩子 → abort 信号注入。

6.3 内置工具速查表

工具文件(src/agents/ 下)职责
read / write / editsessions/tools/*.ts文件读写编辑;sandbox 模式换成沙箱版并包工作区根守卫
grep / find / lssessions/tools/*.ts工作区搜索/列目录
exec / processbash-tools.exec.ts / bash-tools.process.tsshell 执行 + 后台会话管理(见 6.4)
apply_patchapply-patch.ts补丁式多文件编辑(按模型白名单开启)
messagetools/message-tool.ts主动/跨渠道发消息——agent 能「给你发微信」的原因
browser(extensions/browser 插件提供)浏览器控制
canvas / nodes(canvas 插件)/ tools/nodes-tool.ts给配对设备推可视化画布;调用手机摄像头、定位、通知(第 11 章)
crontools/cron-tool.tsagent 给自己定闹钟(第 12 章)
sessions_spawn / sessions_yield / subagentstools/sessions-spawn-tool.ts 等派生子 agent 并等待其完成(见 6.5)
sessions_list / sessions_history / sessions_sendtools/sessions-*-tool.ts跨会话查询与发送
web_search / web_fetchtools/web-tools.ts搜索/抓取(provider 有原生搜索时可被抑制)
image / image_generate / video_generate / music_generate / tts / pdftools/*.ts多媒体理解与生成(按配置和凭据可用性 gating)
gatewaytools/gateway-tool.tsagent 自管 Gateway:查/改配置、重启——「它能给自己做手术」
get_goal / update_plan / heartbeat_responsetools/*.ts目标管理、计划板、心跳回执
tool_search / tool_describe / tool_calltool-search.ts工具目录太大时的延迟加载三件套(同 Claude Code 的 ToolSearch 思路,agent-loop.ts:843-864 的 resolveDeferredTool 是底座)
crestodiantools/crestodian-tool.tsring-zero 安装修复工具,仅 Crestodian 运行器注入——普通 agent 连通配符也拿不到(openclaw-tools.ts:139-142

6.4 exec:默认 10 秒转后台 + 聊天内审批

exec 的参数 schema 就体现了它比 Claude Code 的 Bash 工具多出的维度(src/agents/bash-tools.schemas.ts:13-38):

src/agents/bash-tools.schemas.ts:13-38(节选)
export const execSchema = Type.Object({ command: Type.String({ description: "Shell command to execute" }), yieldMs: Type.Optional(Type.Number({ description: "Milliseconds to wait before backgrounding (default 10000)" })), background: Type.Optional(Type.Boolean({ description: "Run in background immediately" })), timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, kills process on expiry)" })), pty: Type.Optional(Type.Boolean({ ... })), // 伪终端(交互式程序) elevated: Type.Optional(Type.Boolean({ description: "Run on the host with elevated permissions (if allowed)" })), host: optionalStringEnum(EXEC_TOOL_HOST_VALUES, { description: "Exec host/target (auto|sandbox|gateway|node)." }), });
  • yield 后台化:默认 10 秒(钳制 10ms–120s)没跑完就「让出」转后台,进程进注册表,由 process 工具 poll / log / send-keys / kill 继续管理;硬超时默认 1800 秒杀进程。工具调用被 abort 不杀已后台化的会话,timeout 到点仍会杀(bash-tools.exec.ts:1304-1317,1996)。对比 Claude Code 的「超时原地转后台」,同一招。
  • host 多宿主:命令可以跑在沙箱容器(sandbox)、Gateway 宿主机(gateway)、甚至另一台配对设备(node,第 11 章)。
  • 审批:安全策略是 security(deny / allowlist / full)×ask(off / 未命中问 / 总是问)双轴合并取更严(exec.ts:1682-1705)。触发审批时走两阶段 gateway 审批注册bash-tools.exec-approval-request.ts:1-50),用户在聊天窗口收到审批卡或直接回 /approve <id>——system prompt 明确教模型「如果 exec 返回 approval-pending,把 Reply with: 里的命令原样发给用户」,并禁止模型自己去 shell 里执行 /approve(system-prompt.ts:264-266,1107)。还有可选的模型自动审查员(一个小模型先替你审一遍命令,exec.ts:90)。
  • 纵深防御:safeBins 白名单需配合 hardened profile 才生效;宿主机环境变量里的危险项被剥离;elevated 提权需要配置双开关(enabled + allowed)且提权不等于免审批(exec.ts:1335-1379)。

6.5 子 agent:sessions_spawn

Claude Code 的子 agent 是「递归调用 query()」;OpenClaw 的子 agent 是「spawn 一个新会话」(src/agents/tools/sessions-spawn-tool.ts),几个讲究的设计:

  • 默认隔离:子会话默认不带父转录,context:"fork" 才继承——system prompt 里明确教「omit context unless transcript needed」(system-prompt.ts:1060)。
  • 不许自选投递目标:spawn 参数显式禁止 target/channel/threadId——子 agent 不能决定「把结果发到哪个群」,投递上下文只能由父继承注入(sessions-spawn-tool.ts:54-63)。这是防「子 agent 被注入后乱发消息」的契约级防线。
  • 工具面继承:父会话的最终实效 allowlist 以引用传入,子会话继承的是「父被过滤后真实拥有的」工具面而不是配置原文(agent-tools.ts:883-889)。
  • 完成回报是推送式:子 agent 完成作为事件回注父会话;父 agent 被教导用 sessions_yield 让出等待,而不是轮询 subagents listsystem-prompt.ts:1084)。
  • runtime 可以是外人runtime:"acp" 时任务经 ACP(Agent Client Protocol)发给 Claude Code、Gemini CLI 等外部编码 agent 执行(sessions-spawn-tool.ts:69-76)——OpenClaw 可以雇佣 Claude Code 打工。

07权限与安全模型

Claude Code 防的是「模型乱动我的电脑」;OpenClaw 还要防「互联网上的陌生人指挥我的模型动我的电脑」。所以它的安全面多了一整层:入站身份。

7.1 Pairing:陌生人的配对仪式

私聊策略 dmPolicy 四个取值:pairing | allowlist | open | disabled默认 pairingsrc/config/zod-schema.providers-core.ts:270)。陌生人首次私聊 bot 的完整流程:

1

入站判定:sender 不在白名单、dmPolicy=pairing → 裁决 pairingsrc/security/dm-policy-shared.ts:215-231)。消息不进 agent

2

生成 8 位配对码:字母表剔除易混淆的 0/O/1/I,crypto.randomInt 生成;pending 请求 1 小时过期、每账号最多 3 个——洪水攻击下静默不建新请求,既不刷屏也不撑爆存储(src/pairing/pairing-store.ts:33-37,688-696)。

3

回给陌生人一条提示:「OpenClaw: access not configured. Pairing code: XXXX. Ask the bot owner to approve with: openclaw pairing approve telegram XXXX」(pairing-messages.ts:14-27)。

4

Bot 主人在自己的终端(不是聊天窗口!)跑 approve 命令 → 配对码命中 → sender id 写入 allowFrom 存储,之后畅通(pairing-store.ts:714-769)。批准动作必须发生在聊天渠道之外,这样「聊天消息里的提示注入」永远无法自我批准。

细节:dmPolicy=open 也不是裸奔——配了 allowFrom 且不含 * 时仍按名单过滤;通配符 * 写入 pairing 存储时会被归一为空,防止把「全开」持久化进磁盘(dm-policy-shared.ts:88-106 · pairing-store.ts:288-301)。群命令授权刻意不继承 DM 配对批准(dm-policy-shared.ts:310-314)。群策略 groupPolicy 非法或缺省一律归一为 allowlistdm-policy-shared.ts:171-174)——所有默认值都朝关闭的方向倒。

7.2 Gateway 鉴权:拒绝启动比警告更狠

src/gateway/server-runtime-config.ts:154(bind 硬门)
if (!isLoopbackHost(bindHost) && !hasSharedSecret && authMode !== "trusted-proxy") { throw new Error( `refusing to bind gateway to ${bindHost}:${params.port} without auth ` + `(set gateway.auth.token/password, or set OPENCLAW_GATEWAY_TOKEN/...)`, ); }

要把 Gateway 绑到非回环地址(局域网/公网)而没配鉴权?直接拒绝启动,不是打个警告了事。且判定用的是「解析后的真实 secret 非空」而不是 mode 字段——配了 mode=token 但 token 是空串照样拦(server-runtime-config.ts:130-133)。其他硬规则:tailscale funnel(暴露公网)强制 password 模式;缺 token 时自动生成的随机 token 只存内存不落盘——持久化凭据必须走显式的 doctor --fix / configuresrc/gateway/startup-auth.ts:226-243);token/password 比较走常量时间函数 safeEqualSecret 防时序侧信道(src/gateway/auth.ts:405,427)。

7.3 exec 审批与沙箱(执行面)

第 6.4 节讲了机制,这里补策略框架:security(deny / allowlist / full)与 ask(何时问人)两轴独立配置、多来源合并时取更严bash-tools.exec.ts:1682-1705);审批请求两阶段注册防 /approve 竞态,超时走 askFallback(默认 deny)。沙箱方面:配置 sandbox mode 支持把非主会话(群聊、子 agent)的执行整体放进 Docker 容器(agent-tools 装配时给 exec 传入容器 buildExecSpec,agent-tools.ts:845-860),主人私聊的主会话则直跑宿主机——「对自己人方便,对世界谨慎」。安全审计对危险的 bind mount 和 network 模式有专项 finding(见下)。

7.4 openclaw security audit:给自己做体检

这是 Claude Code 没有对应物的子系统(src/security/audit.ts,1548 行):扫描整个部署面,输出结构化 finding(checkId 按「域.子面.问题」命名,severity 三级)。抽几个有代表性的检查项:

checkId在查什么
gateway.bind_no_auth非回环绑定无鉴权(critical)
gateway.token_too_shorttoken 短于 24 字符
channels.*.dm.open某渠道私聊策略全开
dm.scope_main_multiuser多个获准用户共享主会话(互相看得到上下文)
security.exposure.open_channels_with_exec「开放渠道 × exec 工具」的爆炸半径组合项
fs.credentials_dir.* / fs.synced_dir凭据目录权限;state 目录在 Dropbox/iCloud 同步盘里
plugins.installs_missing_integrity插件供应链:安装记录缺完整性校验 / npm 版本未钉死
models.small_params小参数模型带工具 = 提示注入风险更高,也算安全问题

--deep 追加真连 Gateway 探活、已装插件/skill 代码扫描、Docker 容器核对;--fix 只做窄而可逆的修复(groupPolicy open→allowlist、目录 700 / 文件 600 权限收紧——Windows 上用 icacls ACL 而非 chmod),刻意不自动修 bind/auth 类问题src/security/fix.ts:394-478,118-192)。

7.5 信任模型:一句话划清边界

SECURITY.md:5 开宗明义:「OpenClaw is local-first agent infrastructure for trusted operators」——一个 Gateway 只服务一个受信任的操作者;纯 prompt injection(未穿越 auth/policy/sandbox 边界)通常不算安全漏洞;多个敌对用户想共享一个 Gateway 互相隔离?不支持,请拆 Gateway 或拆 OS 用户。「能改 ~/.openclaw 的人即 trusted operator」(docs/gateway/security/index.md:23)。看懂这句话,上面所有设计的取舍就都通了。

08模型 / Provider 层

Claude Code 只对一家 API 做到极致;OpenClaw 要对几十家 provider 做到通用——所以它的模型层是一套「按协议族注册的适配器 + 五级失败恢复链」。

8.1 五层结构

位置职责
① 协议类型packages/llm-core/src/types.tsApi / Model / Context / 事件流等纯类型契约
② 流式运行时packages/ai/src/(@openclaw/ai)各 API 协议族的 stream 适配器 + ApiRegistry(pi-ai 的后继)
③ 核心 facadesrc/llm/进程级默认 registry 装配与 re-export
④ 目录/选择/认证/failoversrc/agents/ + src/model-catalog/模型目录合并、别名解析、auth profile 轮换、fallback 链
⑤ Provider 插件extensions/anthropic · openai · ollama · clawrouter…每家厂商的认证方式、模型声明、动态发现

关键设计:registry 的 key 是 API 协议族而不是厂商(packages/llm-core/src/types.ts:6-18)——内置九族:openai-completions / openai-responses / anthropic-messages / google-generative-ai / google-vertex / bedrock-converse-stream / mistral-conversations…。任何 provider 的模型只要声明 api:"openai-completions" 就复用同一个适配器,这就是「接一个新厂商 = 写一段配置」的原因。底层全用官方 SDK:@anthropic-ai/sdk / openai / @google/genai / @mistralai/mistralaipackages/ai/package.json:70-77)。

packages/ai/src/api-registry.ts:76-108(节选)
export function createApiRegistry() { const providers = new Map<string, RegisteredApiProviderEntry>(); function registerApiProvider(provider, sourceId?) { providers.set(provider.api, { provider: { api: provider.api, stream: wrapStream(provider.api, provider.stream), // model.api 不匹配直接 throw,防路由错协议 streamSimple: wrapStreamSimple(provider.api, provider.streamSimple), }, sourceId, // 支持按插件批量注销 }); } ...

模型 id 统一 provider/model 格式(如 anthropic/claude-opus-4-8)。目录行来自 5 类来源,按权威度合并:用户 config(0,最强)> 插件 manifest(1)> 缓存/运行时刷新(2)> ClawHub 未装插件的 preview(3,最弱)(src/model-catalog/authority.ts:9-15)。

8.2 认证 profile:多账号轮换与冷却

凭据存 ~/.openclaw/agents/<agentId>/agent/auth-profiles.json,三种类型:api_key / token / oauth(OpenClaw 负责刷新)(src/agents/auth-profiles/types.ts:75)。同一 provider 可配多个 profile,轮换规则(auth-profiles/order.ts:244-388):

  • 无显式顺序时按「类型偏好(oauth > token > api_key)+ 同类型内 lastUsed 最旧先用」轮转(round-robin);注释明确刻意忽略 lastGood——否则一个健康 profile 会饿死其他所有 profile(order.ts:379-381,439)。
  • 失败冷却分三层:瞬态阶梯 30s → 1min → 封顶 5min(auth-profiles/usage.ts:427-436);billing / 永久认证错误走指数长退避(billing 5h 起步 24h 封顶);rate_limit / timeout 只冷却触发的那个模型,同 profile 其他模型照用,活跃窗口内第二个模型也挂才扩大到全 profile(usage.ts:683-703)。
  • 冷却过期时顺带清零错误计数,防止陈旧计数把下一次小失败直接升级成长冷却(order.ts:255-258)。openrouter 这类聚合网关整体绕过本地冷却——它们自己管限流(usage-state.ts:11-14)。

8.3 失败恢复五级链

失败恢复的层次(从轻到重)
① SDK 内建重试 同一请求、同凭据(maxRetries 透传) ② operation-retry 传输层瞬态小重试:2 次、250ms 起指数退避;create 默认不重试防双写 [src/provider-runtime/operation-retry.ts:31-53] ③ auth profile 轮换 同 provider 换账号 [embedded-agent-runner/run.ts:1454-1505] ④ model fallback 换模型/换厂商 [src/agents/model-fallback.ts:1397-1933] ⑤ session suspension 全链耗尽 → 挂起会话 lane,等冷却过期 [model-fallback.ts:1551-1607]

模型 fallback 链 = 请求的 primary → agents.defaults.model.fallbacks[] 按序 → 配置默认兜底(model-fallback.ts:1066-1095)。哪些错误触发 fallback 值得细看:用户取消 / 重启类 abort;本地协调错误(防止把本地故障误算成 N 个模型全挂);上下文溢出——应由压缩处理,换个更小窗口的模型只会更糟(model-fallback.ts:1761-1786)。rate_limit 引发的 profile 轮换超上限时直接升级成模型级 fallback(run.ts:1694-1720)。

还有一个精巧机制:provider 全员冷却时的恢复探测(probe)——每 provider 30 秒节流探一次;无 fallback 链的单 provider 每个节流窗都探(否则要等 provider 报的 reset 时间,可能是几天后);每次 fallback run 每 provider 只允许一次瞬态探测(model-fallback.ts:1097-1213,1642-1679)。全链耗尽时抛 FallbackSummaryError:聚合每次尝试的错误、算出全链最早冷却到期时间,给用户「retry in 30s」级别的提示(model-fallback.ts:144-168)。

8.4 流式与 provider 插件

统一抽象 AssistantMessageEventStream:适配器同步返回流对象,内部异步推事件、.result() 聚合成最终消息(packages/ai/src/stream.ts:24-55)。Anthropic 适配器用官方 SDK 的 .asResponse() 拿原始 SSE 自己解析,流没走到 message_stop 就断线会显式报错;逐事件累计 usage 并按模型价目计费——「Fallback-served turns bill at the serving model's rates」(服务端 fallback 边界会切换计价模型,packages/ai/src/providers/anthropic.ts:470-525)。

provider 插件三例:anthropic——API key / setup-token / 复用本机 Claude CLI 凭据三条认证路(装了 Claude Code 的机器可以零配置借用它的登录态,extensions/anthropic/provider-discovery.ts:10-37);openai——API key + ChatGPT OAuth 全套 device-code 流程;clawrouter——不是本地路由算法,而是指向 clawrouter.openclaw.ai服务端代理 provider:一把 key 决定可见模型集与预算,目录从服务端拉、60 秒缓存(extensions/clawrouter/provider-catalog.ts:12-63)。

09会话、压缩与记忆

Claude Code 的记忆活在一个终端会话里;OpenClaw 的 agent 要「活很多年」——所以它有一整套持久身份:转录、压缩、日记、检索索引。

9.1 会话存储:JSONL 转录

目录布局(src/config/sessions/paths.ts:11-38):转录在 ~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl,注册表在同目录 sessions.json(session key → sessionId 映射)。每个 JSONL 文件首行是 header:

src/config/sessions/transcript-header.ts:12
export function createSessionTranscriptHeader(params = {}) { return { type: "session", version: CURRENT_SESSION_VERSION, id: params.sessionId ?? randomUUID(), timestamp: new Date().toISOString(), cwd: params.cwd ?? process.cwd(), }; }

防御性细节:sessionId 白名单正则 ^[a-z0-9][a-z0-9._-]{0,127}$;所有 session 文件路径必须 realpath 收敛在 sessions 目录内,越界直接抛错(防路径穿越,paths.ts:63-74,180-242);序列化遇到 JSON.stringify → undefined 立即 fail-fast,防静默丢转录行(transcript-jsonl.ts:16-29)。转录在逻辑上是树/DAG:改写历史走「分支再追加」(branch-and-reappend)而不是原地改。

9.2 压缩(compaction):触发、分块、防死循环

  • 触发阈值 = 模型上下文窗口 − reserveTokens(预留),预留下限默认 20,000 tokensrc/agents/agent-settings.ts:9)。小窗口模型有钳制保护:floor 被钳到「窗口 − 最小 prompt 预算」,否则 16K 模型会每条 prompt 都判溢出 → 无限压缩循环(agent-compaction-constants.ts:1-12)。
  • 分块摘要src/agents/compaction-planning.ts):目标块 = 窗口 40%(下限 15%),token 估算带 1.2 安全系数;切块绝不拆开 tool_use / tool_result 配对,切完还跑配对修复丢弃孤儿——防 Anthropic 的「unexpected tool_use_id」400(:90-195,378-386);单条超窗口 50% 的巨型消息不进摘要、换占位符 [Large <role> (~NK tokens) omitted from summary](:273-315)。两处 SECURITY 注释:工具结果的 details 与运行时上下文条目绝不进 LLM 摘要
  • 压缩即轮转:压缩生成新 sessionId、换新 JSONL 文件,旧文件按 checkpoint 保留(compaction-session-file.ts:11-36)——历史永远可考古。
  • 时机:prompt 发出前有预检(按字符/token 估算压力,embedded-agent-runner/run/preemptive-compaction.ts);provider 真报溢出后有补救压缩 + 重试,配防死循环守卫。压缩归属三选一互斥(runtime 自动 / OpenClaw safeguard / context-engine 插件全权),防止双压(agent-settings.ts:174-210)。

9.3 cache-ttl 剪枝:压缩之外的轻量层

对应 Claude Code 的 microcompact,但触发条件更聪明——围绕 provider 的 prompt cache 生命周期设计,默认启用

src/agents/agent-hooks/context-pruning/settings.ts:52-69
export const DEFAULT_CONTEXT_PRUNING_SETTINGS = { mode: "cache-ttl", ttlMs: 5 * 60 * 1000, // provider prompt cache 视为过期的 TTL keepLastAssistants: 3, // 最近 3 个 assistant 回合免剪 softTrimRatio: 0.3, // 上下文压力 30% 起软剪(留头尾) hardClearRatio: 0.5, // 50% 起硬清(换占位符) minPrunableToolChars: 50_000, softTrim: { maxChars: 4_000, headChars: 1_500, tailChars: 1_500 }, hardClear: { enabled: true, placeholder: "[Old tool result content cleared]" }, };

原理:prompt cache 还热的时候改历史 = 打破缓存、得不偿失;cache 过期(5 分钟没交互)后,旧工具结果就只剩 token 成本了——这时软剪或硬清才净赚。图片按 8000 字符估价,剪掉换 [image removed during context pruning]

9.4 记忆三层:MEMORY.md + 日记 + SQLite 索引

第 1 层

MEMORY.md

工作区根的长期精华,注入 system prompt。必须是真文件——拒绝 symlinksrc/memory/root-memory-files.ts:41-56)。

第 2 层

memory/YYYY-MM-DD.md 日记

日常追加写。/new 重置后自动带入最近 2 天日记(每文件截 1200 字符)作启动上下文(src/config/types.agent-defaults.ts:91-106)。

第 3 层

SQLite 混合检索

bundled 插件 memory-core 提供 memory_search / memory_get 工具;索引 = FTS5 全文 + sqlite-vec 向量,混合打分 vectorWeight×向量分 + textWeight×文本分,叠加 MMR 去冗余与时间衰减(extensions/memory-core/src/memory/hybrid.ts:32-39 · packages/memory-host-sdk/src/host/memory-schema.ts:7-13)。

Memory flush(压缩前抢救记忆)是点睛之笔:会话逼近压缩阈值(距离 < 4000 token)或转录超 2MB 时,先注入一个额外回合,让模型把「值得长存的内容」append 到当天日记,然后才压缩——压缩丢掉的细节至少精华已经落盘。flush prompt 强制三条安全约束:只写日记文件、只追加不覆盖、MEMORY.md / SOUL.md 等启动文件在 flush 期间一律只读(extensions/memory-core/src/flush-plan.ts:12-27,76-84)。memory-core 还有一套 cron 化的「dreaming」(做梦)整理管线,把日记消化进长期记忆(light/REM/deep 三档;实现细节本文未核实)。

9.5 context-engine:可插拔的上下文管理生命周期

OpenClaw 把「上下文管理」整体抽象成一个可被插件接管的接口(src/context-engine/types.ts:303-456):bootstrap(初始化)/ ingest(逐条吸收)/ assemble(按 token 预算组装上模型的上下文)/ compact / maintain。引擎声明 ownsCompaction:true 时 runtime 的自动压缩被关闭;引擎想改写转录必须通过 rewriteTranscriptEntries() 请求「分支再追加」,磁盘 DAG 的更新权始终在 runtime 手里。注册中心还带隔离机制:引擎连续出错会被 quarantine(隔离),防止一个坏插件把所有会话拖死(src/context-engine/registry.ts)。bundled 的 active-memory 插件就是一个 context-engine 实现(行为细节未核实)。

10UI 层:Control UI(Web)与 TUI(终端)

Claude Code 的 UI 是产品本体;OpenClaw 的 UI 只是 Gateway 的又一批 WebSocket 客户端——「UI 无特权」是这里的关键词。

10.1 Control UI:Vite + Lit 的单页应用

技术栈是 Lit(Web Components)+ Vite,不是 React/Vue(ui/package.json:12-27);由 Gateway 进程直接托管在 http://host:18789/。它连 Gateway 用的就是第 3 章那套 WS 协议——没有任何私有后门 API。功能面 30 个路由:chat(WebChat)、agents、channels、config、sessions、usage、logs、skills(含 skill 编写工坊)、cron、nodes、debug…(ui/src/app-route-paths.ts:4-31)。内嵌终端用的是 ghostty-web(Ghostty 终端的 WASM 版)。

浏览器的身份认证颇为讲究:token 之外还有 Ed25519 设备身份——安全上下文(HTTPS/localhost)下生成持久密钥对,对「deviceId + clientId + role + scopes + 时间戳 + token + 服务端 challenge nonce」整体签名(ui/src/api/gateway.ts:447-479)。新浏览器首连会被 1008 拒绝、等 openclaw devices approve 批准(回环直连自动批);握手成功后 Gateway 签发 deviceToken 存 localStorage 供免密重连(ui/src/api/gateway.ts:820-827 · docs/web/control-ui.md:38-70)。

WebChat 的两条数据路径值得注意:session JSONL 是持久权威转录;Gateway 推的实时事件只是投递投影(非权威)。历史拉取有界化(长文截断 + 占位符,可按 messageId 补拉全文);chat.send 带幂等键防双发(docs/web/webchat.md:24-47)。

10.2 TUI:pi-tui + 双后端

终端 UI 用 @earendil-works/pi-tui(pi-mono 家族里唯一还以外部依赖存在的成员——组件式终端 UI 库,非 Ink,src/tui/tui.ts:6-15)。架构上是 TuiBackend 接口的双实现(src/tui/tui-backend.ts):默认走 Gateway 后端——就是又一个 WS 客户端,调 chat.send / chat.historytui --local 则走嵌入式后端,进程内直接跑 agent 运行时,完全不需要 Gateway。对比 Claude Code 那个 5000 行的 REPL:OpenClaw 的 TUI 主循环只有 1707 行,因为渲染、流式组装、审批交互大都复用 Gateway 侧的既有语义。

10.3 Canvas / A2UI:给设备推「活的界面」

Canvas 是个 bundled 插件(extensions/canvas/),给已配对的节点设备(手机/Mac)提供实时可视化画布。agent 拿到一个 canvas 工具:present / navigate / eval / snapshot(截图给自己看渲染结果)/ A2UI 推送。A2UI 是 Google 的声明式 agent-to-UI 协议——agent 推 JSONL 组件流,设备端渲染(extensions/canvas/index.ts:14-46)。这是「agent 的输出不止是文字」方向上最激进的尝试。

11节点(Nodes)体系:把手机变成 agent 的外设

node = 连到 Gateway 的伴生设备(iPhone / Android / Mac / 无头 CLI),给 agent 暴露摄像头、屏幕、定位、通知等命令面。官方定性:「Nodes are peripherals, not gateways」——节点是外设,不是网关。

11.1 连接与双层配对

node 复用同一个 WS 协议连 Gateway,connect 帧差异只在 mode: NODE、role:"node",外加能力声明(src/node-host/runner.ts:280-296):

src/node-host/runner.ts:280-296(节选)
const client = new GatewayClient({ url, token, password, clientName: GATEWAY_CLIENT_NAMES.NODE_HOST, mode: GATEWAY_CLIENT_MODES.NODE, role: "node", scopes: [], caps: ["system", ...pluginNodeHost.caps], commands: [ ...NODE_SYSTEM_RUN_COMMANDS, // system.run 系列 ...NODE_EXEC_APPROVALS_COMMANDS, ...pluginNodeHost.commands, // 插件注册的节点命令(canvas.* 等) ], deviceIdentity: loadOrCreateDeviceIdentity(), // Ed25519 设备身份 });

配对是双层的:设备配对(device pairing)门禁 WS 握手本身;节点配对(node.pair.*)跟踪该节点被批准的命令面。审批要求随声明的命令自动升级:不带命令 → 普通配对权限即可批;带非 exec 命令 → 需要写权限;带 system.run(能在设备上跑 shell)→ 需要管理员权限(docs/nodes/index.md:22-38)。

11.2 system.run 的防篡改设计

agent 要在你手机/Mac 上跑命令时,审批在设备侧强制执行,且有 TOCTOU(检查时与使用时不一致)防护:审批前先生成规范化的执行计划(systemRunPlan),批准后 Gateway 转发的是存储的 plan 而不是调用方事后可改的 command/cwd;直接执行的脚本绑定具体文件、执行前文件变了即拒绝(docs/nodes/index.md:50-53 · 实现 src/node-host/invoke-system-run-plan.ts)。能力清单:camera(拍照入上下文)、canvas(A2UI 渲染)、screen、location、notifications、语音唤醒等(docs/nodes/ 各页;iOS/Android App 本体不在本仓库,端上实现未核实)。

12扩展生态:插件、Skills、Hooks、MCP、Cron

Claude Code 的扩展收敛为「注入文本」或「注册工具」两种本质;OpenClaw 的插件 API 宽得多——它是在扩展一台服务器,而不是一个会话。

12.1 插件系统:manifest + 同步 register

插件从三类根目录发现:安装包内 extensions/(141 个 bundled)、全局 ~/.openclaw/extensions/、工作区 <workspace>/.openclaw/extensions/src/plugins/roots.ts:18-28),还兼容 Claude / Codex / Cursor 的插件 bundle 布局。每个插件必须带 openclaw.plugin.json manifest——不执行插件代码即可读它做配置校验(含严格 JSON Schema 的 configSchema、能力归属快照 contracts)。安装来源 npm / git / ClawHub(官方注册表),装前有安全扫描(src/plugins/install-security-scan.ts)。

生命周期最有意思的是注册窗口设计:

src/plugins/loader.ts:630-645(register 必须同步)
function runPluginRegisterSync(register, api): void { const guarded = createGuardedPluginRegistrationApi(api); try { const result = register(guarded.api); if (isPromiseLike(result)) { void Promise.resolve(result).catch(() => {}); throw new Error("plugin register must be synchronous"); } } finally { guarded.close(); // 注册窗口关闭,此后 api 上大部分方法失效 } }

register(api) 必须同步返回;api 是个 Proxy 包装的「守卫对象」,注册窗口一关就失效——插件不能在运行期偷偷往注册表里塞东西。注册面超过 30 种:registerTool / registerChannel / registerProvider / registerGatewayMethod / registerCli / registerHttpRoute / registerContextEngine / registerAgentHarness / registerCompactionProvider / registerMemoryCapability…(src/plugins/api-builder.ts:20-95)。TS 源码插件经 jiti 即时编译加载;SDK import 被 alias 重写到宿主内的 plugin-sdk。

12.2 Hooks:两套体系

  • 插件 hooks(进程内,typed)api.on("before_tool_call", handler, {priority, timeoutMs}),可阻断/改写/要求审批工具调用;事件覆盖 before_agent_start / before_prompt_build / before_model_resolve / before_tool_call / before_agent_reply / before_agent_finalize 等,按 priority 降序串行(docs/plugins/hooks.md:10-63 · src/plugins/hooks.ts)。其中 before_agent_finalize 能要求 agent「重写最终回复」(带副作用检测与最大重写次数,run/attempt.ts:3465-3575)——这是比 Claude Code 的 Stop hook 更内嵌的干预点。
  • 目录式 hooks(HOOK.md):操作员安装的脚本目录(HOOK.md + handler 模块),响应 /newagent:bootstrapgateway:startup 等事件;bundled 自带 5 个(boot-md、session-memory、compaction-notifier…)。加载 workspace hook 代码进 Gateway 进程时会打「trusted local code」警告(src/hooks/loader.ts:49-60)。

12.3 Skills:与 Claude Code 同一物种

Skill = 目录 + SKILL.md(frontmatter 带 OpenClaw 专有 metadata:always / requires 依赖声明 / install 规格 / os),三源优先级 workspace > managed(~/.openclaw/skills)> bundled(src/skills/loading/frontmatter.ts:191-203)。合格 skills 被格式化进 system prompt(路径做 ~ 压缩省 token);skill 自带的可执行文件走 exec-approvals 信任链。分发走 ClawHub:搜索/下载/sha256 校验/风险确认,装进 workspace 并写来源溯源文件(src/skills/lifecycle/clawhub.ts:1-40)。Control UI 还有个 skill 编写工坊页面,配套 skill_workshop 工具让 agent 自己写 skill(沙箱内禁用)。

12.4 MCP:先当 server,后当 client

与 Claude Code 相反的侧重:OpenClaw 作为 MCP server 更成熟——src/mcp/ 提供 stdio MCP server,把插件注册的工具暴露给外部 MCP 客户端(典型场景:ACP 会话里的 Claude Code 反过来调 OpenClaw 的 memory_recall)。作为 MCP client 则走 agent bundle 配置:外部 MCP server 在运行时物化成 mcp__server__tool 命名的工具(src/agents/agent-bundle-mcp-tools.ts;完整配置 schema 与传输种类未核实)。

12.5 Cron:agent 的生物钟

src/cron/ 是定时任务子系统,四种 schedule:at(一次性)/ every / cron(标准表达式,croner 解析 + LRU 缓存)/ on-exit(Gateway 退出时触发);三种 payload:systemEvent(往主会话注入系统事件)/ agentTurn(独立 agent 回合)/ commandsrc/cron/types.ts:8-29,238-298)。落库 SQLite;调度是单 setTimeout 指向最近到期的 job,重启补跑、整点错峰防雷群。隔离 job 的执行是一条完整编排:独立 session key → 模型预检 → skills 快照 → 执行 → 按 CronDeliveryPlan 把结果投递到指定渠道 → 会话收割(reaper 在 finally 里保证执行)。目录下有 8+ 个以 issue 号命名的回归测试——看得出这是踩坑大户。心跳(heartbeat)机制让 agent 定期「醒来」读 HEARTBEAT.md 自查——这是「always-on 助手」和「按需 CLI」的分水岭。

13文件索引速查

按「我想看 X」检索,路径相对仓库根。

我想看…去这里
进程入口 / 快路径 / respawnopenclaw.mjs:773 · src/entry.ts:51 · entry.respawn.ts:75 · entry.compile-cache.ts:148
CLI 主流程 / 裸命令分派src/cli/run-main.ts:841,297 · program/core-command-descriptors.ts:10
Gateway 启动 / 关闭src/gateway/server.impl.ts:524,1845 · server.ts:1-36(懒加载壳)
WS 协议帧 / 握手 / 鉴权packages/gateway-protocol/src/schema/frames.ts:30,169 · src/gateway/server/ws-connection.ts:387 · auth.ts:533
配置加载 / 热重载src/config/io.ts:1982 · paths.ts:24,277 · server.impl.ts:1736
daemon(launchd/systemd/schtasks)src/daemon/service.ts:8 · constants.ts:4 · launchd.ts:307 · systemd.ts:67
Telegram 入站 / spool / 授权extensions/telegram/src/bot-handlers.runtime.ts:3267,3510 · telegram-ingress-spool.ts:17 · polling-session.ts:610
渠道回合内核src/channels/turn/kernel.ts:676,523
路由 / session keysrc/routing/resolve-route.ts:611,736 · session-key.ts:222
会话并发(steer/queue)src/auto-reply/reply/agent-runner.ts:1263,1316 · get-reply-run.ts:1232
block 回复流水线 / 分块src/auto-reply/reply/block-reply-pipeline.ts:106 · chunk.ts:1 · extensions/telegram/src/send.ts:705
★ agent 主循环packages/agent-core/src/agent-loop.ts:172,305,446 · agent.ts:374
嵌入式 runner / 单次尝试src/agents/embedded-agent-runner/run.ts · run/attempt.ts · sessions/sdk.ts:406
System prompt / 工作区文件src/agents/system-prompt.ts:680,69 · workspace.ts:32 · bootstrap-files.ts:292
工具装配 / 策略过滤src/agents/agent-tools.ts:384,1088 · openclaw-tools.ts:425
exec / 审批 / processsrc/agents/bash-tools.exec.ts:1304 · bash-tools.schemas.ts:13 · bash-tools.exec-approval-request.ts:1
子 agent spawnsrc/agents/tools/sessions-spawn-tool.ts · subagent-spawn.ts
pairing / DM 策略src/pairing/pairing-store.ts:33,614,714 · src/security/dm-policy-shared.ts:170
安全审计 / 修复src/security/audit.ts · fix.ts:394 · src/gateway/server-runtime-config.ts:148
Provider registry / 流式packages/ai/src/api-registry.ts:76 · stream.ts:24 · providers/anthropic.ts:490
auth profile 轮换 / 冷却src/agents/auth-profiles/order.ts:244,439 · usage.ts:427
模型 fallback 链src/agents/model-fallback.ts:982,1397,1252
会话转录 / 路径安全src/config/sessions/paths.ts:11,180 · transcript-header.ts:12 · transcript-jsonl.ts:16
压缩 / 剪枝src/agents/compaction-planning.ts:90 · agent-settings.ts:9,174 · agent-hooks/context-pruning/settings.ts:52
记忆(flush / 检索)extensions/memory-core/src/flush-plan.ts:12 · memory/hybrid.ts:32 · src/memory/root-memory-files.ts:41
context-engine 接口src/context-engine/types.ts:303 · registry.ts
Control UI 客户端 / 设备签名ui/src/api/gateway.ts:493,447 · app-route-paths.ts:4
TUIsrc/tui/tui.ts:6 · tui-backend.ts · gateway-chat.ts
节点 host / system.run 防篡改src/node-host/runner.ts:280 · invoke-system-run-plan.ts
插件加载 / 注册面src/plugins/loader.ts:630,560 · api-builder.ts:20 · roots.ts:18 · manifest.ts
Skills / ClawHubsrc/skills/loading/frontmatter.ts:191 · workspace.ts:36 · lifecycle/clawhub.ts:1
MCP server / clientsrc/mcp/plugin-tools-serve.ts:1 · src/agents/agent-bundle-mcp-tools.ts
Cronsrc/cron/types.ts:8,238 · schedule.ts:1 · store.ts:39 · isolated-agent/run.ts

写在最后:如果说 Claude Code 展示了「一个 agent 循环可以多精致」,OpenClaw 展示的是「把同一个循环放进真实世界要垫多少层」——消息可靠性、入站身份、会话并发、多模型容灾、常驻记忆、设备联邦。两份解剖对照着读,你就同时拥有了 agent 产品的「内核视角」和「系统视角」。