芥末
发布于 2026-03-19 / 0 阅读
0
0

OpenClaw 架构拆解:Gateway、推理循环、工具系统与 SubAgent

OpenClaw 可以理解为一个“个人 AI 助手操作系统”:它不只是一个聊天机器人,而是把大语言模型、工具调用、聊天平台、本地设备、浏览器、定时任务、记忆、工作区和多智能体协作统一接起来,让 AI 能进入真实的个人工作流。

它的核心设计不是把所有能力都塞进一个进程里,而是采用 本地优先(Local-First)+ 多端联动 + Gateway 控制平面 的方式组织系统。Gateway 负责统一接入和路由,Agent 负责推理和执行,Channels 负责对接外部聊天平台,Nodes 负责连接设备能力,Tools 与 Skills 负责让 Agent 真正“做事”。

从架构角度看,OpenClaw 主要由这些部分组成:

模块作用
Gateway统一控制平面,承载 WebSocket、HTTP API、控制台、会话、节点和频道管理
Pi Agent / Agentic LoopAgent 的推理循环,负责调用大语言模型、处理流式响应和工具调用
Cron定时任务系统,支持一次性任务、周期任务和 Cron 表达式
Tools工具系统,统一管理文件、命令、浏览器、消息、记忆、画布等工具
Channels聊天渠道适配层,连接 Telegram、Slack、Discord、WhatsApp 等平台
Context上下文管理,处理模型窗口、压缩、剪枝和运行时注入
SubAgent子智能体系统,用于后台并行任务和多层任务编排
Nodes设备节点,把 macOS、iOS、Android 等设备能力暴露给 Agent
Sandbox沙箱隔离,把外部输入或不可信任务限制在容器环境内

OpenClaw 的总体架构

OpenClaw 的整体结构可以从这张架构图把握。Gateway 位于系统中心,向上连接控制界面和外部 API,向外连接各类聊天渠道与设备节点,向内调度 Agent、工具、定时任务和会话状态。

OpenClaw 整体架构

图里的关键点是:OpenClaw 并没有把 AI 助手做成单一聊天入口,而是把 Gateway 设计成一个控制平面。所有外部输入都会进入 Gateway,再由 Gateway 判断应该交给哪个 Agent、哪个会话、哪个工作区处理。Agent 在执行过程中可以调用工具、访问浏览器、控制设备节点,也可以把结果投递回原来的聊天渠道。

简化后可以画成这样:

flowchart LR
    User[用户 / 外部平台] --> Channels[Channels 聊天渠道]
    Apps[桌面 / 移动端应用] --> Nodes[Nodes 设备节点]

    Channels --> Gateway[Gateway 控制平面]
    Nodes --> Gateway
    ControlUI[Control UI 控制台] --> Gateway
    HTTPAPI[HTTP API] --> Gateway

    Gateway --> Sessions[Sessions 会话管理]
    Gateway --> Cron[Cron 定时任务]
    Gateway --> Agent[Pi Agent 推理运行时]

    Agent --> Tools[Tools 工具系统]
    Agent --> Context[Context 上下文管理]
    Agent --> SubAgents[SubAgent 子智能体]

    Tools --> Browser[浏览器]
    Tools --> Files[文件系统]
    Tools --> Shell[命令执行]
    Tools --> Memory[记忆]

这种结构解决了三个问题:

  1. 多入口统一接入:同一个助手可以同时服务 Telegram、Slack、Discord、WhatsApp、iMessage、Web 控制台和本地设备。
  2. 多会话隔离:不同群组、不同联系人、不同 Agent 可以拥有独立会话和工作区,避免上下文互相污染。
  3. 能力可控暴露:不同来源的消息可以匹配不同工具策略,例如主会话允许本地命令,外部群聊只能使用安全工具。

Gateway:统一控制平面

Gateway 是 OpenClaw 的中心进程。它本质上是一个 WebSocket(网页套接字)服务器,同时也提供 HTTP(超文本传输协议)API(应用程序编程接口)和控制台 UI(用户界面)。

Gateway 负责的事情很多,但可以归为五类:

职责说明
消息路由接收 Channels 的消息,找到对应 Agent 和会话
会话管理创建、恢复、更新和删除 Agent 会话
工具协调让 Agent 能通过 Gateway 调用特定工具或远程能力
节点通信与 macOS、iOS、Android 等设备节点建立连接
API 暴露提供兼容 OpenAI 风格的 REST(表述性状态转移)接口

Gateway 的内部模型

Gateway 对外提供多个入口,但内部会统一进入 RPC(Remote Procedure Call,远程过程调用)路由层。RPC Router 再根据方法名和连接角色,把请求分发给 Channels、Agents、Nodes 或其他服务。

flowchart TB
    subgraph Gateway[Gateway 进程]
        WS[WebSocket Server]
        HTTP[HTTP API / OpenAI Compatible]
        UI[Control UI]

        WS --> Router[RPC Router]
        HTTP --> Router
        UI --> Router

        Router --> Channels[Channels 消息路由]
        Router --> Agents[Agents 会话管理]
        Router --> Nodes[Nodes 设备节点]
        Router --> Cron[Cron 定时任务]
        Router --> Config[配置管理]
    end

一个重要设计是 单端口复用。默认情况下,WebSocket RPC、HTTP API 和 Control UI 可以共用 18789 端口。这让本地部署、远程转发、Tailscale 暴露和反向代理配置都更简单。

连接握手与帧格式

Gateway 的连接不是裸连。客户端需要声明角色、权限范围和协议版本,服务端会验证认证信息,再返回策略和设备令牌。

sequenceDiagram
    participant C as Client
    participant G as Gateway

    G-->>C: connect.challenge,可选 nonce 挑战
    C->>G: connect,请求携带 auth、role、scopes
    G-->>C: hello-ok,返回 policy 和 device token
    G-->>C: events,持续推送状态变化

RPC 帧主要有三种:

// 请求
{
  type: "req",
  id: "request-id",
  method: "sessions.list",
  params: {}
}

// 响应
{
  type: "res",
  id: "request-id",
  ok: true,
  payload: {}
}

// 事件
{
  type: "event",
  event: "presence.updated",
  payload: {},
  seq: 100,
  stateVersion: 20
}

这种设计的好处是,控制请求、状态事件、工具调用结果都能走同一条 WebSocket 连接。客户端不需要同时维护多套协议。

角色、权限和认证

Gateway 把连接角色分为两类:

角色作用
operator控制平面角色,可以读取或修改配置、会话、任务等
node能力节点角色,用于暴露设备上的摄像头、位置、录屏、系统命令等能力

权限通过 scopes 控制,例如:

Scope含义
operator.read读取控制面状态
operator.write修改会话、配置或任务
operator.admin管理级操作
node.invoke允许调用设备节点能力

认证模式包括:

模式使用场景
token默认共享令牌认证
password共享密码认证
trusted-proxy反向代理已完成身份认证,例如 Pomerium
device-token设备配对后使用设备令牌连接

