扫码打开虎嗅APP
本文来自微信公众号: 叶小钗 ,作者:叶小钗,原文标题:《万字拆解 OpenClaw:从 Gateway、Memory、Skills、多 Agent 到 Runtime》
今天我们再深入一点,从源头开始,看看这个Agent是如何实现的,和其他Agent有啥不同。
但单纯按模块介绍容易显得零散,所以我们回到最底层的问题:
当用户真的发来一条消息时,OpenClaw这个Agent系统内部,到底是怎么跑起来的?
这件事其实比表面上看起来更重要。
因为你只要真的把一条消息从头跟到尾,就会发现OpenClaw跟普通聊天机器人、传统工作流系统、以及很多只会调工具的Agent框架,差别并不在于它会不会聊天,而在于它背后有一整套完整的运行链路:
消息接收、协议适配、路由分发、会话隔离、上下文组装、技能注入、流式执行、工具调用、持久化存储,以及在复杂任务下的多Agent协作。

为了让整篇文章更容易理解,我们先假设一个典型场景:
在钉钉里发来一句:
帮我整理今天的重要邮件,提炼待办
并生成一份给老板的简报
接下来,我们就沿着这条消息,看看它是如何从外部世界的一段文本,最终变成一套真正被Agent执行起来的任务链路的。
希望大家带着三个问题阅读此文:
OpenClaw的整体架构设计到底是什么
一条消息在系统中的完整执行路径是什么
多Agent协作到底是怎么落地实现的
很多人第一次看OpenClaw,很容易把它理解成一个能聊天、能调工具、还能帮你跨平台干活的智能助理。
如果从工程实现的角度看,OpenClaw更像是一个围绕Agent构建出来的运行时网关系统:Agent Runtime。
它不是简单地把一句用户输入丢给大模型,然后把输出再发回来,而是把整个过程拆成了一条清晰的执行链路,并在每个关键节点上做了工程治理。
它的整体架构,基本可以抽象成五层:

第1层:用户接口层
提供CLI、Web UI、移动App、WebSocket API等入口,把用户操作转成统一的内部请求。
对用户来说,可能只是网页里输入一句话,或者在钉钉、飞书里发一条消息;但对系统来说,这些入口最终都要收敛成统一的内部消息模型。
第2层:Gateway核心层
这是OpenClaw的核心运行时。它负责连接管理、请求接入、配置热加载、健康监控等基础治理工作。
换句话说,真正让整个系统常驻运行、能接消息、能回消息、能维持状态的,不是单个Agent,而是这个Gateway。
第3层:消息处理层
这是业务逻辑真正流转的核心,包括:
Agent执行器
路由系统
会话管理
媒体处理
出站投递
...
一条消息从进入系统到最终响应,最核心的执行动作都发生在这一层。
第4层:扩展与插件层
所有可插拔的扩展都在这里:
通道插件,对接钉钉/飞书、Telegram、WhatsApp、Slack等
技能工具系统
sub Agent机制
也正因为有这一层,OpenClaw才能不断往上接新通道、往下接新工具、往内部接多Agent协作。
第5层:基础设施层
这一层为整个系统提供通用能力,包括:
配置与密钥管理
结构化日志
定时任务
事件总线
记忆检索
沙箱安全
这部分平时不太显眼,但没有它,前面几层都跑不稳。
从数据流转的视角看,一条消息的完整路径其实很清楚:
消息源→协议适配→路由分发→会话构建→Agent执行→响应投递→状态持久化
所以接下来,我们就沿着这条路径往下看。

先回到刚才那个例子。
帮我整理今天的重要邮件,提炼待办
并生成一份给老板的简报
从用户视角看,这就是一条普通消息。但从系统视角看,问题马上就来了:
钉钉的消息格式和飞书不一样,Discord和WhatsApp不一样,Telegram和内部WebSocket通道也不一样。
有的平台带message_id,有的平台叫thread_ts,有的消息体里还嵌着复杂的结构,附件、引用、线程信息各不相同。
如果核心逻辑直接去处理这些异构数据,代码很快就会变成一团乱麻。
所以OpenClaw的第一步,不是让Agent去理解任务,而是先做协议适配。

