Agent 架构文档 · Markdown

OpenClaw Agent 架构全景讲解

以 Agent 的生命脉络 为主线,讲清楚 OpenClaw 这套"长在你电脑上、活在你 IM 里"的 个人 AI 助手是怎么被定义、被唤起、怎么思考、怎么记忆、怎么开口、怎么协作的。 所有概念(Gateway / Agent / Skills / Souls / Hooks / Nodes / Canvas / MCP) 都将从"它支撑了 Agent 的哪

来源文件:openclaw-agent-architecture.md · 阅读时间 38 分钟

Agent 的生命脉络 为主线,讲清楚 OpenClaw 这套"长在你电脑上、活在你 IM 里"的 个人 AI 助手是怎么被定义、被唤起、怎么思考、怎么记忆、怎么开口、怎么协作的。 所有概念(Gateway / Agent / Skills / Souls / Hooks / Nodes / Canvas / MCP) 都将从"它支撑了 Agent 的哪个能力"这个角度切入,而不是按模块清单堆叠。

适合:第一次读 OpenClaw 源码 / 想把它当 SDK 拿来改 / 想拿它和别家 agent 框架对位时心里有数。

文档对齐基准:本版基于 openclaw/openclaw@mainpackage.json version 2026.5.20, 推荐 Node 24,最低 Node 22.19+)。2026.05.21 复核:拿 docs.openclaw.ai 与仓库 README 重新对位,标注差异点。 相比上一版(2026.05.11)的主要变化集中在 §0.6(新增子系统总览)、§1.3(workspace 文件多了 USER.md/HEARTBEAT.md/BOOTSTRAP.md/BOOT.md/DREAMS.md)、§4.1(bundled tools 现在带媒体生成/ Web 抓取/计划工具)、§5.2(bundled hook 实际有 5 个,session-memory 触发时机改为 command:new/reset 且文件名走 LLM slug)、§6.1(通道适配 21 个、Canvas 改为同端口能力路径而非独立 18793 端口)。


#目录

  1. 写在前面:项目身份与核心矛盾 0.5. 一张图看懂:OpenClaw = Model + Harness 0.6. 2026.05 新增子系统总览
  2. Agent 是什么 —— Workspace + IDENTITY + SOUL/AGENTS/TOOLS
  3. Agent 怎么活起来 —— Gateway 收信、路由到 Agent、起 Run
  4. Agent 怎么思考 —— 单 Run 串行的 ReAct Loop + Sub-Agent 委派
  5. Agent 怎么行动 —— Skills / Bundled Tools / MCP / Plugins 四类工具
  6. Agent 怎么记忆 —— MEMORY.md + 日记 + Active Memory 子代理
  7. Agent 怎么开口 —— Channels、Nodes、Canvas、WebSocket 事件总线
  8. Agent 的外脑 —— 模型无关层与 OpenRouter / iblai-router 接入
  9. 关键设计权衡(架构师视角)
  10. 附录:组件地图、关键文件、源码索引

#零、写在前面:项目身份与核心矛盾

#项目消歧

"OpenClaw" 这个名字字面有歧义(同名旧项目是一个 Claw 系列 2D 平台游戏的开源 fork, 跟 AI 一点关系没有)。但我们这次说的 OpenClaw 是一个 2025 年下半年起步、2026 年初爆红 的开源个人 AI agent

  • 官方仓库:https://github.com/openclaw/openclaw
  • 官方站:https://openclaw.ai/、文档站:https://docs.openclaw.ai/
  • 历史名字:Clawdbot → Moltbot → OpenClaw(因 Anthropic 商标投诉而连续改名)
  • 维护者:Peter Steinberger(独立开发者,后聚集起 ClawHub / NemoClaw / OpenClaw-RL 等周边)
  • 协议:MIT
  • 主要语言:TypeScript(runtime)+ macOS/iOS/Android 原生 companion

多 agent 协作。它解决的是一件极具个人色彩的需求:

"给我一个自己的 24 小时在线助手,能从 WhatsApp / iMessage / Telegram 任意聊天软件 里被我喊起来、能调本地工具能上网能写文件能用我的日历邮箱、最好还能在 macOS / iOS 上用语音说话给我听。"

#三条核心矛盾

矛盾 OpenClaw 的回答
想随时被找到(多 IM 接入),但 LLM 又是单线程的 Gateway 守着所有 channel,按 binding 路由到 agent;agent 内部 Run 串行(session lane 锁)
要 24×7 跑在用户机器上,但用户机器不是服务器 全套以 macOS 菜单栏 + iOS/Android 节点为 first-class,CLI 一条 openclaw onboard 起家
生态要开放(5400+ skills),但本地敏感数据要安全 Skill 走文件 + frontmatter,需要的能力声明在 metadata.openclaw.requires;DM pairing、hook token 与 gateway token 隔离、可选 Docker sandbox

后面每一节都会回到这三条矛盾,看具体的设计是怎么回应的。

#跟 Claude Code / Cursor / Cline / Aider / Cody 的差异(一句话)

OpenClaw 不是一个 IDE 协作的 coding agent;它把"agent" 当成一个长跑的个人助手进程, 聊天软件是它的输入设备,本地文件 / 应用 / Canvas 是它的输出设备。Coding 只是它能装进 来的众多 Skill 之一,而不是核心场景。这个定位差异会贯穿后面所有章节。


#零·五、一张图看懂:OpenClaw = Model + Harness

业界视角:Anthropic / Cursor / Codex / Aider / Cline 用的是同一批底层模型, 实际表现差距却很大。差的不是模型,是"模型之上的外壳(Harness)"——指令、工具、 基础设施、可观测性这四层共同决定了 agent 的真实能力上限。

套到 OpenClaw 上有趣的是:它的 Harness 不是为了赢 SWE-bench,而是为了在你电脑上 24 小时活着——所以指令层做了 SOUL/SKILL 双轨可发布、基础设施层把"手机当感官"做成 Nodes、记忆层用纯 markdown 文件代替 vector DB。下面按"4 层 + 8 支柱"反向索引。

#0.5.1 四层 Harness 结构

┌──────────────────────────────────────────────────────────────────────┐
│ Agent  =  Model (LLM)  +  Harness (OpenClaw 四层外壳)                 │
│           └ 透传 OpenRouter/Ollama   └ 单进程 Gateway 吃下所有 IO     │
└──────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────┐
│ ① 指令层  │ IDENTITY.md(名字/emoji/avatar)              → §1.1      │
│           │ SOUL.md(可发布的人格,ClawHub 流通)         → §1.1      │
│           │ AGENTS.md / TOOLS.md(协作规则 + 工具说明)    → §1.1      │
│           │ openclaw.json bindings 路由表                 → §1.2      │
├──────────────────────────────────────────────────────────────────────┤
│ ② 能力层  │ Bundled Tools (~25 个,TS in-proc,含媒体生成) → §4.1      │
│           │ Skills (5400+ 社区,仓库内置 53)              → §4.1      │
│           │ MCP servers(stdio 已实现;SSE/HTTP via plugin) → §4.1      │
│           │ Plugins(registerTool 主路径 + bundle entry)  → §4.1      │
│           │ 6 层加载优先级 + session 启动打快照            → §4.1      │
├──────────────────────────────────────────────────────────────────────┤
│ ③ 基础设施│ Gateway daemon (单进程, 127.0.0.1:18789)      → §2.3      │
│           │ Canvas(与 Gateway 同端口,/__openclaw__/canvas 路径)→ §6.4 │
│           │ Nodes(iOS/Android/macOS WS 客户端)          → §6.3      │
│           │ 21 个 Channel adapters(extensions/<name>)   → §6.2      │
│           │ session lane(process/lanes.ts CommandLane)  → §3.2      │
│           │ Docker sandbox(sub-agent 默认隔离 + profile)→ §4.3      │
│           │ Cron(agent 自己的闹钟,isolated agent 模式) → §6.6      │
│           │ Talk / RealtimeTranscription(实时语音通道)  → §0.6      │
│           │ Proxy-Capture(调试代理,自带 CA)            → §0.6      │
├──────────────────────────────────────────────────────────────────────┤
│ ④ 可观测  │ WebSocket 事件总线(统一帧格式 + JSON Schema) → §6.5      │
│           │ agent:lifecycle / tool:start-end / cron:fired              │
│           │ Run-level tokens 统计(不是计费系统)          → §7.2      │
│           │ ⚠ trace / cost 看板 / 失败率聚合 — 不存在                  │
└──────────────────────────────────────────────────────────────────────┘

#0.5.2 八支柱对照表

