OpenClaw 的核心组件有哪些?请描述它们之间的关系

OpenClaw 把整个 Agent 平台拆成了五层组件,各司其职又首尾相连。

Channel Plugins 是最外层,每个消息平台一个插件,Telegram、Discord、Slack 等各一套,干的活就是协议转换,把平台私有格式翻译成 OpenClaw 内部统一的消息结构。

Gateway 是整个系统的中枢,所有请求都从这过。鉴权、限流、幂等去重全在这一层搞定,然后把消息往下游分发。

Routing 是路由层,拿到消息后根据配置规则决定交给哪个 Agent 处理,同时生成 Session Key 来追踪会话。

Agent Runner 是执行引擎,管理 LLM 调用、工具执行、结果回传这个循环,一个 Agent 跑起来的所有脏活累活都在这。

Context Engine 管对话历史,摄入、组装、压缩全包了,接口化设计,随时可以插拔替换成自定义实现。

一条消息的完整链路:Channel 收到用户消息 → Gateway 鉴权限流后分发 → Routing 匹配到目标 Agent → Agent Runner 驱动 LLM 执行 → Context Engine 维护上下文 → 回复原路返回,经 Gateway 交回 Channel 发出。

扩展知识

为什么要分这么多层

很多人第一反应是"搞这么多层不累吗",但你想想一个 Agent 平台要同时对接 Telegram、Discord、Slack、Web 等,每个平台的消息格式、回调方式、鉴权机制都不一样。如果不抽出 Channel 层做协议转换,核心逻辑里到处都是 if-else 判断平台类型,加一个新渠道得把代码翻个底朝天。

Gateway 单独拎出来也是同理。鉴权、限流、幂等这些横切关注点如果散落在各个模块里,维护成本会爆炸。集中到 Gateway 统一处理,下游组件不用操心这些事。

Routing 独立出来是因为 Agent 系统往往不止一个 Agent。比如一个企业部署了客服 Agent、运维 Agent、数据分析 Agent,一条消息进来得先判断交给谁处理。

OpenClaw 在 src/routing/resolve-route.ts 里实现了路由核心逻辑,支持按 peer(具体用户/聊天)、guild(Discord 服务器)+ 角色、team(Slack 工作区)、account、channel 类型等维度做 binding 匹配。

源码层面的落点

在源码里,这五层各有对应目录:

1)Gateway 在 src/gateway/server.impl.ts 负责启动,server-methods.ts 负责请求分发

2)Channel 在 src/telegram/src/discord/src/slack/ 以及 extensions/ 下的扩展

3)Routing 在 src/routing/resolve-route.ts

4)Agent Runner 在 src/agents/pi-embedded-runner/run.ts,这是执行主入口

5)Context Engine 在 src/context-engine/types.ts 定义接口,legacy.ts 是默认实现(它把消息持久化委托给 SessionManager,assemble 直接透传消息列表,compact 委托给 compaction 模块做摘要压缩)

辅助组件

除了五层核心组件,还有几个关键辅助角色。

Plugin Registry 在 src/plugins/registry.ts,统一收集插件注册的工具、Hook、渠道和 Provider,所有扩展点都汇聚到这。

Session Manager 负责会话持久化,用的 JSONL 格式,每轮对话追加一行,既方便调试也方便回放。

Config 层在 src/config/,管理所有配置的 Schema 验证和解析,用 Zod 做运行时校验,启动时就能把配置错误拦住。

面试官追问

提问:如果要给 OpenClaw 新增一个微信渠道,大概要改哪些地方?

回答:只需要在 Channel 层新增一个微信插件,实现消息收发的协议转换接口就行,核心就是把微信的 XML 消息格式转成 OpenClaw 内部的统一消息结构,再把回复转回微信格式。Gateway 往下的所有组件都不用动,这就是分层的好处。具体来说需要实现 ChannelPlugin 接口(定义在 src/channels/plugins/types.plugin.ts),它采用 adapter 模式,按能力拆分为 messaging(消息收发)、outbound(外发)、streaming(流式输出)、gateway(网关集成)等多个适配器,按需实现即可。

提问:Context Engine 说可以插拔替换,那默认实现和自定义实现的边界在哪?

回答:src/context-engine/types.ts 里定义了接口契约,核心就三件事:摄入新消息(ingest)、组装 Prompt 上下文(assemble)、压缩历史记录(compact)。默认的 legacy.ts 实现比较直白:ingest 是 no-op(消息持久化由 SessionManager 负责),assemble 直接透传消息列表,compact 委托给 compaction 模块做 LLM 摘要压缩。如果业务需要更复杂的策略,比如按语义相关性检索历史、或者接入向量数据库做 RAG,只要实现同一套接口,在配置里切换就行。

提问:Agent Runner 的执行循环什么时候终止?有没有防死循环的机制?

回答:Agent Runner 每次循环就是调 LLM 拿到响应,如果响应里包含工具调用就去执行工具,把结果喂回 LLM 继续下一轮。终止条件是 LLM 返回纯文本回复、不再请求调用工具。防死循环靠多层保护:执行超时时间、工具循环检测(tool-loop-detection.ts 内置多种检测策略:generic_repeat 检测连续调同一工具同一参数、ping_pong 检测两个工具交替调用、known_poll_no_progress 检测轮询无进展、global_circuit_breaker 全局断路器兜底)、以及 context overflow 时的自动 compaction 和降级。超过限制就强制终止,返回一个兜底回复。

提问:Gateway 的幂等去重是怎么做的?

回答:消息平台经常会重复推送同一条消息,比如 Telegram 的 Webhook 超时重试。Gateway 拿到每条消息后会提取一个唯一标识,在本地缓存里做去重判断,如果已经处理过就直接丢弃。这样下游组件压根不用关心重复消息的问题,鉴权和去重全在入口收敛掉了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