四大执行模式对比(架构师视角)

当前在 DSH 里有四种"派活"的方式,它们在上下文继承、执行模型、生命周期、阻塞性、适用规模五个维度上有本质区别。下面我从架构师的角度讲清楚区别、用法和陷阱。


一、TL DR 对照表

维度subagentsubagent_forkworkflowralph
上下文继承� 完全隔离✅ 继承本会话已完成轮次❌ 脚本编排,与本会话隔离❌ 全新空白,无任何上下文
执行模型单次/多轮委派单次/多轮委派流水线脚本(agent/pipeline/parallel)循环迭代(每轮全新子代理)
生命周期持久 subagent_id,可 send_message 续聊持久 subagent_id,可 send_message 续聊一次性脚本,跑完即结束多轮循环,由 worker 报告完成/阻塞
默认阻塞后台(fire-and-forget)后台前台(脚本跑完才返回)前台(每轮循环跑完才返回)
共享"长期记忆"无(靠 subagent 自己产物)无(靠 subagent 自己产物)无(靠产物)共享 workspace 作为唯一长期记忆
典型用法独立研究、范围内实现复用上下文做后续分析大规模扇出(审计、迁移、多角度)干净迭代、避免上下文污染

二、逐个详解

1. subagent — 标准委派(无状态承包商)

核心契约:子代理拥有全新的、独立的上下文,看不到本会话的任何历史。它收到的是一个"自包含的 prompt"。

// 典型签名
subagent({
  description: "研究 X",
  prompt: "请调研 X 的实现方案…(必须给出完整背景,因为子代理看不到父会话)",
  run_in_background: true  // 默认 true
}) → 返回 subagent_id

架构要点

  • prompt 必须是自包含的:子代理不知道"我们之前聊过什么"。所以 prompt 里要把背景、约束、产出格式全写清楚。
  • 后台为主run_in_background: true 时立刻返回 id,不阻塞主线程;只有当你下一步动作依赖它的结果时才用 run_in_background: false(同步等待)。
  • 可续聊:返回的 subagent_id 是持久的,后续用 send_message 在同一子会话里继续派活。
  • 结果是一次性返回的:你拿到的是最终答复,看不到中间步骤

适用场景

  • 独立研究:“调研一下 React 19 的新特性”
  • 范围内实现:“实现 user 模块的 CRUD(已有 schema 在 …)”
  • 隔离分析:“分析这个文件为什么慢”
  • 任何和当前会话上下文无关的工作

反模式

  • ❌ 在 prompt 里写"就像我们刚才讨论的那样…"——它不知道你讨论过什么。
  • ❌ 把它当成同步函数用却不关心 id——后台跑完你可能错过通知。

2. subagent_fork — 上下文继承委派(带备忘录的承包商)

核心契约:和 subagent 类似,但子代理能看到本会话的所有已完成轮次(不包括当前正在生成的这轮)。

subagent_fork({
  description: "基于我们刚才的讨论做 X",
  prompt: "在刚才讨论的基础上,请你…",
  run_in_background: true
}) → 返回 subagent_id

架构要点

  • prompt 可以省略大量背景:因为子代理继承了你之前所有的输入输出,包括已读过的文件、做过的决定。
  • 节省本会话 context:虽然子代理看到了全部历史,但它的"思考+产出"不会回流到你的主会话——这是它的关键价值。你能继续在主会话工作而不被它的中间步骤淹没。
  • 生命周期与 subagent 完全一致:同样后台、同样 send_message 可续聊。

适用场景

  • 基于当前进度的下一步:“基于我们刚才定下来的 schema,实现 DAO 层”
  • 复用上下文的审查:“review 我们刚写完的这套方案,给出风险清单”
  • 承接式分析:“把刚才那份调研再深挖一层”
  • 任何显式需要本会话上下文的工作

何时选用 vs subagent

  • 默认用 subagent_fork 当你需要它"知道上下文"。
  • 默认用 subagent 当你想要"干净隔离"(避免子代理被本会话历史污染/分心)。

反模式

  • ❌ 在长会话里 fork 出 N 个 subagent_fork——每个都继承全部历史,子代理端 token 成本会高。
  • ❌ 期望"子代理的中间步骤进入主会话"——不会,它只把最终答复交给你。

3. workflow — 流水线编排(CI/CD 思维)

核心契约:你写一段 JavaScript 编排脚本(顶层 await),用工作流提供的 hooks(agent()pipeline()parallel()phase()log())来调度多个子代理。前台执行,整段脚本跑完才返回。

workflow({
  script: `
    const files = args.files;
    const results = await parallel(files.map(f => () => agent("分析文件 " + f)));
    return results.filter(Boolean);
  `,
  meta: { name: "codebase-audit", description: "代码库审计" }
}) → 返回 JSON 序列化的值