# Harness 支柱 一句话定义 OpenClaw 的实现 对应章节
01 FS + Git state 必须活在 context 之外 workspace 目录就是 agent 的私人办公室:IDENTITY/SOUL/AGENTS/TOOLS.md + MEMORY.md + memory/YYYY-MM-DD.md + sessions/*.jsonl 全是文件,可 cat、可 git、可分享 §1.3 / §5.1
02 Bash 通用工具 "随用随装",避免 token 爆炸 Skills 6 层加载优先级(workspace → .agents → ~/.agents → ~/.openclaw → bundled → extraDirs)+ frontmatter requires.env/bins/os gating,加载时静默跳过 + 一条诊断 §4.1
03 沙箱 + 子工具 行动可隔离 + 可自检 Docker sandbox 默认 sub-agent 用(只放 bash/read/write/edit/sessions,禁 browser/canvas/nodes/cron);hook token ≠ gateway token 强制隔离 §4.3 / §8.6
04 记忆 + 搜索 跨 turn / 跨 session 拿历史 三层结构:MEMORY.md(永久事实)+ memory/YYYY-MM-DD.md(日记按天分桩)+ session.jsonl;全是 markdown 文件,不进任何数据库 §5.1 / §5.3
05 对抗 Context Rot 长对话也别糊 command:new / command:reset 触发 session-memory 写记忆 → session:compact:before/after 包住模型驱动的 compact;session.idle / session.maxAge 参数化兜底(无固定 daily 时间) §5.2 / §5.4
06 长程执行 多 phase 任务不跑偏 session lane 锁让单 session 内 Run 严格串行(append-only transcript 不能并发写);要并发只能显式 spawn sub-agent(独立 session + uuid + 默认 isolated 上下文) §3.2 / §3.3
07 Hooks 强制层 失败转成永久规则 hook 链就是它的软件总线:message:received/transcribed/preprocessed / PreToolUse / PostToolUse / session:compact:before/after / command:new;每个 hook = HOOK.md + handler.ts 可拦截/改写/丢弃 §2.4 / §3.1
08 规划 + 工具选择 别什么都试,先想再调 Active Memory 子代理:在主 LLM 调用前阻塞跑一个隐藏 sub-agent(只有 memory_search/get 工具),把记忆 summary 强制注入 system 段——不信任主 LLM 会"自己想起来去查记忆" §5.3

#0.5.3 当前架构的短板(按 Harness 视角看出来的)

OpenClaw 的 Harness 是专门为"个人 24×7 助手"这个非主流场景设计的,所以它的短板都不是 "设计缺陷",而是"定位选择的副作用"——但用 Harness 通用视角看,这些副作用真实存在:

  • 单用户单设备硬绑定:openclaw.json 在每台 host 上一份、Gateway 监听 127.0.0.1、 session lane 锁是本机文件锁——这套架构不支持"一个 agent 在两台机器上接力"。想多设备 访问只能靠 Tailnet 把 18789 暴露出去,或显式 pairing;workspace 在两台机器间不会 自动同步(要自己上 git/iCloud Drive/Dropbox)。
  • IM 协议适配脆弱:22+ 个 channel adapter 里至少一半依赖非官方协议——WhatsApp 走 Baileys(多账号实例反向工程),iMessage 走 BlueBubbles 或 macOS 私有桥。这些协议 随平台升级容易断,adapter 维护成本高,断了 agent 就"听不见"——这是 OpenClaw 用户群里 反复反馈的痛点。
  • 多设备/多 Gateway 同步靠手动:单进程 daemon "exactly one Gateway per host" 是它的设计宣言,但代价是没有跨 host 的 agent 协作协议;如果你想让"家里 Mac 的 agent 和公司 Mac 的 agent 共享一份 MEMORY.md",得自己用 git / Syncthing 拉同步。Sub-agent 也只是 同进程内的另一个 session,不是跨机器的远程 agent。
  • 没有正式的 cost / cycle observability:每次 Run 会统计 tokens 写到 transcript, WebSocket 事件总线推 lifecycle / tool / cron 事件——但没有"按 agent / 按 SOUL / 按 SKILL 维度的 cost 聚合看板",没有"哪个 skill 调用失败率最高"的分析视图,没有跨 session 的 trace。Harness 视角下的"可观测层"对 OpenClaw 来说基本是空白——这跟它"个人助手不是 企业 agent"的定位匹配,但任何想拿它做团队/小公司部署的用户都会立刻撞上这堵墙。

值得肯定的是:把可观测层做得这么薄,反而给了社区扩展空间——yoloshii/ClawMem 把 "持久记忆 + 混合 RAG"做成 hook 包,iblai/iblai-openclaw-router 把"多 provider 计费" 做成外置路由。Harness 的可观测性最终可能不是核心仓库的事,而是社区 hook 生态的事。


#零·六、2026.05 新增子系统总览(与"个人助手"定位的扩张)

这一节不是叙事章节,而是给读者一份"目前 src/ 多了哪些之前没听过的目录"的速查—— 它们都是为了支撑 OpenClaw "做一个长期共生伙伴"的产品野心,而不是 coding agent。 后续章节按需引用这些子系统,本节只做一次性扫盲。

目录 出现的原因(产品意图) 关键文件
src/talk/ 实时语音对话:电话/麦克风做主交互通道,不再只是 IM 文字。TalkProvider / TalkbackRuntime 跨 STT/LLM/TTS 编排 provider-types.tsagent-talkback-runtime.ts
src/realtime-transcription/ 给 Talk 用的实时 STT provider 抽象(Deepgram/Azure/Whisper 都能挂) provider-registry.ts
src/auto-reply/ "我不在/勿扰" 类的自动回复策略,独立于主 agent loop model-runtime.tstypes.ts
src/tasks/ 任务流:把多步任务建模成"detached task" 自己跑(不占主 session 的 lane) task-flow-registry.tsdetached-task-runtime.ts
src/commitments/ 承诺跟踪:从对话里抽取 "我答应你周三给报告" 这种承诺,到点提醒。Agent 不再靠 cron 凭空起 extraction.tsruntime.tsstore.ts
src/context-engine/ 上下文工程:把"该塞什么进 system prompt"从 ad-hoc 拼字符串升级为 ContextEngineDelegate + registry delegate.tsregistry.ts
src/crestodian/ 对话审计:dialogue / audit service,给"长期助手要回放、要解释"的需求兜底 dialogue.tsaudit.ts
src/trajectory/ 把整段 Run 导出成 trajectory bundle(JSONL),用于离线分析或喂回 RL types.tsexport.ts
src/proxy-capture/ 一个内置的 HTTPS 代理 + CA,给 agent 调试外部 API 用("我让 agent 访问的 API 到底回了啥") proxy-server.tsca.ts
src/memory-host-sdk/ & packages/memory-host-sdk/ 把"记忆"从单 markdown 扩成可被第三方 host 的 engine + dreaming 流程 engine-storage.tsdreaming.ts
src/model-catalog/ 模型清单 / manifest 规划:定价、能力(reasoning/vision/audio)、配额,独立于 provider 适配 manifest-planner.ts
src/provider-runtime/ provider 调用的统一 retry / circuit / fallback 包装 operation-retry.ts
src/node-host/ 节点侧"我有权限跑什么"的策略层 + SystemRunInvoker exec-policy.tsinvoke-system-run.ts
src/pairing/ 设备配对协议(手机←→Gateway)、challenge-response pairing-store.tschallenge.ts
src/secrets/ 凭证/环境变量管理("哪个 skill 需要哪个 secret 已声明") plan.ts
src/status/ UI 看到的 "agent 现在在干嘛" 状态文本队列 status-text.types.tsstatus-queue.runtime.ts
src/plugin-state/ 插件持久化状态(per-install scope,不依赖文件系统约定) plugin-state-store.ts
src/web-fetch/ & src/web-search/ web fetch / search 升级成自己的 runtime(提供商可换) runtime.ts
src/link-understanding/ URL 元数据抽取("用户贴了个链接"自动展开) detect.tsapply.ts
src/media*/ (media, media-generation, media-understanding) 媒体管理(本地资源管理 + 生成 + 理解的三件套) media/store.runtime.ts
src/image-generation/ / music-generation/ / video-generation/ / tts/ 四个独立的生成 provider registry。配合 bundled tools 里的 image-generate / music-generate / video-generate / tts provider-registry.ts
src/acp/ Agent Control Protocol 类型 + persistent bindings(用于 ACP 兼容客户端如 acpx) types.tspersistent-bindings.ts
packages/plugin-package-contract/ 插件包合约——区别于运行时 plugin-sdk,定义"打包/发布"侧的契约 package.json 内 contract 类型

怎么用这张表:当后续章节提到 "Active Memory 子代理"、"trajectory 导出"、"语音 Talk Mode" 时, 回这里看一眼对应目录的实际文件位置。OpenClaw 在 2026 Q1 之后大幅扩张了"非 IM 文字"输入输出 (语音/媒体生成/承诺/任务流),所以 src/ 目录长得比上一版讲解多了快一倍——但核心 agent loop 和 Gateway 的设计骨架没变


#一、Agent 是什么 —— Workspace + IDENTITY + SOUL / AGENTS.md / TOOLS.md

关键认知:在 OpenClaw 里,"一个 Agent" ≠ 一个进程,而是一份 workspace 目录 + 一份 IDENTITY/SOUL 文档 + 一份 binding 路由表。 同一个 Gateway 进程下挂多个 agent, 每个 agent 有自己的 workspace、自己的会话历史、自己的人格。

#1.1 Agent 的"身份证":四类描述文件

OpenClaw 的 agent 配置不是一行 YAML,而是一组分工明确的 markdown 文件,注入到模型 system prompt 的不同位置:

