Orchestrate 插件 root.md 模板深度解析:几行提示词如何定下并行云智能体树的第一块基石
【免费下载链接】pluginsCursor plugin specification and official plugins项目地址: https://gitcode.com/GitHub_Trending/plugins125/plugins
在 orchestrate 技能中,/orchestrate <goal>会把一个大任务分解为一棵由并行 Cursor 云智能体组成的 worker / subplanner / verifier 树,而整棵树的"大脑"——root planner(根规划器)——的行为准则就浓缩在一份 11 行的提示词模板 root.md 中。本文以该模板为主线,逐行拆解每条指令与每个{{占位符}}的语义,并顺着 cli/util.ts 与 core/prompts.ts 的渲染代码,讲清 kickoff 命令如何把变量注入模板、残留占位符如何被严格校验、模板要求规划器写入plan.json的字段如何被 schema 兜底,读者读完可以完整复现一次 kickoff 流程,并准确理解根规划器的职责边界与循环纪律。
一、root.md 在提示词体系中的位置
orchestrate/skills/orchestrate/prompts/ 目录下共有 9 份提示词模板,分别对应智能体树中的不同角色与不同注入场景:
| 模板文件 | 用途 |
|---|---|
| root.md | kickoff 时注入云端根规划器的提示词 |
| subplanner.md | 递归子规划器的提示词 |
| worker.md | 执行具体任务的 worker 提示词 |
| verifier.md | 验收 worker 的 verifier 提示词 |
| loop-hygiene.md | 循环纪律段落,被 root / subplanner 模板以{{loopHygiene}}内嵌 |
| slack-block.md、andon-block.md | 任务级 Slack 线程与 Andon 按钮指令 |
| failure-handoff.md 等 | 脚本侧失败/完结场景的交接物模板 |
模板的加载与渲染由 core/prompts.ts 中的renderPromptTemplate统一完成,流程分四步:
- 按名读取并缓存:以
../../prompts/${name}.md为相对路径readFileSync读取模板,读入结果存入模块级templateCache,同一进程内只读磁盘一次; - 逐键替换:对传入的
vars逐个执行rendered.replaceAll("{{key}}", value); - 残留占位符强校验:用正则
/{{([^{}]+)}}/g扫描渲染结果,只要还有任何一个{{...}}没被填上,立即抛出PlanValidationError("template ... has unrendered placeholders: ...")。
这条强校验正是理解 root.md 的钥匙:模板里出现的每一个占位符都必须在渲染时被完整注入,否则整次 kickoff 直接失败。对照 buildKickoffPrompt,root.md 的 5 个占位符恰好一一对应 5 个变量:
return renderPromptTemplate("root", { goal: args.goal, agentId: args.agentId, dispatcherInstruction: dispatcherInstruction(args.dispatcherFirstName), slackChannelInstruction: slackChannelInstruction(args.slackChannel), loopHygiene: renderPromptTemplate("loop-hygiene", { rootFlag: " --root" }), });注意loopHygiene本身是另一份模板 loop-hygiene.md 的渲染结果,即嵌套渲染:root 场景传rootFlag: " --root"(循环命令变成bun cli.ts run --root <workspace>),而子规划器场景在 prompts.ts 中传rootFlag: ""(命令不带--root)。同一份纪律文本通过一个变量区分了树中两个层级的循环入口。
二、模板逐行拆解
下面是 root.md 的完整内容(含 5 个占位符),随后按行分组解析。
You are the root planner for: {{goal}} Read this skill's `SKILL.md` and follow it. Your cloud agent id is `{{agentId}}`. Set `plan.selfAgentId` in plan.json to that string so spawns record `parentAgentId` and `kill-tree --agent-id` can target this planner. Write `plan.summary` as a one-line orientation for the human in the Slack thread (e.g. `"smoke test of the new orchestrate substrate"`). Kickoff posts the summary; without it, kickoff falls back to a truncated `goal`.{{dispatcherInstruction}}{{slackChannelInstruction}} Discover here before you publish tasks. Bootstrap workers hold reference material for descendants, not one-off discovery. {{loopHygiene}}2.1 身份行与技能自举:You are the root planner for: {{goal}}
第一行完成两件事:宣布角色,并注入用户原始目标{{goal}}。从 references/dispatcher.md 的 "Minimal-goal discipline" 一节可以确认goal的来源约束:dispatcher 必须把用户目标原样透传,不得追加规划启发式、子规划器数量或结构性规定——因为规划器会自行阅读 orchestrate 技能并决定分解方式,过度预设会污染规划器的判断窗口。
紧接的第二行Read this skill's SKILL.md and follow it.是典型的"提示词自举":模板不重复技能手册全文,而是把行为准则外包给 SKILL.md。该技能定义了让任务树"无全局协调也能自收敛"的 6 条核心原则,其中对根规划器约束最强的是:
- 规划器只管 scope、只发任务,不写代码——写
plan.json、读 handoff、决定下一步是规划器的工作;编辑文件、执行git merge、就地解冲突不是; - 规划器不知道谁接走它的任务——路由由脚本完成,规划器的心智模型停在任务层面;
- 连续运动:规划器认为完成了也可能收到迟到的 handoff 而重新规划,在规划器决定停止发布任务之前不存在"finished"状态;
- 传播而非同步:兄弟节点之间无通信,无共享状态,每一层只能看到自己子代的 handoff。
2.2{{agentId}}与plan.selfAgentId:杀树的锚点
第 5 行是模板中技术含量最高的一句:
Your cloud agent id is
{{agentId}}. Setplan.selfAgentIdin plan.json to that string so spawns recordparentAgentIdandkill-tree --agent-idcan target this planner.
它要求规划器把自己这个云智能体的 id 原样写进plan.json的plan.selfAgentId字段,服务于两个机制:
- 父子链记账:从 core/agent-manager.ts 可以看到,脚本在为子任务 spawn 新智能体时写入
parentAgentId: this.plan.selfAgentId ?? null,即整棵树的每一层 spawn 都挂在这条链上; - 按树剪枝:
kill-tree --agent-id依据selfAgentId/parentAgentId定位一整棵子树并停止其中的运行中智能体。cli/forensics.ts 中的报错文案 "no running agents under ... (bad id, already stopped, or missing selfAgentId/parentAgentId in state)" 从侧面印证了该参数对状态字段的要求。
schema 侧对应有 schemas.ts 中的可选字段selfAgentId(描述为 "This planner's cloud agent id.")。该字段还有第三个用途:当plan.selfAgentId存在时,Slack 消息会带上智能体页脚(agent-manager.ts 的formatAgentFooter),使运行线程里的每条状态镜像都能溯源到具体智能体。
2.3plan.summary:写给人类的一句话
模板第 7 行要求:
Write
plan.summaryas a one-line orientation for the human in the Slack thread (e.g."smoke test of the new orchestrate substrate"). Kickoff posts the summary; without it, kickoff falls back to a truncatedgoal.
即summary是给 Slack 线程里的人类看的一句话导引(示例值 "smoke test of the new orchestrate substrate" 出自模板原文)。references/dispatcher.md 的 "Run summary" 一节给出了具体行为:kickoff 会发布<rootSlug>: <summary> <agent-link>;缺少summary时回退为截断到约 200 字符的goal。
模板把summary与goal的分工与 schema 描述完全对齐:schemas.ts 中goal被要求 "verbatim at every planner depth"(在每一层规划器中保持原文,是面向智能体的完整上下文),而summary是可选、min(1)的字符串,"One-line orientation for the human reading the Slack run thread. Kickoff falls back to a truncatedgoalwhen unset."。
2.4{{dispatcherInstruction}}与{{slackChannelInstruction}}:条件注入的两段指令
这两个占位符紧跟在 summary 句后、没有换行分隔,因为它们是条件性的:无值时渲染为空字符串,有值时各自带一个前导空行插在 summary 指令与后续段落之间。
dispatcherInstruction(util.ts):当 dispatcher 的第一名解析成功时,注入形如
Operator: Alex. Set `plan.dispatcher = { firstName: "Alex" }` so the kickoff bot reads "Alex's bot".的指令,让规划器把操作者写入plan.dispatcher,kickoff 机器人因此显示为<firstName>'s bot(schema 见 schemas.ts:该字段由 dispatcher CLI 设定,"planners don't author it")。这段代码里最值得注意的是一次性的注入防御:操作者名字来自 Slack 的 first_name,属"受控但未审"的外部数据,代码用raw.replace(/[\r\n{}]/g, " ")剥掉换行、反引号与花括号——注释明确说明,这是为了防止恶意名字"malform the prompt、crash renderPromptTemplate's leftover-placeholder check、break the JSON literal the planner copies into plan.json",再配合JSON.stringify处理双引号与反斜杠。这与 2.1 节提到的残留占位符强校验首尾呼应:一个未净化的{{...}}` 或反引号确实可能让模板渲染直接抛错。
slackChannelInstruction(util.ts):当 kickoff 携带了 Slack 频道时,注入
Set `plan.slackChannel = "C123..."` in plan.json. Subplanners inherit this value.对应 schema 字段slackChannel(schemas.ts:"Set from --slack-channel or SLACK_CHANNEL_ID by kickoff or the first root run")。子规划器继承该值后,整棵树的 Slack 状态镜像都汇入同一条运行线程;若未配置 Slack,按 README.md 的说明,脚本只记录一次日志并继续运行,正确性不受影响。
2.5 "Discover here before you publish tasks":根节点独有的探索纪律
模板倒数第二段只有一句:
Discover here before you publish tasks. Bootstrap workers hold reference material for descendants, not one-off discovery.
这是对根规划器独有的工作方式约束:在发布任务之前,探索要在"这里"(即规划器自己所在的会话)完成;bootstrap 类 worker 的定位是为后代任务承载参考资料,而不是一次性发现工具。结合 SKILL.md 中"一个 worker 可以是完整的云智能体、拥有数小时运行时长"的规划建议,这句话的实际含义是:规划器不应把"了解仓库长什么样"这类前置工作拆成任务外包出去,而应在自己上下文中先摸清地形,再发布携带充分上下文的任务。
2.6{{loopHygiene}}:循环纪律的全文注入
模板最后一段整段注入 loop-hygiene.md(rootFlag已替换为--root),其内容可归纳为两组规则:
循环操作纪律
- 必须在前台运行
bun cli.ts run --root <workspace>——Shell 默认后台化会打断心跳; - 退出码 100 是计划内的检查点重启,不是错误:立即重跑同一条命令,从已提交的
state.json恢复; - 退出码 1 且错误集非空才是规划器该出手的时候:循环因任务崩溃而退出,脚本已为死掉的 worker 写入合成的
handoffs/<task>-failure.md、为无结构化交接的 worker 写入handoffs/<task>-finished-no-handoff.md;在飞任务继续运行,下一次run通过recoverRunning重新附着; run返回后调用tree,只要还有pending/running任务就必须再循环;- 工作区存在非终态任务时,不得结束自己的回合。
失败交接的响应策略(按Failure mode行分类决策)
| Failure mode | 处置策略 |
|---|---|
cap-hit/oom | 缩小范围重试:拆更窄的任务、收紧pathsAllowed、精简scopedGoal |
network-drop | 视为瞬态故障,原样重试 |
tool-error | 换一个model重试 |
unknown | 读Last activity与SDK error行;无信号则按瞬态处理重试,再失败则放弃 |
并附带预算纪律:"Each retry costs another cloud-agent run... After 2 retries on the same task, prefer abandon (drop the task fromplan.json, replan around it) over a 3rd attempt"——同一任务两次重试后优先放弃并重新规划,除非有明确证据表明第三次会成功。
三、模板要求写入的 plan.json 字段及其 schema 兜底
把模板所有"写入指令"汇总,root planner 在 kickoff 后被要求维护的 plan 字段为:
| 字段 | 模板来源 | schema 定义 |
|---|---|---|
selfAgentId | "Setplan.selfAgentIdin plan.json to that string" | schemas.ts#L269-L272,可选 |
summary | "Writeplan.summaryas a one-line orientation..." | schemas.ts#L205-L211,可选、min(1) |
dispatcher.firstName | {{dispatcherInstruction}}(条件注入) | schemas.ts#L212-L220,strict 对象 |
slackChannel | {{slackChannelInstruction}}(条件注入) | schemas.ts#L242-L246,可选 |
模板没有明说、但 schema 强制执行的是 plan 的其余骨架字段:goal(非空)、rootSlug(kebab-case,用于分支名)、baseBranch、repoUrl(GitHub URL)、tasks数组(schemas.ts#L202-L277)。schemas.ts#L280-L329 的superRefine进一步约束:tasks必须非空、任务名不得重复、verifier 不能验证自己、verifies与dependsOn引用的任务必须存在。这些校验与模板中"Discover here before you publish tasks"形成配合——规划器发布任务前自行消化探索,脚本在校验层守住任务图的合法性底线。机器可读的 schema 副本位于 schemas/plan.schema.json,planner 手册 references/planner.md 给出了带$schema指针的完整 plan.json 示例(含goal/summary/rootSlug/baseBranch/repoUrl/tasks各字段),可作为模板所述字段的落地参照。
四、把模板跑起来:kickoff 全链路
结合 orchestrate/README.md,从环境准备到 root.md 被渲染注入的完整流程是:
依赖与密钥准备:PATH 中有
bun;在技能的 scripts 目录安装依赖(脚本刻意放在宿主仓库包管理器工作区之外):cd skills/orchestrate/scripts bun install export CURSOR_API_KEY="cursor_..."可选的 Slack 可见性:如需运行线程镜像,设置
SLACK_BOT_TOKEN(所需 scope 明细见 README.md 与 references/planner.md 的清单:chat:write、chat:write.customize、chat:write.public、files:write、files:read、reactions:read、channels:history等)。发起 kickoff:
bun skills/orchestrate/scripts/cli.ts kickoff "<goal>" \ [--repo <url>] [--ref main] [--model claude-opus-4-7] \ [--slack-channel <id>] [--dispatcher-name "<first name>"]CLI 读取
CURSOR_API_KEY、从git config --get remote.origin.url自动探测仓库(可用--repo覆盖)、经 buildKickoffPrompt 渲染出 root.md 提示词、通过 cursor-sdk 发起 spawn,最后打印{ agentId, runId, status, url }。之后交由根规划器自驱:
{{agentId}}即第 3 步打印的 agentId。规划器读取 SKILL.md 与 references/planner.md 手册,写 plan.json、以bun cli.ts run --root <workspace>前台循环发布任务、读取 handoff 决定下一步;dispatcher 返回 URL 后职责即告一段落,进度可通过bun cli.ts status、bun cli.ts crawl <repo-path> <branch> <root-slug>或 Slack kickoff 线程观察。
五、小结
root.md 只有 11 行,但它是整个 orchestrate 运行树的起点:身份行把"谁负责什么"锚定到用户目标;{{agentId}}指令通过plan.selfAgentId建立 spawn 父链与kill-tree --agent-id的剪枝锚点;plan.summary划定人类可见性与智能体上下文(goal)的边界;两个条件占位符由 cli/util.ts 注入操作者与 Slack 频道指令,并内置针对提示词注入的净化逻辑;"Discover here" 一行确立了根节点先探索、后发布的纪律;{{loopHygiene}}则把退出码语义、失败分类重试策略与重试预算整段压入规划器的操作手册。模板渲染层的残留占位符强校验(core/prompts.ts#L42-L50)与 plan 的 Zod schema 校验(schemas.ts#L280-L329),共同保证了这份短模板在真实运行中不会被半成品变量或非法任务图绕过。
【免费下载链接】pluginsCursor plugin specification and official plugins项目地址: https://gitcode.com/GitHub_Trending/plugins125/plugins
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考