OpenClaw 快速上手(三):让 Agent 循环跑起来的六层架构
Gateway、Pi Agent、Tools、Skills、Memory、Channels——每一层在做什么、它们怎么拼在一起、以及为什么这种分层在你开始写 Skill 时会变得很重要。
OpenClaw 用几个月可以完全不读这一篇。但你第一次写 Skill、调试一个走错路的消息、或者搞不清 Agent 为什么忘事时,你会想知道每个盒子在做什么。
六层结构
+-----------------------------------------------+
| Channels | ← 钉钉 / Telegram / ...
+-----------------------------------------------+
|
+-----------------------------------------------+
| Gateway | ← :18789,归一化消息
+-----------------------------------------------+
|
+-----------------------------------------------+
| Router + Sessions + Pi Agent (loop) | ← 决定谁来处理
+-----------------------------------------------+
|
+-----------------------------------------------+
| Tools (26) Skills (53+ 内置) | ← "能做什么" + "怎么做"
+-----------------------------------------------+
|
+-----------------------------------------------+
| Memory + ContextEngine | ← 持久化上下文
+-----------------------------------------------+
|
+-----------------------------------------------+
| LLM Provider | ← DashScope / Anthropic / ...
+-----------------------------------------------+
我从上往下走。
Channels——是适配器,不是传输
一个 Channel 就是把"一条钉钉 Stream 消息"变成"OpenClaw 标准消息"再变回去的代码。每个渠道都有自己的怪癖:钉钉用 Stream WebSocket 推事件,Telegram 走轮询或 webhook,Discord 自带一个 Gateway WebSocket。Channel 层把这些都藏起来。
要记住的是:
- Channel 是按实例配置的。零个、一个、二十个都行。
- 一条消息的路径是 Channel → Gateway → Agent → Gateway → Channel。Channel 不直接跟 Agent 对话。
- 平台限流和怪癖都封装在 Channel 适配器里,这就是为什么钉钉的回复手感跟 Telegram 不一样。
Gateway——中枢神经
Gateway 跑在 :18789。它接受来自任何渠道的消息,去重(钉钉偶尔会重投),分配或恢复 Session,把消息交给 Router。
Gateway 也是唯一跟模型 Provider 通信的角色。每次工具结果、每次记忆读写、每次 Prompt 拼装都过它。这就是为什么你只需要一份 API Key 配置。
Router 与 Sessions
Router 决定哪个 Agent 来处理这条消息——只有当你配置了多个 Agent 时才有意义(默认安装就一个,叫 Pi)。Sessions 是 OpenClaw 用来区分"微信里的一个对话"和"Telegram 里的一个对话"——即使它们都打到同一个 Agent 上。Session ID 由 (channel, conversation_id) 决定。
如果你看到过"Agent 把两个对话混了",那就是 Session ID 撞了,几乎肯定是自定义渠道的 Bug。
Pi Agent——循环本体
这是真正的 Agent 循环,长这样:
while True:
plan = LLM(messages, tools=enabled_tools, skills=hot_skills)
if plan.is_terminal:
return plan.reply
for tool_call in plan.tool_calls:
result = run_tool(tool_call)
messages.append(result)
OpenClaw 在这里几个有意思的选择:
- Skills 懒加载。 系统 Prompt 里只有 Manifest,正文等模型触发某个 Skill 时才加载。Token 成本因此很低。
- 工具错误返回给模型,而不是抛出。 模型有机会自我修复。这听起来理所应当,但很多 Agent 框架直接 throw。
- 循环有硬轮数上限。 默认 30。第 30 轮还在循环就停下,输出一句"我好像卡住了",而不是连夜烧光你的 Token 预算。
Tools——Agent 能 做什么
Tools 是动词。读文件、写文件、跑 shell、抓 URL、搜网页。默认安装带 26 个。每个 Tool 有:
- 名字(
read,exec,web_search等) - Schema(带类型的参数)
- Handler(实际跑的代码)
- 权限等级
exec 是最危险的那个。它跑任意 shell。默认每次调用都需要确认;可以在 openclaw.json 里把可信模式列出来。
Skills——怎么 做
Skills 是"知识名词"。一个 Skill 是 ~/.openclaw/skills/<name>/SKILL.md 加可选辅助文件。文件头的 Manifest 长这样:
| |
正文是 SOP——指令、模板、示例。Agent 启动时加载 Manifest,所以模型能看到每个 Skill 的一句话摘要。当模型决定要用某个 Skill 时,Gateway 把正文展开塞进下一轮 Prompt。
Skills 是把 LLM 变成"在你的具体任务上靠谱的工人"的方式。Tools 回答"我能不能读文件"。Skills 回答"我现在要写会议笔记,正确的模板是哪个、放哪儿、应该 link 什么"。
Memory + ContextEngine
Memory 是按用户隔离的、持久的、有类型的。常见类型:
user/profile.md——偏好project/<name>.md——项目状态feedback/*.md——你给 Agent 的反馈纠正reference/*.md——希望 Agent 长期记住的事实
ContextEngine 是 v2026.3.7 加的——决定下一轮 Prompt 里塞哪些记忆片段。它按时效、当前消息相关性、显式 tag 打分。引擎可以换——有 noop、recency-only、默认的语义引擎。
这一层让 Agent 感觉它认识你。如果你的不像,几乎都是因为 ContextEngine 没拿到足够的写入机会——Agent 必须被引导去写 Memory。
这种分层的两个实际后果
你写 Skill,不写 Agent。 Agent 循环是固定的。你的定制发生在 Skill 层(知识)和 Tool 层(动词)。基本不需要碰 Gateway。
同一个 Agent 服务每个渠道。 因为循环和渠道解耦,你为终端写的生产力 Skill 在钉钉和 Telegram 上一字不变也能用。不需要移植。
下一篇讲配置——openclaw.json、模型提供商、以及在国内最划算的百炼 Coding Plan。