1. 一次消息从进来到回复,OpenClaw 到底走了哪几段路
OpenClaw 是一个把 IM 消息、LLM 推理、工具调用串成一条链路的 Agent 框架。它最容易被误解的地方,是很多人以为「Agent 就是那个大模型」。实际上在 OpenClaw 里,真正干活的角色被拆成了四个:Gateway 负责接入和路由,Agent 负责思考和调工具,WebSocket 负责把这两端连起来,Session 负责记住「这是谁在跟谁说话」。你如果只盯着 Agent 的 prompt 调优,链路一长就会发现问题根本不在 prompt,而在 Session 路由或者 Gateway 的鉴权上。
这篇拆解聚焦一次完整会话:从客户端连上 Gateway 的 WebSocket,到 Session 被创建、Agent 被加载、LLM 被调用、工具被执行、回复被分发回原渠道。中间我会给出 Gateway 与 Agent 的配置片段、WebSocket 握手与 Session 生命周期的验证步骤,并说明怎么用 TaoToken 统一 Key/API 通道完成鉴权接入,让你能复现一条可观测的调用链路。
适合谁看:已经在本地跑起 OpenClaw、但搞不清消息为什么没进对 Agent 的人;想把 OpenClaw 接到自己业务渠道、需要理解鉴权与路由边界的人;以及想给 Agent 加可观测性、排查「消息发出去了但没回复」这类问题的人。下面所有配置都以~/.openclaw/openclaw.json为准,路径和字段名保持和官方一致,你可以直接对照改。
先说结论性的链路:客户端 → WebSocket 握手(首帧req:connect)→ Gateway 鉴权 → Session Router 按 Session Key 找 Agent → Gateway 组装 Agent 装备(Session Store / Skills / Tool Policy)→ Agent Runner 调 LLM → Agentic Loop 执行工具 → 回复经 Gateway 分发回 Channel。这条链路里任何一环断了,表现都是「消息没反应」,但原因完全不同,所以必须能分段验证。
2. TaoToken 前置:把 Key 和 API 通道统一到一处
在拆链路之前,先把鉴权这块讲清楚,因为 Gateway 的 WebSocket 鉴权和 LLM 的 API 鉴权是两套东西,很多人会混。Gateway 那层是「谁可以连我的控制平面」,LLM 那层是「Agent 拿什么去调模型」。前者用gateway.auth.token,后者用各 provider 的apiKey。如果你每个 provider 都单独配 Key,Agent 一多、模型一换,Key 管理就会散得到处都是。
我的做法是把模型调用统一走 TaoToken 的 API 通道,这样 Agent 侧只需要认一个 Base URL 和一个 Key,模型切换在服务端完成,本地配置不用动。TaoToken 的 API 地址是https://taotoken.net/api,官网在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。它的作用是给你一个兼容 OpenAI 协议的入口,OpenClaw 里api: "openai-completions"的 provider 可以直接指过来。
为什么要在架构拆解里先讲这个?因为 Gateway 到 Agent 的链路里,Agent Runner 调 LLM 是最容易失败的一段,而失败原因十有八九是 Key 或 Base URL 配错。把这块前置统一,后面排查就能快速排除掉「模型侧」的干扰,专注看 WebSocket 和 Session。
具体配置上,OpenClaw 的 provider 支持baseUrl+apiKey+api三件套。你可以在models.providers里加一个指向 TaoToken 的 provider,然后让 Agent 的model.primary指向它。这样 Gateway 加载 Agent 装备时,注入的 LLM 解析器就会用这个 provider。注意apiKey建议用环境变量引用,别硬编码进openclaw.json,因为 Gateway 有配置热重载,文件可能被反复读取。
这里有个容易踩的点:OpenClaw 的 provider 配置里api字段决定用哪套协议适配器,TaoToken 走 OpenAI 兼容协议,所以填openai-completions。如果你填成anthropic-messages,握手能过但请求体格式不对,报错会出现在 Agent Runner 阶段,而不是 Gateway 阶段,排查时容易误判成 WebSocket 问题。
另外,Gateway 自身的gateway.auth.token和 TaoToken 的 Key 是两回事,别把 TaoToken 的 Key 填到gateway.auth.token里。前者是控制平面准入,后者是模型调用凭证。我见过有人图省事把两者设成同一个值,结果 Gateway 能连上、Agent 却调不动模型,因为 Gateway 拿这个 token 去当 LLM Key 用了。
统一到 TaoToken 之后,你的openclaw.json里模型相关的配置会收敛成一处,Agent 列表里每个 Agent 只写model.primary指向 provider 别名即可。这样 Multi-Agent 场景下,main、claudecode、writing 几个 Agent 共享同一个 API 通道,但各自可以用不同的模型 ID,切换成本很低。
3. 可复制配置:Gateway、WebSocket 与 Agent 三件套
这一节给可直接复制的配置片段。先看 Gateway 部分,它决定了 WebSocket 监听在哪、鉴权怎么做、哪些远程命令被禁。下面这段是 loopback 模式,适合本地开发,auth.mode用 token:
{ "gateway": { "port": 18789, "mode": "local", "bind": "loopback", "auth": { "mode": "token", "token": "${OPENCLAW_GATEWAY_TOKEN}" }, "nodes": { "denyCommands": [ "camera.snap", "camera.clip", "screen.record", "calendar.add", "contacts.add", "reminders.add" ] } } }注意port我写的是 18789,和默认一致;bind: "loopback"时 Auth 是可选的,但强烈建议开,因为 Onboarding 默认会生成一个 token。如果你改成lan或tailnet,gateway.auth.token或gateway.auth.password必须设置,否则 Gateway 会拒绝启动。这是硬性约束,不是建议。
接着是模型 provider,指向 TaoToken:
{ "models": { "mode": "merge", "providers": { "taotoken": { "baseUrl": "https://taotoken.net/api", "apiKey": "${TAOTOKEN_API_KEY}", "api": "openai-completions", "models": [ { "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6" }, { "id": "gpt-4.1", "name": "GPT-4.1" } ] } } } }mode: "merge"表示和内置 provider 合并,不是替换。apiKey用${TAOTOKEN_API_KEY}引用环境变量,Gateway 启动时会解析。模型 ID 按你实际在 TaoToken 侧开通的填,这里只是示例。
然后是 Agent 侧,把模型指向上面这个 provider,并配好 Session 隔离和工具策略:
{ "agents": { "defaults": { "sessionIsolation": "per-channel-peer", "model": { "primary": "taotoken/claude-sonnet-4-6", "fallbacks": ["taotoken/gpt-4.1"] }, "maxConcurrent": 4, "subagents": { "maxConcurrent": 8, "maxSpawnDepth": 2, "tools": { "allow": ["read", "exec"], "deny": ["browser", "nodes"] } } }, "list": [ { "id": "main", "workspace": "~/.openclaw/workspace", "tools": { "profile": "messaging", "deny": ["browser"] } } ] } }sessionIsolation: "per-channel-peer"是多用户场景的推荐值,每个聊天对象独立会话。model.primary指向taotoken/claude-sonnet-4-6,fallbacks是备用模型,主模型失败时自动降级。subagents.tools默认收回敏感工具,这是框架层面的安全边界,别轻易放开。
最后是 Channel 和 bindings,决定消息怎么路由到 Agent:
{ "channels": { "feishu": { "enabled": true, "groupPolicy": "open", "dmPolicy": "pairing", "allowFrom": ["*"], "connectionMode": "websocket", "accounts": { "default": { "appId": "${FEISHU_APP_ID}", "appSecret": "${FEISHU_APP_SECRET}", "domain": "feishu" } } } }, "bindings": [ { "agentId": "main", "match": { "channel": "feishu", "accountId": "default" } } ] }connectionMode: "websocket"表示 Channel 和 Gateway 之间也走 WebSocket,不是 HTTP 回调。bindings把 feishu 的 default 账户绑到 main Agent。如果你有多个 Agent,就加多条 binding,按accountId区分。
这三段配置合起来,就是「Gateway 监听 + 模型通道 + Agent 装备 + 消息路由」的完整闭环。改完openclaw.json后,Gateway 会按gateway.reload.mode决定热更新还是重启。模型和 Agent 配置一般能热更新,Gateway 的port、bind改动需要重启。
4. 验证请求:WebSocket 握手与 Session 生命周期
配置写完,得能验证链路真的通了。分三步:先验 WebSocket 握手,再验 Session 创建,最后验一次完整请求。
第一步,验 WebSocket 握手。Gateway 的 endpoint 是ws://127.0.0.1:18789,首帧必须是req:connect。你可以用wscat或任意 WebSocket 客户端连上去,发第一帧:
{ "type": "req", "id": "conn-1", "method": "connect", "params": { "token": "你的 gateway.auth.token" } }如果鉴权通过,Gateway 会回一个res帧,ok: true,payload里带当前支持的 RPC 方法和事件列表。如果 token 错,回ok: false,error里会有鉴权失败信息。这一步过了,说明 Gateway 控制平面是活的。
第二步,验 Session 创建。握手成功后,发一条消息触发 Session 创建。Session Key 的格式是agent:[agentId]:[mainKey],比如agent:main:feishu:default:peer:ou_xxx。你可以通过 Gateway 的sessions_list方法查看当前有哪些 Session:
{ "type": "req", "id": "list-1", "method": "sessions_list", "params": {} }返回的payload里会列出所有活跃 Session 及其 Key。如果消息发出去了但这里没有新 Session,说明 Session Router 没匹配到 binding,回去检查bindings的channel和accountId是否和消息来源一致。
第三步,验完整请求。在 IM 里发一条消息,观察 Gateway 日志。正常链路会依次出现:收到 Message event → Session Router 匹配到 Agent → 加载 Session Store → 构建 Skills Snapshot → 计算 Tool Policy → Agent Runner 调 LLM → Agentic Loop 执行 → 回复分发。你可以在 Gateway 日志里看到每个阶段的耗时。如果卡在「调 LLM」之前,是装备加载问题;卡在「调 LLM」之后,是模型或工具问题。
Session 的生命周期也值得单独看。Session Store 在~/.openclaw/agents/[agentId]/sessions/下,每个 Session 一个session.jsonl。第一行是 Session Header,带id、cwd、timestamp。后面是消息记录。你可以直接cat这个文件看对话历史有没有写进去。如果文件创建了但没内容,说明 Agent 没真正执行。
Session 有 reset 机制,配置在session.reset里,比如mode: "daily"、atHour: 4,表示每天凌晨 4 点重置。重置后旧文件会加.reset.date后缀。验证时可以手动触发一次 reset,看文件是否按预期归档。
Skills Snapshot 只在 New Session 第一轮构建,后续同 Session 复用。所以如果你更新了 Skills 列表但没生效,先 New Session 再试。这个行为在验证时很容易被忽略,导致误以为 Skills 没加载。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
链路跑不通时,报错信息是最直接的线索。这一节对照几个真实报错讲排查思路。
401 Unauthorized:分两种。如果出现在 WebSocket 握手阶段,是gateway.auth.token不对,检查客户端发的params.token和配置里的是否一致。如果出现在 Agent Runner 调 LLM 阶段,是 TaoToken 的apiKey不对或过期,检查${TAOTOKEN_API_KEY}环境变量有没有被 Gateway 进程读到。注意 Gateway 是守护进程,环境变量要在启动它的 shell 里 export,不是在你当前终端 export 就行。
local proxy failed:这个报错通常出现在 Gateway 尝试连外部服务时。如果你配了baseUrl指向 TaoToken,但网络层有本地代理拦截,就会报这个。排查方法是先用curl https://taotoken.net/api确认基础连通性,再看 Gateway 进程的网络环境是否和你的终端一致。注意别在配置里写任何代理相关的字段,OpenClaw 的 provider 配置不支持代理参数,写了也不生效。
reading choices:这个报错来自 LLM 响应解析阶段,通常是返回体格式和api适配器不匹配。如果你把 TaoToken 的 provider 配成了anthropic-messages,但 TaoToken 返回的是 OpenAI 格式,解析choices字段就会失败。改回openai-completions即可。这个错在 Agent Runner 阶段抛出,不在 Gateway 阶段,所以 WebSocket 握手是正常的,别往那个方向查。
OAuth 相关报错:如果你用的是需要 OAuth 的 provider(比如某些需要设备码登录的),报错会提示 token 过期或 refresh 失败。OpenClaw 的 provider 配置里,OAuth 类 provider 和 API Key 类 provider 走不同分支。TaoToken 走 API Key,不涉及 OAuth,所以如果你看到 OAuth 报错,说明 Agent 的model.primary指向了错误的 provider,检查是不是指向了内置的 OAuth provider 而不是taotoken。
排查时有个通用方法:把agents.defaults.model.primary临时改成一个确定可用的 provider,看链路是否通。如果通了,说明问题在模型侧;如果不通,问题在 Gateway 或 Session 侧。这样能快速二分定位。
另外,sessions_send相关的报错常伴随tools.sessions.visibility没配。Agent-to-Agent 通信需要visibility: "all",否则 A 的会话写不进 B 的session.jsonl。这个配置特别容易漏,报错信息也不直观,建议配 Agent 间通信时先检查这一项。
6. 把链路跑通之后,怎么继续用起来
链路验证通过后,你可以开始做可观测性。Gateway 的事件流里有agent、chat、health、heartbeat等事件类型,订阅这些事件就能拿到每个阶段的耗时和状态。我一般会在客户端订阅agent事件,记录每次 Agent 执行的开始和结束时间,这样哪一段慢一目了然。
如果你想长期跑编码或 Agent 任务,可以考虑用 Coding Plan 把模型调用额度固定下来,避免按次计费的不确定性。接入文档在https://taotoken.net/doc,API Keys 管理在https://taotoken.net/api-keys,模型对话调试在https://taotoken.net/chat。这几个入口分别对应「查文档」「管 Key」「验模型」三个动作,排查时按需取用。
最后留一个实操建议:把openclaw.json纳入版本管理,但apiKey和gateway.auth.token用环境变量引用。这样配置改动可追溯,密钥不落盘。Gateway 的热重载机制会在文件变化时按规则处理,模型和 Agent 配置能热更新,端口和绑定改动需要重启,心里有数就不会被「改了没生效」困扰。