每个外部渠道都有一个专属适配器插件,把原始消息清洗成统一的内部对象,MsgContext。大概长这样:
interfaceMsgContext{
Body:string;
BodyForAgent?:string;
BodyForCommands?:string;
RawBody?:string;
SessionKey:string;
Provider:string;
Surface?:string;
ChatType?:"direct"|"group";
SenderId?:string;
SenderName?:string;
SenderUsername?:string;
OriginatingChannel?:string;
OriginatingTo?:string;
AccountId?:string;
MessageThreadId?:string;
CommandAuthorized?:boolean;
MessageSid?:string;
GatewayClientScopes?:string[];
}
这里最关键是统一抽象。
也就是说,不管消息是从哪个犄角旮旯来的,进了网关之后,都会变成这个标准格式。后面的流程只需要对着MsgContext干活,完全不用操心来源平台。

web端消息适配
这一步把平台差异隔离在了网关入口,而不是污染到整个Agent执行链路里。
所以如果以后要接一个新通道,也不需要改核心逻辑,通常只要做四件事:
在Registry中添加通道元数据
实现对应的ChannelPlugin
在插件加载器里注册新插件
更新配置Schema和文档测试
整个过程无需改核心系统代码,这就是插件化设计真正有价值的地方。
PS:这里要特别提一句,OpenClaw很多代码都是工程产物,如果大家真的想了解他的设计核心,只需要做一个渠道就好了,就会少很多工程策略,比如这里的收束信息
所有封装好的MsgContext,最后都会流入一个统一关卡:dispatchInboundMessage。
这一步的作用,不是做复杂业务,而是把所有入站消息的处理入口收敛成同一个总开关。源码大概是这样:
exportasyncfunctiondispatchInboundMessage(params){
constfinalized=finalizeInboundContext(params.ctx);
returnawaitwithReplyDispatcher({
dispatcher:params.dispatcher,
run:()=>dispatchReplyFromConfig({
ctx:finalized,
cfg:params.cfg,
dispatcher:params.dispatcher,
replyOptions:params.replyOptions,
replyResolver:params.replyResolver,
}),
});
}
从结构上看,它先做了两件事:
1.最终化入站上下文
也就是finalizeInboundContext。这个步骤主要负责:
补全缺失字段
标准化格式
统一上下文表示
它的意义在于,前面虽然已经做了通道适配,但不同通道在细节上仍可能有不一致的地方,所以在真正进入核心处理逻辑前,还要再做一次最终收束。
2.交给回复分发器继续往下跑
这一步之后,消息才真正开始进入OpenClaw的处理主干。
也就是说,到这里为止,系统干的还不是“理解任务”,而是先确保:
这条消息在格式上是可被系统安全处理的。

消息一进入主链路,OpenClaw不会立刻把它扔给模型,而是先做三类关键判断:
要不要处理
有没有重复
该交给哪个Agent
这一步非常像真实系统里的“前置治理层”。
去重:先防止消息被重复处理
真实生产环境里,消息重复投递是非常常见的。
Webhook可能重试,平台可能重复推送,网络抖动也可能导致同一条消息被系统收两次。如果不做幂等控制,最坏的结果不是“多回复一句”,而是:
同一任务被执行两次
同一工具被调用两次
同一个外部API被重复触发
同一笔成本被重复消耗

