动态工作流(Dynamic Workflows):模型现场写的一段 JavaScript 假设你要审查src/routes/下 487 个路由文件看哪些漏了鉴权检查。让模型直接干它会逐轮读文件、报发现、再读下一个读到第三十个左右开始概括宣布其余同理。动态工作流换了个做法。模型不去读文件而是先写出一段 JavaScriptconstfoundawaitagent(列出 src/routes/ 下所有 .ts 文件,{schema:/* ... */})constauditsawaitpipeline(found.files,fileagent(审${file}有没有漏鉴权),)returnaudits.filter(Boolean)写完模型就退场了。接下来是运行时执行这段脚本读到agent(...)就派一个子 Agent 去干活读到pipeline(...)就对列表里每一项各派一个同时在跑的最多 16 个一次运行最多 1000 个。子 Agent 各自有完整的读写执行工具审完把结果返回脚本把它们收进audits这个数组。于是有三个角色写脚本的模型只负责决定这活怎么切、每一项该跟 Agent 说什么写完不再参与运行时照着脚本派活、限并发、记账、显示进度子 Agent是唯一真正读文件、跑命令、改代码的。脚本是一份派工单。用法上它是个会话内的开关在自己敲的提示词里带上ultracode关键词或者把/effort设成ultracode让本次会话的每个大任务都走这条路。跑得满意可以把那段脚本存成命令下次直接调用。pi-dynamic-workflows这个原型用大约 1500 行 TypeScript 把这套东西跑通了代码短到能一次读完下面的实现细节大多来自它。这套设计的担保集中在中间那段派活上列表里每一项都派到了、并发压得住、挂掉的能看见。列表本身从哪来、Agent 报回来的问题谁去修它都管不了。脚本能碰到的只有八个全局脚本正文被包成一个立即执行的 async 函数扔进 Node 的vm沙箱跑一遍constwrapped(async () {\n${body}\n})()awaitnewvm.Script(wrapped).runInContext(context)沙箱里注入八个全局派活用的agent、parallel、pipeline辅助用的phase、log、args、cwd、budget。除此之外只有一批无害的内置对象JSON、Math、Array、Object、Set、Map、Promise等加上一个被重定向到log的console和一个冻成只剩cwd()的process。没有require、没有import、没有fs、没有网络。Math是整个注入进去的Math.random()靠解析阶段的门禁拦不是靠不给MathDate.now()和new Date()同理。所以读文件、跑命令、调接口都得让子 Agent 去做脚本只决定派谁、按什么顺序派、把谁的产出喂给谁。剥到这个程度不是为了安全是为了让运行时成为唯一的出口。拿并发上限举例运行时想承诺最多同时跑 16 个 Agent这句话能成立的前提是脚本除了调agent()没有别的办法启动任务。要是它能require(child_process)自己 fork 出 500 个进程信号量数到 16实际在跑 500 个那个上限就是个摆设。网络同理脚本自己发出去的请求进度视图看不到、取消信号停不掉、token 也记不上。拿掉这些之后脚本的每一个动作都必须穿过运行时自己写的那几个函数运行时于是能数、能排队、能记日志、能中断、能缓存。限制不产生能力限制消除例外。cwd最能说明这条边界有多别扭它只是一个路径字符串。脚本知道活儿在哪个目录却没有任何办法看进去拿到/repo/src这个字符串之后它列不出里面有什么文件也读不了其中任何一个。于是开头那个脚本的第一个 Agent 干的活就很怪它负责列出src/routes/下所有.ts文件。ls src/routes/*.ts一条命令的事这里要花一整个模型会话发提示词、模型决定调哪个工具、调用、可能还要多轮。它没有任何判断要做只是替脚本伸手拿一份目录清单。要判断的是第二批 Agent逐个文件看鉴权检查缺不缺。真实的权限边界来自宿主的工具集与用户环境。你的权限模式只管要不要弹窗问你是否启动这个工作流工作流派生的子 Agent 一律以acceptEdits模式运行文件改动不再逐次问你并继承你的工具白名单与会话本身的模式无关。入口也收紧过ultracode只在人工输入的提示词里生效-p传进去的提示词、定时任务的提示词、webhook 载荷、被转达进对话的 PR 评论都不触发。依赖是 await 出来的这里没有addNode、没有addEdge、没有共享状态对象。先后顺序由 JavaScript 顺带表达constinventoryawaitagent(盘一遍这个仓库的结构)constsummaryawaitagent(根据这份清单总结主要模块\ninventory)第二个 Agent 排在第一个之后不是因为谁声明了一条边而是因为 JS 就得这么执行。真正的 DAG 引擎得存一张节点表、一张边表、每个节点的状态等待中就绪在跑已完成还得有个调度循环反复扫哪些节点的前驱都完成了、可以放行。这套东西一样也没有。pi给一次运行维护的状态只有五个字段当前阶段名、日志、阶段名列表、派了几个 Agent、花了多少预算全是给进度视图和事后复盘看的没有一个字段参与调度。运行时不需要知道summary 依赖 inventory它只要看到脚本调了agent()就派一个看到脚本在await就等着。事后从日志里能画出一张图但运行时执行的时候手上并没有那张图。这一点划清了它和 plan 的界限。计划模式生成自然语言读者是模型自己模型逐轮读、逐轮决定下一步于是计划可以被忽略、误读、跑偏写着然后并行审查每个文件模型审了三个就开始总结。而await parallel(files.map(f () agent(...)))是每个文件一个 Agent一个不落父模型写完脚本就退场运行期间无从干预。官方文档把这一点概括成谁持有计划用子 Agent、Skill、Agent 团队时模型是编排者每个结果都落回上下文窗口用工作流时循环、分支、中间结果都由脚本持有模型的上下文里只留最终答案。三个派活函数怎么实现的开头那段脚本里出现的agent和pipeline加上一个parallel是全部的派活手段。它们都是pi自己写的加起来不到一百行。agent(prompt, opts)派一个子 Agent等它干完把结果返回。真正起会话的是里面那句agentRunner.run()外面这一层只负责排队、记账、通知界面、失败兜底constagentasync(prompt,opts{}){if(budget.remaining()0)thrownewError(预算耗尽)construnlimiter(async(){// 排队闸门在这里onAgentStart({label,prompt})// 通知进度视图try{constresultawaitagentRunner.run(prompt,{label,schema,signal})state.spentestimateTokens(result)// 记账onAgentEnd({label,result})returnresult}catch(e){if(signal.aborted)throwelog(agent${label}failed:${e.message})returnnull// 失败降级成 null}})pendingAgentRuns.add(run)// 登记在飞任务returnrun}limiter是十几行的 FIFO 信号量上限clamp(hardwareConcurrency - 2, 1, 16)减 2 是给宿主进程和你自己的编辑器留核。pendingAgentRuns那个集合的用处在收尾脚本return之后跑一句await Promise.allSettled([...pendingAgentRuns])防止脚本已经返回而 Agent 还在跑。pipeline(items, ...stages)对一个列表逐项派活每项可以走多个阶段是个双层循环constpipelineasync(items,...stages){returnPromise.all(items.map(async(item,index){// 外层item 之间并行letvalueitemfor(conststageofstages){// 内层stage 之间串行try{valueawaitstage(value,item,index)// 上一段的产出喂给下一段}catch(e){log(pipeline[${index}] failed:${e.message})returnnull// 整项作废}}returnvalue}))}两个细节值得记住。一是pipeline自己完全不管并发Promise.all把 487 个 item 一口气全部启动压住并发的是每个 stage 内部调agent()时在limiter那里的排队。二是return null那行写在for循环的catch里所以第二个 stage 挂了整项直接返回nullvalue里攒着的第一个 stage 结果就地丢弃后面讲迁移那个坑根就在这一行。parallel(thunks)把一批活同时铺开更薄就是Promise.all加逐项try/catch返null。它要求你传() agent(...)而不是agent(...)传错直接抛错错误信息里把正确写法也写上了。原因在求值时机agent()是 async 函数调用它那一刻函数体就同步跑到第一个 await任务已经进队items.map(x agent(x))在数组构造完成时五百个任务全在队列里信号量只能限制同时执行几个管不住入队。包一层函数创建这个动作才被推迟到parallel内部逐个调用的时候。剩下四个全局都很轻phase(title)只是把当前阶段名记下来给进度视图分组log(msg)往运行日志写一行args是存成命令后调用时传进来的参数budget查预算但它按JSON.stringify(result).length / 4粗估完全没计入子 Agent 内部的提示词消耗和工具往返只能当启发式护栏。四条担保和一个缺口中断能续靠拒掉时间和随机数。解析用 acorn 出 AST拒掉Date.now()、Math.random()、new Date()。这是纯形状匹配Math[random]()拦不住它防的是模型无意写出不确定代码不防绕过。为什么值得拦得看恢复机制Claude Code 的重放按 Agent 启动顺序位置匹配缓存脚本里有随机数重放时序列就变了缓存对不上号。确定性在这里不是哲学追求是位置式重放缓存的前置条件。pi恰好实现了这条检查却还没有恢复功能它在为一个尚不存在的特性预付账单README 自己标注这是原型没有持久化与可恢复运行中断即全部重跑。同一道门禁还要求首条语句必须是纯字面量的export const meta { name, description }因为 UI 要在执行之前就知道这个工作流叫什么报错的前提是先有名字。并发压得住因为启动 Agent 只有agent()一条路而它每次都得先过limiter那道闸门。数据能回到脚本只有 schema 一条路。子 Agent 之间不能直接通信每个agent()都新建一个 in-memory 会话不继承父会话历史也看不到兄弟 Agent 的状态。产物往下传主要靠拼进下一个 prompt而这条边承载的是字符串下游 Agent 看到它和看到任务描述没有区别它并不知道哪部分是上一阶段的结论、哪部分是我该做的事。传了 schema 也一样schema 保证的是父脚本拿到干净对象不保证下游 Agent 拿到结构化输入。所以schema 是数据从 Agent 跨回脚本这条边界上的唯一通道脚本要拿来做分支、做循环、做计数的任何数据都只能这么进来因为脚本自己什么也读不到。实现是给子 Agent 挂一个结构化输出工具参数校验由框架免费做掉调用即终止省掉一轮好的我已完成分析的总结发言。失败一律变成null。前面三个函数里的catch动作都一样记一条日志、返回null只在用户主动取消时如实抛出。这就是挂掉一个不影响其余十九个的实现也是后面一切麻烦的来源。收尾还有两道校验等所有在飞的 Agent 落地拿structuredClone试一下返回值抛异常就报你是不是忘了 await模型写异步编排最高频的错就是漏一个await。以及一条硬拒绝一个 Agent 都没派就判失败堵住只声明阶段、返回静态对象这种语法正确但什么也没干的脚本。担保清单上有个真实的缺口文件系统。所有子 Agent 共享同一个cwd各自握有完整的读写执行工具Agent A 写个文件、Agent B 读它这条边脚本里看不见运行时也不知道并发写的冲突就出在这里。pi的AgentOptions里有个isolation: worktree选项但它只被拼成一行文字Requested isolation: worktree塞进提示词没有任何代码去创建工作区。官方那句每个文件在自己的独立副本里改最终也只是作为文字进到子 Agent 的 prompt 里。运行时提供的是会话隔离不是文件系统隔离。大部分约定靠提示词教运行时不拦脚本是普通 JavaScript语法本身不认识工作流这回事pipeline和parallel只是两个注入进来的函数。那模型凭什么写出前面那种脚本pi的做法是给编排模型挂 14 条提示词指南随工具描述一起发过去。运行时真正硬拦的只有上一节那四件事其余全是纸面约定写错了不会报错只会安静地降级。属于不遵守就出问题但运行时不管的有这几条parallel(thunks)传函数不传 promise。指南把正反两种写法都列了出来因为这个坑不报错只是闸门失效。每个agent()都给一个 2–5 个词的唯一 label理由是进度视图和错误报告要靠它区分谁是谁。汇总结论之前先检查null。别假设子 Agent 有父会话的仓库上下文每个 prompt 里要带足任务背景和相关路径子 Agent 是干净会话父模型知道的它一概不知道。phase(title)在新一组工作开始时才调不要为了好看预先声明一堆可能跳过的阶段。只在用户明确要求 fan-out 或多 Agent 编排时才用工作流单个文件的读改别用。还有一条合并多个子 Agent 结果时加一个最终汇总 Agent返回一个紧凑的、带ok/verdict字段的 JSON。这条恰好在鼓励最不可靠的那种判据ok: true是模型对自己工作的自述。官方给的六个例子官方文档给了六个例子每个都是一句能直接敲进去的话译文扫全库用工作流审查src/routes/下每一个路由处理器找出缺失的鉴权检查每条发现在上报前做对抗式复核。修到通过用工作流跑npx tsc --noEmit持续修报出来的错误直到类型检查通过或者连续两轮没有进展。批量迁移用工作流把src/components/下每个组件从 styled-components 迁到 Tailwind每个文件在自己的独立副本里改。逐文件评审用工作流评审这个 PR 里每个改动过的文件查正确性问题然后把各文件的发现合并成一份排好序的总结。多源调研用工作流调研三家竞品怎么做限流——并行读它们的公开文档和近期 changelog然后对比几种做法。找到不再增长用工作流找出这个仓库里的 flaky test——反复跑测试套件记录哪些测试间歇失败连续两轮没有新发现就停。注意这六句话里已经写进了停止条件和质量要求“连续两轮没有进展”、“每个文件在自己的独立副本里改”、“上报前做对抗式复核”、“合并成一份排好序的总结”。这些不是运行时的功能是你在提示词里要求的最终变成脚本里的for循环、pipeline阶段和一个汇总 Agent。文档还给了每个例子一句结构说明以及兑现它各自需要什么例子结构说明兑现它需要扫全库每文件一个 Agent收集并核实发现pipeline单阶段 一个汇总 Agent修到通过跑检查 → 修失败项 → 重复直到通过或不再有进展for轮次轮内parallel批量迁移发现文件 → 各自在独立副本里改 → 验证每个结果pipeline双阶段逐文件评审每文件一个评审者全部发现交给一个 Agent 排序去重pipeline 单个 merge Agent多源调研并行读 changelog / issue / 文档然后综合parallel异构 交叉核对找到不再增长分轮搜索新一轮没有新增就停while 一个集合六个例子里只有第一个给了真实脚本就是开头那段完整版长这样exportconstmeta{name:audit-routes,description:Audit every route handler for missing auth checks,}constfoundawaitagent(List every .ts file under src/routes/.,{schema:{type:object,required:[files],properties:{files:{type:array,items:{type:string}}}},})constauditsawaitpipeline(found.files,fileagent(Audit${file}for missing authentication checks.,{label:file}),)returnaudits.filter(Boolean)脚本里每个取舍都能对上前面的机制。schema只出现在第一个 Agent 上判据是返回值要不要被 JS 代码消费found.files得拿来迭代审计结果只是回给父模型看。同构 fan-out 用pipeline单阶段而非parallel因为pipeline的 stage 直接收 item、不必自己包 thunk。全程没有phase()调用印证了阶段是纯可选的展示分组。结尾那行.filter(Boolean)是给null降级收尾的。但这个脚本比它对应的那句提示词少了一步提示词要求上报前做对抗式复核脚本里没有任何复核和汇总。文档也承认它是最小形状不是推荐形状。把六个例子摊开看它们是同一个漏斗的变体一个带 schema 的 Agent 发现并产出列表每项铺一个 Agent可选的逐项校验最后一个装得下全部结果的 Agent 收口。扫全库是 discover → map → verify → reduce逐文件评审少一个 verify批量迁移的 map 分两段多源调研的 map 是异构的两个循环例子是 loop 包着 map。没有一个例子有菱形依赖没有一个在运行中改变拆法没有一个让不同类型的子任务互相路由。深度三到四层宽度上千。所以模型生成的是一个数据并行管道的参数怎么切、每项喂什么 prompt、怎么归并。给了一整个 JavaScript 沙箱的自由度实际用掉的是Array.prototype.map加一个while。不过这两个参数确实没法配置化。按文件切还是按模块切、每个 Agent 该被告知多少上下文都得看过任务才知道这是真的模型工作。剩下的部分不是。入口是软的穷尽性只硬到列表那一层穷尽性是这套设计最硬的一条。pipeline(files, ...)保证列表里每一项都派了 Agent一个不落数目可核。开头那个模型读到第三十个就开始概括pipeline不会差别不在模型变聪明在for循环不会累。这条价值别的方案给不了。但一个不落是相对于found.files不是相对于那个目录。而found.files是模型给的ls是确定的一个模型来做ls不是它可能漏文件、截断长列表、顺手把.test.ts算进来或排除掉、把路径写错。不可靠性就这么坐在整个 fan-out 的根上而且没有任何东西会发现它。单个审计 Agent 挂掉至少会变成一个nullfilter(Boolean)那行还提醒你它存在过发现阶段漏掉的文件是静默的pipeline忠实地遍历它拿到的那个列表漏掉的 30 个文件根本不会进入结果数组连一个null都不留。你拿到一份全部 457 个路由都审过了的报告而目录里有 487 个。修法是文档自己给的只是没往这个方向说args。已保存的工作流可以通过args接收调用时传入的结构化输入文档举的例子正是目标路径清单。文件列表因此可以在工作流外面用真实代码算好find、git diff --name-only、构建产物清单作为结构化数据传进来脚本直接在args上调数组方法、不必解析发现阶段那个不可靠的 Agent 就没了。前提是args只对已保存的工作流成立。一次性运行没有地方接确定性输入只能让 Agent 代做 I/O。所以动态生成阶段那些看着蠢的地方有一部分是还没固化的症状不是设计错误。顺着这条线看两个循环例子判据的可靠性也分两级// 修到通过判据是 Agent 报的constcheckawaitagent(跑 npx tsc --noEmit报告错误,{schema:{/* ok, count */}})if(check?.ok)return{ok:true}// 它说过了就是过了// 找到不再增长判据是脚本算的constbeforeseen.sizefor(consttoffound?.tests??[])seen.add(t)if(seen.sizebefore)break// 集合没长与 Agent 的说法无关前者是断言后者是对账。断言可以被糊弄模型可以吞掉一个报错、可以把警告读成通过集合的大小没法被它的说法改变。文档把两者并列成同样靠收敛条件可信度实际差一个量级。出口是软的校验发现问题之后没有回边六个例子里只有修到通过有真正的反馈边其余五个的校验都是过滤器。而失败有三种情形被混成了一种。校验跑通了、但说这条不对。这只是个返回值运行时不做任何事。官方那个脚本从头到尾没有分支filter(Boolean)只滤null、不看内容。默认归宿是丢弃内置调研工作流把没通过交叉核对的说法直接从报告里滤掉审计那条提示词也是复核不过就不上报。一个真实存在的问题如果对抗 Agent 成功把它辩掉了它就无声消失了报告里既没有它也没有发现过但驳回了的记录。对抗式复核在压低误报的同时制造了一类不可见的漏报。校验自己挂了agent()resolve 成null。这时没查出问题和根本没查成长得一模一样都是数组里一个null。v2.1.196 那次改动就是为这个内置调研工作流把无法核实的说法列为未验证而不是计为已推翻。这是三态里唯一被做进机制的一态而且只做在内置工作流里自己写的工作流没有这层保护。校验说不清。两态 schema 表达不了这一态模型只能被迫二选一。落地写法是三态枚举加null归位而且三样都进最终产物、别 filter 掉constcheckedawaitpipeline(findings,fagent(独立核实${f},{schema:{type:object,required:[verdict,reason],properties:{verdict:{enum:[confirmed,rejected,unverified]},reason:{type:string}}}}))constrowschecked.map((r,i)rnull?{finding:findings[i],verdict:unverified,reason:核实 Agent 未返回}:r)驳回项留在产物里人才能翻案未验证项单列故障就不会被读成通过。这个修法在 schema 层面不在提示词层面那次官方改版的实质也是这个。副作用那头更硬前面那行return null的后果在迁移上最重第二个 stage 挂了整项变null磁盘上的改动留下了、改动的记录没了父模型不知道这个文件到底改没改。而迁移是六个例子里唯一有大量不可逆副作用的一个。也没有重试原语源码里三处 catch 全是记日志加返回null没有retry、没有退避、没有失败的项攒起来再跑一轮。加上限制表那条运行中不接受用户输入只有 Agent 的权限提示能暂停运行脚本在校验发现问题时只能丢弃、重试、或带着判定继续没有停下来问一句这个选项。中段的担保也要打三处折父模型的上下文不进中间产物这条立得住二十份结果留在局部变量父模型只见最后那个返回值这是规模能从一轮派三五个上到单次上百个的前提。但瓶颈只是挪了位置挤不爆的是父模型不是收口那个 Agent500 个文件各出三条发现最后那个排序去重的 Agent 照样装不下。运行时给了 1000 个 Agent 的额度却没有任何分层归并机制想做 tree reduce 得脚本自己写。独立 Agent 对抗式复核买到的是去锚定不是统计独立。子会话不继承父历史、看不到兄弟状态去掉的是上下文锚定模型还是同一个模型同样的盲区、同样的先验。同源复核抓得住读同一段上下文导致的偏差抓不住这个模型本来就会这么错。确定性只覆盖编排结构。脚本每次都是重新生成的子 Agent 本身也不确定确定的只有一件事给定同一份脚本和同一批 Agent 输出派出的 Agent 序列一样。而这条恰好是恢复机制要的性质所以还得跟着重放规则再打一次折中断时仍在运行的 Agent 不保存恢复时从头开始重放按启动顺序进行缓存只到第一个未完成的 Agent 为止在它之后启动的每个 Agent 都要重跑即使已经完成。脚本依次启动 A、B、C、D你在 B 还在跑时停掉恢复后 A 命中缓存B、C、D 全部重跑。所以摊得碎更能保住进度要补一句同批耗时相近把一个慢 Agent 和一批快 Agent 放进同一个parallel慢的那个还没结束时中断后面所有已完成的短 Agent 全部作废先单独await长任务再 fan-out就只丢它自己。哪些活值得派给它先记住量级文档自己说单次运行的 token 消耗明显高于在对话里做同一件事排程超过 25 个 Agent 或预计 token 破 150 万就会弹出大型运行警告。也就是说每铺开一次成本都是几十上百个模型会话。最不划算的是找 flaky test。它要干的事是反复跑测试套件、记下哪些测试时而失败时而通过、连续两轮没有新发现就停pytest --count10跑一遍再写几行代码解析输出就完事了更快、更便宜还能挂进 CI 每天自动跑。更别提这里 Agent 根本没在判断新增没新增是脚本比对集合算出来的Agent 只负责帮我跑一下测试这个动作。花一个模型会话去执行一条命令就是前面那个 I/O 代理只是这次连找文件的借口都没有。审计缺失鉴权要看判据。如果这个路由有没有调鉴权中间件能写成一条匹配规则那 semgrep 或 eslint 规则更合适结果确定、每次一样、能挡在 CI 上不让代码合进来。Agent 铺开只在判据写不成规则时才值这个钱比如这个鉴权检查够不够严中间件调了但漏了某个角色这种要读懂上下文才知道。迁移同理能机械替换的部分交给 codemodAgent 处理 codemod 啃不动的那些判断。对得起这个价钱的是评审和调研判据本身需要读懂东西来源需要互相印证没有一个确定性工具能替。Bun 那次移植也属于这类只是规模大得多从 Zig 到 Rust 约 75 万行十一天现有测试套件 99.8% 通过这个量人力做不完codemod 也做不了。它的做法是四个工作流接力不是一个大工作流分四段第一个为每个 struct 字段推导 Rust lifetime第二个逐文件写出行为等价的.rs几百个 Agent 并行、每个文件配两个评审者第三个是修复循环驱动 build 和测试跑到干净移植落地后一个过夜工作流处理多余的数据拷贝每处开一个 PR 等人复核。最后那步没有直接改代码而是开 PR。这就是出口那一头的实际解法不在工作流里解决推到工作流外面交回给人。动态是过渡态这套运行时给你的是并发闸门、看得见的进度、随时能停、结构化输出校验、省掉进程启动的内存内会话、token 记账、位置式的重放缓存。这些都是管道活儿。同一件事你用 bash 加xargs -P 16循环调 CLI 也能跑起来跑不出来的是那份进度视图、那个取消信号和那套恢复缓存。所以它的收益在看得见、管得住不在做得更好。而需要模型的地方决定怎么切、每一项该跟 Agent 说什么只需要想对一次。这就是为什么产品化的终点是存成命令跑得满意的那份脚本落盘存成项目级或个人级命令用args传参还能打进插件分发。存下来的那一刻它已经是一张预先画好的流程图了跑之前可读、可 diff、可审计的好处全回来了前面那两处软的地方也终于有地方下手入口能接一份用find算出来的真实清单出口能接人工复核。它替你省掉的是手写一遍管道配置代价是第一次跑之前你没法审计它而且每次生成可能都不一样。被改掉的从来不是要不要有稳定流程而是流程从哪来不再是人坐下来先画一张而是从一次跑通的运行里捡出来。至于捡出来之后出错那条回边归谁固化成命令解决了入口出口还停在开个 PR 等人看谁来看、按什么标准判两份文档都没写。参考资料pi-dynamic-workflowshttps://github.com/michaelliv/pi-dynamic-workflows —— 实现细节出自该仓库源码。src/workflow.tsvm沙箱与 async 包装、注入的全局清单八个工作流函数 console/process/内置对象、acorn AST 门禁与确定性检查、meta 字面量校验、agent/parallel/pipeline三个函数的实现本文伪代码依此简化省略了 label 生成、options 归一化等细节、createLimiterFIFO 信号量与clamp并发取值、pendingAgentRuns与Promise.allSettled收尾、逐层 catch 与 null 降级、estimateTokens、structuredClone验收。src/workflow-tool.ts工具 schema、promptGuidelines十四条parallel 传函数不传 promise 的正反例、2–5 词唯一 label、汇总前检查 null、子 Agent 不继承父仓库上下文、phase 不预声明、只在用户明确要求 fan-out 时使用、以及「加一个最终汇总 Agent 返回带 ok/verdict 的紧凑 JSON」、agentCount 0断言、快照与取消处理。src/agent.tsin-memory 会话、输出契约文本、capture.called检查。src/structured-output.ts终止型结构化输出工具。isolation: worktree仅被拼入buildAgentInstructions的提示词文本无对应实现。「这是一个原型尚未实现持久化/可恢复运行与/workflows管理器」是该仓库 README 的自我标注。Claude Code 官方文档《Orchestrate subagents at scale with dynamic workflows》https://code.claude.com/docs/en/workflows —— 「谁持有计划」对比表、六种任务形状的结构说明与提示词、audit-routes示例脚本及agent()返回 null 的两种原因、16 并发与单次 1000 Agent 上限、「运行中不接受用户输入阶段之间要签字就拆成独立工作流」、两条重放规则与 A/B/C/D 例子、规模建议默认medium、25 个 Agent 与 150 万 token 预警、子 Agent 一律acceptEdits并继承白名单、白名单外的 shell / 网络 / MCP 工具仍会中途弹窗、claude -p与 Agent SDK 无人可问故不做交互确认、ultracode关键词只在人工输入的提示词里生效-p、定时任务、webhook 载荷、转达的 PR 评论都不触发v2.1.210 之前这些途径同样能触发、脚本落盘与 diff / 编辑 / 重跑、存为命令与args传参文档明写args可用于传入目标路径清单且以结构化数据传入、脚本无需解析、插件分发命名空间、/deep-research自 v2.1.196 起把无法核实的说法标为未验证而非已推翻均出自该页。Anthropic 产品公告《Introducing dynamic workflows in Claude Code》https://claude.com/blog/introducing-dynamic-workflows-in-claude-code —— Bun 从 Zig 移植到 Rust 的案例约 75 万行 Rust、十一天、测试套件 99.8% 通过、四段工作流接力、每个文件两个评审者、过夜工作流逐处开 PR出自该文。