安全约束也比较明确:

  • Gateway 如果绑定到非本机地址,必须开启认证。
  • 明文 ws:// 不允许连接非本机地址,避免敏感数据在网络中裸传。
  • 设备能力通过配对机制获取令牌,而不是默认信任局域网内所有设备。

绑定模式

Gateway 可以绑定到不同网络地址:

模式地址适合场景
loopback127.0.0.1默认模式,仅本机访问
lan0.0.0.0局域网访问
tailnetTailscale IP通过 Tailscale 安全访问
auto自动判断根据环境选择地址
custom自定义地址特殊网络环境

本地优先并不等于只能本地用。更常见的部署方式是 Gateway 跑在本机或小型 Linux 实例上,再通过 Tailscale Serve/Funnel、SSH 隧道或反向代理暴露给其他设备。

服务生命周期

macOS 上通常使用 launchd 管理 Gateway:

openclaw gateway install
openclaw gateway start
openclaw gateway stop
openclaw gateway restart

Linux 上可以使用 systemd

openclaw gateway install
systemctl --user enable --now openclaw-gateway.service

常用排查命令:

# 启动网关
openclaw gateway --port 18789

# 查看状态
openclaw gateway status
openclaw gateway status --deep

# 健康检查
openclaw gateway health
openclaw channels status --probe

# 发现局域网网关
openclaw gateway discover

# 实时日志
openclaw logs --follow

配置热重载

Gateway 支持不同级别的配置重载:

模式行为
off不自动重载
hot只应用安全的热更新
restart检测到需要重启的配置时自动重启
hybrid能热更新就热更新,必要时重启,默认模式

典型配置如下:

{
  "gateway": {
    "port": 18789,
    "bind": "loopback",
    "mode": "local",
    "auth": {
      "mode": "token",
      "token": "your-token"
    },
    "tls": {
      "enabled": true,
      "certPath": "/path/to/cert.pem",
      "keyPath": "/path/to/key.pem"
    },
    "reload": {
      "mode": "hybrid",
      "debounceMs": 300
    }
  }
}

对应源码位置可以按模块查:

模块路径
CLI(命令行接口)入口src/cli/gateway-cli/
Gateway 客户端src/gateway/client.ts
协议定义src/gateway/protocol/
HTTP 服务端src/gateway/server-http.ts
Gateway 配置类型src/config/types.gateway.ts

Pi Agent 与 Agentic Loop:Agent 的推理循环

Agentic Loop 是 OpenClaw 真正“思考和执行”的地方。大语言模型(LLM,Large Language Model)不是一次性生成最终回答,而是在一个循环里持续接收上下文、输出文本或工具调用、等待工具结果,再继续推理。

OpenClaw 的推理循环是事件驱动架构。整体流程如图所示:

OpenClaw 推理循环

图中的核心是三层:

  1. run.ts 管主循环,负责重试、错误分类、上下文溢出处理和 profile 轮换。
  2. attempt.ts 管单次推理尝试,负责创建会话、构建工具、注入系统提示词并启动模型调用。
  3. subscribe.ts 管事件订阅,把模型流式输出、工具执行事件、Agent 生命周期事件转换成 OpenClaw 内部事件。

主循环:负责稳定性

主循环不是简单调用一次模型。它会用 while 循环包住一次推理尝试,并处理几类常见失败:

失败类型处理方式
上下文溢出触发自动压缩,再重新尝试
认证失败尝试轮换 profile
调用超时按策略重试或返回错误
达到重试上限停止运行并报告失败

可以用伪代码表示:

async function runEmbeddedPiAgent() {
  while (true) {
    if (exceedMaxLoopIterations()) {
      throw new Error("too many run loop iterations");
    }

    try {
      const result = await runEmbeddedAttempt();
      return result.payloads;
    } catch (err) {
      if (isContextOverflow(err)) {
        await compactSessionHistory();
        continue;
      }

      if (isAuthFailure(err)) {
        await rotateProfile();
        continue;
      }

      if (isTimeout(err) && canRetry()) {
        continue;
      }

      throw err;
    }
  }
}

这个结构让 Agent 在面对长会话、模型接口波动、上下文爆掉等问题时,不会轻易中断。

单次尝试:准备上下文、工具和会话

一次推理尝试大致分四步:

flowchart TD
    A[准备阶段] --> B[初始化会话]
    B --> C[执行推理]
    C --> D[收集结果]

    A --> A1[创建 workspace 和 session]
    A --> A2[构建工具列表]
    A --> A3[构建 system prompt]
    A --> A4[创建 session manager]

    B --> B1[createAgentSession]
    B --> B2[设置 streamFn]
    B --> B3[安装事件订阅器]

    C --> C1[activeSession.prompt]
    C1 --> C2[LLM 流式调用]
    C2 --> C3[工具调用循环]

    D --> D1[assistantTexts]
    D --> D2[toolMetas]
    D --> D3[usage tokens]

关键调用链可以压缩成这样:

用户消息
  ↓
runAgentTurnWithFallback()
  ↓
runEmbeddedPiAgent()
  ↓
runEmbeddedAttempt()
  ↓
createAgentSession() + activeSession.prompt()
  ↓
LLM 调用 + 工具循环
  ↓
subscribeEmbeddedPiSession()
  ↓
onPartialReply / onBlockReply / onToolResult
  ↓
回复投递

LLM 调用函数

OpenClaw 把模型调用抽象成 streamFn

类型说明
streamSimple默认流式调用函数,来自 @mariozechner/pi-ai
createOllamaStreamFn()面向 Ollama 本地模型
applyExtraParamsToAgent()包装调用参数,例如添加额外模型参数

这样做可以让 Agentic Loop 不直接绑定某个模型供应商。只要能提供兼容的流式函数,就能接进推理循环。

工具调用循环

工具调用由底层 SDK(Software Development Kit,软件开发工具包)管理。当模型返回 tool_use 时,系统会执行工具,然后把 tool_result 写回消息历史,再继续调用模型。

sequenceDiagram
    participant M as AI Model
    participant SDK as Agent SDK
    participant T as Tool
    participant S as Subscriber

    M-->>SDK: tool_use(name, args)
    SDK-->>S: tool_execution_start
    SDK->>T: execute(args)
    T-->>SDK: streaming updates
    SDK-->>S: tool_execution_update
    T-->>SDK: result
    SDK-->>S: tool_execution_end
    SDK->>M: tool_result
    M-->>SDK: 继续推理或生成最终回复

这套机制让 Agent 可以多轮调用工具。例如用户要求“查资料并生成报告”,模型可能先调用浏览器搜索,再调用文件写入工具保存草稿,最后生成回复。Agentic Loop 不需要提前知道会调用几次工具,而是由模型和工具结果共同驱动后续步骤。

流式事件处理

subscribeEmbeddedPiSession() 会订阅并分发各种事件:

事件处理逻辑
message_start重置状态,准备接收新消息
message_update处理文本增量、thinking 块、局部回复
message_end提取最终文本,推送完整回复
tool_execution_start记录工具开始,广播工具事件
tool_execution_update处理工具流式进度
tool_execution_end处理工具结果,触发 after hook
agent_start / agent_end广播 Agent 生命周期事件

