
1. 从 GUI 到 SDK多智能体编排到底解决什么问题Cursor 用久了会形成一种肌肉记忆打开侧边栏输入需求等它改完再手动检查然后继续下一个任务。这套流程在单文件、小改动时非常顺手但一旦需求变成“把用户模块从 session 迁移到 JWT同时补测试和文档”你就会发现自己在反复做同一件事——把上下文喂给 AI、等结果、纠偏、再喂下一个任务。整个过程里人始终是那个“调度器”。我真正想验证的是能不能把调度这件事从人手里拿走交给一段可循环执行的 TypeScript 脚本。脚本负责拆任务、分角色、传上下文、收结果、标记状态然后自动推进下一个任务直到整批任务跑完。Cursor 的 GUI 适合“人和 AI 对话”但程序驱动 AI 连续执行流程这件事GUI 天然不擅长。SDK 的价值就在这里它把 Cursor 从 IDE 里的聊天窗口变成一个可以被脚本调用的能力模块。这套骨架适合谁如果你已经在用 Cursor 做日常开发并且开始觉得“每次都要手动喂任务”很累那这套东西就是为你准备的。它不要求你一开始就搭得很复杂最小可行版本只需要解决三件事任务能不能拆清楚、流程能不能自动循环、结果能不能被可靠追踪。本文会给出可复制的config.toml与settings.json片段并演示一次本地循环执行与结果校验最后把统一 Key/API 通道接到 TaoToken 上跑通。2. 前置准备TaoToken 统一 Key 与 API 通道在写编排脚本之前先把模型调用通道固定下来。多智能体编排会频繁发起请求如果每个角色都单独配一套 Key后面排查问题会非常痛苦。我的做法是统一走 TaoToken 的 API 通道所有角色共用同一个 Key通过不同的 prompt 模板区分职责。TaoToken 在这里扮演的角色是“统一入口”你不需要为 planner、implementer、verifier 分别申请不同的凭证只需要一个 Key然后在脚本里按角色切换 system prompt 和参数即可。这样做的另一个好处是日志里所有请求都走同一条通道出问题时能快速定位是哪个角色的调用出了偏差。具体操作上先到控制台创建一个 API Key然后把它写进环境变量不要硬编码在脚本里。接入文档里有完整的请求格式说明建议先跑通一次最简单的对话请求确认通道没问题再往上叠编排逻辑。注意Key 只放在环境变量或本地.env文件里.env要加进.gitignore。编排脚本会频繁调用Key 泄露的风险比单次对话高得多。如果你还没创建 Key可以先到 API Keys 管理页 生成一个再对照 接入文档 确认请求头和 endpoint 格式。想先验证模型是否正常响应可以直接在 模型对话 里发一条测试消息确认通道通畅后再回到脚本。3. 可复制配置config.toml 与 settings.json编排骨架的核心是把“角色定义”和“运行参数”分离。角色定义放在config.toml里运行参数放在settings.json里。这样改角色职责时不用动运行逻辑调参数时也不用碰 prompt。先看config.toml。这里定义了三个角色以及每个角色对应的模型参数和输出约束# config.toml [orchestrator] max_loops 20 task_file ./tasks/queue.json log_dir ./logs retry_limit 2 [roles.planner] model claude-sonnet temperature 0.2 system_prompt 你是 planner只负责拆解任务不写实现代码。 输入一段需求描述。 输出JSON 数组每个元素包含 id、title、scope、depends_on。 约束单个任务不超过 200 行改动量scope 必须明确到文件或模块级别。 [roles.implementer_frontend] model claude-sonnet temperature 0.1 system_prompt 你是前端 implementer只处理 scope 中标记为 frontend 的任务。 输出 unified diff 格式的代码变更外加一段变更说明。 约束不得修改 scope 之外的任何文件不得顺手重构无关代码。 [roles.implementer_backend] model claude-sonnet temperature 0.1 system_prompt 你是后端 implementer只处理 scope 中标记为 backend 的任务。 输出unified diff 格式的代码变更外加一段变更说明。 约束不得修改 scope 之外的任何文件不得改动数据库 schema 除非任务明确要求。 [roles.verifier] model claude-sonnet temperature 0.0 system_prompt 你是 verifier负责验收 implementer 的输出。 输入原始任务描述 implementer 的 diff。 输出JSON包含 pass、reasons、risks、unverified。 约束pass 为 false 时必须给出具体原因不得给出模糊评价。 再看settings.json这里放的是运行时参数和通道配置{ api: { base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, timeout_ms: 60000, max_retries: 3 }, runtime: { concurrency: 1, loop_interval_ms: 2000, stop_on_verify_fail: false, persist_state: true }, logging: { level: info, save_raw_response: true, save_diff: true } }concurrency先设成 1因为多智能体循环执行时并发会让状态流转变得难以追踪。等流程稳定了再考虑提高。stop_on_verify_fail设成 false意思是 verifier 判定不通过时任务回到队列重新执行而不是整个流程停掉。persist_state打开后每次循环的状态会写到./logs/state.json方便中断后恢复。4. 循环执行骨架TypeScript 侧的关键逻辑配置写好后TypeScript 侧要做的事情其实很线性读队列、取任务、按角色分发、收结果、写状态、推进下一个。核心是一个while循环加上状态机。先定义任务和状态的结构// types.ts export type Role planner | implementer_frontend | implementer_backend | verifier; export interface Task { id: string; title: string; scope: string; depends_on: string[]; status: pending | running | verifying | done | failed; attempts: number; diff?: string; verify_result?: VerifyResult; } export interface VerifyResult { pass: boolean; reasons: string[]; risks: string[]; unverified: string[]; }然后是主循环。这里的关键是每次循环只处理一个任务处理完写状态再进入下一轮。不要试图在一个循环里并行处理多个任务状态会乱。// orchestrator.ts import fs from fs; import { Task, VerifyResult } from ./types; import { callRole } from ./client; const config JSON.parse(fs.readFileSync(./settings.json, utf-8)); const queue: Task[] JSON.parse(fs.readFileSync(./tasks/queue.json, utf-8)); async function runLoop() { let loopCount 0; while (loopCount 20) { const task queue.find(t t.status pending || t.status failed); if (!task) break; task.status running; task.attempts 1; persist(queue); const role pickRole(task); const result await callRole(role, task); if (role verifier) { const verify JSON.parse(result) as VerifyResult; task.verify_result verify; task.status verify.pass ? done : failed; } else { task.diff result; task.status verifying; } persist(queue); loopCount 1; await sleep(config.runtime.loop_interval_ms); } } function pickRole(task: Task): Role { if (task.status verifying) return verifier; if (task.scope.includes(frontend)) return implementer_frontend; if (task.scope.includes(backend)) return implementer_backend; return planner; } function persist(q: Task[]) { fs.writeFileSync(./logs/state.json, JSON.stringify(q, null, 2)); } function sleep(ms: number) { return new Promise(r setTimeout(r, ms)); } runLoop();callRole负责拼请求、发到 TaoToken 通道、拿回结果。这里要注意的是每个角色的 system prompt 从config.toml里读不要写死在代码里。这样改 prompt 不用重新编译。// client.ts import fs from fs; import TOML from iarna/toml; const config TOML.parse(fs.readFileSync(./config.toml, utf-8)); const settings JSON.parse(fs.readFileSync(./settings.json, utf-8)); export async function callRole(role: string, task: any): Promisestring { const roleConfig (config.roles as any)[role]; const apiKey process.env[settings.api.api_key_env]; const res await fetch(${settings.api.base_url}/v1/messages, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify({ model: roleConfig.model, temperature: roleConfig.temperature, system: roleConfig.system_prompt, messages: [{ role: user, content: JSON.stringify(task) }] }) }); const data await res.json(); return data.content?.[0]?.text ?? ; }这段代码跑起来后你会看到logs/state.json里的任务状态在pending → running → verifying → done/failed之间流转。每次流转都有时间戳和角色标记出问题时可以直接回溯。5. 验证请求与成功结果配置和脚本都就位后先跑一次最小验证只放一个任务进队列看它能不能走完 planner → implementer → verifier 的完整链路。准备一个最简单的任务文件[ { id: task-001, title: 给用户列表接口加一个分页参数, scope: backend/src/routes/users.ts, depends_on: [], status: pending, attempts: 0 } ]然后运行export TAOTOKEN_API_KEY你的Key npx ts-node orchestrator.ts预期看到的行为是脚本先调用 plannerplanner 返回一个拆解后的任务数组然后脚本取第一个子任务按 scope 分发给 backend implementerimplementer 返回 diff脚本把 diff 和原始任务一起发给 verifierverifier 返回pass: true或pass: false。成功时logs/state.json里对应任务的status会变成done并且verify_result.pass为true。同时logs/目录下会多出几个文件planner-taskid.json、implementer-taskid.diff、verifier-taskid.json。这些就是留痕后面排查问题全靠它们。如果 verifier 返回pass: false任务状态会回到failed下一轮循环会重新取这个任务attempts加一。retry_limit设的是 2超过后任务会被标记为failed并跳过避免死循环。提示第一次跑建议把max_loops设成 3观察状态流转是否符合预期。确认没问题后再放开到 20。6. 本篇常见错排查报错一401 Unauthorized或invalid api key先确认TAOTOKEN_API_KEY环境变量在当前 shell 里确实存在用echo $TAOTOKEN_API_KEY检查。如果是在 IDE 里跑注意 IDE 的终端可能没有继承你手动 export 的变量。另一个常见原因是 Key 复制时带了空格或换行重新从 API Keys 页面 复制一次。报错二verifier返回的 JSON 解析失败这是最常见的问题。verifier 的 system prompt 里虽然要求输出 JSON但模型有时会加一段解释文字再给 JSON。解决办法是在callRole里加一层提取找到第一个{和最后一个}截取中间部分再JSON.parse。如果还是失败把temperature调到 0.0并且在 prompt 里加一句“只输出 JSON不要任何额外文字”。报错三任务状态卡在verifying不动检查pickRole的逻辑。如果任务的scope既不含frontend也不含backend它会落到planner分支但此时任务状态是verifyingplanner 不会返回 diff状态就卡住了。修法是在pickRole里优先判断status verifying这个判断要放在 scope 判断之前。报错四循环跑了几轮后重复处理同一个任务检查persist是否在每次状态变更后都被调用。如果只在循环结束时写一次中间状态丢失下一轮会重新取到旧状态的任务。另外确认queue.find的条件是pending或failed不要把verifying也包含进去。报错五implementer 改了 scope 之外的文件这是 prompt 约束问题不是代码问题。在 implementer 的 system prompt 里加一条硬约束“如果任务需要修改 scope 之外的文件直接返回NEED_SCOPE_EXPAND不要自行修改。”然后在脚本里处理这个返回值把任务退回给 planner 重新拆解。7. 把编排接到长期编码流里这套骨架跑通之后你会发现它和单次对话最大的区别是任务有了状态状态有了流转流转有了日志。这三件事加起来才让“AI 持续参与研发流程”变得可追踪、可复盘。如果你打算把它用在日常编码里下一步可以考虑接 Coding Plan把长期编码任务和 Agent 调用统一到一条通道上。这样 planner、implementer、verifier 的请求都走同一个入口日志格式一致排查问题时不用在多个平台之间切换。我自己的做法是先用这套骨架跑一周的小任务把 prompt 模板和角色边界磨稳定再逐步放开任务复杂度。真正难的不是“能不能跑”而是“能不能稳定跑”。而稳定的前提是每个角色的输出格式足够统一状态流转足够清晰日志足够完整。这三件事做到了后面的扩展就是水到渠成的事。