<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>工作流 on 姚玉亮的网络日志</title><link>https://yusanwen-code.github.io/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/</link><description>Recent content in 工作流 on 姚玉亮的网络日志</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Sun, 30 Aug 2026 18:00:00 +0800</lastBuildDate><atom:link href="https://yusanwen-code.github.io/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/index.xml" rel="self" type="application/rss+xml"/><item><title>高低模型分工：用最少的 token 做最难的任务</title><link>https://yusanwen-code.github.io/posts/post-74/</link><pubDate>Sun, 30 Aug 2026 18:00:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-74/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-74-cover.jpg" alt="Featured image of post 高低模型分工：用最少的 token 做最难的任务" /&gt;&lt;h2 id="我最初的做法高模型出计划低模型执行"&gt;我最初的做法：高模型出计划，低模型执行&#10;&lt;/h2&gt;&lt;p&gt;在炼丹炉（&lt;a class="link" href="https://github.com/yusanwen-code/alchemy-furnace" target="_blank" rel="noopener"&#10; &gt;Alchemy Furnace&lt;/a&gt;，我的开源项目）上开发大功能时，我的模型分工一开始是这样的：高模型出计划，低模型去试执行。逻辑听起来无懈可击：计划是最费脑子的活，给最强模型；执行是照着做，给便宜模型省 token。&lt;/p&gt;&#10;&lt;p&gt;跑了几轮之后我意识到，这不是最佳实践：省下的 token 在别的地方加倍还了回去。坑在哪、后来怎么分，一条条说。&lt;/p&gt;&#10;&lt;h2 id="三个坑为什么计划高执行低不够"&gt;三个坑：为什么&amp;quot;计划高、执行低&amp;quot;不够&#10;&lt;/h2&gt;&lt;p&gt;坑一：计划里的任务难度不均。&lt;/p&gt;&#10;&lt;p&gt;一个实施计划拆出来的任务，难度天差地别。同一个计划里，既有&amp;quot;写一个纯函数渲染器&amp;quot;这种机械活，也有&amp;quot;设计 SSRF 校验 + 提供者熔断 + 证据等级判定&amp;quot;这种高难活。一刀切全丢给低模型，难任务在低模型手里反复试错，每次失败都是一轮&amp;quot;试错 + 看错误 + 再试&amp;quot;，token 没省多少，时间翻倍，最后往往还是得升级。&lt;/p&gt;&#10;&lt;p&gt;坑二：低模型失败没有出口。&lt;/p&gt;&#10;&lt;p&gt;低模型试了，失败了，然后呢？如果升级路径不存在，低模型只有两个选项：硬编一个成功（最危险，测试都可能一起编出来），或者卡死空转。我早期踩过前者，代价是返工整个文件。&lt;/p&gt;&#10;&lt;p&gt;坑三：验证被一起降级了。&lt;/p&gt;&#10;&lt;p&gt;执行降级了，自测也跟着降级：低模型自己测自己，绿灯也不能全信。判断性工作（&amp;ldquo;这样测算不算数&amp;rdquo;）和机械性工作（&amp;ldquo;跑一遍测试&amp;rdquo;）不是一回事，不能一起降级。&lt;/p&gt;&#10;&lt;p&gt;模型分级的问题不在&amp;quot;分级&amp;quot;本身，而在分级的依据：按角色分（计划/执行）是错的，按难度分（任务）才对。&lt;/p&gt;&#10;&lt;h2 id="正确框架按难度分级--三个旋钮"&gt;正确框架：按难度分级 + 三个旋钮&#10;&lt;/h2&gt;&lt;p&gt;Claude Code 的 Workflow 里每个子 Agent 可以独立指定模型档位和努力度，这就是三个旋钮：&lt;/p&gt;&#10;&lt;p&gt;旋钮一：模型档位。高（Opus）/ 中（Sonnet）/ 低（Haiku）。&lt;/p&gt;&#10;&lt;p&gt;旋钮二：努力度（effort）。从 low 到 max。很多任务根本不用换模型：同一个模型把 reasoning 预算拧低，就是廉价版本。换模型是换&amp;quot;能力上限&amp;quot;，调 effort 是调&amp;quot;用力程度&amp;quot;，两回事。&lt;/p&gt;&#10;&lt;p&gt;旋钮三：任务边界清晰度。这是最便宜的杠杆：spec 拆得越细、验收标准写得越死，任务越接近&amp;quot;机械执行&amp;quot;，能用的模型档位就越低。&lt;/p&gt;&#10;&lt;p&gt;我的任务分配表：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;难度&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;模型&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;例子&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;判断型&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;高模型 + 高 effort&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;需求探索与脑暴、接口契约设计、代码评审、根因定位、安全核对&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;常规型&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;中模型 + 中 effort&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;常规功能实现、测试编写、中等重构、普通调试&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;机械型&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;低模型 + low effort&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模板化改动、纯函数实现、i18n 文案、跑测试与验证&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;img alt="按难度分级的三个模型档位与三个旋钮" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://yusanwen-code.github.io/images/post-74-tiers.svg"&gt;&lt;/p&gt;&#10;&lt;h2 id="用最少的-token-做最难的任务"&gt;用最少的 token 做最难的任务&#10;&lt;/h2&gt;&lt;p&gt;说白了就一条：把大部分任务变简单，让高模型 token 只花在&amp;quot;决定&amp;quot;上，不花在&amp;quot;执行&amp;quot;上。&lt;/p&gt;&#10;&lt;p&gt;几个真实管用的杠杆：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;计划是高模型性价比最高的投资。计划阶段多花的高模型 token，换来的是执行阶段任务边界清晰、可降级给便宜模型。这是&amp;quot;1 单位的贵，换 100 单位的省&amp;quot;。不是精确数字，是量级感受。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;低模型任务必须满足三个条件，缺一不可：&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;边界明确：任务描述到文件级，依赖关系写清&lt;/li&gt;&#10;&lt;li&gt;测试兜底：TDD 红→绿，失败能被测试抓住&lt;/li&gt;&#10;&lt;li&gt;终止条件：做不了就停，明确写&amp;quot;连续失败就停止报告，不许编成功&amp;quot;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;第三条最重要。我在炼丹炉的 Workflow 脚本里给每个子 Agent 都注入了终止条件段（A3：文件不存在、依赖无法确认、连续 3 次修复失败，立即停止报告）。允许低模型说&amp;quot;我卡住了&amp;quot;，比让它硬编一个成功安全一个量级。&lt;/p&gt;&#10;&lt;ol start="3"&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;升级路径要显式。低模型失败不是终点：带着&amp;quot;红测试 + 尝试过程 + 卡点描述&amp;quot;升级给高模型。高模型拿到的是完整上下文，一次到位给解法或改计划，而不是重新摸底。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;评审和验证分开降级。跑回归（机械）可以低模型；安全核对、代码评审（判断）留给高模型。炼丹炉的验收任务里，回归和&amp;quot;ZIP 解压检查有没有泄露 key&amp;quot;是两件事：前者低模型跑，后者高模型过。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="炼丹炉实例一个计划怎么分"&gt;炼丹炉实例：一个计划怎么分&#10;&lt;/h2&gt;&lt;p&gt;以女娲蒸馏 + Skill 导出的开发为例（6 个任务、4 个波次）：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;计划本身（文件级任务清单）：高模型出。这是唯一一个&amp;quot;必须先花&amp;quot;的成本。&lt;/li&gt;&#10;&lt;li&gt;Task3 Skill 渲染器：纯函数、格式明确（slug 规则、SKILL.md 结构、ZIP 命名全写死）、测试完备，典型的机械型任务，低模型 + low effort 就能扛。&lt;/li&gt;&#10;&lt;li&gt;Task2 Python 蒸馏链路：SSRF 校验、来源熔断、证据等级判定，属于判断与工程混合，配中模型 + 中高 effort，比低模型反复试错便宜得多。&lt;/li&gt;&#10;&lt;li&gt;Task6 端到端验收：回归跑测低模型，产物安全核对（解压查 sk-、token、内部日志）高模型。&lt;/li&gt;&#10;&lt;li&gt;A3 终止条件：给每个&amp;quot;低模型试&amp;quot;的任务一个明确出口，试不动就停，升级。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="收尾"&gt;收尾&#10;&lt;/h2&gt;&lt;p&gt;用下来我的感觉是：判断性的活给最贵的模型，机械的活给最便宜的；任务写得清就可以降档，失败超过两次就升级，别耗着。&lt;/p&gt;&#10;&lt;p&gt;模型分级省的不是高模型的钱，是浪费在错模型上的钱。最难的活依然要最贵的模型，但反过来也成立：大部分活之所以难，是因为你没把它写清楚。把任务写清楚，是高模型 token 花得最值的地方。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://commons.wikimedia.org/wiki/File:Clock_gears_in_the_St_Maximus_church_in_Magnac-Laval_02.jpg" target="_blank" rel="noopener"&#10; &gt;Krzysztof Golik / Wikimedia Commons&lt;/a&gt; · CC BY-SA 4.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>Claude Code 的 Workflow 功能：6 任务分 4 波，多 Agent 并行开发三端功能</title><link>https://yusanwen-code.github.io/posts/post-73/</link><pubDate>Thu, 27 Aug 2026 22:30:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-73/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-73-cover.jpg" alt="Featured image of post Claude Code 的 Workflow 功能：6 任务分 4 波，多 Agent 并行开发三端功能" /&gt;&lt;h2 id="一个-141-行的脚本指挥一群-ai-干活"&gt;一个 141 行的脚本，指挥一群 AI 干活&#10;&lt;/h2&gt;&lt;p&gt;炼丹炉（&lt;a class="link" href="https://github.com/yusanwen-code/alchemy-furnace" target="_blank" rel="noopener"&#10; &gt;Alchemy Furnace&lt;/a&gt;，我的开源项目）最近在做一个大功能：女娲蒸馏的范围收紧 + Skill 导出。它横跨三个端（Next.js 前端、Go 网关、Python 引擎），拆出来 6 个开发任务，有依赖、有先后、还要并行。&lt;/p&gt;&#10;&lt;p&gt;我用的方案是 Claude Code 的 Workflow 功能：写一个 141 行的 mjs 脚本，把 6 个任务分成 4 个波次，每波派多个 AI Agent 并行干活。脚本开头长这样：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;export&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;const&lt;/span&gt; meta &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;nuwa-scope-skill-export&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; description&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;按计划实现女娲炼丹范围收紧 + Skill 导出（6 Task 分波并行）&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; phases&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { title&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Wave A: 并行底座&amp;#39;&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Task1 前端入口 + Task2 Python 链路 + Task3 Skill 渲染器&amp;#39;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { title&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Wave B: 服务端接合&amp;#39;&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Task2b Go/前端错误透传 + Task4 导出接口&amp;#39;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { title&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Wave C: 前端导出 UI&amp;#39;&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Task5 详情导出对话框&amp;#39;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { title&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Wave D: 端到端验收&amp;#39;&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Task6 全量回归与修复&amp;#39;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Workflow 的编排原语就几个：&lt;code&gt;agent()&lt;/code&gt; 派一个 Agent 做一件事，&lt;code&gt;parallel()&lt;/code&gt; 并行跑一组，&lt;code&gt;pipeline()&lt;/code&gt; 流水线，&lt;code&gt;phase()&lt;/code&gt; 分阶段。流程是确定性的（脚本写死的），干活是并行的（agent 同时跑）：确定性编排 + 并行吞吐，这就是 Workflow 和&amp;quot;让 AI 自由发挥&amp;quot;最大的区别。&lt;/p&gt;&#10;&lt;h2 id="分波设计依赖关系决定顺序"&gt;分波设计：依赖关系决定顺序&#10;&lt;/h2&gt;&lt;p&gt;6 个任务不是一把梭全并行，它们之间有依赖。分波的核心原则：同一波内的任务互不依赖且文件隔离，波与波之间靠产物衔接。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Claude Code Workflow：6 Task × 4 波次并行编排" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://yusanwen-code.github.io/images/post-73-waves.svg"&gt;&lt;/p&gt;&#10;&lt;p&gt;Wave A 三个任务互不依赖：前端改入口、Python 改链路、Python 写渲染器，改动文件完全不重叠，并行跑。Wave B 接合服务端：错误透传要读 Task2 的产物（Python 结构化错误），导出接口要调 Task3 的渲染器，等 A 波完成再动。C、D 依此类推。&lt;/p&gt;&#10;&lt;h2 id="多-agent-协调的六个细节"&gt;多 Agent 协调的六个细节&#10;&lt;/h2&gt;&lt;p&gt;分波只是骨架，真正让 6 个 Agent 不打架的是这些细节：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;COMMON 纪律注入。每个 Agent 的 prompt 都是 &lt;code&gt;COMMON + TASK + 终止条件&lt;/code&gt; 三段拼起来的。COMMON 里写死硬约束：TDD 先红后绿、只改自己任务列出的文件、提交格式 &lt;code&gt;type(scope): 中文描述&lt;/code&gt;、绝不 &lt;code&gt;git add -A&lt;/code&gt;、绝不提交 docs/superpowers/ 和 specs/ 等元文档、i18n 必须中英双语、测试命令、凭据保密。纪律写进 prompt 而不是靠自觉，6 个 Agent 才不会互相污染。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;文件隔离 + 以磁盘为准。每个任务只允许碰自己的文件，但并发任务可能改到同一文件，所以约束里有一条：&amp;ldquo;编辑前先 Read 磁盘最新内容，不要基于记忆&amp;rdquo;。并行和踩脚之间的平衡，靠这条纪律兜底。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;依赖确认靠 git log。Wave B/C 的任务 prompt 里写着：&amp;ldquo;先 git log 确认依赖任务已完成，并读它的接口签名再动手&amp;rdquo;。Agent 之间不通信，通过提交记录对齐。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;终止条件。每个任务都附一段 A3：文件不存在或结构差异巨大、关键依赖无法确认、测试连续 3 次修复失败，立即停止并报告，不许硬撑。这防止 Agent 在不确定的情况下编造&amp;quot;成功&amp;quot;。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;TDD 是硬约束。每个任务第一步都是&amp;quot;写失败测试 → 跑红 → 最小实现 → 跑绿&amp;quot;。6 个 Agent 并行写代码，质量一致性靠测试框架兜底。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;进度可视化。每波完成打一条 &lt;code&gt;log()&lt;/code&gt;：&lt;code&gt;task1=ok task2=FAILED&lt;/code&gt;，失败的任务肉眼可见，可以在下一波前人工干预。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="三端是怎么被协调的"&gt;三端是怎么被协调的&#10;&lt;/h2&gt;&lt;p&gt;这个功能本身横跨三端，Workflow 的波次刚好和端的分工对齐：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;端&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;技术栈&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;对应的任务&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;前端&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Next.js&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Task1 收紧女娲入口、Task5 导出对话框&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go 网关&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Gin + GORM&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Task2b 错误透传、Task4 导出接口&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Python 引擎&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FastAPI&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Task2 蒸馏链路、Task3 Skill 渲染器&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;有意思的是 Python 端同时有 Task2 和 Task3 两个并行 Agent：它们文件不同（链路 vs 渲染器）所以不冲突，但同端并行也要求 prompt 里把边界写得特别清楚：&amp;ldquo;不要碰 backend/go/ 与 frontend/ 的 UI 文件——那些由另一任务负责&amp;rdquo;。&lt;/p&gt;&#10;&lt;h2 id="复盘workflow-教给我的"&gt;复盘：Workflow 教给我的&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;复杂功能先拆波再并行，依赖决定顺序。先画出任务依赖图，互不依赖的并行，有依赖的排波次。比&amp;quot;一个 Agent 从头做到尾&amp;quot;快得多，比&amp;quot;全并行&amp;quot;稳得多。&lt;/li&gt;&#10;&lt;li&gt;纪律要写进 prompt，不是靠提醒。提交纪律、文件边界、TDD、终止条件，每一条都防止一种 Agent 翻车方式。&lt;/li&gt;&#10;&lt;li&gt;终止条件比任务本身重要。允许 Agent 说&amp;quot;我卡住了&amp;quot;，比让它硬编一个成功报告安全一个量级。&lt;/li&gt;&#10;&lt;li&gt;上下文隔离是并行的前提。每个 Agent 只拿自己的任务片段，看不到其他任务的 prompt：专注 + 不越界。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这个脚本不是银弹：它适合任务边界清晰、文件可隔离、有测试兜底的场景。如果任务互相纠缠，先拆任务再谈并行。项目、计划文档和完整脚本都在 &lt;a class="link" href="https://github.com/yusanwen-code/alchemy-furnace" target="_blank" rel="noopener"&#10; &gt;GitHub&lt;/a&gt; 上，Wave A 的并行底座写法可以直接抄。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://www.flickr.com/photos/richard_clyborne/32874464137" target="_blank" rel="noopener"&#10; &gt;richard_clyborne / Flickr&lt;/a&gt; · CC BY 2.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>复杂任务的快速实现：我的 Claude Code 工作流</title><link>https://yusanwen-code.github.io/posts/post-72/</link><pubDate>Thu, 27 Aug 2026 21:00:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-72/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-72-cover.jpg" alt="Featured image of post 复杂任务的快速实现：我的 Claude Code 工作流" /&gt;&lt;h2 id="复杂任务为什么难"&gt;复杂任务为什么难&#10;&lt;/h2&gt;&lt;p&gt;先说结论：复杂任务难的不是写代码，是下面四件事。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;需求模糊：说需求的人自己都没想清楚。&amp;ldquo;文档核验要留痕&amp;rdquo;，留什么痕？按什么查？入口放哪？全要靠推。&lt;/li&gt;&#10;&lt;li&gt;代码陌生：改的不是自己写的代码库，路由注册在哪、DAO 怎么组织、有没有现成的组件，一无所知。&lt;/li&gt;&#10;&lt;li&gt;链路长：一个需求横跨后端服务、管理后台、上层服务三个仓库，联调对不上是常态。&lt;/li&gt;&#10;&lt;li&gt;步骤多：建表、写记录、加接口、做页面、上层同步，十几步里漏掉任何一步，都要返工。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;过去我处理复杂任务靠&amp;quot;经验 + 加班&amp;quot;：经验让我少踩坑，加班补上流程损耗。现在我用 Claude Code 的 workflow 跑，发现上面四类复杂度可以分别拆掉。&lt;/p&gt;&#10;&lt;h2 id="一需求模糊先探索再一次一个问题"&gt;一、需求模糊？先探索，再一次一个问题&#10;&lt;/h2&gt;&lt;p&gt;需求模糊是复杂任务的第一个坑，也是最贵的坑：设计错了，后面全白做。&lt;/p&gt;&#10;&lt;p&gt;我的做法是两段式：先让 AI 探索代码，再让它一次只问一个问题。&lt;/p&gt;&#10;&lt;p&gt;拿最近一个&amp;quot;核验记录&amp;quot;需求举例。AI 先翻了两个仓库，带回几个关键事实：文档表上已经有&amp;quot;核验状态&amp;quot;字段，核验逻辑集中在文档服务的一个方法里，前端代码里已经有现成的下拉加载 hook。这些事实直接改变了设计：记录表和状态字段是两回事，但很多人第一版会把它们混在一起。&lt;/p&gt;&#10;&lt;p&gt;然后进入提问环节，一次一个问题：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;我：核验记录存哪些字段？&#10;AI：除了核验人和核验时间，要不要存版本号？这决定表结构。&#10;我：先只存核验人和核验时间。&#10;AI：好。那列表按数据集 ID 过滤、分页返回，对吗？&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;每个答案都在修正下一个问题。一次抛十个问题，AI 只能靠猜排优先级，猜错一半就得返工。模糊需求最怕的不是问得少，而是问得乱。&lt;/p&gt;&#10;&lt;h2 id="二代码陌生子代理并行探索"&gt;二、代码陌生？子代理并行探索&#10;&lt;/h2&gt;&lt;p&gt;复杂任务往往意味着大型代码库。让主会话把每个文件都读一遍，上下文直接爆炸；不让它读，问的问题全是空对空。&lt;/p&gt;&#10;&lt;p&gt;我用子代理解决：只读的探索型子代理，各自负责一个方向，跑完只把结论带回来。&lt;/p&gt;&#10;&lt;p&gt;还是那个需求。两个仓库，我开了两个子代理并行：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;一个查后端：核验逻辑在哪个方法里？用什么 ORM？表结构长什么样？&lt;/li&gt;&#10;&lt;li&gt;一个查前端：数据管理页面结构？有没有现成的分页/下拉加载组件？&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;主会话的上下文只收到两条结论，干净利落。复杂任务拆给子代理，等于给主会话外挂了好几个只读大脑。&lt;/p&gt;&#10;&lt;h2 id="三步骤多用-spec-锚定用计划兜底"&gt;三、步骤多？用 spec 锚定，用计划兜底&#10;&lt;/h2&gt;&lt;p&gt;十几步的任务，光靠&amp;quot;记住&amp;quot;一定会漏。我的流程里有两个强制环节：&lt;/p&gt;&#10;&lt;p&gt;第一步，设计获批后写 spec 文档。表结构、接口定义、前端改动点、上层同步点全部落盘。这份文档是后续所有工作的锚点：代码评审不再对着记忆逐条想，而是对着 spec 逐条核对。&lt;/p&gt;&#10;&lt;p&gt;第二步，用计划技能把设计翻译成有序任务。先改什么、后改什么、每个任务做完怎么验证，都拆成文件级清单。执行阶段照着走，不会顾此失彼。&lt;/p&gt;&#10;&lt;p&gt;复杂任务最怕的不是步子慢，而是做完了发现做的是另一个需求。spec 就是防这件事的。&lt;/p&gt;&#10;&lt;h2 id="四链路长add-dir-多项目一个会话挂三个仓库"&gt;四、链路长？add-dir 多项目，一个会话挂三个仓库&#10;&lt;/h2&gt;&lt;p&gt;跨仓库任务的真正成本不在写代码，在于两边对不上：前端叫 &lt;code&gt;page&lt;/code&gt; 后端叫 &lt;code&gt;current&lt;/code&gt;，接口路径差一个斜杠，联调半天。&lt;/p&gt;&#10;&lt;p&gt;Claude Code 的 add-dir 可以把多个项目目录加进同一个会话，三个仓库的上下文同时可见。字段名、接口路径、分页参数在计划阶段就定死，代码照着写就行，联调成本趋近于零。&lt;/p&gt;&#10;&lt;p&gt;以前这种需求：拉群 → 各自开工 → 联调发现对不上 → 返工。现在：一个会话、一份 spec、三份代码同时改。&lt;/p&gt;&#10;&lt;h2 id="五改错返工hooks-门禁--验证闭环"&gt;五、改错返工？hooks 门禁 + 验证闭环&#10;&lt;/h2&gt;&lt;p&gt;复杂任务最容易在最后一步翻车：改完了、自测过了，忘了检查脱敏、忘了跑构建、忘了验证线上。&lt;/p&gt;&#10;&lt;p&gt;我的解法是 hooks：把检查前置到事件本身。比如写文件前必须声明事实（这个文件被谁引用、里面有没有敏感内容），执行破坏性命令前必须给出回滚方案。检查不是&amp;quot;记得就做&amp;quot;，而是流程的必经环节，不做就卡住。&lt;/p&gt;&#10;&lt;p&gt;收尾阶段再加一道验证：构建无错误、线上返回 200、关键内容比对通过，才算完成。质量不是最后检查出来的，是流程逼出来的。&lt;/p&gt;&#10;&lt;h2 id="我的工作流时间线"&gt;我的工作流时间线&#10;&lt;/h2&gt;&lt;p&gt;一个复杂任务在我这里是这样的节奏（示意）：&lt;/p&gt;&#10;&#10;&lt;pre class="mermaid"&gt;&#10;flowchart LR&#10; T1[&amp;#34;探索 + 脑暴&amp;#34;] --&amp;gt; T2[&amp;#34;方案 + spec&amp;#34;] --&amp;gt; T3[&amp;#34;实施计划&amp;#34;] --&amp;gt; T4[&amp;#34;执行三仓&amp;#34;] --&amp;gt; T5[&amp;#34;评审 · 验证 · 部署&amp;#34;]&#10;&lt;/pre&gt;&#10;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;环节&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;干什么&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;用时&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;探索 + 脑暴&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;读代码、一次一个问题澄清需求&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一个上午&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;方案 + spec&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2-3 个方案对比，获批后落盘设计文档&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;半小时&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;实施计划&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;设计翻译成文件级任务清单&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;十分钟&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;执行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;按清单改三个仓库&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;主工作量&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;评审 + 验证 + 部署&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;对着 spec 评审，构建、线上验证&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;收尾&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;对比以前的节奏：需求评审会一轮、文档两轮、联调两轮、返工一轮，光流程损耗就吃掉一半时间。&lt;/p&gt;&#10;&lt;h2 id="收尾"&gt;收尾&#10;&lt;/h2&gt;&lt;p&gt;复杂任务的复杂度分布大概是这样：需求 &amp;gt; 链路 &amp;gt; 步骤 &amp;gt; 代码。Claude Code 的 workflow 没有任何魔法，它只是把&amp;quot;该问的、该查的、该记的、该验的&amp;quot;变成了流程的默认动作，让 AI 和人的精力都集中在真正难的地方。&lt;/p&gt;&#10;&lt;p&gt;真正快的不是写代码那一下，而是不返工。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://www.flickr.com/photos/jurvetson/6858583426" target="_blank" rel="noopener"&#10; &gt;jurvetson / Flickr&lt;/a&gt; · CC BY 2.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>一次聊需求，三仓同落地：我的 superpowers 全栈开发工作流</title><link>https://yusanwen-code.github.io/posts/post-71/</link><pubDate>Thu, 27 Aug 2026 09:00:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-71/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-71-cover.jpg" alt="Featured image of post 一次聊需求，三仓同落地：我的 superpowers 全栈开发工作流" /&gt;&lt;h2 id="一个跨三个仓库的需求"&gt;一个跨三个仓库的需求&#10;&lt;/h2&gt;&lt;p&gt;我最近接到一个挺典型的需求：数据集管理里，&amp;ldquo;文档核验&amp;quot;这个动作要留痕：谁核验的、什么时候核验的；核验完还要能按数据集分页翻历史记录；管理后台加一个&amp;quot;核验记录&amp;quot;入口，点击弹列表，下拉就能一直加载；上层的知识库问答服务也要同步暴露这个列表接口。&lt;/p&gt;&#10;&lt;p&gt;这个需求横跨三个仓库：数据集管理服务、数据管理后台、知识库问答服务。&lt;/p&gt;&#10;&lt;p&gt;以前这种需求在我这儿是这么过的：拉群对齐需求 → 各写各的需求文档 → 前后端各自开工 → 联调时发现字段名对不上、分页参数叫法不一样 → 返工。现在我用 Claude Code 的 superpowers 工作流走了一遍完整流程，发现整个链条可以压缩成四步：brainstorming 聊需求 → 方案对比 → 写 spec → 生成实施计划。&lt;/p&gt;&#10;&#10;&lt;pre class="mermaid"&gt;&#10;flowchart LR&#10; A[&amp;#34;brainstorming 聊需求&amp;#34;] --&amp;gt; B[&amp;#34;方案对比&amp;#34;] --&amp;gt; C[&amp;#34;写 spec 落盘&amp;#34;] --&amp;gt; D[&amp;#34;生成实施计划&amp;#34;] --&amp;gt; E[&amp;#34;add-dir 挂三仓并行落地&amp;#34;]&#10;&lt;/pre&gt;&#10;&#10;&#10;&lt;style&gt;&#10;&#9;.mermaid {&#10;&#9;&#9;background: #ffffff;&#10;&#9;&#9;border: 1px solid #e5e7eb;&#10;&#9;&#9;border-radius: 10px;&#10;&#9;&#9;padding: 1rem;&#10;&#9;&#9;text-align: center;&#10;&#9;&#9;overflow-x: auto;&#10;&#9;}&#10;&#9;.mermaid svg {&#10;&#9;&#9;max-width: 100%;&#10;&#9;&#9;height: auto;&#10;&#9;}&#10;&lt;/style&gt;&#10;&lt;script src="https://yusanwen-code.github.io/js/mermaid.min.js" defer&gt;&lt;/script&gt;&#10;&lt;script&gt;&#10;&#9;document.addEventListener("DOMContentLoaded", function () {&#10;&#9;&#9;if (window.mermaid) {&#10;&#9;&#9;&#9;mermaid.initialize({ startOnLoad: true, theme: "default", flowchart: { curve: "basis" } });&#10;&#9;&#9;}&#10;&#9;});&#10;&lt;/script&gt;&#10;&#10;&lt;h2 id="第一步先探索代码再开口问"&gt;第一步：先探索代码，再开口问&#10;&lt;/h2&gt;&lt;p&gt;superpowers 的 brainstorming 第一步不是问&amp;quot;你想要什么&amp;rdquo;，而是先看项目上下文：路由注册在哪、handler 怎么写的、DAO 用的什么 ORM、前端页面结构、有没有现成的分页组件。&lt;/p&gt;&#10;&lt;p&gt;我让 Claude 把两个仓库都翻了一遍，结论：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;数据集管理服务是 Hertz + GORM 的架构，核验逻辑在文档服务的 revise 方法里，文档表上已经有一个&amp;quot;核验状态&amp;quot;字段，核验时会把它置为&amp;quot;已核验&amp;quot;&lt;/li&gt;&#10;&lt;li&gt;管理后台是 React + antd，代码里已经有现成的 &lt;code&gt;useInfiniteScroll&lt;/code&gt; 下拉加载 hook，数据详情页结构也很清晰&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这一步的价值在于：问题基于真实代码，而不是基于想象。我不用花十分钟给 Claude 讲架构，它自己读出来的上下文，比任何人的转述都准确。比如&amp;quot;核验状态字段已经存在&amp;quot;这个事实，直接影响了后面的设计：记录表和状态字段是两回事，但很多人第一版设计会混在一起。&lt;/p&gt;&#10;&lt;h2 id="第二步一次只问一个问题"&gt;第二步：一次只问一个问题&#10;&lt;/h2&gt;&lt;p&gt;brainstorming 最反直觉的一条规则是：一次只问一个问题。&lt;/p&gt;&#10;&lt;p&gt;我一开始也觉得慢。但跑下来发现，需求是逐层展开的：先问&amp;quot;核验记录需要存哪些字段&amp;quot;，确定存核验人和核验时间；再问&amp;quot;列表按什么过滤&amp;quot;，确定按数据集 ID；再问&amp;quot;前端入口放在哪&amp;quot;，确定放数据详情页。每个答案都会修正下一个问题，一次抛十个问题，模型只能靠猜排优先级，猜错一半就得返工。&lt;/p&gt;&#10;&lt;p&gt;对话节奏大概是这样的（简化示意，实际要比这细致）：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;我：核验记录需要展示哪些字段？&#10;Claude：先确认一下，除了核验人和核验时间，要不要把核验前后的版本号也存下来？这会决定表结构。&#10;我：先只存核验人和核验时间，跑通第一版。&#10;Claude：好。那列表接口按数据集 ID 过滤、支持分页返回，这个方向对吗？&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;一次一个问题看着慢，其实最快：每个问题都建立在之前答案的基础上，方向不会歪。&lt;/p&gt;&#10;&lt;h2 id="第三步给-2-3-个方案而不是直接开写"&gt;第三步：给 2-3 个方案，而不是直接开写&#10;&lt;/h2&gt;&lt;p&gt;需求聊清楚之后，brainstorming 会要求给出 2-3 个实现方案对比。这个需求当时有两个主流方案：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;方案 A：复用现有的行为记录表。那张表是 JSON 半结构化的流水表，加一种行为类型就能记录，不用建新表。&lt;/li&gt;&#10;&lt;li&gt;方案 B：新建独立的核验记录表。字段干净，查询直接，列表接口就是一条简单的 &lt;code&gt;WHERE dataset_id = ?&lt;/code&gt; 分页查询。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;我选了 B。核验记录是高频查询的业务动作，独立表让列表接口简单直接；方案 A 虽然少建一张表，但查列表要拆 JSON，而且行为流水表的数据量会持续膨胀，不适合做业务查询的底座。&lt;/p&gt;&#10;&lt;p&gt;选完方案，设计按块确认：表结构一块、接口定义一块、前端改动一块、上层同步一块，确认完再写进 spec 文档，落盘到 &lt;code&gt;docs/superpowers/specs/&lt;/code&gt; 目录下。这份文档是后续实施计划的输入，也是代码评审时对照的依据：评审不再靠记忆，而是对着 spec 逐条核对。&lt;/p&gt;&#10;&lt;h2 id="第四步add-dir-多项目前后端同时动"&gt;第四步：add-dir 多项目，前后端同时动&#10;&lt;/h2&gt;&lt;p&gt;设计获批后进入实施阶段。Claude Code 的 add-dir 可以把多个项目目录加进同一个会话，这个需求一次挂了三个目录：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;数据集管理服务：建表、在核验接口里写记录、新增分页列表接口&lt;/li&gt;&#10;&lt;li&gt;管理后台：数据详情页加入口、列表抽屉、下拉加载&lt;/li&gt;&#10;&lt;li&gt;知识库问答服务：同步暴露列表接口&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;多目录的意义不只是&amp;quot;能改多个仓库&amp;quot;，而是 AI 同时持有两边的上下文：字段名、接口路径、分页参数天然一致，联调成本趋近于零。以前前后端联调要查半天&amp;quot;到底叫 &lt;code&gt;page&lt;/code&gt; 还是 &lt;code&gt;current&lt;/code&gt;&amp;quot;，现在在计划阶段就把契约定死了，代码照着写就行。&lt;/p&gt;&#10;&lt;h2 id="几个真实的感受"&gt;几个真实的感受&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;一次一个问题反直觉，但确实是最快的提问方式。需求是逐层展开的，一次一个才能保证每个问题都有价值。&lt;/li&gt;&#10;&lt;li&gt;探索比提问重要。让 AI 先读代码，它问出来的问题才有质量；不探索就问，问的都是网上搜得到的通用问题。&lt;/li&gt;&#10;&lt;li&gt;设计文档不是走流程。跨仓库改动里，它是唯一让所有人（包括几周后的你自己）对齐的锚点。代码评审对着 spec 审，比对着记忆审靠谱一个量级。&lt;/li&gt;&#10;&lt;li&gt;YAGNI。AI 很容易过度设计，比如顺手提一句&amp;quot;要不要顺便做个操作审计&amp;quot;。不需要，砍掉。每个设计阶段都要主动问：这个功能现在真的需要吗？&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="建议"&gt;建议&#10;&lt;/h2&gt;&lt;p&gt;如果你也在用 AI 写代码，别只停留在&amp;quot;让它写函数&amp;quot;的层面。试试把流程也交给它：从一个小需求开始，完整走一遍 brainstorming → 方案对比 → spec → 计划 → 实现。第一次会不习惯&amp;quot;一次只问一个问题&amp;quot;，但跑完一个需求你就会发现，多花在澄清上的半小时，会在联调阶段加倍省回来。&lt;/p&gt;&#10;</description></item><item><title>智能体工作流编排：从知识库问答到多步任务</title><link>https://yusanwen-code.github.io/posts/post-65/</link><pubDate>Tue, 02 Jun 2026 10:30:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-65/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-65-cover.jpg" alt="Featured image of post 智能体工作流编排：从知识库问答到多步任务" /&gt;&lt;h2 id="单轮-rag-接不住了"&gt;单轮 RAG 接不住了&#10;&lt;/h2&gt;&lt;p&gt;知识库问答服务上线初期只有一条链路：用户提问 → 检索 → 拼 Prompt → 调 LLM → 返回答案。简单可控，但很快被顶到了天花板。&lt;/p&gt;&#10;&lt;p&gt;有人问&amp;quot;帮我查一下上个月引用量最高的三篇文献，对比它们的方法，再导出成 Word&amp;quot;。这一句话里藏着检索、排序、对比分析、文档生成四步，单轮 RAG 根本接不住。&lt;/p&gt;&#10;&lt;p&gt;我当时的判断是两头都不能走极端：全交给 ReAct 让模型自由发挥，可控性和成本都扛不住；全硬编码成固定 DAG，又失去了灵活性。最后落在一个折中上：工作流骨架 + Agent 节点。&lt;/p&gt;&#10;&lt;h2 id="一张图两类节点"&gt;一张图，两类节点&#10;&lt;/h2&gt;&lt;p&gt;我把任务拆成两类节点：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;确定性节点：检索、SQL 查询、文件解析、导出，这些用代码写死，输入输出明确&lt;/li&gt;&#10;&lt;li&gt;Agent 节点：需要推理、选择、总结的环节，交给 LLM 决定下一步&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;节点之间用有向图描述，允许条件分支和循环，运行时由一个轻量编排器驱动。每一步的状态写进统一的 &lt;code&gt;WorkflowState&lt;/code&gt;，节点之间不直接耦合。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="工作流骨架 + Agent 节点的混合编排" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://yusanwen-code.github.io/images/post-65-agent-workflow.svg"&gt;&lt;/p&gt;&#10;&lt;p&gt;核心结构就这么多：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;18&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;19&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;20&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;21&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;22&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;23&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;24&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;25&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;26&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;27&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;28&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;29&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;30&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;31&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;32&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;33&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;34&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;35&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;36&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;37&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;38&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;39&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;40&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;41&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;42&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;43&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;44&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;45&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;WorkflowState&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Query&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Documents&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]Document&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Draft&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Artifact&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Trace&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]StepRecord&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;mu&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sync.Mutex&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Node&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;interface&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Name&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Run&lt;/span&gt;(ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;context.Context,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;st&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;WorkflowState)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Orchestrator&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;nodes&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;map&lt;/span&gt;[&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;]Node&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;edges&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;map&lt;/span&gt;[&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;][]Edge&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// from -&amp;gt; edges&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Edge&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;To&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Cond&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;(st&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;WorkflowState)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;bool&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// nil 表示无条件&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(o&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;Orchestrator)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Run&lt;/span&gt;(ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;context.Context,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;start&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;st&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;WorkflowState)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;{start}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;visited&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;map&lt;/span&gt;[&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;]&lt;span style="color:#ff7b72"&gt;int&lt;/span&gt;{}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;for&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;len(queue)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&amp;gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;name&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue[&lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;]&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue[&lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;:]&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;visited[name]&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;5&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 防止死循环&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;fmt.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Errorf&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;node %s exceeded max retries&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;name)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;visited[name]&lt;span style="color:#ff7b72;font-weight:bold"&gt;++&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;o.nodes[name].&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Run&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;st);&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;for&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;_,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;e&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;range&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;o.edges[name]&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;e.Cond&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;==&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;||&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;e.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Cond&lt;/span&gt;(st)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;append(queue,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;e.To)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Agent 节点内部走 ReAct 循环：模型输出思考 + 工具调用，执行工具后把结果喂回去，直到给出最终答案或触发步数上限。工具调用走 MCP，上一篇讲过。拿&amp;quot;查文献→对比→导出&amp;quot;来说，检索和导出是确定性节点，中间的对比分析交给 Agent 节点。&lt;/p&gt;&#10;&lt;h2 id="四个坑"&gt;四个坑&#10;&lt;/h2&gt;&lt;p&gt;第一个坑，state 越攒越肥。一开始我们把所有中间结果都塞进 &lt;code&gt;WorkflowState&lt;/code&gt;，跑到后面堆满文档全文和历史消息，token 直接爆炸。后来立了规矩：节点只输出下一阶段需要的最小字段，长文本进对象存储，state 里只留 ID 和摘要。&lt;/p&gt;&#10;&lt;p&gt;第二个坑，循环没有刹车。LLM 偶尔会陷入&amp;quot;反复检索但不给出答案&amp;quot;的死循环。现在每个节点有最大重试次数，整图有总步数和总 token 预算，超限直接中断，返回当前最优结果，而不是无限烧钱。&lt;/p&gt;&#10;&lt;p&gt;第三个坑，没有 trace 就是瞎子。多步工作流出问题，不查 trace 根本说不清哪一步偏了。我们在每个节点进入/退出时写结构化日志（节点名、输入摘要、输出摘要、耗时、token 数），用 trace_id 串起来，在 Jaeger 里能看完整的节点瀑布图。代码里的 &lt;code&gt;StepRecord&lt;/code&gt; 就是干这个的。&lt;/p&gt;&#10;&lt;p&gt;第四个不算坑，算一条划边界的原则：流程稳定、容错要求高的（支付、对账、数据同步）用确定性 DAG，甚至上 Temporal；探索性、开放式的任务（调研、写作、分析）用 Agent 节点。两者可以混在一张图里，但关键路径上的不可恢复操作，别让 Agent 碰。&lt;/p&gt;&#10;&lt;h2 id="后来"&gt;后来&#10;&lt;/h2&gt;&lt;p&gt;回头看，从单轮 RAG 走到多步工作流，是把&amp;quot;模型一次想清楚&amp;quot;换成了&amp;quot;系统分步兜底&amp;quot;：确定性节点负责可靠，Agent 节点负责灵活，编排器把两者粘起来，顺便守住成本和循环上限。&lt;/p&gt;&#10;&lt;p&gt;下一步我们在试把常用工作流做成模板，让业务方自己拖拽配置，不用每次都找后端写代码。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://www.flickr.com/photos/39415781@N06/7522618254" target="_blank" rel="noopener"&#10; &gt;ell brown / Flickr&lt;/a&gt; · CC BY 2.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item></channel></rss>