架构要点

  • 脚本本身就是编排层:子代理是"工人",脚本是"产线"。
  • parallel() 是 barrier:所有 thunk 全部完成才继续——只在某阶段确实需要全部前置结果时用。
  • pipeline(items, ...stages) 是无 barrier 的:每个 item 独立跑完所有阶段,阶段间不互相阻塞——批处理首选。
  • 结构化产出agent() 支持 opts.schema(受限 JSON Schema),可以拿到强类型结果而不是自然语言。
  • 前台阻塞:跑完才返回,意味着你不能同时做别的事。适合可预期的批量任务
  • 没有持久子代理:脚本结束,所有临时子代理就散了——不要指望"回头再问某一步"。

适用场景

  • 大规模扇出:审计 100 个文件、迁移 50 个模块、跨 10 个角度并行调研。
  • 多阶段流水线:每个文件先 lint、再测、再生成报告。
  • 对抗性验证:让多个 agent 从不同视角审同一个方案,汇总分歧。
  • 结果需要强结构:用 schema 收口,避免自然语言解析。

反模式

  • ❌ 单任务用它——杀鸡用牛刀,直接 subagent
  • ❌ 用 parallel() 包单个 agent——它和 agent() 没区别还多一层开销。
  • ❌ 期望子代理跨阶段共享上下文——每个 agent() 调用都是新的、独立上下文。

4. ralph — 全新迭代循环(每日立会 + 黑板)

核心契约前台循环。每一轮启动一个全新的、没有任何对话历史的子代理。子代理的"记忆"只有共享 workspace 里的文件。子代理跑完会报告 completionblocker,循环据此决定下一轮或终止。

ralph({
  objective: "把 src/legacy/ 全部迁移到新架构",  // 不可变目标
  maxRounds: 20  // 轮数上限
}) → 返回最终报告

架构要点

  • 每轮全新:子代理不知道上一轮说过什么——这是故意的设计,用来对抗上下文累积带来的偏见/分心/锚定。
  • workspace 是唯一长期记忆:每轮子代理必须从文件里读出"项目当前状态"才能继续。这就是为什么这种模式适合有明确可验证产物的任务。
  • 不可变 objective:循环开始后目标不能改(除非显式 edit)。
  • worker 自行判断完成:完成/阻塞由子代理报告,不是独立评估——这意味着你需要把"如何判断完成"写进 objective 里。
  • 前台阻塞:每轮跑完才进下一轮,你看不到中间过程,但每轮结束后会拿到子代理报告。

适用场景

  • 长跑、有明确终止条件的实现任务:“把 X 模块全部重构完,每完成一个文件就在 progress.md 划掉”
  • 需要"清零"避免上下文污染:“调试这个诡异 bug,每次试一个新假设”
  • 探索式 + 有工件:任务产物能落盘,让下一轮子代理有据可查。

反模式

  • ❌ 一次性研究——用 subagent
  • ❌ 任务没有可写产物——子代理下轮会"失忆",循环会卡。
  • ❌ 期望循环"智能调度"——它只是反复启动新子代理,子代理自己决定下一步。
  • ❌ 在会话里讨论细节——子代理听不到,细节必须写到 workspace 文件里。

三、选用决策树(架构师速查)

要派活给 AI 子代理?
│
├─ 是一次性、可预期的批量任务(≥3 个独立子任务)?
│  └─ YES → workflow(流水线,前台跑完拿结构化结果)
│
├─ 是长跑、有可写产物的迭代任务?
│  └─ YES → ralph(每轮全新,靠 workspace 续命)
│
├─ 工作需要看到本会话上下文?
│  ├─ YES → subagent_fork(继承历史,省主会话 context)
│  └─ NO  → subagent(干净隔离)
│
└─ 你的下一步依赖子代理结果?
   └─ YES → run_in_background: false(同步等待)
   └─ NO  → run_in_background: true(后台跑)

四、高级程序员使用建议

  1. 默认组合subagent_fork(后台)+ 必要时 send_message 续聊。90% 的"派活"场景适用。

  2. 隔离 vs 继承的选择

    • 子任务和当前讨论直接相关(review、扩展、追问)→ subagent_fork
    • 子任务是独立研究 / 干净实现subagent(避免被本会话历史带偏)
  3. 批量任务三步走

    • 1~2 个:subagent
    • 3~10 个、结构相似:用 workflow + pipeline()parallel()
    • 长跑迭代:有可写产物 + 明确终止条件 → ralph
  4. 成本意识

    • subagent_fork 子代理会"重读"父会话全部历史,token 成本高;上下文真的需要才用它。
    • workflow 编排脚本本身便宜,但 N 个子代理的并发可能撞到部署上限(部署有 cap)。
    • ralph 每轮全新子代理 + 重读 workspace,单轮成本固定总成本 = 轮数 × 单轮成本,要控制 maxRounds。
  5. 不要混用语义:四种模式不是"调用同一个东西的不同写法",它们各自有独立的生命周期和上下文契约。把它们当成四种不同的架构组件来用——就像 RPC、消息队列、定时任务、事件循环的关系,而不是"四个名字一样的函数"。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