文件 角色 类比
SOUL.md 人格/价值观/语气/拒绝什么——agent 的"灵魂" 演员的角色设定本
AGENTS.md 多 agent 协作的根规则、子目录 scope(兼容符号链接 CLAUDE.md 公司员工手册
TOOLS.md 当前 workspace 里能用的工具白名单/用法 工具间的钥匙板
IDENTITY.md 名字、emoji、avatar、theme(外观) 工牌
USER.md(新增) 用户画像/喜好的稳定快照(被注入 system 段,给 agent 一个"主人是谁"的最小信息) 主人档案
HEARTBEAT.md(可选) 周期心跳要做的事——配合 infra/heartbeat-runner.ts 周期触发 巡店清单
BOOTSTRAP.md(可选) 首次启动一次性仪式(创建文件、问 API key 等) 入职单
BOOT.md(可选) 每次 Gateway 启动时执行的指令(需启用 boot-md bundled hook) 开机自检
DREAMS.md(可选,非官方) dreaming sweep 的累积摘要(配合 memory-host-sdk/dreaming.ts),供人类回看;docs.openclaw.ai/concepts/agent-workspace 当前列表里没列 DREAMS.md,它是 SDK / 社区扩展层把"dreaming 总结"落盘的约定(待核实是否升入主流程) 梦境日记

这些文件在 agent 启动时按顺序读出并按段拼到 system prompt,组合成 agent 的初始人格。 官方 docs.openclaw.ai/concepts/agent-workspace 列出的"workspace 根文件"共 9 个 (IDENTITY / SOUL / AGENTS / TOOLS / USER / MEMORY / HEARTBEAT / BOOT / BOOTSTRAP), DREAMS.md 不在该列表内,是社区/SDK 层的扩展约定(见上表脚注)。 根 markdown 从最早的 4 个扩到现在的 9 个——背后逻辑是:长跑 agent 比单 session agent 需要更多"出厂资料",把"用户画像 / 心跳 / 启动仪式"都正交化成独立 markdown,让用户 和社区都能改。各文件之间的拼接顺序仓库目前没有文档化,按 src/agents/harness/ 实现合理推断。 其中 SOUL.md 是最有意思的——它被独立设计成一个可发布、可复用、可在 ClawHub 上交易 的最小单元(参见 ClawHub docs/soul-format.md):

# SOUL.md 顶部 frontmatter(来自 clawhub 的 spec)
---
name: "lobster-mom"
description: "An overprotective mom who happens to be a lobster"
version: "1.2.0"
tags: ["personality", "lobster", "caring"]
---
# 下面是大段 markdown,描述这个角色应该怎么说话、怎么拒绝、什么时候转移话题

重要洞察:SOUL 是"可分发的人格",SKILL 是"可分发的能力",两者是 OpenClaw 生态 给社区交付的两个最基础的乐高块。一个 agent ≈ workspace + 1 个 SOUL + N 个 SKILL + M 个 channel binding。

#1.2 Agent 的"户口":openclaw.json

所有 agent 在中央配置文件 ~/.openclaw/openclaw.json 里登记:

{
  "agents": {
    "list": [
      {
        "id": "main",
        "workspace": "~/.openclaw/workspace/main",
        "skills": ["browser", "canvas", "memory-search"],
        "model": "openrouter/anthropic/claude-sonnet-4.5"
      },
      {
        "id": "ops",
        "workspace": "~/.openclaw/workspace/ops",
        "skills": ["nodes", "shell", "kubectl"]
      }
    ],
    "defaults": {
      "skills": ["bash", "read", "write", "edit"]
    }
  },
  "bindings": [
    { "channel": "telegram", "accountId": "ops_channel", "agentId": "ops" },
    { "channel": "whatsapp", "accountId": "*",          "agentId": "main" }
  ]
}

这套配置由 openclaw agents add / bind / unbind / set-identity 等子命令来管。 理解 openclaw.json 的关键点:

  • agents.list[].skills 是这个 agent 能见到的工具白名单(基于 agents.defaults.skills 叠加)
  • bindings 决定从哪条聊天通道进来的消息会落到哪个 agent——多 agent 路由的全部秘密
  • 每个 agent workspace 物理上是一个目录,里面有 IDENTITY.md、本地 skills、session 转写、memory 文件,workspace 是隔离单元

#1.3 Workspace = Agent 的"私人办公室"

workspace 目录的典型结构:

~/.openclaw/workspace/main/
├── IDENTITY.md           ← 名字/emoji/avatar
├── SOUL.md               ← 人格(可选,没有则继承默认)
├── AGENTS.md             ← 协作规则(兼容 CLAUDE.md 软链)
├── TOOLS.md              ← 工具说明
├── USER.md               ← 用户画像(2026.04 新增)
├── MEMORY.md             ← 长期记忆(详见第五章)
├── HEARTBEAT.md          ← 心跳清单(可选,可被 heartbeat-runner 触发)
├── BOOTSTRAP.md          ← 首次启动一次性仪式(可选)
├── BOOT.md               ← Gateway 启动时执行(可选,需 boot-md hook)
├── DREAMS.md             ← dreaming sweep 摘要(可选)
├── memory/
│   ├── 2026-05-12.md           ← 当日日记
│   └── 2026-05-13-1830-foo.md  ← 由 session-memory hook 写入,文件名带 LLM 生成的 slug
├── skills/               ← workspace 私有 skills
│   └── my-custom-skill/
│       └── SKILL.md
├── sessions/             ← 会话转写(session.jsonl,含 sub-agent session)
├── hooks/                ← workspace 私有 hooks(覆盖 bundled)
└── .agents/              ← project-scoped agent 配置 / skills / hooks

跟 Claude Code 里 .claude/CLAUDE.md 那一套是同一个心智模型,但层级换了名字:

Claude Code OpenClaw
~/.claude/ ~/.openclaw/
<project>/.claude/ <workspace>/.agents/
CLAUDE.md AGENTS.md(且支持 CLAUDE.md 作为同名 sibling 软链)
.claude/agents/ agents.list[] 配 + workspace 目录
.claude/skills/ skills/(多层级,详见第四章)
.claude/commands/ slash commands(详见 4.4)
settings.json openclaw.json

OpenClaw 比 Claude Code 多的两层是 SOUL(人格独立)和 bindings(IM 路由); 少的一层是 IDE 集成(它压根没那个野心)。

#1.4 一份配置驱动一切:找它就找配置

谁在线:             openclaw.json -> agents.list[]
来 X 通道找谁:       openclaw.json -> bindings[]
某 agent 能用啥工具: workspace/TOOLS.md + skills 加载链 + agents.list[].skills 白名单
某 agent 是怎样的人: workspace/SOUL.md + IDENTITY.md
某 agent 记得啥:     workspace/MEMORY.md + memory/YYYY-MM-DD.md

理解完这一节,"一个 OpenClaw Agent 是什么"就清楚了。下一节看它怎么"活起来"。


#二、Agent 怎么活起来 —— Gateway 收信、路由到 Agent、起 Run

"用户在 Telegram 给 bot 发了一句 '帮我看看刚到的那封邮件',按下回车—— 这条消息要怎么走到 agent 的 system prompt 里?"

#2.1 端到端时序

[1] Telegram 服务器
    用户消息 → Telegram Bot API webhook
        │
        ▼
[2] Channel Adapter (extensions/channels/telegram,TS)
    │  grammY 客户端把消息封装成统一的 ChannelMessage 帧
        │
        ▼
[3] OpenClaw Gateway  (本机 daemon, TS, 默认 127.0.0.1:18789)
    │
    │  3.1 入站校验
    │      ├─ 验 connect handshake(是 control-plane 还是 node 还是 channel?)
    │      ├─ JSON Schema 校验消息帧
    │      └─ idempotency 键去重
    │
    │  3.2 hook 链:message:received → message:transcribed → message:preprocessed
    │      (每个 hook 是 hooks/<name>/HOOK.md + handler.ts,可拦截 / 改写 / 丢弃)
    │
    │  3.3 binding 路由
    │      lookup({channel: "telegram", accountId: "ops_channel"})
    │        → agentId = "ops"
    │
    │  3.4 起 Run
    │      sessionKey = "agent:ops:tg:<chatId>"
    │      acquire session lane lock         ← 同一 session 不允许并发 Run
    │      assign runId, return {runId, acceptedAt} 给 channel adapter
        │
        ▼
[4] Agent Loop (in-process, pi-agent-core 运行时)
    │
    │  4.1 Context Assembly(拼上下文)
    │      ├─ 读 SOUL.md / AGENTS.md / TOOLS.md / IDENTITY.md
    │      ├─ 读 MEMORY.md + 最近 N 天 memory/YYYY-MM-DD.md
    │      ├─ 加载已启用的 skills 的 SKILL.md(按工作区/managed/bundled 优先级合并)
    │      ├─ 拉历史 session 转写(如果该 session 之前活过)
    │      └─ 跑 Active Memory 子代理(详见第五章),结果作为 hidden context 拼到 system 段
    │
    │  4.2 进入 Agent Loop(详见第三章)
    │      while not done:
    │          response = await model.infer(...)            ← 走 model provider,详见第七章
    │          emit lifecycle event "model:end"
    │          if response.tool_calls:
    │              for tc in response.tool_calls:
    │                  hook chain: PreToolUse / tool:pre   ← 可拒绝
    │                  result = await execute_tool(tc)     ← 详见第四章
    │                  hook chain: PostToolUse
    │                  yield tool event 到 WS 流
    │          else:
    │              yield assistant deltas 到 WS 流
    │              break
    │
    │  4.3 Persistence
    │      ├─ 增量写 sessions/<key>.jsonl(带文件锁)
    │      ├─ 必要时触发 session:compact:before/after hook(压缩对话)
    │      └─ 触发 session-memory bundled hook(把关键事实落到 MEMORY.md)
        │
        ▼
[5] Reply Channel Adapter
    │  Gateway 把最终 assistant 文本走回 telegram adapter
    │  → bot.sendMessage(chatId, text)
        │
        ▼
[6] 用户的 Telegram 客户端
    ✓ 看到 bot 回了

#2.2 这条链路的几个关键事实

事实 含义
Gateway 是单进程 daemon,不是分布式服务 整个 OpenClaw 在一台机器上,没有内网调度层
每个 host 正好一个 Gateway 文档原话:"Exactly one Gateway controls a single Baileys session per host."
Gateway 默认监听 127.0.0.1:18789(HTTP + WS 复用同一端口) 远程访问要走 Tailnet 或显式 pairing;Canvas 现在与 Gateway 共用 18789,路径 /__openclaw__/canvas/ + /__openclaw__/a2ui/(社区代码里能看到细分到 /__openclaw__/cap/<capId>/canvas/... 的 per-capability token 路径,详见 §6.4)
Channel adapter 跟 Gateway 用 WebSocket 互通,不是直接函数调用 因此 channel 可以扩展(写一个新 adapter,连上 WS 就行);当前 21 个 adapter(extensions/<channel>
WS 帧统一 {type: "req"|"res"|"event"} 格式 + JSON Schema 校验 这就是它的"内部协议",没有 HTTP REST API。role 取值实际为 node / operator / channel adapter 自报,不是文档化的三选一
Run 有 runId、立刻返回 ack,事件流式回推 跟 OpenAI streaming 是同一个心智
Agent 运行时已迁移到 src/agents/pi-embedded-runner/ + src/agents/harness/ 不再叫 "pi-agent-core"。harness 暴露 runAttempt / compact / reset 三接口,内部走 ReAct 但带 context-engine-lifecycle 钩子可介入上下文装配

#2.3 为什么要有 Gateway 这一层

调度、推送、聚合、鉴权、IM 适配全压在 Gateway 这一个进程里。这是因为它的物理拓扑 就是一台机器:

  • 没有"agent 工作器复用"的需求——agent 就在你机器上,启动是 ms 级
  • 没有"流推送给多端"的需求——你自己的 Telegram、WhatsApp 跟 macOS app 各订各的
  • 不存在多租户——这台机器上就一个用户

但 Gateway 自己干的事并不少:

Gateway 做啥                         为什么必须它做
─────────────────────────────────  ─────────────────────────────
绑定一堆 channel adapter (20+)       全平台只有一个真长链场景
按 binding 路由消息到 agent           IM 通道是用户唯一入口
session lane 锁,Run 串行             避免同 session 并发改 transcript
触发各种 hook                        权限/记忆/审计的总抓手
托管 Canvas HTTP 服务                 给 agent 一个"画布"输出设备
管理 Cron                            agent 自己的定时任务调度
管 Nodes (iOS/Android/macOS) 接入    手机充当语音 / 摄像头 / 位置传感器

类比:Gateway 是家里的总配电箱 + 路由器 + 智能音箱中枢的合体——所有外面的信号 进家先到它这里、所有家电对外讲话也走它出去。

#2.4 没有 dispatcher、没有 courier,但有 hook 链

OpenClaw 把它们替换成了hook 链 + WS 事件流

入站                                出站
─────────────                      ──────────────
message:received    ▲              lifecycle:end       │
   │                │              ↑                   │
message:transcribed │              tool:end            │
   │                │              ↑                   │
message:preprocessed│              tool:start          ▼
   │                │              ↑                   流向 channel adapter
   ▼                │              assistant:delta     输出到 IM
   binding 路由 ────┘              ↑
   │                                lifecycle:start
   ▼
   Agent Loop

这个 hook 链就是它的"软件总线"。第八章会讨论这个设计的取舍。


#三、Agent 怎么思考 —— 单 Run 串行的 ReAct Loop + Sub-Agent 委派

"Agent 拿到 context 之后是怎么决定下一步做什么?是单纯让 LLM 自己决定,还是有 外层的 plan-execute 框架?" OpenClaw 的回答:朴素的 ReAct,但极度强调"一个 session 同时只跑一个 Run"。

#3.1 单层 ReAct,没有 plan-execute 外壳

OpenClaw 的 agent loop 是经典的 ReAct(Reason + Act):模型自己决定 reason、自己选 工具、自己决定停——官方文档没用 "ReAct" 这个词,但描述出来的形状就是 ReAct, docs.openclaw.ai/concepts/agent-loop 的原话是:

"Core flow: intake → context assembly → model inference → tool execution → streaming replies → persistence."

"Runs are serialized per session key (session lane) and optionally through a global lane. This prevents tool/session races and keeps session history consistent."

也就是说:所有 reasoning / action selection / stopping 都压在一次模型 inference 里, 框架不在外面套 planner,也没有强制的"先 plan 再 execute"两段式("ReAct-style" 是 作者归纳,不是官方原话)。如果你想要 plan-execute,那就写一个叫 planner 的 SOUL/Skill 让模型自己去模仿这种风格——框架不替你做这个决定。

简化伪代码(综合 docs concepts/agent-loop.md 描述):

// 简化伪代码,对应文档里描述的 lifecycle phases
async function* runAgent(input: ChannelMessage): AsyncGenerator<RunEvent> {
  emit({type: "lifecycle", phase: "start"});

  // ① 上下文装配
  const ctx = await assembleContext({
    soul: read("SOUL.md"),
    agents: read("AGENTS.md"),
    tools: read("TOOLS.md"),
    skills: loadSkills(...),
    memory: await activeMemoryBlockingSubAgent(input),  // 详见 5.3
    history: readSessionTranscript(),
  });

  // ② ReAct 循环
  while (true) {
    const resp = await model.infer(ctx);                // 流式:assistant deltas

    if (!resp.tool_calls?.length) {
      emit({type: "assistant", delta: resp.content});
      break;                                            // 自然结束
    }

    for (const tc of resp.tool_calls) {                 // 顺序执行,无并行
      const ok = await runHookChain("PreToolUse", tc);
      if (!ok) {
        ctx.appendToolResult(tc, "denied by hook");
        continue;
      }
      emit({type: "tool", phase: "start", call: tc});
      const result = await executeTool(tc);             // 详见第四章
      emit({type: "tool", phase: "end",   call: tc, result});
      await runHookChain("PostToolUse", tc, result);
      ctx.appendToolResult(tc, result);
    }
  }

  // ③ 持久化
  await persistTranscript(ctx);                         // 文件锁
  await maybeCompact(ctx);                              // session:compact:* hooks
  emit({type: "lifecycle", phase: "end"});
}

#3.2 单 Run 串行:session lane 锁

这里是 OpenClaw 跟很多其它框架最不一样的地方:

"Runs are serialized per session key (session lane) and optionally through a global lane. This prevents tool/session races and keeps session history consistent." ——docs.openclaw.ai/concepts/agent-loop(核实于 2026.05.21)

也就是说:同一个 session(例如 agent:main:tg:42)一次只能跑一个 Run。如果 用户在 agent 还没回完上一句的时候连发三条新消息,后两条会排队,不会并行起 Run。 锁是进程感知 + 文件锁实现的,因此即使 Gateway 进程重启,也能恢复 lane 状态。

实现位置(2026.05 重组后):

  • 通用 lane 抽象:src/process/lanes.tsCommandLane
  • agent embedded runner 里的 lane 解析:src/agents/pi-embedded-runner/lanes.tsresolveEmbeddedSessionLane()
  • 心跳/活体检测:src/infra/heartbeat-runner.ts (早期版本散落在 src/sessions/ 下,新版本把锁的语义提到了 process 层,session 层只负责 transcript IO)

为什么要这么严?因为 transcript 是 append-only 的 session.jsonl,并发写就乱套了。 而 transcript 又是 agent 记忆的根基。牺牲并发换一致性,这是个明确的设计选择。

副作用:你不能让 agent 在等浏览器加载的时候同时去回另一句问候——除非你显式 spawn 一个 sub-agent。

#3.3 Sub-Agent:唯一的"并发逃生口"

OpenClaw 不在主 loop 里搞并发,但提供了 sub-agent 机制让你显式 fork 出去跑:

父 session:                            子 session:
agent:main:tg:42                       agent:main:subagent:<uuid>
        │
        │ /subagents spawn ops "去看一下生产 pod 的状态"
        │ 或者 model 调用 sessions_spawn() 工具
        ▼
   [ session lane 父锁释放 ]
        │
        │  (并行)
        ▼
   父 agent 继续干别的事        子 agent 在新 session 跑独立 Run
                                      │
                                      │  完成
                                      ▼
                                announce step:
                                把 "summary + 状态 + token 统计 + transcript path"
                                作为一条 user 消息回写到父 session 的 channel

关键设计点:

  • isolation 是默认:子 agent 默认 context: "isolated",干净的 transcript, 跟父 session 没有共享历史;想继承上下文就用 context: "fork"
  • session 命名带 uuidagent:<id>:subagent:<uuid>;nested 时再追加一段。
  • announce 回写:子完成后会自动在父的 channel 发一条消息("任务完成 / 失败 / 超时"), 可以通过模型回 NO_REPLY 来抑制这个回复。
  • 自动归档:sub-agent session 默认 60 分钟后归档(文件改名为 *.deleted.<ts>)。
  • slash 命令族/subagents list / kill / log / info / send / steer / spawn

跟 Claude Code 的差异:Claude Code 的 subagents 是 .claude/agents/*.md配置式——每个 subagent 是一个 frontmatter 文件,主 agent 通过 Task 工具触发。 OpenClaw 的 sub-agent 是运行时 spawn 出来的 session,没有"subagent 定义文件" 这一层;所有 agent 都是平级 agent,sub-agent 只是"我现在临时多起一个 Run"。

#3.4 单 Run 之外没有"plan/聪明工具调度"

为了避免误解,明确列一下 OpenClaw 没做 的事:

  • 没有自动并行 tool_call(即使 LLM 一次返回多个 tool_calls,框架顺序执行)
  • 没有自动 plan-execute 两段式
  • 没有强制 max_steps 上限(依赖 agent 超时和 LLM idle 超时兜底)
  • 没有内置的"血缘追踪 / Voucher Graph"
  • 没有内置的"prompt cache 压缩点对齐"机制(取决于 model provider)

它的设计哲学很清楚:核心 loop 保持极简,复杂度推到 hook 和 skill 里去


#四、Agent 怎么行动 —— Skills / Bundled Tools / MCP / Plugins 四类工具

"工具系统是 agent 框架最容易长得复杂的地方。OpenClaw 选择了把工具拆成 四种存在形态——但接口统一。"

#4.1 四类工具

                       ┌─────────────────────┐
                       │ 模型看到的 tools 数组 │
                       │ (OpenAI tools 协议) │
                       └──────────┬──────────┘
                                  │
       ┌──────────────┬───────────┴───────────┬──────────────┐
       ▼              ▼                       ▼              ▼
┌─────────────┐ ┌─────────────┐        ┌─────────────┐ ┌─────────────┐
│ Bundled     │ │ Skills      │        │ MCP Servers │ │ Plugin      │
│ Tools       │ │ (markdown)  │        │ (外部进程)   │ │ Tools       │
│             │ │             │        │             │ │ (in-proc)   │
│ TS in-proc, │ │ frontmatter │        │ stdio/SSE/  │ │ registerTool│
│ 永驻        │ │ + body 触发  │        │ HTTP        │ │ + Service + │
│ ~40 个      │ │ ~5400 个     │        │ N 个        │ │ Command     │
└─────────────┘ └─────────────┘        └─────────────┘ └─────────────┘

四类工具到 LLM 看起来是同一类:都是 OpenAI tools schema 里的一项。但加载和执行路径 完全不同:

#Bundled Tools("内置工具")

OpenClaw 出厂自带 ~25 个 first-class 工具,TS 实现,源码集中在 src/agents/tools/,跑在 Gateway 进程内。当前清单(按职责分组):

工具集 实际工具名 干嘛
文件/shell (由 codex/pi harness 提供 exec 等基础工具,见 src/agents/harness/codex-app-server-extensions.ts shell、读写编辑
会话/子代理 sessions_list / sessions_history / sessions_send / sessions_spawn / sessions_yield / subagents / session_status spawn/查/收发 sub-agent
网络 web_fetch / web_search 抓网页、搜结果(provider 可换:Brave/DDG/Exa/Firecrawl/...)
媒体生成 image_generate / music_generate / video_generate / tts 调对应 provider registry(fal/comfy/ElevenLabs/...)
媒体处理 image / pdf 看图、解析 PDF
节点/外设 nodes 调度 iOS/Android/macOS 节点(语音、摄像头、位置)
计划/状态 update_plan / session_status / heartbeat_response 显式更新当前计划、回 heartbeat
编排 cron / gateway / agents_list / message 闹钟、网关查询、列出 agent、跨 session 发消息
记忆 memory_search / memory_get / memory_recall(lancedb 路径) 翻 markdown 记忆或向量记忆

每个 agent 在 openclaw.jsonagents.list[].skills / tools 里指定能见到哪些。 工具白名单走两阶段:tools.profile 选 preset,然后 tools.alsoAllow / deny 微调。 sub-agent 默认禁用的是 4 个 session 管理工具sessions_list / sessions_history / sessions_send / sessions_spawn——避免子代理翻看/打扰兄弟会话、或自己再 spawn 一层 (参见 docs.openclaw.ai/tools/subagents 的 "sub-agent restriction layer")。 当 maxSpawnDepth >= 2 时,第一层 orchestrator sub-agent 会拿回这 4 个再加 subagents, 以便管理它的下层。gateway / cron / browser / canvas / nodes / web_* 不在硬禁名单里, 是否能用由 tools.profile + Docker sandbox 配置决定(生产里通常会进一步收紧)。

#Skills("markdown 触发的能力")

OpenClaw 生态里最大的能力载体就是 SKILL,5400+ 个社区 skill 的来源。 一个 skill 是一个目录:

skills/jira/
├── SKILL.md           ← 必需,frontmatter + 步骤描述
├── jira-search.ts     ← 可选脚本/资源
└── ...

SKILL.md 的关键 frontmatter(来自 clawhub docs/skill-format.md):

---
name: "jira"
description: "Search, create, update, and comment on Jira issues."
version: "1.4.0"
metadata:
  openclaw:
    requires:
      env:    ["JIRA_HOST", "JIRA_EMAIL"]
      bins:   ["jq"]
      anyBins: []
      config: []
      os:     ["darwin", "linux"]
    primaryEnv: "JIRA_API_TOKEN"
    install:
      brew: ["jq"]
    always: false
---
# Jira

When the user asks to find a Jira ticket, follow these steps:
1. Use `bash` to call `curl ... /rest/api/3/search`
2. ...

加载策略——6 层优先级(from docs/tools/skills.md):

1. <workspace>/skills           ← 工作区私有,最高
2. <workspace>/.agents/skills   ← project-scoped agent skills
3. ~/.agents/skills             ← personal agent skills
4. ~/.openclaw/skills           ← managed/local
5. <install>/skills             ← bundled
6. skills.load.extraDirs        ← 兜底扩展目录

同名时高优先级覆盖低优先级。Session 启动时打快照——session 跑起来后再改 SKILL.md 默认不影响这一轮(除非开了 watcher)。

skill 加载时会被 gating 过滤掉:

  • requires.env / bins / config / os 任一不满足 → 这次 session 不上 skill
  • agent allowlist 不在白名单 → 不上
  • disable-model-invocation: true → 模型看不到,但用户可以通过 slash command 触发

#MCP Servers

OpenClaw 作为 MCP host 消费第三方 MCP server 的工具;同时也作为 MCP server 对外暴露 OpenClaw 自己的工具。

  • 当 host 消费时:通过 mcporter 这个 skill 把任意 MCP server 接入(stdio/SSE/HTTP 都能跑), 也可以在 openclaw.json 里直接声明 MCP server。
  • 当 server 暴露时:仓库实际实现的是 stdio transportsrc/mcp/tools-stdio-server.tsopenclaw-tools-serve.ts),目前对外只把 cron 等少数工具直接暴露给外部 MCP 客户端。 SSE/HTTP 路径走 plugin 扩展(如 acpx)。

MCP 工具到 LLM 眼里也只是 tools schema 里的一项。

特别的是 OpenClaw 把"绕路接 Codex / Claude / Cursor 生态"也包装成了 plugin bundle: 你可以把 Cursor 的 mcp.json 或 Claude Code 的 settings.json 整体作为 bundle 装进来, OpenClaw 负责把里面的 tool 重映射成 native skill / hook / MCP(参考 docs plugins/bundles)。

#Plugin Tools(registerTool / registerService / registerCommand)

OpenClaw 还有一套 native plugin 体系(in-process、TypeScript)。一个 plugin 通过三个 注册原语暴露能力:

原语 用途
registerTool(name, schema, handler) 注册一个新的 agent 工具(同 bundled tools 同构)
registerService(...) 后台长连接服务(比如订阅一个事件源、跑一个 watcher)
registerCommand(name, handler) 注册一个 slash command

这是给"想深度集成"的开发者准备的。Plugin 的 hook token 跟 Gateway auth token 是 强制隔离的(hooks.token 必须不等于 auth.token)——这是个安全设计。

#4.2 工具选择:模型自己选 vs slash 命令显式触发

OpenClaw 给了用户两条路触发能力:

模型自动选:     LLM 看到 tools 列表 → tool_call → 框架执行
用户显式触发:   用户输入 /<command>  → Gateway hook → 直跑某个 skill / 工具

第二条路是 OpenClaw 比较有特色的设计——它支持 disable-model-invocation: true 的 skill:模型完全看不见,但用户可以通过 /skill-name args 主动触发。这把"日常对话 里不该被 LLM 自己滥用"和"我作为用户偶尔需要的开关"分得很清楚。

#4.3 Sandbox:Docker 隔离 + 工具 profile

Gateway 支持把 sub-agent / 不信任的 session 放进 Docker sandbox。工具白名单走两阶段 策略tools.profile 选 preset → tools.alsoAllow / deny 微调,deny 优先):

sub-agent 默认 restriction layer(在 profile 之上再剥)
├─ 默认硬禁:  sessions_list, sessions_history, sessions_send, sessions_spawn
│              (4 个 session 管理工具——见 docs/tools/subagents.md)
├─ 例外:      maxSpawnDepth >= 2 时,第一层 orchestrator sub-agent 会拿回这 4 个 + subagents
└─ 其它工具:  按 profile 决定,可通过 alsoAllow 显式打开 / deny 显式关闭(deny 优先)

设计意图很直接:子代理默认不应该有"调度兄弟代理、读所有 session 历史"的能力, 否则一个被 prompt-injection 的子代理能在 agent 网络里横向移动。gateway / cron / browser / canvas / nodes / web_* 不在硬禁名单里(profile 决定),但生产里通常会把 sub-agent 跑在 Docker sandbox + 裁剪过的工具集里。

#4.4 Slash Commands

Slash command 是用户输入面里类似 /help / list / focus / unfocus / agents / subagents list / verbose on / trace on 这种以 / 开头的指令,由 Gateway 在 hook 链早期识别并 分流(命令事件:command:new / command:reset / command:stop)。它们不进 LLM, 直接由 Gateway 处理,因此响应是确定性的、不消耗模型 token

跟 Claude Code 的 .claude/commands/*.md(用户写一段 markdown 当成可重用 prompt) 机制不同:Claude Code 的 slash commands 实质是 prompt 模板;OpenClaw 的内置 slash commands 是 Gateway 命令。OpenClaw 的"prompt 模板"那一层一般用 SKILL 的 user-invocable: true 来达成。


#五、Agent 怎么记忆 —— MEMORY.md + 日记 + Active Memory 子代理

"长跑的助手最大的需求不是更快,是 '它还记得我上周说过我对花生过敏'。"

#5.1 三层记忆结构

                ┌─────────────────────────────┐
                │ 当下 context(模型看见的那段)│
                │ session.jsonl 滚动 transcript │
                └────────────┬────────────────┘
                             │ 大了 → compact hook + memory flush hook
                             ▼
              ┌──────────────────────────────┐
              │ Compacted summary + 最近 N 轮 │
              └──────────────┬───────────────┘
                             │
              ┌──────────────┼───────────────┐
              ▼                              ▼
   ┌──────────────────────┐       ┌────────────────────────┐
   │ MEMORY.md            │       │ memory/YYYY-MM-DD.md   │
   │ "长期事实"           │       │ "日记,按天分桩"        │
   │ 用户偏好/关键关系    │       │ 当天发生的事/决定/状态  │
   │ 永久保留             │       │ 永久保留               │
   └──────────────────────┘       └────────────────────────┘
                             ▲
                             │ session 启动 / Active Memory 子代理 触发
                             │
              ┌──────────────┴───────────────┐
              │ memory_search / memory_get   │ ← 模型也能主动调
              └──────────────────────────────┘

这是它"个人单机部署"哲学的直接体现:你的记忆不进任何外部数据库、永远在 workspace 目录里、可以直接 cat 出来读、可以 git 起来。

补充:DREAMS.md 与 dreaming sweepmemory-host-sdk/dreaming.ts 还提供了一种"梦境" 模式——agent 空闲时跑一轮 dream sweep,把当天的 memory 文件整理总结成 DREAMS.md 的一条新条目,供人类回看。这条线是社区项目 yoloshii/ClawMem 风格被吸收进 SDK 的结果,不属于核心写入路径。

可选:lancedb 向量记忆。除了 markdown 三层,OpenClaw 现在还内置 memory_recall 工具(基于 lancedb)的可选路径——这是给"用户偏好之外、长文本/事件链"型记忆准备的。 默认 active-memory 子代理走 memory_search/get(markdown 搜索);开 lancedb 时切到 memory_recall。两条路径互斥,由 plugin 配置选择。

#5.2 写记忆:5 个 bundled hook(含 session-memory

OpenClaw 自带 5 个 bundled hooksrc/hooks/bundled/,含 README 描述):

Hook 监听事件 干什么
session-memory command:new / command:reset 取最近 ~15 轮 user/assistant 消息,让 LLM 生成一个 slug,写到 <workspace>/memory/YYYY-MM-DD-HHMM-<slug>.md
bootstrap-extra-files agent:bootstrap 把额外的 AGENTS.md/TOOLS.md(如 monorepo 子目录)拼进 system prompt
command-logger command(全部命令) 追加 JSONL 到 ~/.openclaw/logs/commands.log
compaction-notifier session:compact:before / session:compact:after 在 compact 前后给用户/audit 总线发个通知
boot-md gateway:startup Gateway 启动后跑 workspace 的 BOOT.md 指令

注意触发时机的变化:早期版本里 session-memory 挂在 session:compact:before, 所以叫做"在 compact 之前抢救一次"。当前版本挂在 command:new / command:reset—— 也就是说:是用户主动开新会话(/new)或重置(/reset)的时机才会把 transcript 蒸馏成 memory 文件。这是一个显式触发 vs 隐式抢救的语义切换:

session 内 transcript 增长 → 接近 context 上限
   │
   ▼
Gateway 触发 hook: session:compact:before
   │
   ▼
session-memory hook 启动一次 "silent agentic turn"
   │  (这是一次额外的 LLM 调用,专门干"挑出哪些事实值得长期记"这件事)
   │
   ├─ 读最近 transcript
   ├─ 提取关键事实 / 决定 / 用户偏好
   └─ 把它们按性质 append 到:
        ├─ MEMORY.md(永久事实)
        └─ memory/YYYY-MM-DD.md(当天发生的)
   │
   ▼
然后 OpenClaw 才执行真正的 compact,缩短 transcript
   │
   ▼
session:compact:after 触发

这个设计的意思是:/new/reset 这两个用户行为本身就被默认视为"该把刚才那段 对话攒下来"的信号——用户只要愿意走流程,记忆就不会丢;如果用户从不重置而是无限聊下去, compaction 仍由 compaction-notifier + compact 流程兜底,但 memory 文件的写入则只跟着 /new / /reset 走。

历史痛点(issue #56072 "Daily session reset silently discards context without memory flush"):早期版本的 daily reset 不走这个 flush,导致用户起床发现 agent 把 昨晚说的事全忘了。后来把 memory flush 拆成独立 hook(session-memory)且让 command:new 也触发——这把"忘事"这个问题从"框架行为漏洞"转成了"用户行为"层面的契约。

#5.3 读记忆:Active Memory 子代理(启动前的"暗工序")

这是 OpenClaw 最有特色的一个机制。当你给 agent 发一条消息,在主 LLM 调用之前, Gateway 会起一个临时的 sub-agent——叫 active memory sub-agent——专门干一件事:

  用户消息: "那个项目今天怎么样了?"
       │
       ▼
  active memory sub-agent (blocking, 只有 memory_search / memory_get 工具)
       │
       │  memory_search("项目状态 最近")
       │  memory_get("MEMORY.md")
       ▼
  返回 summary: "用户提到的项目应该是 'XX',上周决定走 A 方案,今天 deploy 计划在 16:00"
       │
       ▼
  这段 summary 作为 hidden <active_memory_plugin> 块拼到主 LLM 的 system 段
       │
       ▼
  主 agent 这才正式 infer,回答用户

关键参数(docs/concepts/active-memory):

配置 作用
queryMode message / recent / full —— 拿哪些上下文去搜记忆
promptStyle strict ↔ preference-only —— 子代理的"挑剔程度"
timeoutMs 硬超时(≤120s)
maxSummaryChars 注入到主 prompt 的字数上限
persistTranscripts 默认 false(子代理 transcript 用完即删)
  • OpenClaw:用一个隐藏子代理在主调用前抢跑一遍,强制把记忆塞给主 agent

OpenClaw 的好处是用户感受不到延迟(与主调用并行/前置流水),坏处是多了一次 LLM 开销 和"agent 不知道自己看到的 context 来自哪里"的不可解释性。

#5.4 Compaction:手动 + 周期 + 阈值

OpenClaw 现在有三类 reset/compact 入口:

  • Manual/new(开新 session)、/reset / /reset soft [message](保留 transcript 软重置)、/compact [instructions](只 compact 不开新)
  • Idle resetsession.idle 配置;线程级可用 /session idle <duration|off>
  • Hard max-agesession.maxAge 配置;线程级可用 /session max-age <duration|off>

每次 reset / compact 期间会触发 session:compact:before → 模型驱动的 compact → session:compact:aftercompaction-notifier bundled hook 默认订这两个事件,把状态通知到 用户/audit。"默认每天 04:00 daily reset" 这一项已经在仓库里搜不到固定时间—— 当前版本不强加默认 daily 时间,而是把节奏交给 session.maxAge / session.idle 这种参数 化配置。如果你的文档延用"04:00 默认 daily reset",需要核实你部署的具体版本

派生命令:/export-trajectory [path] 把当前 session 导出成 trajectory bundle(JSONL, 见 src/trajectory/export.ts),用于离线分析、问题复盘、或喂回 RL 训练(Gen-Verse/OpenClaw-RL)。


#六、Agent 怎么开口 —— Channels、Nodes、Canvas、WebSocket 事件总线

而是"通过 N 种 IM 通道、N 种节点设备、和一块 Canvas 跟人对话"。

#6.1 三种输出设备

┌──────────────────────────────┐
│ Channels(聊天通道)           │
│  WhatsApp / iMessage / TG /   │
│  Slack / Discord / Signal /   │
│  Feishu / msteams / line /    │  ← 主要输入也是它们
│  matrix / nostr / qqbot /     │
│  tlon / twitch / synology-... │
│  21 个仓库内置 adapter +      │
│  WebChat(内置 web UI)       │
└──────────────────────────────┘

┌──────────────────────────────┐
│ Nodes(设备节点)              │
│  macOS 菜单栏 app             │
│  iOS app(语音/位置/相机)     │
│  Android app                  │
│  Headless 节点                │  ← Voice Wake / Talk Mode 都靠这层
│  macos-mlx-tts 节点(本地 TTS)│
└──────────────────────────────┘

┌──────────────────────────────┐
│ Canvas(视觉工作区)           │
│  agent 可写的 HTML/CSS/JS     │
│  现走 Gateway 同端口能力路径   │  ← /__openclaw__/cap/<capId>/canvas/...
│  + A2UI(/__openclaw__/a2ui) │
│  Canvas 进程仍隔离             │
└──────────────────────────────┘

#6.2 Channels 适配——单向理解:channel adapter 是 plugin

每个 channel 是 extensions/<name> 下的一个 TypeScript 模块,挂着 defineBundledChannelEntry() 合约。它是一个独立的 WS 客户端,干两件事:

  • 上行:从外部平台收消息 → 转换成内部 ChannelMessage → 走 Gateway 的 message hook 链
  • 下行:从 Gateway 收 assistant payload → 转换成对应平台的格式 → 调外部 API 发出去

仓库内置 21 个 adapter(截 2026.05.21):

discord, feishu, googlechat, imessage, irc, line, matrix, mattermost,
msteams, nextcloud-talk, nostr, qqbot, signal, slack, synology-chat,
telegram, tlon, twitch, whatsapp, zalo, zalouser

README 公开口径常说"22+/23 通道"——多出来的是 WebChat(内置 web UI,不在 extensions/)、 以及配套的 Voice/Phone 通道(属于 Talk 系统,见 §0.6 src/talk/)。

不同平台的现实差异巨大:

  • Telegram 走 grammy 1.42
  • WhatsApp 走 Baileys 7.x(多账号实例非官方协议)
  • iMessage 走 BlueBubbles 或 macOS 私有桥(仓库内是 imessage adapter + skill imsg
  • Discord 用 discord-api-types + 原生 ws
  • Signal 走 signal-cli HTTP 桥
  • msteams(Microsoft Teams)、googlechat、feishu 是各家官方 bot API
  • WebChat 是 OpenClaw 自己提供的 web UI(在 ui/apps/swabble/

上行/下行的内部表达是统一的——这点跟 ai-gateway "把 LLM 厂商抽象成 OpenAI schema"是同一个套路:抽象差异、暴露统一接口。

Discord 线程绑定(2026 Q1 新增):/focus <thread> / /unfocus 把某个线程绑成 当前 channel 的"主话题",配合 commands.enforceOwnerForCommands 做 per-channel owner 校验。这套机制只在 Discord channel 完整支持,配套配置在 session.threadBindings

#6.3 Nodes 适配——把手机当感官

Nodes 是另一类 WS 客户端,但 handshake 时 role: "node",并声明自己拥有的 caps:

// 一台 iOS 节点连上来时的 connect 帧(简化)
{
  "role": "node",
  "deviceId": "iphone-15-pro",
  "caps": {
    "voiceWake": true,
    "talkMode": true,
    "camera": true,
    "location": true,
    "canvasRender": true
  },
  "permissions": ["mic", "location"],
  "commands": ["camera_capture", "speak", "wake_listen", "location_get"]
}

主 agent 想用手机的某个能力时,调 nodes bundled 工具:

LLM tool_call: nodes.invoke({nodeId: "iphone-15-pro", command: "camera_capture", args: {...}})
   │
   ▼
Gateway 找到对应的 WS 节点连接 → 发请求 → 等响应 → 把图片转给 agent

这就是为什么 OpenClaw 能做 Voice Wake / Talk Mode / 实时拍照之类的事—— 不是 Gateway 自己干的,而是它控制了你手机这个节点

#6.4 Canvas——agent 的"画板"输出

Canvas 是 OpenClaw 给 agent 一块可写的 HTML/CSS/JS 工作区。Canvas 进程仍然独立运行 (隔离崩溃 / 严格沙箱),但对外不再绑独立端口 18793——访问入口收敛到 Gateway 本身:

路由形态(截 2026.05)
  Gateway 端口(默认 18789)
   ├─ /__openclaw__/canvas/...               ← docs.openclaw.ai 公开口径的 canvas 根路径
   ├─ /__openclaw__/cap/<capId>/canvas/...   ← 源码里 per-capability token 的细分路径(待核实)
   └─ /__openclaw__/a2ui/...                 ← agent-to-UI 协议接口

docs.openclaw.ai/concepts/architecture 原文只说 "served under /__openclaw__/canvas/ and /__openclaw__/a2ui/,using the same port as the Gateway";社区代码里能看到 /__openclaw__/cap/<capId>/canvas/... 形态的 per-capability token 路径(每个 Canvas 文档/会话拿一段独立 token,跟 Gateway auth 分离),这一层是否在主仓 v2026.5.20 默认开启 待核实。无论哪种路径形态,设计意图都是:把"Canvas 内容是 agent 写的"的安全边界跟 "Gateway 是 user 控制面"的安全边界用 URL 路径而不是端口隔开。

agent 通过 canvas skill / a2ui 协议操作(创建页面、注入数据、监听用户点击)。 用户用 macOS app 或浏览器看 Canvas。

#6.5 WebSocket 事件总线(内部协议)

Gateway 与 channels / nodes / control-plane(macOS app, CLI, web UI)之间全部都是 WebSocket,统一帧格式:

连接:           {role, deviceId/clientId, caps, auth}
请求:           {type: "req", id, method, params}
响应:           {type: "res", id, ok, payload | error}
事件(推送):   {type: "event", event, payload}

事件流类型(节选):

事件 谁推 谁订
agent:lifecycle Gateway macOS app(看运行状态)/ web UI
agent:assistant:delta Agent loop Channel adapter(实时打字效果)/ control-plane
agent:tool:start/end Agent loop macOS app(实时显示在干啥)
chat:received Channel adapter 其它订阅者
presence Gateway 同一用户的多客户端互相知道在线状态
cron:fired Gateway Agent(醒来执行定时任务)
health / heartbeat 各方 监控

通信,没有 Kafka,没有 Redis pub/sub**。代价:所有连接都依赖 Gateway 进程在线。

#6.6 Cron——agent 的"闹钟"

cron bundled tool 让 agent 给自己设定时任务

LLM tool_call: cron.add({schedule: "0 9 * * MON", action: "wake", payload: "周一早会准备"})
   │
   ▼
Gateway 把这条 cron 持久化(在 workspace 下)
   │
   ▼
到点 Gateway emit cron:fired → 起一个 Run,把 payload 当成新 user message 灌进去

这就是 OpenClaw 实现 "24×7 主动叫你" 的物理基础。Cron 触发的 Run 跟 IM 进来的 Run 一样走完整 hook 链 + agent loop。


#七、Agent 的外脑 —— 模型无关层与 OpenRouter / iblai-router 接入

"OpenClaw 不写自己的 ai-gateway。它把 model provider 这事做得极简:"你给我一个 兼容 OpenAI 或 Anthropic 协议的 endpoint 就行。"

#7.1 配置面:openclaw.json 里的 models 段

{
  "models": {
    "default": "openrouter/anthropic/claude-sonnet-4.5",
    "providers": {
      "openrouter": {
        "baseUrl": "https://openrouter.ai/api/v1",
        "api": "openai-chat",
        "apiKey": "${OPENROUTER_API_KEY}"
      },
      "iblai-router": {
        "baseUrl": "http://127.0.0.1:8402",
        "api": "anthropic-messages",
        "apiKey": "passthrough",
        "models": [
          {"id": "auto", "contextWindow": 200000, "maxTokens": 8192, "reasoning": true}
        ]
      },
      "ollama": {
        "baseUrl": "http://127.0.0.1:11434/v1",
        "api": "openai-chat"
      }
    }
  }
}

模型 ref 的统一格式<provider>/<model-id> 或者 <provider>/<sub-provider>/<model>。 比如 openrouter/anthropic/claude-sonnet-4.5ollama/qwen3:32b

#7.2 它没做什么

OpenClaw 这一层故意做得很瘦:

你可能期待的能力 OpenClaw 做不做
多 key 池 不做(让 OpenRouter / 路由器去做)
厂商间 failover 不做
模型组(weighted/random) 不做(让外部 router 做)
自动 token 计费/配额 部分做(统计每次 Run 的 tokens,但不是计费系统)
Prompt cache 透传(取决于 provider)
协议归一 :内部用 OpenAI tools schema,对接 anthropic-messages 时由 provider 适配

它把"路由复杂性"全部外包给 OpenRouteriblai-router(社区项目,号称 41+ 模型、 USDC 计费)这种第三方路由器。这是生态分工的设计选择

仓库内置的 provider 扩展(截 2026.05.21)——

  • LLManthropic / anthropic-vertex / openai / openrouter / google / ollama / groq / cerebras / mistral(社区/插件)/ deepseek / deepinfra / fireworks / huggingface / byteplus / alibaba / arcee / chutes / cloudflare-ai-gateway / copilot-proxy / github-copilot / codex(OpenAI subscription)/ azure-speech(既 有 speech 也有 LLM 部分)
  • Speech / TTS / STTelevenlabs / deepgram / azure-speech(加上 skills 里的 openai-whisperopenai-whisper-apisherpa-onnx-tts
  • 媒体生成fal / comfy 等通过 image-generation-core 接入;视频/音乐生成各自 有 provider registry
  • 特化amazon-bedrock / amazon-bedrock-mantlegoogle-meet(不是 LLM 而是 会议数据接入)、bonjour(本机服务发现)

这套清单解释了为什么 extensions/ 下有 130+ 个目录——其中 21 个是 channel,剩下 大部分是 provider/能力扩展,再加几个 hook 包(active-memorydiagnostics-oteldiagnostics-prometheusdevice-pairdiffsdocument-extractfirecrawlexabraveduckduckgofile-transfercanvasbrowser 等)。

#7.3 调用链(极简)

Agent loop
   │  model.infer(messages, tools, stream=true)
   │
   ▼
ModelProviderRegistry.resolve("openrouter/anthropic/claude-sonnet-4.5")
   │
   ├─ provider = providers["openrouter"]
   ├─ realModel = "anthropic/claude-sonnet-4.5"
   └─ apiKey = provider.apiKey
   │
   ▼
OpenAIAdapter.chat({...}) → POST https://openrouter.ai/api/v1/chat/completions
   │
   ▼
SSE 流式响应 → 解析为 RunEvent (assistant deltas + tool_calls)
  • 配额 + 计费的路子完全相反——OpenClaw 是单用户,不需要这些。

#八、关键设计权衡(架构师视角)

把前面所有章节的"为什么"提炼出来,形成可复用的设计原则:

#8.1 一个 Gateway 进程 vs 一组微服务

问题:调度、推送、聚合、IM 适配、鉴权,都要做。要拆几个服务? 回答:单进程 daemon。所有事都压在 Gateway 里。

复用启发:跟用户量级匹配的拓扑才合理。OpenClaw 是单机单用户,没有跨节点协作需求, "分布式"的优势完全不适用,反而带来部署复杂度。先看物理拓扑再设计软件拓扑。

#8.2 Run 串行 + Sub-Agent 显式并发

问题:用户在 agent 还没回完时连发三句怎么办? 回答:session lane 锁串行排队;要并发就显式 spawn sub-agent。

复用启发:在"transcript append-only"的世界里,串行是对的——并发写一份历史 就是在制造数据竞争。如果你的系统也是"以 log 为真",先锁后并发。

#8.3 SOUL/Skill 分别可发布

问题:社区生态里"人格"和"能力"应该耦合还是分离? 回答:分开。SOUL = 人格(带 lobster-mom 也带 strict-cto),SKILL = 能力。 一个 agent = workspace + 1 个 SOUL + N 个 SKILL。

复用启发:当你想做"乐高式"插件生态,分清楚"风格"和"功能"。它们的更新频率、 验证方式、发布权限都不一样——硬绑在一起会让生态僵硬。

#8.4 Active Memory 子代理 vs 主 agent 自查记忆

问题:长期记忆怎么进 context? 回答:开一个隐藏子代理在主 LLM 调用前先查一遍 → summary 注入。

复用启发:当你不信任主 LLM 会"自己想起来去查记忆"时,用子代理强制前置。 代价是多一次 LLM 调用、多一份不可解释性。适合"用户隐性偏好/长期事实"型记忆, 不适合"很大量、很具体"的知识库(那种该走 RAG 工具)。

#8.5 hook 链作为软件总线

问题:要在消息处理链路里塞 N 种切面(鉴权、审计、记忆、压缩、自定义)怎么办? 回答:定义一组离散事件名(message:received / command:new / session:compact:before …), 让 hook 监听+拦截,每个 hook 是 HOOK.md + handler.ts

复用启发hook 本质上是事件溯源的软件总线。当你的系统横切关注点很多、又想让 社区扩展时,hook 比"中间件链"更可发布、更模块化。代价:调试链路变长(要追"事件 谁触发的、谁吃的、谁改的")。

#8.6 hook token 与 gateway token 隔离

问题:插件能调 Gateway,那它会不会能干所有事? 回答hooks.token 必须 ≠ auth.token。Gateway 拒绝两个值相同。

复用启发最小权限原则的工程化。两个权限边界就强制两个凭证,连配置层都不允许 重用。这种"宁愿在配置 schema 上多一行约束,也不让用户偷懒"的取舍很有借鉴意义。

#8.7 Skill 6 层加载优先级

问题:bundled / managed / personal / project / workspace 同名 skill 怎么办? 回答:定义清晰的 6 层优先级,高的覆盖低的,session 启动时打快照。

复用启发任何"配置可叠加"的系统都需要明确的优先级表 + 快照时机。优先级是 覆盖语义;快照时机决定"运行中改配置算不算数"。两者都不写清楚就会变成 bug 来源。

#8.8 SKILL.md 把"环境依赖"写进 frontmatter

问题:用户装了一堆社区 skill,但不是每个 skill 都能在他的机器上跑起来。 回答:每个 skill 在 frontmatter 声明 requires.env / bins / config / os, session 启动时自动 gating 过滤。

复用启发让运行依赖前置可声明。比起"运行时报错","加载时静默跳过 + 一条 诊断"对用户友好得多。所有"插件市场"型项目都该学这一招。

#8.9 session reset + memory flush 抢救

问题:长跑 agent 的 context 会无界增长,必须 reset。但 reset 又会丢上下文。 回答:把"reset 时机"参数化(session.idle / session.maxAge + 用户的 /new /reset /compact),并把"reset 前先写 MEMORY/日记"做成独立的 session-memory bundled hook(监听 command:new / command:reset不再绑死凌晨 04:00——早期版本 那个固定时间在当前源码已查不到)。

复用启发任何"周期性 GC"的系统都要配套"GC 前抢救机制"。GC 本身是简单的, 难的是确保 GC 不丢业务关心的状态。这也是为什么 OpenClaw 的 5 个 bundled hook 里 专门有一个就叫 session-memory


#九、附录:组件地图、关键文件、源码索引

#9.1 组件地图(一页式,对齐 v2026.5.20)

圈层 组件 形态 职责 关键依赖
入口 Channel adapters (21 个仓库内置) TS, WS client WhatsApp/Telegram/Slack/iMessage/msteams/feishu/... grammY 1.42 / Baileys 7 / 各家 SDK
入口 Nodes (iOS / Android / macOS / macos-mlx-tts) 原生 app, WS client 语音、相机、位置、Canvas 渲染、本地 TTS apps/{ios,android,macos} + OpenClawKit
入口 Control-plane (macOS app / CLI / web UI / swabble) 各种 client 用户操作 agent 的 UI WS / HTTP
入口 Talk / RealtimeTranscription TS in-proc 实时电话/麦克风通道 src/talk/src/realtime-transcription/
核心 Gateway daemon TS, 单进程 18789 路由、调度、hook、Cron、Canvas hosting、cap 路由 文件状态、WS+HTTP server
核心 Agent harness (pi-embedded-runner) TS in-proc 单 session 单 Run 的 ReAct 循环 + context-engine 钩子 model provider
核心 Context engine TS in-proc "system prompt 该塞啥"的 delegate + registry src/context-engine/
核心 Commitments / Tasks / Auto-reply TS in-proc 承诺跟踪、detached task、自动回复 src/commitments/src/tasks/src/auto-reply/
能力 Bundled tools (~25) TS in-proc sessions_* / web_fetch / web_search / image_generate / music_generate / video_generate / tts / pdf / nodes / cron / memory_* / update_plan / ... 各类 native API
能力 Skills (5400+ ClawHub, 仓库内 53) markdown + 资源 "agent 能干啥"的开放生态 clawhub registry
能力 MCP(host & server) stdio in repo;SSE/HTTP via plugin 第三方协议接入 + OpenClaw 工具对外暴露 MCP spec、mcporter skill
能力 Plugins TS in-proc registerTool 主路径 + defineBundledChannelEntry + plugin-state packages/plugin-sdk/packages/plugin-package-contract/
记忆 MEMORY.md / memory/ / DREAMS.md 文件 长期事实 + 日记 + 梦境总结
记忆 session-memory hook (bundled) hook /new / /reset 时把最近转写蒸馏写入 memory/YYYY-MM-DD-HHMM-<slug>.md LLM (silent turn) + LLM slug 生成
记忆 Active memory sub-agent 隐藏 sub-agent 主调用前查记忆 memory_search/get/recall(含 lancedb 选项)
记忆 Memory host SDK + dreaming engine + sweep 把记忆从 markdown 扩成可被第三方 host 的 engine packages/memory-host-sdk/src/memory-host-sdk/
外脑 Model provider adapter TS in-proc 接 OpenRouter / Ollama / 各家 LLM / TTS / STT OpenAI / Anthropic / 各家 API
外脑 Model catalog + provider-runtime TS in-proc 模型清单/定价/能力 + retry/circuit src/model-catalog/src/provider-runtime/
可视 Canvas + A2UI 隔离进程 + Gateway 同端口路径 agent 写的 HTML/CSS/JS /__openclaw__/canvas/*/__openclaw__/a2ui/*(社区代码可见 /__openclaw__/cap/<id>/canvas/... 形态,待核实)
审计 Crestodian / Trajectory TS in-proc dialogue/audit、JSONL 轨迹导出 src/crestodian/src/trajectory/
调试 Proxy-capture (CA + proxy) TS in-proc agent 外呼网络流量自检 src/proxy-capture/

#9.2 关键概念表(中英对照速查)

概念 英文 一句话
网关 Gateway 单机的 daemon,所有 IO 的总线
工作区 Workspace 一个 agent 的私人办公室目录
灵魂 SOUL.md 可发布的人格(来自 ClawHub)
工作手册 AGENTS.md 多 agent 协作的根规则(Claude Code 兼容 CLAUDE.md
工具说明 TOOLS.md 当前 workspace 的工具用法
身份 IDENTITY.md 名字/emoji/avatar
技能 SKILL.md 一个能力的最小可发布单元,frontmatter + body
钩子 HOOK.md + handler.ts 监听 Gateway 事件、拦截/改写
节点 Node 连上 Gateway 的设备(iOS/Android/macOS/headless)
通道 Channel 连上 Gateway 的 IM 适配(Telegram/Slack/...)
画布 Canvas agent 可写的 HTML/JS 工作区
会话车道锁 Session lane 同 session key 内 Run 串行的锁
主动记忆 Active Memory 主 LLM 调用前的记忆抢跑子代理
子代理 Sub-agent 父 Run 显式 spawn 出来的并行 Run
模型路由 Model providers openclaw.json 里的 provider 表 + <provider>/<model> ref

#9.3 关键数据结构(节选)

#openclaw.json(中央配置)

{
  "agents": {
    "list": [{ "id", "workspace", "skills?", "model?" }],
    "defaults": { "skills": [...] }
  },
  "bindings": [
    { "channel", "accountId?", "agentId" }
  ],
  "models": {
    "default": "<provider>/<model>",
    "providers": { "<name>": { "baseUrl", "api", "apiKey", "models?" } }
  },
  "skills": { "load": { "extraDirs": [...] } },
  "hooks": {
    "internal": { "entries": { ... } },
    "token": "...",      // ≠ auth.token
    "wake": { ... }
  },
  "auth": { "token": "..." }
}

#Workspace 结构

<workspace>/
├── IDENTITY.md / SOUL.md / AGENTS.md / TOOLS.md
├── MEMORY.md
├── memory/YYYY-MM-DD.md
├── skills/<name>/SKILL.md
├── sessions/<sessionKey>.jsonl
└── .agents/skills/...     ← project-scoped

#Session key 格式

agent:<agentId>:<channel>:<chatId>           ← 主 session
agent:<agentId>:subagent:<uuid>              ← 子代理
agent:<agentId>:subagent:<u1>:subagent:<u2>  ← 嵌套子代理

#9.4 源码 / 文档索引(对齐 v2026.5.20)

行号无法稳定核实,以下仅给到文件级。需要更精确的位置请直接用仓库搜索。

关注点 仓库路径
项目入口与总览 openclaw/openclaw 仓库 README + AGENTS.md + VISION.md
CLI 入口 openclaw.mjssrc/entry.tssrc/cli/program/register.*.ts
Bootstrap / Wizard src/bootstrap/src/wizard/setup.*.ts
Agent loop 文档 docs/concepts/agent-loop.md
Agent harness 源码 src/agents/pi-embedded-runner/src/agents/harness/
Gateway 架构文档 docs/concepts/architecture.md
Gateway 服务器实现 src/gateway/server.tsserver-http.tsserver-ws-runtime.tsserver-chat.ts
Gateway 协议 / handshake src/gateway/handshake-timeouts.tssrc/daemon/
Session lane / 并发控制 src/process/lanes.tssrc/agents/pi-embedded-runner/lanes.tssrc/infra/heartbeat-runner.ts
Skills 文档 docs/tools/skills.md
Skills 内置目录 skills/(53 个)
Sub-agents 文档 docs/tools/subagents.md
Slash commands 文档 docs/tools/slash-commands.md
Hooks 文档 / 实现 docs/automation/hooks.mdsrc/hooks/、bundled: src/hooks/bundled/{session-memory,bootstrap-extra-files,command-logger,compaction-notifier,boot-md}/
Hook 事件类型 src/hooks/internal-hook-types.tssrc/hooks/types.ts
Plugin bundles 文档 docs/plugins/bundles.md
Plugin SDK packages/plugin-sdk/packages/plugin-package-contract/src/plugin-sdk/src/plugins/
Plugin 状态持久化 src/plugin-state/plugin-state-store.ts
Active memory 文档 / 源码 docs/concepts/active-memory.mdextensions/active-memory/index.ts
Memory host SDK & dreaming packages/memory-host-sdk/src/memory-host-sdk/{engine-storage,dreaming}.ts
Model providers 文档 docs/concepts/model-providers.md
Model catalog / provider-runtime src/model-catalog/manifest-planner.tssrc/provider-runtime/operation-retry.ts
OpenRouter 接入 docs/providers/openrouter.md
Channel 适配集 extensions/{discord,feishu,googlechat,imessage,irc,line,matrix,mattermost,msteams,nextcloud-talk,nostr,qqbot,signal,slack,synology-chat,telegram,tlon,twitch,whatsapp,zalo,zalouser}/
Bundled tools src/agents/tools/{sessions-*,web-*,image-*,music-*,video-*,tts-*,pdf-*,nodes-*,cron-*,update-plan-*,message-*,heartbeat-response-*,session-status-*,agents-list-*,gateway-*,subagents-*}.ts
MCP 服务端 src/mcp/tools-stdio-server.tssrc/mcp/openclaw-tools-serve.ts
Canvas / A2UI ui/src/ui/canvas-url.tsextensions/canvas/(docs 公开路径 /__openclaw__/canvas/__openclaw__/a2ui;社区代码可见 /__openclaw__/cap/<id>/canvas 细分形态,待核实)
Cron 服务 src/cron/{types,service-contract,isolated-agent}.ts
Talk(实时语音) src/talk/{provider-types,agent-talkback-runtime}.ts
Realtime STT src/realtime-transcription/provider-registry.ts
媒体生成 src/{image-generation,music-generation,video-generation,tts}/provider-registry.ts
Tasks / Commitments / Auto-reply src/tasks/src/commitments/src/auto-reply/
Context engine src/context-engine/{delegate,registry,types}.ts
Trajectory 导出 src/trajectory/export.ts(命令 /export-trajectory
Crestodian audit src/crestodian/{dialogue,audit}.ts
Proxy capture src/proxy-capture/{proxy-server,ca}.ts
Pairing / Secrets / Status src/pairing/src/secrets/plan.tssrc/status/
Node host policy src/node-host/{exec-policy,invoke-system-run}.ts
ACP(Agent Control Protocol) src/acp/{types,persistent-bindings}.tsextensions/acpx/
配套 / 节点 app apps/{android,ios,macos,macos-mlx-tts,shared,swabble}/(共享库:OpenClawKit
Skill 格式规范 openclaw/clawhub 仓库 docs/skill-format.md
Soul 格式规范 openclaw/clawhub 仓库 docs/soul-format.md
ClawHub 注册表 openclaw/clawhub(社区 skills / souls)
第三方 LLM 路由 BlockRunAI/ClawRouteriblai/iblai-openclaw-router
持久记忆插件 yoloshii/ClawMem(hooks + MCP + 混合 RAG)
RL 衍生 Gen-Verse/OpenClaw-RL
awesome-skills VoltAgent/awesome-openclaw-skills(5,400+ 社区 skill 筛选)
Mission Control(外部 dashboard) abhi1693/openclaw-mission-control

#9.5 一句话总结(贴在脑子里)

OpenClaw 是一个把"个人 IM 助手"做到极致的、跑在你自己机器上的单进程 agent 框架。 Gateway 是它唯一的中枢,channels 是它的耳朵,nodes 是它的手脚, Canvas 是它的画板,SOUL/SKILL 是社区给它换上的灵魂和技能,hook 链是它的事件总线, markdown 文件就是它的长期记忆。

整个系统的灵魂在三个设计上:Gateway 单进程吃下所有 IO(与"分布式 agent" 旗帜性区分)、SOUL/SKILL 双轨可发布生态(让人格和能力解耦演化)、 Active Memory 隐藏子代理 + bundled session-memory hook(让长跑助手真的记得你)。


数据来源(2026.05.21 复核):OpenClaw 官方仓库 openclaw/openclaw@main(v2026.5.20)、 docs.openclaw.aiopenclaw.ai/blog,以及 ClawHub 规范文档 (github.com/openclaw/clawhub/docs/),均为公开资料。本版用 WebFetch 直读了 package.jsondocs/concepts/{agent-loop,architecture,agent-workspace,active-memory}docs/automation/hooksdocs/tools/{skills,subagents} 等页面对位。

本版核实记录

  • package.json version 为 2026.5.20(raw.githubusercontent.com 直读)
  • ✅ 官方 docs/concepts/agent-workspace 列出的 workspace 根 markdown 共 9 个: IDENTITY / SOUL / AGENTS / TOOLS / USER / MEMORY / HEARTBEAT / BOOT / BOOTSTRAP。 DREAMS.md 不在官方列表——已在 §1.1 标"非官方/SDK 派生";拼接顺序未文档化, 按 src/agents/harness/ 实现推断
  • ✅ Active Memory 是同步阻塞 sub-agent(默认 timeoutMs=15000maxSummaryChars=220queryMode="recent"promptStyle="balanced"persistTranscripts=false
  • ✅ Bundled hook 实际 5 个session-memory (command:new / command:reset) / bootstrap-extra-files (agent:bootstrap) / command-logger (command) / compaction-notifier (session:compact:before / :after) / boot-md (gateway:startup)
  • ✅ Gateway 默认端口 127.0.0.1:18789;docs 公开口径 Canvas 路径 /__openclaw__/canvas/ + /__openclaw__/a2ui/与 Gateway 同端口(不再 18793)
  • ✅ Sub-agent 默认 deny 只有 4 个sessions_list / sessions_history / sessions_send / sessions_spawnmaxSpawnDepth >= 2 的 orchestrator 会拿回这 4 个 + subagents
  • ✅ Skill 6 层加载优先级 + session 启动打快照 + watcher 触发 mid-session 刷新 (docs/tools/skills.md 原文一致)
  • ⚠ Canvas per-capability token 路径 /__openclaw__/cap/<id>/canvas/... 在 docs 公开口径 里未出现——可能是源码细节 / 未升入文档(待核实)
  • ⚠ "默认每天 04:00 daily reset" 在当前源码 / docs 都查不到固定值——已统一改成 session.idle / session.maxAge 参数化口径
  • ⚠ Channel 总数:仓库内置 extensions/ 21 个(不含 WebChat / Voice / Phone),README 外部口径 "22+/23" 包括了 WebChat 和 Talk 通道
  • ⚠ Agent harness 内部路径 src/agents/pi-embedded-runner/ / src/agents/harness/ / src/process/lanes.ts 等源码位置基于 README 描述 + WebFetch 推断,未逐文件核实

返回 Agent 资料库