Conference Talk · Agent Skill 设计
六种 Agent 编排模式如何在 Skill 中表示、运行与收口,以及它们各自的可靠性边界
一个自然而然的问题
Agent 执行拓扑已经有成熟的设计模式归纳。那我们想想,如果不上类似 LangGraph 这类重型 workflow 框架,靠 Skill 自己能不能把这些模式跑起来?
今天想带大家一起探索、验证一下,在 Skill 中六种模式分别该怎么实现以及可靠性如何。
2/37一个复杂任务,应该按什么顺序、以什么边界、由谁来完成?
编排回答的就是这一个问题。任务简单的时候自然用不着;任务一旦复杂起来,自然而然就有了编排的需求。
4/37flowchart LR
classDef input fill:#d4c9b8,stroke:#b8a898,color:#201d18
classDef skill fill:#c14a3a,stroke:#9c3628,color:#fff
A["指令 —— 怎么做"]:::input --> S(["Skill"]):::skill
B["编排模式 —— 按什么拓扑走"]:::input --> S
C["工具协议 —— 靠什么动手"]:::input --> S
D["验证门禁 —— 凭什么收口"]:::input --> S
往深一层想
拓扑不只是"先做什么、后做什么"的流程图,它决定的是资源在系统里怎么传播。8/37
| 维度 | 拓扑在管什么 | 举例 |
|---|---|---|
| 数据 | 节点间怎么流 | 链式逐步传递 |
| 控制权 | 谁接手、怎么交接 | Route 一判定型 |
| 错误 | 怎么扩散、在哪爆发 | 链一路带下;并行汇合爆发;层级衰减失真 |
| 延迟 | 怎么叠加 | 并行会等待最慢的那个;链式累加 |
| 责任 | 最终落在谁头上 | Orchestrate 编排汇总结果;Hierarchy 层级分责兜底 |
不是靠某个节点自己做得足够好就能补救。选拓扑,就是在选这五件事分别会怎么发生。
9/37flowchart LR
classDef step fill:#98d4bb,stroke:#70b898,color:#201d18
A["请求数据"]:::step
B["更新 state"]:::step
C["触发渲染"]:::step
D["浏览器绘制"]:::step
A --> B --> C --> D
链越长,上一环的问题越难被发现。接口数据错了,state 层只会照单全收,渲染层只会想着怎么把它渲染得像模像样,没人会想起回头查一下请求那一步是不是出了什么问题。
10/37flowchart LR
classDef step fill:#98d4bb,stroke:#70b898,color:#201d18
A["加载并审阅计划"]:::step
B["逐任务执行\n标记+验证"]:::step
C["收尾\n转交后续 Skill"]:::step
A --> B --> C
每一步的产出由 Skill 要求写进对话或 Todo,下一步自然读取就等于拿到了上一步的输出。Chain 靠上下文维持管道,不需要显式传参,但中途遇到阻塞必须停下来问,不能靠猜往下走。
flowchart LR
classDef io fill:#d4c9b8,stroke:#b8a898,color:#201d18
classDef worker fill:#c7b8ea,stroke:#a89ed4,color:#201d18
In["发起请求"]:::io
W1["用户信息"]:::worker
W2["商品列表"]:::worker
W3["推荐位"]:::worker
M["Promise.all\n合并渲染"]:::io
In --> W1 & W2 & W3 --> M
并行的关键在"合并",不在"发起"。三个请求一起发很容易,谁超时、谁失败、要不要兜底,才是真正考验 merge 逻辑的地方。没有好的合并策略,并行只会让出错的机会变得更大。
12/37flowchart LR
classDef io fill:#d4c9b8,stroke:#b8a898,color:#201d18
classDef worker fill:#c7b8ea,stroke:#a89ed4,color:#201d18
In["6 个失败测试\n按独立领域分组"]:::io
A1["Agent 1\nabort 测试"]:::worker
A2["Agent 2\nbatch 测试"]:::worker
A3["Agent 3\nrace 测试"]:::worker
Out["审查整合\n跑完整测试"]:::io
In --> A1 & A2 & A3 --> Out
三个 agent 必须写进同一条消息里同时派生(dispatch/spawn)才算并行,如果还是一个一个来就退化成串行;收尾那一次完整测试,是唯一能发现各 agent 的返回,彼此之间是否会互相冲突的机会(比如有没有改到共享逻辑)。
flowchart TD
classDef io fill:#d4c9b8,stroke:#b8a898,color:#201d18
classDef route fill:#f4b8c5,stroke:#d89aab,color:#201d18
classDef step fill:#f4b8c5,stroke:#d89aab,color:#201d18
In["URL 变化"]:::io
R{{"路由匹配"}}:::route
A["列表页组件"]:::step
B["详情页组件"]:::step
C["404 页面"]:::step
Out["渲染"]:::io
In --> R
R -- "/list" --> A
R -- "/detail/:id" --> B
R -- "未匹配" --> C
A & B & C --> Out
flowchart TD
classDef io fill:#d4c9b8,stroke:#b8a898,color:#201d18
classDef route fill:#f4b8c5,stroke:#d89aab,color:#201d18
classDef step fill:#f4b8c5,stroke:#d89aab,color:#201d18
In["要验证的问题"]:::io --> R{{"问的是哪种?"}}:::route
R -- "逻辑/状态模型" --> A["LOGIC.md\n可交互终端程序"]:::step
R -- "长什么样" --> B["UI.md\n多套 UI 变体"]:::step
A & B --> Out["答案\n是唯一该留下的"]:::io
问题真的模糊、又联系不到人时,Skill 给了兜底规则:按代码类型判断,后端模块归 LOGIC,页面或组件归 UI,并在开头写明这是个假设。
flowchart LR
classDef gen fill:#a8d8ea,stroke:#80b8ce,color:#201d18
classDef critic fill:#f0c88a,stroke:#c8a455,color:#201d18
classDef done fill:#d4c9b8,stroke:#b8a898,color:#201d18
Gen(["注释一半代码"]):::gen
Critic(["保存看效果"]):::critic
Done(["问题消失\n定位到代码块"]):::done
Gen -->|"保存"| Critic
Critic -->|"问题还在"| Gen
Critic -->|"问题消失"| Done
| 维度 | Open Loop | Closed Loop |
|---|---|---|
| 目标 | 开放,边走边定 | 有界,事先定好 |
| 路径可见性 | 走的时候才知道 | 大致看得清 |
| 验收 | 模糊,靠 Agent 自判 | 明确,每步可判定 |
| 开销 | 难预估 | 可控、可估 |
| 适用场景 | 探索、找方向 | 交付确定的事 |
Closed Loop 更常用:更确定、更可控。可控的关键往往在验收是客观的,比如「问题还在 / 消失」、测试、typecheck。写 Skill 时先尝试 Closed Loop,把客观验收标准定义清楚,再让 loop 跑起来。
17/37flowchart LR
classDef gen fill:#a8d8ea,stroke:#80b8ce,color:#201d18
classDef critic fill:#f0c88a,stroke:#c8a455,color:#201d18
classDef done fill:#d4c9b8,stroke:#b8a898,color:#201d18
Red["写失败测试\nRed"]:::gen --> Green["最小实现\nGreen"]:::gen
Green --> T{"测试通过?"}:::critic
T -- "否" --> Green
T -- "是,还有边界" --> Red
T -- "是,边界用完" --> D["交给 review\n重构在此阶段"]:::done
测试过 / 不过就是客观验收,和上面二分定位里「问题还在 / 消失」是一类信号。red→green 只负责让行为正确;重构被故意排除在循环外,一旦把「顺手重构」也塞进来,测试就分不清自己在验证正确性,还是在给重构背书。
对照 TDD 就很清楚:Open Loop 的验收靠 Agent 自判,退出也靠自判。停止条件必须可测量——最大轮数、改动幅度小于阈值,或外部信号介入。缺了客观验收,Loop 就会越改越忙、越忙越偏。
19/37flowchart TD
classDef orch fill:#f0c88a,stroke:#c8a455,color:#201d18
classDef worker fill:#98d4bb,stroke:#70b898,color:#201d18
Orch(["CI 编排\n发布前检查"]):::orch
W1["Lint"]:::worker
W2["Test"]:::worker
W3["Build"]:::worker
Orch --> W1 & W2 & W3
W1 & W2 & W3 -.->|"结果回传"| Orch
GitLab CI 的 release job 通过 yaml 定义 lint / test / build 的触发顺序和失败策略,中心节点只管派发任务、汇总结果,业务代码由各个 job 自己跑,这就是 Orchestrate。失败模式通常是拆错边界:该顺序执行的塞进并行(比如 build 其实依赖 lint 先过),该独立跑的又互相等待。
20/37flowchart TD
classDef orch fill:#f0c88a,stroke:#c8a455,color:#201d18
classDef worker fill:#98d4bb,stroke:#70b898,color:#201d18
classDef critic fill:#f4b8c5,stroke:#d89aab,color:#201d18
C(["Controller\n逐任务派发"]):::orch
I["Implementer\n实现 + 测试"]:::worker
R{"Reviewer\n复核通过?"}:::critic
F["Fix subagent\n修复问题"]:::worker
N["标记完成\n进入下一任务"]:::orch
C --> I --> R
R -- "否" --> F --> R
R -- "是" --> N
N -.->|"还有任务"| C
这个 Skill 按任务逐个推进:Controller 派实现、等复核、处理修复,验收通过后才进入下一项。Parallel 关注多件独立任务能否同时做;Orchestrate 关注谁持续管理流程、门禁与最终判断。
flowchart TD
classDef mgr fill:#d8b8a0,stroke:#b89882,color:#201d18
classDef mid fill:#e8d0bc,stroke:#c8b09c,color:#201d18
classDef worker fill:#98d4bb,stroke:#70b898,color:#201d18
Mgr(["技术负责人\n定义整体目标"]):::mgr
L1(["前端负责人\n负责交互切片"]):::mid
L2(["服务端负责人\n负责数据切片"]):::mid
W1["UI Agent"]:::worker
W2["State Agent"]:::worker
W3["API Agent"]:::worker
W4["DB Agent"]:::worker
Mgr --> L1 & L2
L1 --> W1 & W2
L2 --> W3 & W4
flowchart TD
classDef mgr fill:#d8b8a0,stroke:#b89882,color:#201d18
classDef mid fill:#e8d0bc,stroke:#c8b09c,color:#201d18
classDef worker fill:#98d4bb,stroke:#70b898,color:#201d18
P(["Planner\n发布任务"]):::mgr
SP(["Subplanner\n负责一个切片"]):::mid
W1["Worker"]:::worker
W2["Worker"]:::worker
W3["Worker"]:::worker
P --> W1
P --> SP
SP --> W2 & W3
W1 -.->|"交接记录"| P
W2 & W3 -.->|"交接记录"| SP
SP -.->|"汇总交接记录"| P
这里引用 /orchestrate,是因为它内部的 Subplanner 可以递归委派。根 Planner 直接调 Worker 时是一层 Orchestrate;Subplanner 再往下拆,执行图才长成 Hierarchy。Worker 只向父级交接,每一层只能看到直接孩子,层级越深越容易丢失全局信息。
从模式到 Skill
拓扑是任务运行的形状,Skill 是把这种形状写成操作协议。
六种模式已经认识完了。接下来不再逐个看形状,而是把共同结构抽出来:什么时候进入、分几步、哪里分支、何时循环、靠什么工具验证,以及最终交付什么。
24/37从上到下,逐渐从"要不要做、做哪些步骤"收敛到"怎么做、交付什么"。
25/37从流程到确定性
流程写清楚,只解决了怎么走;执行者是概率模型,还要解决怎么收口。
Skill 可以规定步骤、分支和交互边界,但不能让模型天然变成确定性程序。接下来要看的是:哪些地方可以保留自由,哪些判断必须留下状态、证据和硬门禁。
28/37flowchart LR
classDef loose fill:#d4c9b8,stroke:#b8a898,color:#201d18
classDef mid fill:#a8d8ea,stroke:#80b8ce,color:#201d18
classDef tight fill:#f0c88a,stroke:#c8a455,color:#201d18
classDef hero fill:#c14a3a,stroke:#9c3628,color:#fff
A["语言表达\n允许自由变化"]:::loose
B["决策过程\n尽量稳定"]:::mid
C["外部动作\n必须可控"]:::tight
D["交付结果\n必须可验证"]:::hero
A --> B --> C --> D
越往右,越不能靠模型自己摸索。不是要把随机性消掉,而是在该约束的地方约束到位。
29/37从约束到预算
The model spends; the harness budgets. 模型负责花,Harness 负责控制预算。
这些约束不能继续依赖模型自觉。最大轮数需要计数器,改动阈值需要比较器,外部动作需要权限门禁,客观验收需要模型之外的事实依据。模型负责生成下一步,Harness 负责记录状态、核验条件,并在目标达成或预算耗尽时停止。
30/37Loop 里的最大轮数和客观验收,都属于反思的校正预算;推理预算管的是这道题值得想多深。更完整地看,一个 Agent 一直在分配有限资源:看什么、记什么、想多深、能做什么,以及允许回头改几次。
31/37五笔预算让单个 Agent 转起来;协作负责在装不下时分流,治理负责给所有循环划边界。
32/37这六个做法的共同点,是把关键判断从模型的临场自觉,变成 Harness 可以记录、验证和拦截的外部机制。
33/37但「更适合重型」不等于「放弃 Skill」。确定性(Graph,像函数)和自主性(Skill,像对话)本就是互补的两侧——把必须确定的那一段(一条数据管道、一次发布流程)封装成一个工具,确定性锁在工具内部,何时调用、拿结果做什么仍交给 Skill 决策。越过边界是给 Skill 添一件确定性工具,不是推翻它。
34/37这是基于 context window 行为和前面案例得到的工程判断,不是 benchmark 结论。Parallel / Orchestrate / Hierarchy 超过一定规模,就该把这一段封装成确定性工具(如 LangGraph 编排)交回 Skill 调用,而不是强迫 Skill 做不擅长的事。
35/37在 Agent 中靠 Skill,六种拓扑都能跑起来;但拓扑越深、协作越多,越要把确定性下沉到 Harness 和工具。