这种设计把“模型输出文本”“工具正在执行”“工具已经返回”“Agent 结束”都变成统一事件。控制台、聊天渠道、日志系统、SubAgent 注册表都可以订阅这些事件。

Cron:定时任务系统

个人助手经常需要做后台任务,例如定时总结消息、每天早上生成计划、每隔一段时间检查服务状态、在某个时间提醒用户。Cron 系统负责把这些任务可靠地调度起来。

OpenClaw 的 CronService 由三部分组成:

flowchart TB
    subgraph CronService
        Timer[Timer 定时器]
        Store[Store 持久化存储]
        State[State 运行状态]
    end

    Timer --> Jobs[(jobs.json)]
    Store --> Jobs
    State --> Jobs

    Timer --> Runner[Job Runner]
    Runner --> Agent[Agent / Heartbeat]
    Runner --> Webhook[Webhook 回调]

调度类型

Cron 支持三种调度模式:

type CronSchedule =
  | { kind: "at"; at: string }
  | { kind: "every"; everyMs: number; anchorMs?: number }
  | { kind: "cron"; expr: string; tz?: string; staggerMs?: number };
模式说明适合场景
at指定时间执行一次,执行后自动禁用明天 9 点提醒、某个时间点触发任务
every固定间隔执行每 10 分钟检查状态
cron标准 Cron 表达式,支持时区和错峰每天 8 点、每周一上午

staggerMs 用于错峰。多个任务如果集中在同一时间触发,可以通过 stagger 分散启动,避免瞬间把模型和工具调用打满。

单一定时器设计

CronService 不为每个任务维护一个 setTimeout,而是只维护一个全局定时器。每次根据所有任务的 nextRunAtMs 找到最近唤醒时间,到点后执行该执行的任务,再重新计算下一次唤醒。

const MAX_TIMER_DELAY_MS = 60_000;
const MIN_REFIRE_GAP_MS = 2_000;

export function armTimer(state: CronServiceState) {
  const nextAt = nextWakeAtMs(state);
  const delay = Math.max(nextAt - Date.now(), 0);
  const clampedDelay = Math.min(delay, MAX_TIMER_DELAY_MS);

  state.timer = setTimeout(() => {
    void onTimer(state).catch((err) => {
      state.logger.error(err);
    });
  }, clampedDelay);
}

两个常量很关键:

常量含义
MAX_TIMER_DELAY_MS = 60_000单次定时器最多等待 60 秒,减少系统休眠或时钟漂移带来的影响
MIN_REFIRE_GAP_MS = 2_000最小重触发间隔,避免任务异常导致紧密循环

持久化与运行日志

任务默认存储在:

~/.openclaw/cron/jobs.json

任务运行日志存储在:

~/.openclaw/cron/runs/<jobId>.jsonl

JSONL(JSON Lines,一行一个 JSON 对象)适合记录连续运行日志,因为可以按行追加、裁剪和流式读取。

任务结构大致如下:

type CronStoreFile = {
  version: 1;
  jobs: CronJob[];
};

type CronJob = {
  id: string;
  name: string;
  enabled: boolean;
  schedule: CronSchedule;
  sessionTarget: "main" | "isolated";
  payload: CronPayload;
  state: CronJobState;
};

持久化采用“临时文件 + rename”的原子写入方式。这样即使进程在写入过程中崩溃,也不容易留下半截 JSON 文件。

启动恢复

Gateway 重启后,CronService 会恢复任务状态:

flowchart TD
    A[启动 CronService] --> B[加载 jobs.json]
    B --> C[清理 runningAtMs 标记]
    C --> D[运行错过的任务]
    D --> E[重新计算 nextRunAtMs]
    E --> F[启动全局定时器]

核心逻辑可以写成:

export async function start(state: CronServiceState) {
  await ensureLoaded(state, { skipRecompute: true });

  for (const job of state.jobs) {
    if (job.state.runningAtMs) {
      job.state.runningAtMs = undefined;
    }
  }

  await runMissedJobs(state);
  recomputeNextRuns(state);
  armTimer(state);
}

这一步的意义是避免“任务正在运行”这种临时状态在进程崩溃后永久卡住。

两种执行模式

Cron 任务可以投递到主会话,也可以创建隔离 Agent 会话。

模式sessionTarget说明
Main Sessionmain向主会话注入系统事件
Isolated Agentisolated启动独立 Agent 会话执行任务

主会话模式要求 payload 是系统事件:

{
  "kind": "systemEvent",
  "text": "现在生成今天的日程摘要"
}

隔离模式用于后台任务:

{
  "kind": "agentTurn",
  "message": "检查仓库中最近的错误日志,并生成摘要",
  "model": "claude-sonnet-4",
  "timeoutMs": 600000
}

隔离模式不会污染主会话上下文,适合长任务、周期巡检和后台分析。

超时、退避与并发控制

Cron 任务执行时会套一层超时控制:

export async function executeJobCoreWithTimeout(state, job) {
  const abortController = new AbortController();
  const jobTimeoutMs = resolveCronJobTimeoutMs(job);

  return await Promise.race([
    executeJobCore(state, job, abortController.signal),
    new Promise((_, reject) => {
      setTimeout(() => {
        abortController.abort();
        reject(new Error("cron: job execution timed out"));
      }, jobTimeoutMs);
    })
  ]);
}

失败后会按指数退避重试,例如:

30s → 1min → 5min → 15min → 60min

同时还有并发上限 maxConcurrentRuns,避免大量任务同时启动把模型调用、工具执行和本地资源打满。

与 Heartbeat 集成

Cron 可以和 Heartbeat 配合,让任务不是简单地“直接发消息”,而是唤醒 Agent 运行一次。

const cron = new CronService({
  enqueueSystemEvent: (text, opts) => {
    enqueueSystemEvent(text, {
      sessionKey: opts.sessionKey,
      contextKey: opts.contextKey
    });
  },

  requestHeartbeatNow: (opts) => {
    requestHeartbeatNow({
      reason: opts.reason,
      agentId: opts.agentId,
      sessionKey: opts.sessionKey
    });
  },

  runHeartbeatOnce: async (opts) => {
    return await runHeartbeatOnce({
      cfg,
      reason: opts.reason,
      agentId: opts.agentId,
      sessionKey: opts.sessionKey
    });
  }
});

Wake 模式有两种:

模式行为
next-heartbeat等待下一次心跳处理
now立即触发一次心跳

如果任务完成后需要通知外部系统,也可以配置 Webhook:

if (webhookTarget && evt.summary) {
  await fetch(webhookTarget.url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${webhookToken}`
    },
    body: JSON.stringify(evt)
  });
}

常用命令:

openclaw cron status
openclaw cron list
openclaw cron add
openclaw cron edit
openclaw cron remove <id>
openclaw cron run <id>

Tools:让 Agent 真正执行动作

没有工具系统,Agent 只能生成文本。OpenClaw 的工具系统把文件读写、代码编辑、命令执行、浏览器控制、消息发送、记忆检索、画布操作、网关管理等能力统一包装成模型可以调用的工具。

工具系统可以分成六层:

flowchart TB
    A[Tool Creation 创建层] --> B[Tool Definition 定义层]
    B --> C[Schema Normalization 参数 Schema 规范化]
    C --> D[Policy Pipeline 策略管道]
    D --> E[Execution 执行层]
    E --> F[Hook System 钩子]
    A --> P[Plugin System 插件工具]
    G[HTTP Invocation API] --> D

工具创建入口

createOpenClawCodingTools() 是工具系统主入口,负责把基础编码工具、OpenClaw 特定工具、插件工具合并起来,再经过策略过滤和 Schema 规范化。

伪代码如下:

function createOpenClawCodingTools() {
  const policy = resolveEffectiveToolPolicy();

  const coding = createBaseCodingTools();
  const exec = createExecTool();
  const openclaw = createOpenClawTools();
  const plugins = resolvePluginTools();

  const merged = [
    ...coding,
    exec,
    ...openclaw,
    ...plugins
  ];

  const filtered = applyToolPolicyPipeline(merged, policy);
  const normalized = normalizeToolParameters(filtered);

  return normalized.map(wrapToolWithBeforeToolCallHook);
}

常见工具可以按能力分类:

类型示例
编码工具readwriteeditbash
浏览器工具页面快照、点击、输入、上传、截图
消息工具向指定渠道发送消息、回复、表情反应
记忆工具搜索或写入长期记忆
Gateway 工具查询会话、状态、节点
Canvas 工具操作实时画布
TTS 工具TTS(Text-to-Speech,文本转语音)
进程工具查看、启动、停止任务进程

工具定义

核心类型是 AnyAgentTool。一个工具至少需要名称、描述、参数 Schema 和执行函数:

type AnyAgentTool = {
  name: string;
  label?: string;
  description: string;
  parameters?: unknown;
  execute?: (
    id: string,
    args: unknown,
    signal: AbortSignal
  ) => Promise<unknown>;
  ownerOnly?: boolean;
};

字段含义:

字段作用
name工具名称,通常要求小写且唯一
label给 UI 展示的名称
description给模型看的工具说明,影响模型是否正确调用
parametersJSON Schema(JavaScript Object Notation Schema,JSON 结构约束)或 TypeBox Schema
execute实际执行函数
ownerOnly是否只允许所有者使用

工具描述非常关键。模型并不知道工具内部代码,只能根据名称、说明和参数 Schema 决定什么时候调用、怎么传参。

Schema 规范化

不同模型供应商对工具参数 Schema 的兼容程度不同。OpenClaw 在工具发送给模型前会做规范化:

提供商处理方式
Anthropic保留完整 JSON Schema draft 2020-12 兼容结构
OpenAI确保顶层存在 type: "object"
Google/Gemini清理不支持的 format 或约束关键字
所有供应商合并或转换 anyOf / oneOf 等 union schema

如果不做这层适配,同一个工具在 Claude 上能用,在 Gemini 或 OpenAI 模型上可能会因为 Schema 不兼容而报错。

策略管道

工具能力越强,风险也越高。文件写入、命令执行、浏览器登录态、消息发送都可能造成真实影响,所以 OpenClaw 使用多级工具策略管道。

策略应用顺序为:

Profile Policy
  → Provider Profile Policy
  → Global Policy
  → Agent Policy
  → Group Policy
  → Sandbox Policy
  → Subagent Policy

策略配置结构:

type ToolPolicyConfig = {
  allow?: string[];
  alsoAllow?: string[];
  deny?: string[];
  profile?: "minimal" | "coding" | "messaging" | "full";
};

各字段含义:

字段说明
allow白名单,只允许这些工具
alsoAllow在已有白名单基础上追加允许项
deny黑名单,明确禁止这些工具
profile预设工具集合,例如最小、编码、消息、完整

这种管道式策略可以表达很细的权限规则:

  • 主会话可以使用 bash 和文件编辑。
  • 外部群聊只允许消息和搜索类工具。
  • 沙箱会话禁止访问宿主机命令。
  • 叶子 SubAgent 不能继续创建会话。
  • 某个 Agent 只能使用浏览器,不能发消息。

工具调用完整流程

flowchart TD
    A[用户消息进入 Gateway] --> B[构建工具列表]
    B --> C[应用策略过滤]
    C --> D[规范化参数 Schema]
    D --> E[发送给 AI 模型]
    E --> F{模型是否返回 tool_use}
    F -- 否 --> G[生成文本回复]
    F -- 是 --> H[before_tool_call 钩子]
    H --> I{是否允许调用}
    I -- 否 --> J[返回拒绝结果]
    I -- 是 --> K[tool.execute 执行]
    K --> L[after_tool_call 钩子]
    L --> M[返回 tool_result]
    M --> E

钩子系统分两类:

钩子能做什么
before_tool_call修改参数、阻止调用、记录审计日志
after_tool_call记录结果、统计耗时、检测循环调用

工具执行事件还会被发送到流式事件系统,控制台或聊天界面可以实时显示“正在调用浏览器”“命令执行完成”等状态。

插件工具与 HTTP 调用

插件可以注册工具:

type PluginToolRegistration = {
  pluginId: string;
  factory: OpenClawPluginToolFactory;
  names: string[];
  optional: boolean;
  source: string;
};

例如:

插件工具
Microsoft Teamsmsteams-sendmsteams-react
Matrixmatrix-sendmatrix-react
Zalozalo-send
Voice Callvoice-call

外部系统也可以通过 HTTP 调用工具:

POST /tools/invoke
Content-Type: application/json
Authorization: Bearer your-token
{
  "tool": "browser",
  "action": "screenshot",
  "args": {
    "url": "https://example.com"
  },
  "sessionKey": "agent:main"
}

这个入口仍然会经过认证和策略校验,不是绕过工具权限直接执行。

Channels:把 AI 接入聊天和协作平台

Channels 是 OpenClaw 与外部社交、办公平台连接的抽象层。它负责接收 Telegram、Discord、Slack、WhatsApp、Signal、iMessage、Microsoft Teams、Matrix、Zalo 等平台的消息,并把 Agent 回复投递回去。

ChannelPlugin 抽象

每个渠道都实现一个 ChannelPlugin,插件由元信息、能力描述和多个适配器组成。

flowchart TB
    Plugin[ChannelPlugin]
    Plugin --> Meta[meta: label / docsPath / aliases]
    Plugin --> Cap[capabilities: chatTypes / polls / threads]
    Plugin --> Config[config 账户配置]
    Plugin --> Setup[setup 账户设置]
    Plugin --> Outbound[outbound 消息发送]
    Plugin --> Status[status 状态探测]
    Plugin --> Gateway[gateway 生命周期]
    Plugin --> Security[security 安全策略]
    Plugin --> Pairing[pairing 配对]
    Plugin --> Groups[groups 群组管理]
    Plugin --> Threading[threading 线程处理]
    Plugin --> Mentions[mentions 提及解析]
    Plugin --> Directory[directory 目录查询]
    Plugin --> Resolver[resolver 路由解析]
    Plugin --> Actions[actions 消息动作]

把一个渠道拆成多个适配器,而不是写成一个巨大类,有两个好处:

  1. 某些平台不支持的能力可以不实现,例如有的平台没有线程。
  2. 路由、发送、配置、配对、安全可以单独演进,避免平台实现互相影响。

入站消息流

flowchart TD
    A[外部平台消息] --> B[Channel Monitor 接收]
    B --> C[去重和预处理]
    C --> D[Allowlist 验证]
    D --> E[resolveAgentRoute 路由解析]
    E --> F[Session Key 构建]
    F --> G[持久化会话元数据]
    G --> H[Agent 处理消息]
    H --> I[生成回复]
    I --> J[Outbound Deliver]
    J --> K[消息分块]
    K --> L[Channel Outbound Adapter]
    L --> M[外部平台]

关键是路由解析。OpenClaw 需要判断一条消息到底应该交给哪个 Agent、哪个会话。路由优先级可以按精确度排序:

优先级匹配类型
1binding.peer,精确用户或群组
2binding.peer.parent,线程继承
3binding.guild + roles,服务器和角色
4binding.guild,服务器
5binding.team,团队
6binding.account,账号
7binding.channel,渠道
8default agent,默认 Agent

Session Key 会把 Agent、主键、渠道、账号、会话对象拼在一起,例如:

{agentId}:{mainKey}:{channel}:{accountId}:{peerKind}:{peerId}

这样同一个用户在 Telegram 私聊、Discord 群组、Slack 频道里触发的会话可以完全隔离。

出站消息流

出站时不会直接把一段长文本塞给平台。不同平台有不同限制:

  • Telegram、Discord、Slack 的单条消息长度不同。
  • 有些平台支持线程,有些不支持。
  • 有些平台支持图片、文件、投票、按钮。
  • 有些平台需要回复特定消息或带 mention。

所以 OpenClaw 会经过 Outbound Deliverchunker

flowchart LR
    Reply[Agent 回复] --> Deliver[Outbound Deliver]
    Deliver --> Dock[ChannelDock 轻量元数据]
    Deliver --> Chunker[消息分块]
    Chunker --> Adapter[OutboundAdapter]
    Adapter --> Platform[Telegram / Discord / Slack / WhatsApp]

ChannelDock 是轻量加载机制,只加载渠道的元数据、能力和发送配置,不必每次都加载完整插件实现。

Channel 生命周期

flowchart TD
    A[注册阶段 registerChannel] --> B[初始化]
    B --> B1[getChannelDock 轻量加载]
    B --> B2[getChannelPlugin 完整加载]
    B --> C[配置阶段]
    C --> C1[resolveAccountId]
    C --> C2[applyAccountConfig]
    C --> D[运行阶段]
    D --> D1[startAccount]
    D --> D2[inbound handlers]
    D --> D3[resolveAgentRoute]
    D --> D4[outbound sendText/sendMedia]
    D --> E[监控阶段]
    E --> E1[probeAccount]
    E --> E2[auditAccount]
    E --> E3[checkReady]

适配器职责表

适配器职责
config账户列表、账号解析、启停状态
setup账号初始化、配置校验
outbound发送文本、媒体、投票,编辑或删除消息
status探测账号状态,生成状态快照
gateway启动或停止账号连接,创建入站处理器
securityDM(Direct Message,私信)策略和安全警告
pairing配对码验证
groups群组 mention 策略、群组工具策略
threading线程回复模式、线程上下文
mentions提及解析和格式化
directory用户和群组目录查询
resolver自定义路由逻辑
actions消息动作,例如按钮或菜单
messaging消息元数据和格式化

目录结构可以按职责理解:

src/
├── channels/
│   ├── plugins/
│   │   ├── types.plugin.ts
│   │   ├── types.adapters.ts
│   │   ├── types.core.ts
│   │   └── registry-loader.ts
│   ├── dock.ts
│   ├── registry.ts
│   ├── allow-from.ts
│   ├── channel-config.ts
│   └── session.ts
├── routing/
│   ├── resolve-route.ts
│   ├── bindings.ts
│   └── session-key.ts
├── telegram/
├── discord/
├── slack/
├── signal/
├── imessage/
├── web/
├── infra/outbound/
└── plugins/

extensions/
├── msteams/
├── matrix/
├── zalo/
└── voice-call/

安全隔离

Channels 直接面对外部输入,所以安全策略不能只依赖模型判断。常见控制包括:

机制作用
Allowlist限定哪些用户、群组、账号可以触发 Agent
Mention gating群聊里只有提及机器人时才响应
DM pairing未知私信用户必须通过配对码
Group policy群组级工具策略
Sandbox policy外部会话放入沙箱,限制本地能力

这样可以避免陌生人通过聊天平台让 Agent 执行本地命令、读取文件或访问带登录态的浏览器。

Context:上下文窗口、压缩与剪枝

Context 指一次模型调用时发送给模型的所有内容。它包括系统提示词、工具描述、会话历史、工作区文件、运行时状态、Skills 元数据等。Context 受模型上下文窗口限制。

上下文构成可以通过这张图理解:

OpenClaw 上下文构成

图中的重点是:Context 不是 Memory。Memory 是持久化记忆,可以长期存储在磁盘或数据库里;Context 是当前模型窗口中真正发送给模型的内容。模型只能基于窗口里的内容直接推理。

上下文窗口解析

OpenClaw 会按优先级确定当前模型可用的上下文窗口:

优先级来源
1显式覆盖 contextTokensOverride
2context1m: true,Anthropic 1M 模型使用 1,048,576 tokens
3模型注册表,例如 models.json 或 provider catalog
4配置文件覆盖 models.providers.*.models[].contextWindow
5默认值

上下文窗口守卫会做两级判断:

const CONTEXT_WINDOW_HARD_MIN_TOKENS = 16_000;
const CONTEXT_WINDOW_WARN_BELOW_TOKENS = 32_000;
结果条件含义
shouldWarn小于 32K tokens可以运行,但复杂任务容易出问题
shouldBlock小于 16K tokens阻断运行,窗口太小
来源标记model / modelsConfig / agentContextTokens / default标识窗口值来自哪里

自动压缩 Compaction

长会话迟早会超过上下文窗口。OpenClaw 的 Compaction 会把旧消息交给模型总结,再把摘要写入历史。

flowchart LR
    Old[旧消息历史] --> Estimate[估算 token]
    Estimate --> Chunk[按预算分块]
    Chunk --> Summarize[LLM 总结]
    Summarize --> Entry[紧凑摘要条目]
    Entry --> Store[(JSONL 持久化)]
    Store --> NewContext[新上下文]

核心步骤:

步骤函数
Token 估算estimateMessagesTokens
分块chunkMessagesByMaxTokens
摘要生成summarizeWithFallback
历史裁剪pruneHistoryForContextShare

自适应分块参数:

const BASE_CHUNK_RATIO = 0.4;
const MIN_CHUNK_RATIO = 0.15;
const SAFETY_MARGIN = 1.2;

如果单条消息非常大,例如一段巨大的日志或工具输出,占了上下文窗口的 50% 以上,就无法安全地直接总结:

isOversizedForSummary(message, contextWindow);

处理策略通常是:

  1. 尝试完整压缩。
  2. 如果失败,只压缩较小消息,并记录过大消息。
  3. 如果仍失败,回退为消息计数说明,避免整个会话崩掉。

剪枝 Pruning

Compaction 和 Pruning 容易混淆。它们解决的问题不同:

特性CompactionPruning
作用范围整个历史
主要对象用户消息、助手消息、工具结果等
持久化写入 JSONL
触发时机接近上下文窗口上限
内容变化生成摘要
Pruning 作用范围仅 toolResult 消息
Pruning 持久化只改内存中的上下文
Pruning 触发时机每次请求前,TTL 过期后检查
Pruning 内容变化软修剪或硬清除

默认剪枝配置:

const DEFAULT_CONTEXT_PRUNING_SETTINGS = {
  mode: "cache-ttl",
  ttlMs: 5 * 60 * 1000,
  keepLastAssistants: 3,

  softTrimRatio: 0.3,
  hardClearRatio: 0.5,
  minPrunableToolChars: 50_000,

  softTrim: {
    maxChars: 4_000,
    headChars: 1_500,
    tailChars: 1_500
  },

  hardClear: {
    enabled: true,
    placeholder: "[Old tool result content cleared]"
  }
};

剪枝流程:

flowchart TD
    A[请求前检查] --> B{TTL 是否过期}
    B -- 否 --> Z[不处理]
    B -- 是 --> C[计算上下文占用比例]
    C --> D{超过 softTrimRatio}
    D -- 否 --> Z
    D -- 是 --> E[软修剪工具结果,保留头尾]
    E --> F{仍超过 hardClearRatio}
    F -- 否 --> Z
    F -- 是 --> G[硬清除旧工具结果,替换占位符]

保护机制包括:

  • 不修改用户消息和助手消息。
  • 跳过包含图片的工具结果。
  • 保护 bootstrap 阶段消息。
  • 保护最近 N 条助手消息之后的工具结果。

工具结果上下文守卫

工具结果特别容易撑爆上下文,例如网页快照、日志、代码搜索结果、命令输出。OpenClaw 对单条工具结果和总预算都做限制:

const SINGLE_TOOL_RESULT_CONTEXT_SHARE = 0.5;
const TOOL_RESULT_CHARS_PER_TOKEN_ESTIMATE = 2;

执行逻辑可以理解为:

const maxSingleToolResultChars =
  contextWindowTokens * 2 * 0.5;

const contextBudgetChars =
  contextWindowTokens * 4 * 0.75;

// 超预算时压缩最旧工具结果

当旧工具结果被压缩时,会替换为类似占位符:

[compacted: tool output removed to free context]

这比无节制保留工具输出更可靠。模型如果确实需要旧内容,可以重新调用工具读取更小范围的数据。

运行时上下文注入

OpenClaw 会从工作区读取一些约定文件,注入到上下文中:

文件作用
AGENTS.md项目规则
SOUL.md角色定义
TOOLS.md工具使用说明
IDENTITY.md身份信息
USER.md用户偏好
HEARTBEAT.md心跳状态
BOOTSTRAP.md首次运行引导

默认截断配置:

{
  "agents": {
    "defaults": {
      "bootstrapMaxChars": 20000,
      "bootstrapTotalMaxChars": 150000
    }
  }
}

压缩完成后,系统会重新注入 AGENTS.md 里的关键章节,例如:

  • ## Session Startup
  • ## Red Lines

这是为了避免压缩把关键规则折叠掉,导致后续模型运行忘记启动规则或安全红线。

Sandbox 上下文

沙箱会话会额外注入容器信息:

信息示例
容器名称containerName
容器工作目录containerWorkdir
工作区映射workspaceDiragentWorkspaceDir
Docker 配置docker
工具权限tools
浏览器桥接browserfsBridge

这样模型知道自己处在容器里,文件路径和工具权限也不会和宿主机混淆。

调试命令

命令作用
/status查看上下文窗口占用率和会话设置
/context list查看注入文件大小、工具 Schema 大小
/context detail展开上下文组成
/usage tokens显示每次回复的 token 使用量
/compact手动触发压缩

典型配置:

{
  "agents": {
    "defaults": {
      "contextTokens": 200000,
      "bootstrapMaxChars": 20000,
      "bootstrapTotalMaxChars": 150000,
      "compaction": {
        "mode": "auto",
        "targetTokens": 0.7
      },
      "contextPruning": {
        "mode": "cache-ttl",
        "ttl": "5m",
        "keepLastAssistants": 3,
        "softTrimRatio": 0.3,
        "hardClearRatio": 0.5
      }
    }
  },
  "models": {
    "providers": {
      "anthropic": {
        "models": [
          {
            "id": "claude-sonnet-4",
            "contextWindow": 200000
          }
        ]
      }
    }
  }
}

SubAgent:后台并行任务与多智能体编排

SubAgent 是从当前 Agent 会话中派生出来的后台独立运行实例。它适合处理并行研究、长任务、复杂项目拆分和多层任务编排。

它有四个关键特征:

特征说明
会话隔离每个 SubAgent 有独立 session key、上下文和工具策略
后台执行派生后非阻塞运行,主会话可以继续工作
结果通告完成后自动把结果摘要推回请求者
嵌套支持可以允许 SubAgent 再派生 SubAgent,但有深度限制

会话键和深度

SubAgent 的会话键表达了层级关系:

深度会话键格式角色是否可继续派生
0agent:<id>:main主智能体可以
1agent:<id>:subagent:<uuid>编排者子智能体取决于 maxSpawnDepth
2agent:<id>:subagent:<uuid>:subagent:<uuid>叶子工作者通常不允许

深度计算不只靠字符串解析,还会结合会话存储里的 spawnDepthspawnedBy 链:

export function getSubagentDepthFromSessionStore(
  sessionKey: string | undefined | null,
  opts?: {
    cfg?: OpenClawConfig;
    store?: Record<string, SessionDepthEntry>;
  }
): number {
  const fallbackDepth = getSubagentDepth(sessionKey);

  const entry = loadSessionEntry(sessionKey);
  const storedDepth = normalizeSpawnDepth(entry?.spawnDepth);

  if (storedDepth !== undefined) {
    return storedDepth;
  }

  const parentDepth = depthFromStore(entry?.spawnedBy);
  return parentDepth + 1 || fallbackDepth;
}

运行注册表

SubAgent 运行记录会进入注册表。注册表用于跟踪活跃运行、恢复未完成任务、清理孤儿运行和级联停止。

type SubagentRunRecord = {
  runId: string;
  childSessionKey: string;
  requesterSessionKey: string;
  requesterOrigin?: DeliveryContext;

  task: string;
  cleanup: "delete" | "keep";
  label?: string;
  model?: string;
  runTimeoutSeconds?: number;
  spawnMode?: SpawnSubagentMode;

  createdAt: number;
  startedAt?: number;
  endedAt?: number;

  outcome?: SubagentRunOutcome;
  suppressAnnounceReason?: "steer-restart" | "killed";
  endedReason?: SubagentLifecycleEndedReason;
};

注册表职责:

职责说明
运行跟踪记录活跃和历史 SubAgent
生命周期监听监听 agent_startagent_enderror 等事件
持久化写入磁盘,Gateway 重启后可恢复
级联停止父运行停止时停止子运行
孤儿检测会话缺失时清理异常记录

恢复流程:

flowchart TD
    A[Gateway 启动] --> B[从磁盘恢复 SubAgent 运行记录]
    B --> C[合并到内存注册表]
    C --> D[检查孤儿运行]
    D --> E[持久化修正结果]
    E --> F[恢复未完成运行]

派生流程

SubAgent 派生不是简单创建一个新对话。它需要权限检查、会话创建、线程绑定、启动运行、注册记录和钩子通知。

flowchart TD
    A[调用 sessions_spawn] --> B[权限和深度检查]
    B --> C{是否允许}
    C -- 否 --> X[返回拒绝]
    C -- 是 --> D[创建子会话]
    D --> E[写入 spawnDepth / spawnedBy]
    E --> F[可选线程绑定]
    F --> G[构建子智能体系统提示]
    G --> H[gateway.agent 启动运行]
    H --> I[注册 SubAgent 运行记录]
    I --> J[触发 subagent_spawned 钩子]
    J --> K[后台等待完成]

检查项包括:

检查目的
调用者深度 < maxSpawnDepth防止无限递归派生
活跃子运行数 < maxChildrenPerAgent防止单会话创建过多后台任务
全局并发 < maxConcurrent控制系统总负载
agentId 允许列表限定可以派生哪些 Agent
工具策略限定子智能体可用工具

子智能体系统提示

SubAgent 会得到专门的系统提示,强调它的任务边界:

function buildSubagentSystemPrompt(params: {
  requesterSessionKey?: string;
  childSessionKey: string;
  label?: string;
  task?: string;
  childDepth?: number;
  maxSpawnDepth?: number;
}) {
  const canSpawn = params.childDepth < params.maxSpawnDepth;

  const lines = [
    "# Subagent Context",
    "You are a subagent spawned for a specific task.",
    "",
    "## Your Role",
    `- You were created to handle: ${params.task}`,
    "- Complete this task. That's your entire purpose.",
    "",
    "## Rules",
    "1. Stay focused.",
    "2. Complete the task.",
    "3. Don't initiate unrelated actions.",
    "4. Be ephemeral.",
    "5. Trust push-based completion.",
    "6. Re-read with smaller chunks if tool output was compacted."
  ];

  if (canSpawn) {
    lines.push(
      "## Sub-Agent Spawning",
      "You CAN spawn your own sub-agents using sessions_spawn.",
      "Use the subagents tool to steer, kill, or check status."
    );
  } else {
    lines.push(
      "## Sub-Agent Spawning",
      "You are a leaf worker and CANNOT spawn further sub-agents."
    );
  }

  return lines.join("\n");
}

这个提示的重点是把 SubAgent 限制在具体任务内。主 Agent 可以做开放式对话,SubAgent 更像一个后台 worker。

结果通告

SubAgent 完成后不会像函数调用一样同步返回结果,而是通过通告机制把结果推回请求者。

flowchart TD
    A[等待 SubAgent 结束] --> B[读取最新助手回复或工具结果]
    B --> C[构建通告消息]
    C --> D[统计耗时 / token / 成本]
    D --> E[确定投递目标]
    E --> E1[线程绑定]
    E --> E2[delivery target 钩子]
    E --> E3[请求者来源回退]
    E1 --> F[投递通告]
    E2 --> F
    E3 --> F
    F --> G{请求者是否忙}
    G -- 是 --> H[入队或 steer]
    G -- 否 --> I[直接投递]
    H --> J[清理或保留子会话]
    I --> J

如果父 SubAgent 已经结束,通告会继续向上冒泡,直到找到仍可投递的祖先会话或原始渠道。

这种“推式通知”比轮询简单。主会话不需要不断问“子任务完成了吗”,只要等待结果通告即可。

SubAgent 管理工具

subagents 工具提供三个动作:

动作说明
list列出活跃和最近的子运行
kill <target>停止指定子运行,支持级联停止
steer <target> <message>向运行中的子智能体发送指导消息

权限模型和深度有关:

function resolveRequesterKey(params: {
  cfg: OpenClawConfig;
  agentSessionKey?: string;
}) {
  const callerSessionKey = resolveInternalSessionKey(params.agentSessionKey);

  if (!isSubagentSessionKey(callerSessionKey)) {
    return {
      requesterSessionKey: callerSessionKey,
      callerIsSubagent: false
    };
  }

  const callerDepth = getSubagentDepthFromSessionStore(callerSessionKey);
  const maxSpawnDepth =
    params.cfg.agents?.defaults?.subagents?.maxSpawnDepth ?? 1;

  if (callerDepth < maxSpawnDepth) {
    return {
      requesterSessionKey: callerSessionKey,
      callerIsSubagent: true
    };
  }

  const spawnedBy = loadSessionEntry(callerSessionKey)?.spawnedBy;
  return {
    requesterSessionKey: spawnedBy || callerSessionKey,
    callerIsSubagent: true
  };
}

叶子 SubAgent 通常不能管理自己的子任务,因为它不应该再派生任务。编排者 SubAgent 可以查看和控制自己派生的子运行。

级联停止会递归停止子运行和孙运行:

async function cascadeKillChildren(params: {
  parentChildSessionKey: string;
}) {
  const childRuns = listSubagentRunsForRequester(
    params.parentChildSessionKey
  );

  let killed = 0;

  for (const run of childRuns) {
    if (!run.endedAt) {
      const result = await killSubagentRun(run);
      if (result.killed) {
        killed += 1;
      }
    }

    const cascade = await cascadeKillChildren({
      parentChildSessionKey: run.childSessionKey
    });

    killed += cascade.killed;
  }

  return { killed };
}

配置和限制

典型配置如下:

{
  agents: {
    defaults: {
      subagents: {
        maxSpawnDepth: 2,
        maxChildrenPerAgent: 5,
        maxConcurrent: 8,
        runTimeoutSeconds: 900,
        archiveAfterMinutes: 60,
        model: "claude-3-haiku",
        thinking: "medium"
      }
    },
    list: [
      {
        agentId: "orchestrator",
        subagents: {
          allowAgents: ["*"]
        }
      }
    ]
  },
  tools: {
    subagents: {
      tools: {
        deny: ["gateway", "cron"],
        allow: ["read", "exec"]
      }
    }
  }
}

配置项含义:

配置说明
maxSpawnDepth最大派生深度,防止无限嵌套
maxChildrenPerAgent单个会话最多活跃子运行数
maxConcurrent全局 SubAgent 并发上限
runTimeoutSeconds子运行超时时间
archiveAfterMinutes运行记录归档时间
model子智能体默认模型
thinking默认思考级别
allowAgents允许派生的目标 Agent

默认工具策略通常是:

角色工具策略
主智能体可以使用 sessions_spawn 派生子智能体
深度 1 编排者在允许时可用 sessions_spawnsubagentssessions_listsessions_history
深度 2 叶子工作者禁止继续派生,专注完成任务

插件钩子

SubAgent 生命周期提供插件钩子:

钩子触发时机用途
subagent_spawning派生前准备线程绑定、验证权限
subagent_spawned派生成功后记录日志、更新 UI
subagent_delivery_target投递通告前自定义通告路由
subagent_ended运行结束清理资源

Discord 扩展可以用钩子为 SubAgent 创建专用线程:

export function registerDiscordSubagentHooks() {
  const hookRunner = getGlobalHookRunner();

  hookRunner.register("subagent_spawning", async (event) => {
    const thread = await createDiscordThread({
      channelId: event.requester.to,
      name: `Subagent: ${event.label || event.agentId}`
    });

    return {
      status: "ok",
      threadBindingReady: true,
      threadId: thread.id
    };
  });

  hookRunner.register("subagent_delivery_target", async (event) => {
    const binding = getThreadBinding(event.childSessionKey);

    if (binding) {
      return {
        origin: {
          channel: "discord",
          to: binding.channelId,
          threadId: binding.threadId
        }
      };
    }

    return {
      origin: event.requesterOrigin
    };
  });
}

这种扩展方式让 SubAgent 可以在 Discord、Slack 等支持线程的平台里拥有独立讨论空间。

并发 Lane 和队列

OpenClaw 用 Lane 区分不同类型任务:

export const enum CommandLane {
  Main = "main",
  Cron = "cron",
  Subagent = "subagent",
  Nested = "nested"
}

SubAgent 使用专属 Subagent lane。这样主会话、Cron 任务和 SubAgent 后台任务可以分别限流,不会互相完全抢占。

当请求者会话正忙时,SubAgent 通告可能不会立即插入,而是进入队列:

async function maybeQueueSubagentAnnounce(params) {
  const queueSettings = resolveQueueSettings(params);
  const isActive = isEmbeddedPiRunActive(params.sessionId);

  if (queueSettings.mode === "steer") {
    const steered = queueEmbeddedPiMessage(
      params.sessionId,
      params.triggerMessage
    );

    if (steered) {
      return "steered";
    }
  }

  if (isActive && queueSettings.mode === "followup") {
    enqueueAnnounce({
      key: buildAnnounceQueueKey(params.sessionKey, params.origin),
      item: params.announce,
      settings: queueSettings,
      send: sendAnnounce
    });

    return "queued";
  }

  return "none";
}

常见队列模式:

模式行为
steer尝试把通告作为指导消息注入正在运行的会话
followup当前运行结束后追加一次后续消息
collect收集多条通告后统一处理
interrupt中断当前流程并处理通告
steer-backlog优先 steer,失败后进入积压队列

典型使用方式

并行研究:

用户:研究 A、B、C 三个主题并生成报告

主智能体:
  - sessions_spawn(task: "研究主题 A", label: "research-a")
  - sessions_spawn(task: "研究主题 B", label: "research-b")
  - sessions_spawn(task: "研究主题 C", label: "research-c")

research-a 完成后通告结果
research-b 完成后通告结果
research-c 完成后通告结果

主智能体汇总三个结果,生成最终报告

编排者模式:

用户:重构这个大型项目

主智能体:
  - sessions_spawn(
      task: "协调重构工作",
      agentId: "orchestrator",
      label: "refactor-coordinator"
    )

refactor-coordinator:
  - sessions_spawn(task: "重构模块 A", label: "worker-a")
  - sessions_spawn(task: "重构模块 B", label: "worker-b")
  - sessions_spawn(task: "重构模块 C", label: "worker-c")

worker-a / worker-b / worker-c 完成后通告给 refactor-coordinator
refactor-coordinator 汇总后通告主智能体
主智能体向用户报告完成情况

线程绑定会话:

用户在 Discord 线程中:监控这个服务的性能

主智能体:
  - sessions_spawn(
      task: "启动性能监控",
      thread: true,
      mode: "session"
    )

Discord 扩展创建专用线程
子智能体在该线程中持续运行

用户:当前状态如何?
消息路由到绑定的子智能体会话

用户:/unfocus
解除线程绑定,后续消息回到主智能体

设计取舍与容易踩坑的点

OpenClaw 这套架构很强,但也有明确代价。理解这些边界,比单纯记住模块名更重要。

设计好处代价
Gateway 单控制平面接入统一,状态集中,便于多端联动Gateway 需要稳定运行,故障会影响整体控制面
WebSocket RPC适合双向事件流和实时状态协议版本、认证和断线恢复要设计清楚
工具策略管道可以精细控制不同来源的工具权限策略层级多,排查“为什么工具不可用”需要看完整链路
自动上下文压缩长会话可持续运行摘要会丢细节,关键规则需要重新注入
工具结果剪枝防止大输出撑爆窗口模型可能需要重新读取旧工具输出
SubAgent 推式通告后台任务无需轮询,适合并行处理通告路由、队列和父子会话生命周期要处理好
Channel 插件化新增渠道成本低,平台差异可隔离每个平台的消息限制、线程、mention、权限模型都要单独适配
Sandbox 隔离外部输入更安全文件路径、浏览器桥接、工具权限会更复杂

实际部署或二次开发时,优先检查这些点:

  1. Gateway 暴露到网络时必须开启认证和 TLS(Transport Layer Security,传输层安全协议),不要把本地控制面裸露到公网。
  2. 外部渠道默认使用更严格工具策略,尤其是群聊和未知私信。
  3. 为长任务配置超时和并发上限,Cron 与 SubAgent 都可能持续消耗模型和本地资源。
  4. 观察上下文占用率,工具输出和注入文件过大时,优先调剪枝和压缩配置。
  5. SubAgent 不要无限嵌套,一般深度 1 或 2 已经能覆盖并行研究和编排者模式。
  6. 插件渠道要实现状态探测和去重,消息平台经常重放 webhook 或断线重连。

OpenClaw 的核心不是某个单点能力,而是把 Agent 运行所需的控制面、推理循环、工具执行、上下文预算、外部渠道和后台子任务组合到一起。Gateway 负责“连接和调度”,Agentic Loop 负责“思考和工具循环”,Tools 负责“执行真实动作”,Context 负责“让模型在窗口内保持连续性”,SubAgent 负责“把复杂任务拆出去并行完成”。这几个部分配合起来,才构成一个能接入真实工作流的个人 AI 助手系统。


评论