所以OpenClaw会为每条消息生成一个幂等键,也就是idempotencyKey。核心逻辑由buildInboundDedupeKey控制:
exportfunctionbuildInboundDedupeKey(ctx:MsgContext):string|null{
const provider=normalizeProvider(
ctx.OriginatingChannel??ctx.Provider??ctx.Surface
);
const messageId=ctx.MessageSid?.trim();
if(!provider||!messageId){
returnnull;
}
const peerId=resolveInboundPeerId(ctx);
if(!peerId){
returnnull;
}
const sessionKey=ctx.SessionKey?.trim()??"";
const accountId=ctx.AccountId?.trim()??"";
const threadId=ctx.MessageThreadId?String(ctx.MessageThreadId):"";
return[provider,accountId,sessionKey,peerId,threadId,messageId]
.filter(Boolean)
.join("|");
}
生成格式大概是:
{provider}|{accountId}|{sessionKey}|{peerId}|{threadId}|{messageId}
比如:
whatsapp||main:+1234567890|msg_123
discord|default|agent:assistant:123|987654321||11223
slack|default|main:default|U12345678||C12345678
都会变成自己的唯一标识。
只要缓存里发现这个键已经处理过,系统就直接返回,避免重复调用昂贵的LLM API,默认TTL是20分钟。
拦截
有些消息不是给Agent干活的,而是给系统发控制命令的,所以除了去重,系统还会做一些快速拦截。
例如用户输入/stop,那它就不该继续往下跑任务理解和工具调用,而应该立刻中断对应的AbortController,强行停止正在执行的Agent任务。
这说明OpenClaw不是一个单纯的“问一句答一句”的壳,它本质上还是一个长期运行的任务系统,所以控制命令是它必须支持的一类特殊输入。
快速响应
对于Web请求,系统通常还会先通过WebSocket返回一个started状态,然后再异步执行后续处理。
这一步看起来小,但很重要。
因为模型思考、工具调用、网络请求都可能很慢,如果前端一直等最终结果,很容易超时,也很容易让用户产生“卡死了”的感觉。
所以从体验上看,OpenClaw会先让你知道:任务已经进入执行状态了。
去重和拦截只是前置治理,真正进入业务之前,系统还得回答一个根本问题:这条消息,应该由谁来处理?这就是路由系统的工作。
OpenClaw的路由根据通道类型,采用两套不同策略。
Web内部通道
Web客户端通常可以直接传sessionKey,格式一般是:
{agentId}:{scope}
例如:
assistant:main
在这种情况下,系统可以直接使用这个会话键,不需要再查绑定规则。
外部通道
但像Slack、WhatsApp、Discord这类外部通道,客户端本身并不知道系统内部的会话组织方式,所以必须通过配置里的绑定规则来决定该交给哪个Agent。
例如:
{
"bindings":[
{
"agentId":"assistant",
"match":{"channel":"whatsapp","accountId":"my_bot"}
},
{
"agentId":"vip-assistant",
"match":{"channel":"whatsapp","peer":{"id":"+1234567890"}}
}
]
}
每个绑定规则都可以根据不同维度匹配:
通道标识
账户ID
对等体用户或群组
Discord服务器ID
团队ID
角色列表
系统会按优先级从高到低匹配:
精确对等体匹配
Discord服务器+角色匹配
Discord服务器匹配
通道账户级匹配
通道级匹配
默认Agent
所以回到我们的例子:
这条钉钉消息先被识别为来自哪个通道、哪个账号、哪个用户、哪个频道,然后系统根据配置决定,最终是由assistant来处理,还是由某个专门的support-agent、vip-assistant、coder来处理。
唯一会话键
一旦Agent确定下来,系统就会为这次对话构建sessionKey,格式一般是:
{agentId}:{scope}
比如:
assistant:main
assistant:whatsapp:direct:+1234567890
assistant:discord:channel:987654321
support:telegram:group:-1001234567890
这个键非常重要,因为它后面承担两件事:会话隔离与并发控制。
也就是说,用户看到的只是一句消息,但系统真正管理的,其实是:这句话属于哪个Agent的哪一条会话。
PS:这一段处理逻辑其实挺复杂的,我们这里不展开
车道机制
到这里,消息已经完成了路由,知道该交给哪个Agent,也知道自己属于哪个会话。

但系统仍然不会直接开跑。原因很简单:如果同一会话里两条消息同时跑,很容易出现上下文错乱。
比如用户前一秒说:
帮我整理今天的重要邮件
下一秒又说:
顺便把待办改成按优先级排序
如果两条消息并行处理,就可能出现:
第二条先完成
第一条后完成
上下文互相污染
输出顺序颠倒
工具调用状态不一致
所以OpenClaw设计了会话车道机制。
会话级车道
相同sessionKey的消息必须串行执行,确保上下文连贯。
这一步本质上是在保证:一个会话在任意时刻,只有一条消息真正占用它的执行上下文。
这样你才能把同一条对话当成“连续对话”来理解,而不是并发乱流。
全局级车道
除了会话级串行,系统还可以配置全局最大并发数。
如果整个系统同时进来太多消息,超出容量,就先进入等待队列。
这相当于两层节流:
会话层防止同一条对话乱序
系统层防止整个运行时被打爆
这一步很有OpenClaw的工程味道。它不是单纯依赖模型能力,而是在运行时层面主动治理资源。
当消息终于排到自己,真正进入Agent执行阶段时,最重要的一件事来了:系统要为模型组装完整上下文。
大家马上会发现,要“爆炸”了...
很多人理解Agent时,容易想成这样:
用户输入一句话
模型理解一下
调用几个工具
结束
但实际在OpenClaw里,模型看到的不是单独一句用户输入,而是一整套被拼装好的上下文环境。组装顺序大致是:
系统提示词→技能提示→对话历史→当前消息
这个顺序很重要,因为它实际上定义了模型的认知层级:
先知道自己是谁
再知道自己能做什么
再知道之前发生了什么
最后才看用户刚刚说了什么

