1. 多智能体系统里,调度为什么比写 Agent 难十倍
多智能体系统(Multi-Agent System)说白了就是让一堆 Agent 分工干活,每个 Agent 只负责一小块能力,最后拼成一个完整流程。写单个 Agent 真不难,套个框架、调几轮 Prompt,半天就能跑通。难的是让它们协同——谁先跑、谁等谁、谁挂了怎么办、资源不够时先保谁。这些全是调度问题,而调度恰恰是大多数教程一笔带过的地方。
我手上有个典型场景:5 个 Agent 协同,一个清洗数据,一个做分析,一个生成报告,另外两个分别处理异常和用户交互。单跑都没问题,串起来就出幺蛾子。最典型的三类坑:
死锁。Agent A 等 B 的输出,B 等 C,C 又要 A 的中间结果,环一形成,日志里风平浪静,实际啥也没动。你盯着屏幕怀疑人生,其实系统早就卡死了。
资源争抢。5 个 Agent 同时打大模型接口,Token 消耗像开了闸。更气人的是低优先级任务抢了配额,高优先级的反而排队,用户体验直接崩。
错误传播。B 挂了,D 和 E 还在傻等它的输出,算力白烧,最后一起超时。
用 LangGraph 画流程图确实漂亮,但一旦要动态调度——比如根据负载临时加个 Agent 实例——就得改一堆代码,改完还得重新测依赖关系。Celery 手搓调度层我也试过,优先级队列、分布式锁、重试策略全自己写,两周下来人麻了,稳定性还一般。
OpenClaw 这个项目是我在翻 GitHub 时撞见的,第一反应是"又一个轮子",但看完它的调度设计,确实把上面几个痛点按住了。它把每个 Agent 的输入输出建模成 DAG 节点,提交任务时就检测循环依赖,直接报错,而不是运行时死锁;优先级队列带抢占,高优先级任务不会直接 kill 低优先级,而是等当前 step 跑完再优雅切换;每个 Agent 跑在独立沙箱里,一个崩了,调度器自动把下游标记为 blocked,触发重试或降级。最实用的是动态扩缩容——某个 Agent 队列积压就 fork 新实例,空闲了再回收。
这篇就围绕"调度"这条主线,把 OpenClaw 在 Sealos 上的部署、配置、验证、排障完整走一遍。适合已经在写多 Agent、但被调度层卡住的人,也适合想先跑通一套可观测调度链路再决定要不要自研的团队。下面每一步都能复制,跑完你能亲眼看到任务怎么分发、状态怎么同步、失败怎么重试。
2. 前置准备:TaoToken 接入与 Sealos 环境确认
在动手部署 OpenClaw 之前,得先把两件事搞定:模型调用通道和环境。OpenClaw 本身是调度框架,它不生产模型能力,所有 Agent 的推理都要走外部 API。这里我用 TaoToken 作为统一接入层,好处是 Base URL 和 Key 一套配置,多个 Agent 共用,不用每个 Agent 单独维护一套凭证。
TaoToken 的定位是模型 API 聚合接入,兼容 OpenAI 风格的接口协议,所以 OpenClaw 里凡是走 OpenAI SDK 的地方,把base_url和api_key换掉就能用。官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点固定为 https://taotoken.net/api ,注意这个地址后面不加任何 UTM 参数,配置里写错会直接 404。
先说 Key 怎么拿。登录后进控制台,找到 API Keys 页面(deep link:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite ),新建一个 Key,复制出来。这个 Key 就是后面所有 Agent 共用的凭证。如果你只是想先验证模型通不通,可以先用模型对话页面(https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite )发一条消息,确认账号和额度正常,再去配 OpenClaw,能省掉一半排障时间。
环境这边,Sealos 是必须的。Sealos 是个云操作系统,应用市场里能一键拉起各种服务,省掉自己搭 K8s 的功夫。打开 cloud.sealos.run,用手机号或账号登录,进去就是一个云端桌面。确认两件事:一是你的账号有应用部署权限,二是默认命名空间还有配额。免费额度一般够跑一个 2C4G 的实例,如果之前部署过别的服务把配额占了,先去控制台清理一下。
模型 ID 这块要提前定好。OpenClaw 的调度配置里每个 Agent 都要指定 model,TaoToken 支持的模型 ID 以文档为准(https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite )。常见的有 gpt-4o、claude 系列等,写配置时直接填模型 ID 字符串,别填展示名。我踩过的坑是把展示名填进去,结果请求返回 model not found,排查了半天才发现是名字对不上。
还有一点,OpenClaw 的调度器会合并相似请求来省 Token,这意味着同一个模型 ID 的多个 Agent 请求可能被批处理。所以尽量让同类任务的 Agent 用同一个模型 ID,跨模型的合并它做不了。这个细节在配置阶段就要规划好,不然后面调优时发现合并率上不去,还得回头改。
Redis 是可选项。单机部署、Agent 数量在 10 个以内,可以不填 REDIS_URL,调度器用内存态锁也能跑。但如果你打算开多个 OpenClaw 实例做高可用,或者 Agent 数量超过 20,就必须配 Redis 做分布式锁,否则状态同步会出问题。Sealos 应用市场里也有 Redis,可以顺手拉一个。
3. 可复制配置:OpenClaw 调度参数与 Agent 定义
这一节是核心,所有配置都能直接复制。OpenClaw 的配置分两块:一块是调度器全局配置,一块是 Agent 定义。全局配置用 TOML,Agent 定义用 JSON,两个文件都放在实例的/app/config目录下。下面这份是我实测能跑通的版本,路径和字段名跟官方一致。
先看调度器全局配置scheduler.toml:
[scheduler] # 调度模式:dag 表示基于有向无环图解析依赖 mode = "dag" # 最大并发 Agent 实例数,超过会排队 max_concurrency = 10 # 单个任务最大重试次数 max_retries = 3 # 重试退避策略:exponential 指数退避 retry_backoff = "exponential" # 基础退避时间(秒) retry_base_seconds = 2 # 任务超时时间(秒),超时标记为 failed task_timeout = 300 # 是否开启动态扩缩容 auto_scale = true # 队列积压阈值,超过就 fork 新实例 scale_threshold = 5 # 空闲回收时间(秒) idle_recycle_seconds = 60 [model] # TaoToken 统一接入地址,注意不加 UTM base_url = "https://taotoken.net/api" # 从控制台复制的 Key api_key = "sk-你的TaoToken密钥" # 默认模型 ID default_model = "gpt-4o" # 请求超时(秒) request_timeout = 60 [state] # 状态同步后端:memory 单机,redis 分布式 backend = "memory" # 如果 backend 用 redis,填下面这行 # redis_url = "redis://default:密码@redis服务地址:6379/0"再看 Agent 定义agents.json,这里定义 5 个 Agent 的依赖关系:
{ "agents": [ { "id": "cleaner", "name": "数据清洗", "model": "gpt-4o", "priority": 1, "depends_on": [], "sandbox": true, "prompt_template": "清洗以下数据,去除空值和重复项:{{input}}" }, { "id": "analyzer", "name": "数据分析", "model": "gpt-4o", "priority": 2, "depends_on": ["cleaner"], "sandbox": true, "prompt_template": "分析以下数据并给出结论:{{cleaner.output}}" }, { "id": "reporter", "name": "报告生成", "model": "gpt-4o", "priority": 3, "depends_on": ["analyzer"], "sandbox": true, "prompt_template": "根据分析结果生成报告:{{analyzer.output}}" }, { "id": "exception_handler", "name": "异常处理", "model": "gpt-4o", "priority": 1, "depends_on": [], "sandbox": true, "prompt_template": "处理以下异常:{{input}}" }, { "id": "user_interaction", "name": "用户交互", "model": "gpt-4o", "priority": 2, "depends_on": ["exception_handler"], "sandbox": true, "prompt_template": "响应用户请求:{{input}}" } ] }几个关键字段解释一下。depends_on就是 DAG 的边,cleaner没有依赖,是入口节点;analyzer依赖cleaner,必须等它完成;reporter依赖analyzer,形成链式。exception_handler和user_interaction是另一条链,跟主链并行。这样调度器提交任务时就能检测出有没有环——如果你不小心把cleaner的depends_on写成["reporter"],提交时直接报循环依赖,不会等到运行时死锁。
priority数字越小优先级越高。cleaner和exception_handler都是 1,属于高优先级,资源紧张时先保它们。sandbox: true表示这个 Agent 跑在独立沙箱里,崩了不影响别人。
prompt_template里的{{cleaner.output}}是变量引用,调度器会把上游 Agent 的输出注入进来。这个引用语法要跟id严格对应,写错会报变量未定义。
配置写完后,在 Sealos 应用市场搜索 "Clawdbot - AI 智能体网关",点部署,配置面板里填:
- 实例规格:2C4G(够跑 10 个以内 Agent)
- 存储:默认 10G,中间文件多的话加到 50G
- 环境变量:
OPENAI_API_KEY填你的 TaoToken Key,OPENAI_BASE_URL填https://taotoken.net/api,REDIS_URL单机可不填
部署时把上面两个配置文件挂载到/app/config目录。Sealos 支持在部署面板里直接粘贴配置内容,或者用 ConfigMap 挂载。确认后等 2-3 分钟,系统会分配一个域名,类似openclaw-xxx.cloud.sealos.io。
如果你用的是 Claude Code 做本地开发,想把 OpenClaw 的调度能力接进去,可以走 Coding Plan(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ),把 Base URL、Key、Model ID 三件套配好,本地调试和云端调度用同一套凭证,省得来回切。
4. 验证请求:启动多 Agent 实例并观察调度日志
配置挂上去只是第一步,真正要确认的是调度器有没有按 DAG 分发、状态有没有同步、失败有没有重试。这一节用具体动作验证。
先访问分配到的域名,进 OpenClaw Dashboard。默认会有一个 Demo Pipeline,先点一次 "Run" 确认基础链路通。如果 Demo 都跑不起来,说明模型接入有问题,先回去检查base_url和api_key。
Demo 通了之后,触发我们自己的 5 Agent 流程。Dashboard 上有个 "Submit Task" 按钮,或者用 API 触发:
curl -X POST https://openclaw-xxx.cloud.sealos.io/api/tasks \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 你的TaoToken密钥" \ -d '{ "pipeline": "default", "input": "这是一批待清洗的原始数据...", "priority": 1 }'提交后会返回一个task_id,拿这个 ID 去查调度日志:
curl https://openclaw-xxx.cloud.sealos.io/api/tasks/你的task_id/logs \ -H "Authorization: Bearer 你的TaoToken密钥"日志里你会看到类似这样的输出:
[00:00.001] task submitted, dag validated, no cycle detected [00:00.012] cleaner dispatched, priority=1, sandbox=allocated [00:02.340] cleaner completed, output cached [00:02.345] analyzer dispatched, depends_on=[cleaner] satisfied [00:05.120] analyzer completed [00:05.125] reporter dispatched, depends_on=[analyzer] satisfied [00:05.130] exception_handler dispatched, priority=1, parallel branch [00:07.890] reporter completed [00:08.010] user_interaction dispatched, depends_on=[exception_handler] satisfied [00:09.450] all agents completed, task finished重点看几个地方。dag validated, no cycle detected说明提交时依赖检查通过。cleaner dispatched后面跟着analyzer dispatched,中间隔了 2 秒多,说明调度器确实在等上游完成才分发下游,不是一股脑全扔出去。exception_handler和主链并行,说明 DAG 的并行分支被正确识别。
再验证失败重试。手动把analyzer的模型 ID 改成一个不存在的值,重新提交任务。日志会变成:
[00:02.345] analyzer dispatched [00:03.100] analyzer failed: model not found, retry 1/3 in 2s [00:05.100] analyzer dispatched [00:05.800] analyzer failed: model not found, retry 2/3 in 4s [00:09.800] analyzer dispatched [00:10.500] analyzer failed: model not found, retry 3/3 in 8s [00:18.500] analyzer marked as failed, downstream reporter blocked退避时间从 2 秒到 4 秒到 8 秒,指数退避生效。重试 3 次后标记失败,下游reporter被标记为 blocked,不会傻等。这就是故障隔离——一个 Agent 挂了,调度器主动切断下游,避免错误传播。
验证动态扩缩容。连续提交 10 个任务,让cleaner的队列积压超过scale_threshold=5。日志里会出现:
[00:00.001] cleaner queue length=6, exceeds threshold=5, forking new instance [00:00.050] cleaner-2 allocated, sandbox=allocated [00:00.100] task dispatched to cleaner-2空闲 60 秒后,cleaner-2会被回收:
[00:60.000] cleaner-2 idle for 60s, recycling这套验证跑完,你对调度器的行为就有底了。分发、同步、重试、扩缩容,全在日志里看得见,不是黑盒。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
调度链路跑起来后,报错基本集中在模型接入和状态同步两块。下面这几个是我实际撞过的,对照日志排查能省不少时间。
401 Unauthorized。日志里出现model request failed: 401,八成是 Key 的问题。先确认scheduler.toml里的api_key跟控制台复制的一致,注意别把前后空格带进去。再确认base_url是https://taotoken.net/api,不带 UTM 参数,带参数会 404 不是 401,但容易混淆。如果 Key 没问题,去控制台看额度是不是用完了,额度耗尽也会返回 401。还有一种情况是 Key 被禁用,新建一个 Key 换上试试。
local proxy failed。这个报错通常出现在 Sealos 部署的实例里,日志显示local proxy failed: connection refused。原因是 OpenClaw 容器内的网络策略没放行外部 API 请求。去 Sealos 的网络配置里确认出站规则,允许访问taotoken.net的 443 端口。如果是用 ConfigMap 挂载配置,检查base_url有没有被环境变量覆盖——Sealos 的环境变量优先级高于配置文件,OPENAI_BASE_URL如果填错,会覆盖 TOML 里的值。
reading choices 相关报错。日志里出现error reading choices: unexpected end of JSON input或者choices field missing,说明模型返回的响应格式不对。先确认模型 ID 填的是 TaoToken 支持的 ID,别填展示名。再确认request_timeout够长,默认 60 秒,如果模型响应慢,超时后拿到的是空响应,解析choices就报错。把超时调到 120 秒试试。还有一种可能是请求被批处理合并后,响应结构跟单请求不一样,这种情况检查调度器的合并逻辑,或者临时关掉合并。
OAuth 相关报错。如果你在 OpenClaw 里配了需要 OAuth 的模型通道,日志出现oauth token expired或oauth refresh failed,说明凭证过期了。TaoToken 的接入用的是 API Key 模式,不涉及 OAuth,所以这个报错一般出现在你混用了其他通道的情况。统一换成 TaoToken 的 Key 认证,把 OAuth 相关的配置删掉。如果你用的是 Claude Code 接入,走 ClaudeCodeAnthropic 通道(https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite ),按文档配好三件套,别自己手搓 OAuth。
状态不同步。多实例部署时,日志出现state lock conflict或task state mismatch,说明state.backend还是memory,多个实例各存各的状态。改成redis,填上redis_url,重启实例。单机部署不会遇到这个问题,但如果你开了auto_scale且实例数超过 1,就必须用 Redis。
循环依赖报错。提交任务时直接返回cycle detected in dag,说明agents.json里的depends_on形成了环。用工具把依赖关系画出来,或者手动检查每个 Agent 的depends_on链,确保没有 A→B→C→A 这种结构。这个报错是好事,说明调度器在提交阶段就拦住了,没让它跑到运行时死锁。
排查时有个通用技巧:先把max_retries设为 0,关掉重试,让错误直接暴露,别被重试日志淹没。定位到根因后再把重试打开。另外,Dashboard 的日志面板比 API 返回的日志更全,排障时优先看面板。
6. 把调度层交给 OpenClaw,把精力留给 Agent 本身
跑完这一整套,最大的感受是:调度这层东西,真不该自己手搓。DAG 解析、优先级抢占、故障隔离、动态扩缩容,每一项单独写都不难,但凑在一起还要保证稳定性,就是几周的工作量。OpenClaw 把这些做成了开箱即用,配置两个文件就能跑,日志还全透明,出问题能定位。
TaoToken 在这套链路里的角色是统一接入层。5 个 Agent 共用一套 Base URL 和 Key,不用每个 Agent 单独维护凭证,调度器合并请求时也不会因为凭证不同而失败。如果你后面要加 Agent,或者换模型,只改agents.json里的model字段就行,接入层不用动。
几个实用建议。第一,priority别全设成一样的,高优先级的入口 Agent 设 1,中间处理设 2,输出类设 3,资源紧张时调度器才有腾挪空间。第二,sandbox全开,多花一点内存换故障隔离,值。第三,auto_scale的scale_threshold别设太小,设成 5 左右比较稳,太小会导致频繁 fork 和回收,反而增加开销。第四,Redis 该配就配,别等出了状态不同步再回头补。
如果你还在用 LangGraph 或 Celery 手搓调度,建议花 10 分钟在 Sealos 上部署一套 OpenClaw,把现有流程迁一个过来对比。至少能省掉你手搓调度层的那几周,而且日志可观测性比自研强太多。调度跑通之后,你就能把精力真正放回 Agent 的 Prompt 和业务逻辑上——那才是多智能体系统里真正体现差异的地方。