系统提示词
系统提示词的职责,是定义Agent的角色、行为规则和安全边界。
OpenClaw这里做得很工程化,它不是把一整段prompt硬写死,而是通过Bootstrap文件系统来注入。
大概会把这些文件装进上下文:
AGENTS.md:定义Agent行为规则和工具使用指南
SOUL.md:定义个性和人格
TOOLS.md:工具使用说明
IDENTITY.md:身份标识信息
USER.md:用户偏好
HEARTBEAT.md:心跳检测提示
BOOTSTRAP.md:初始化引导
MEMORY.md/memory.md:长期记忆
这些文件位于工作区目录,默认是~/.openclaw/workspace。
也就是说,在真正回答你“整理邮件”之前,模型先会被告知:
我是谁
我该遵守什么规则
我能用哪些工具
这个用户有什么偏好
这个系统有哪些长期记忆
这和普通聊天机器人有个本质区别:它不是每次都从零开始聊,而是从一个被预先塑形过的Agent身份出发。
记忆召回规则
在src/agents/system-prompt.ts里,系统提示词里还会显式包含记忆召回规则:
##Memory Recall
Before answering anything about prior work,decisions,dates,people,preferences,or todos:
run memory_search on MEMORY.md+memory/*.md;thenuse memory_get to pull only needed lines.
If low confidence after search,say you checked.
这段话很有意思。
它不是告诉模型“你要尽量记住”,而是告诉模型:
碰到和历史决策、偏好、待办、日期等相关的问题时,先查记忆,再说话。
这就把“记忆”从模型模糊的上下文残留,升级成了一种显式检索机制。
大小限制
因为这些文件每次运行都会消耗tokens,所以系统会限制:
单文件最大字符数
总注入字符数上限
是否显示截断警告
这说明OpenClaw的思路不是把所有东西都喂给模型,原因很简单:系统提示词本身也要受到上下文预算约束。
系统提示词组装完之后,接下来要做的就是技能提示注入。这部分很关键,因为很多人谈OpenClaw时,最容易误解Skills。

从源码实现上看,Skills并不是简单的一堆函数列表。它更像是:
先把一组可用能力的使用说明、调用边界、适用场景告诉模型,再在模型决定调用时,去连接真实的工具实现
也就是说,Skills首先是让Agent知道自己该怎么用工具的方法包。
技能加载流程大致分四步:
发现
系统会从多个来源扫描技能文件:
工作区
用户全局目录
内置目录
插件目录
过滤
不是所有发现到的技能都能直接用,系统会根据多个条件过滤:
平台
消息通道
发送者权限
黑白名单配置
安全检查
OpenClaw对Skills做了三层策略管道:
Profile过滤
Sandbox隔离
Subagent继承
也就是说,一个技能能不能被调用,不只是看它存不存在,还要看当前Agent有没有权限、安全边界允不允许、子Agent是否继承到对应能力。
生成提示词
最后系统会把可用技能描述格式化为文本,注入系统提示词,供LLM在需要时调用。
所以在我们的案例里,当用户说“帮我整理今天的重要邮件,提炼待办,并生成给老板的简报”时,模型不是凭空想象自己能做什么,而是会在这套技能描述中判断:
有没有邮件处理相关能力
有没有摘要生成能力
有没有文档组织能力
是否需要进一步调用子Agent
这才是Skills真正的作用。
系统提示词和Skills搞定后,接下来才轮到对话历史和当前消息。
会话历史
OpenClaw采用双层存储管理会话历史。

一、轻量索引:sessions.json
里面存的是元数据,例如:
会话ID
会话键
转录文件路径
最后更新时间
模型覆盖配置
技能快照
位置通常在:
~/.openclaw/agents/{agentId}/sessions/sessions.json
内容如下

二、重度转录:{sessionId}.jsonl
记录完整的对话历史,采用JSON Lines格式,每行一个JSON对象,便于流式读取和追加。文件同样位于agents目录下的sessions文件夹中。

历史消息加载
系统从转录文件中读取历史时,会做几件事:
从最新消息向前读取指定token数量的轮次
过滤掉不需要的消息类型
保证时间顺序正确
估算历史消息占用的token
所以在真正调用模型前,系统已经在做一件很现实的事情:这次上下文预算里,到底还能放多少历史。
当前消息
当前消息作为上下文最后一部分注入,包括:
用户输入文本
发送者信息
时间戳
元数据
通道信息
会话键
这也意味着,模型看到这次请求时,不是只看到一句新消息,而是站在一整条会话上下文的尾部,来理解它的语义。
记忆压缩
一旦把系统提示词、技能提示、历史记录、当前消息全塞进去,另一个问题马上就来了:上下文总有一天会爆。
所以OpenClaw这里专门做了一整套防爆机制。

历史轮次限制
系统会根据通道配置限制保留的历史轮数,从最新消息开始往前扫描,丢弃更早的部分。
这是一层最简单也最直接的防线。
工具结果截断
工具调用结果可能非常大,例如:
长文本
大JSON
多页日志
大段网页内容
如果一股脑全塞回上下文,很容易直接撑爆窗口。
所以系统会自动截断工具输出,并判断是否要保留尾部关键信息。如果尾部有错误信息或JSON结构,就会采取“头尾保留”策略,否则只保留开头。
自动压缩
当上下文窗口接近模型限制时,系统会把早期历史分块,然后为每块生成摘要,用摘要替换早期历史,同时保留最近几轮完整对话。
摘要生成时要求保留:
活跃任务
操作进度
用户最后请求
已做决策
后续依赖信息
所以这不是机械裁剪,而是一种语义压缩。
容错与降级
如果压缩后仍然超限,系统还会继续尝试:
切换到上下文更大的模型
降低Agent的thinking级别
最终回退为提示用户重置会话
这说明OpenClaw不是把上下文管理交给模型自己,而是把它视为运行时层面的硬约束问题。
PS:从这里大家就可以看出,如果没有最近一年的模型上下文极速增长,根本不可能有OpenClaw这类Agent啥事
除了会话历史,OpenClaw还单独维护两类记忆:
一、长期记忆
文件名通常是:
MEMORY.md
memory.md
用于存常青知识,例如:
项目规则
API文档
设计决策
长期偏好
这类记忆在Agent启动时通过Bootstrap系统直接注入系统提示词。
二、每日记忆
存放在:
memory/YYYY-MM-DD.md
用于记录时效性内容,例如:
每日纪要
当天待办
临时决策
会议记录
这类记忆不直接注入提示词,而是通过记忆搜索工具按需检索,并且会带时间衰减权重,越新的内容权重越高。

三、记忆什么时候写入
长期记忆通常由用户或Agent通过编辑工具手动维护。
而每日记忆则会通过Memory Flush机制自动触发。
触发条件:
会话token数接近上下文窗口上限(默认软阈值4000 tokens)
会话转录文件大小超过阈值(默认2MB)
当系统发现会话快接近压缩阈值时,会先发一个特殊提示给Agent:
Pre-compaction memory flush.
Store durable memories now(use memory/YYYY-MM-DD.md;create memory/ifneeded).
IMPORTANT:If file already exists,APPEND new content only anddonot overwrite existing entries.
也就是说,在压缩发生前,系统会先提醒Agent:把这轮对话中值得长期保留的信息,先沉淀进每日记忆。
这相当于在上下文压缩前,先打一层记忆护城河。
到这里,上下文已经准备好,Agent才真正开始运行。

在我们的例子里,这时候模型可能会判断:
当前任务不是普通问答
它是一个执行型任务
里面包含邮件整理、待办提炼、简报生成三类需求
需要调用相应技能和工具
如果任务过于复杂,可能还要拆给子Agent
这个阶段最核心的三件事是:
流式响应
OpenClaw使用SSE或WebSocket做流式输出。
当LLM开始生成内容时,系统会把内容块实时推送给客户端,让用户立即看到输出开始,而不是等全部完成后一次性返回。
这能明显降低感知延迟,也更适合长任务。
工具调用
当LLM判断需要调用工具时,系统会暂停文本流,执行对应工具,例如:
读取文件
调API
搜索记忆
访问邮件
执行脚本
执行完后,再把结果反馈给LLM,让它继续推理。
对用户来说,看到的可能只是正在执行工具或者一段中断后的继续输出;但对系统来说,这其实是一次完整的推理—执行—再推理循环。
错误处理与模型回退
真实生产环境里,出错是常态。所以OpenClaw做了多级回退策略:
限流:指数退避或切换备用模型
认证错误:轮换多个API Key
超时错误:降低thinking级别或换更快模型
上下文溢出:触发压缩或换更大上下文模型
这部分很能说明OpenClaw的工程取向:它不假设模型永远稳定,而是假设运行过程随时可能失败,所以提前把兜底路径都铺好。

当Agent终于处理完
整理今天的重要邮件,提炼待办
并生成给老板的简报
这件事后,系统仍然不能立刻算结束。它还要做三类收尾动作:
响应投递
回复分发器会根据消息上下文中的来源通道和目标地址,调用对应通道的出站适配器发送消息。
如果配置了跨通道回复,也可以覆盖原始目标。例如:
在钉钉发起任务
最终把结果发回WhatsApp
这一点非常像智能网关的思路,而不只是聊天框里的模型。
但还是那句话,OpenClaw这块的源码不适合初学者,初学者直接搞一个IM渠道就好,OpenClaw很多工程代码,就是为了兜底,这些代码量全部加剧了学习成本。
会话持久化
系统会把这次交互的完整记录写下来:
更新sessions.json里的元数据
把用户消息、AI回复、工具调用等内容追加到JSONL转录文件
这样下一次会话继续时,系统才能知道之前发生过什么。
资源释放与清理
执行结束后,还要做:
释放会话车道锁
释放全局并发配额
标记幂等键为已处理
定期清理过期会话
归档旧转录
轮换大文件
这一步很像一个长期运行系统的善后逻辑,没有它,系统迟早会越来越重。
记忆索引
既然记忆是Markdown文件,系统就还要解决一个问题:怎么让Agent高效查它们。
OpenClaw的做法是给记忆系统单独建索引,索引数据库通常位于:
~/.openclaw/memory/index.db
里面大致会有这些表:
files
chunks
chunks_vec
chunks_fts
embedding_cache
也就是同时支持:
文件元数据管理
文本分块
向量检索
全文搜索
向量缓存
为了保证文件和索引同步,系统还会做三种同步机制:
文件监视器自动触发
定期同步
增量同步
必要时还会全量重建索引:
创建临时数据库
遍历记忆文件
分块
生成embedding
建全文索引
最后原子替换旧索引
综上,OpenClaw的记忆并不是把Markdown当备忘录丢在那,而是真把它做成了一层可检索、可维护的知识基座。
前面讲到这里,其实已经是一条完整的单Agent执行链路了:
消息进门
协议适配
去重拦截
路由分发
会话排队
上下文组装
技能注入
流式执行
响应投递
状态持久化
如果任务简单,到这里就闭环了。
但现实问题是,很多复杂任务并不适合由一个Agent单独完成。还是刚才那个例子:
帮我整理今天的重要邮件,提炼待办
并生成一份给老板的简报
看起来一句话,实际上可能包含至少三块工作:
筛选和归类邮件
提炼关键待办
组织成适合老板阅读的简报格式
如果全让一个Agent一把抓,它当然也能硬做,但往往会出现:
上下文太重
推理链过长
工具调用太杂
专业能力混在一起
中间状态难管理
所以OpenClaw在单Agent之上,又做了多Agent协作系统。
它实现了几件关键事情:
Agent隔离
动态任务分发
层级协作
生命周期管理
安全边界控制
也就是说,主Agent可以根据任务需要,临时创建一个或多个子Agent,把某些子任务交出去,自己做总控和汇总。

一个典型的多Agent流程
在我们的案例里,主Agent完全可能这么干:
先创建一个research-agent去筛重要邮件
再创建一个analysis-agent去提炼待办和风险点
最后由主Agent自己把这些结果整合成给老板的简报
这时候,多Agent就不再是抽象概念,而是变成了一种很具体的任务拆解策略。
创建subAgent
主Agent决定创建子Agent时,通常会走sessions_spawn工具。
系统先做一轮严格校验:
嵌套深度检查,防止无限递归
并发限制检查,防止资源耗尽
允许列表检查
沙箱状态检查
通过后,系统会:
生成唯一子会话键
例如agent:{agentId}:subagent:{uuid}
应用模型配置和thinking级别
处理附件和上下文传递
为子Agent生成专门的系统提示词
例如:
#Subagent Context
You are a subagent spawned by main agentfora specific task.
##Your Role
-You were created to handle:${taskDescription}
-Complete this task.That's your entire purpose.
-You are NOT main agent.Don't try to be.
##Rules
1.Stay focused
2.Complete task
3.Don't initiate
4.Be ephemeral
这个提示词不是在强化子Agent的人格,而是在强调它的边界:你不是主Agent,你就是来做这一个子任务的。
结果返回
子Agent完成任务后,系统会:
触发生命周期事件
读取输出结果
经过通知队列
判断目标是谁
决定注入主Agent会话,还是直接发给用户
如果请求者是主Agent,那么结果通常会以内部事件的方式重新注入主Agent会话,供主Agent下一轮推理时使用。
于是整条协作链路就变成了:
主Agent接收用户任务
主Agent拆子任务
子Agent各自执行
子Agent把结果回传
主Agent汇总结果
主Agent最终回复用户
这个过程本质上就是一个层级化协作网络。

配置继承
OpenClaw在Agent配置上采用三级继承机制:
Agent级配置优先级最高
全局默认配置次之
代码默认值兜底
例如某个coding-agent可以有自己的:
模型
工作区
可用工具
子Agent策略
沙箱模式
而没有单独定义的部分,再回落到全局默认值。这种设计让多Agent系统既灵活,又不至于配置爆炸。

再回到最初那句话:
帮我整理今天的重要邮件,提炼待办
并生成一份给老板的简报
那么它在OpenClaw里真正经历的大致过程,其实是这样的:
钉钉原始消息进入系统
通道插件把它适配成统一的MsgContext
网关做最终化处理
系统检查去重、拦截控制命令、快速响应started状态
路由系统根据绑定规则找到目标Agent
生成sessionKey
进入会话车道排队,确保同一会话不乱序
组装完整上下文:系统提示词、Bootstrap文件、Skills、历史记录、当前消息
模型在技能描述和规则约束下开始推理
过程中可能调用工具,也可能spawn子Agent
子Agent完成后把结果回流给主Agent
主Agent生成最终答复
回复分发器把结果投递回目标通道
会话和转录被持久化
记忆被更新,索引同步
资源释放,执行闭环结束
这样一看你就会发现,OpenClaw真正有价值的地方,不是它能不能回答一句话,而是:
它把一条消息从进入系统到完成执行,做成了一条可治理、可扩展、可追踪、可恢复的Agent Runtime链路
通过完整追踪一条消息的旅程,我们可以更清楚地看到OpenClaw的几个核心设计原则。
第一,它是分层的。
各层职责清晰,从通道适配到执行治理再到基础设施,边界都比较明确。
第二,它是运行时导向的。
去重、会话车道、上下文压缩、错误回退、资源清理,这些都说明它不是一个把prompt包一层UI的玩具,而是真在认真处理长期运行中的工程问题。
第三,它是可扩展的。
通道插件、技能系统、子Agent机制、记忆索引,都让它更像一个开放的Agent网关,而不是一个封闭应用。
第四,它开始具备分布式协作的雏形。
多Agent的出现,意味着OpenClaw已经不满足于“一个模型处理一切”,而是在朝任务拆解、层级协作、并行执行的方向发展。
所以如果你问,OpenClaw到底是什么。
我的回答会是:
它不是一个更会聊天的机器人,也不只是一个会调工具的Agent壳。它更像是一个把消息入口、会话治理、上下文管理、技能调用、持久化存储和多Agent协作缝合在一起的Agent Runtime+Gateway
而理解它最好的方式,就是去开发一个Mini-OpenClaw,这也是我们正在做的。
所以,后续我们再基于一个更简单版本,没有那么多工程控制的系统做讨论...