Rebirth s16 chapter from the Agent Writing Brief

Full rewrite of EN/ZH/JA as a progressive essay with harness-essay spine,
six-pattern toolbox, neighbor comparisons, and SVG-first figures
(dynamic-vs-static, primitives, six patterns). Keep null-isolation and
longest-prefix resume in the teaching runtime; bump translation-sync to v15.

Co-authored-by: Xinlu Lai <CrazyBoyM@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-08-12 13:57:47 +00:00
parent 06fef3be44
commit 4c4160a2df
10 changed files with 629 additions and 518 deletions

View File

@@ -8,43 +8,45 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
>
> **Harness 層**: Orchestration — single-agent loop の上に multi-agent script を載せます。
>
> モデルを信頼し、harness をエンジニアリングする。Workflow は、その考えを orchestration の階へ運んだものです。
> モデルを信頼し、harness をエンジニアリングする。Workflow は、その考えを一階上げたものです。
---
友だちとチャットだけで料理しているところを想像してください。「玉ねぎを切って」。待つ。「できた?」。次はフライパン、塩。一品ならそのリズムでも持ちます。二十卓の宴会では持ちません。手順は抜け、同じ言葉が返り、スマホが落ちたら冷たいところからやり直しです。
モデルがシェフとクリップボードを兼ねるときも、同じ感触です。計画と実行が一つの会話に押し込まれます。**Workflow** は書かれたレシピです。厨房runtimeがそれに従い、助手subagentが味見と判断をし、仕掛かりの器はカウンターに置かれます。グループスレッドの中ではありません。
モデルがシェフとクリップボードを兼ねるときも、同じ感触です。計画と実行が一つの会話に押し込まれます。**Workflow** は書かれたレシピです。厨房(小さな runtimeがそれに従い、助手subagentが味見と判断をし、仕掛かりの器はカウンターに——変数と journal に——置かれます。グループスレッドの中ではありません。
## なぜ、もう一枚の harness が要るのか
デフォルトの Claude Code harness は、コーディング型の仕事にもう十分強いです。直す、走らせる、エラーを読む、また試す。一つのループ一つの頭で、かなりの手業が出ます。
デフォルトの Claude Code harness は、コーディング型の仕事にもう十分強いです。直す、走らせる、エラーを読む、また試す。一つのループ一つの頭かなりの手業が出ます。
ただ、形の違う仕事もあります。深い調査、セキュリティの洗い出し、agent teams、変更一式を広げて review するような仕事です。そういうとき、人は昔からその上に第二の harness を載せてきました。SDK で先に手書きしてもよい。あるいは——ここが生きているところですが——Claude に**このタスク用**の harness を書かせ、走らせ、良いものを残せます。
ただ、ある仕事にはその上に**定制の harness** が要ります。深い調査、セキュリティの洗い出し、agent teams、変更一式を広げて review するような仕事です。SDK で先に手書きしてもよい。あるいは——ここが生きているところですが——Claude に**このタスク用**の harness を書かせ、走らせ、良いものを残せます。
コースのモットーを一階上げると、こうなります。各ステップの中ではモデルを信頼する。ステップの並びは、自分で形を決める。
Claude Code の設計者は率直です。dynamic workflow は、モデルがその場で自分用の multi-agent harness を書けるようにします。コースのモットーを一階上げると、こうなります。各ステップの中ではモデルを信頼する。ステップの並びは、自分で形を決める。
## 長いチャットで見かける癖
## 長いチャットが静かにやりがちなこと
s01 から s15 まで、計画と行動は同じ context window を共有します。次の一手が直前の発見に依るときは、とても心地よいです。
ところが仕事が長く、大規模に並行し、硬い構造を求め、あるいは疑り深い第二意見が要ると、脆くなります。長いチャットをじっと見ていると、見覚えのある癖に出会います。50 項目のうち 35 で勝利宣言をする。自分の宿題を採点させると甘くなる——狐が鶏小屋を採点する。多ターンと圧縮のあいだに、「X には触るな」という静かな制約が薄れて、なぜそれがそこにあったのか誰も覚えていない。
ところが仕事が長く、大規模に並行し、硬い構造を求め、あるいは疑り深い第二意見が要ると、脆くなります。長いチャットをじっと見ていると、用語を覚える前に、見覚えのある癖に出会います。
Claude Code の設計者は、これらを agentic laziness、self-preferential bias、goal drift と呼びます。名前より感触が大事です。仕事をする窓が、計画を覚える窓でもある。会話履歴は、並行性や安定した結果の形、落ちたあとの再開を預けるには柔らかい場所です。多くのファイルを review する、調査してから検証する、N 個のモジュールを同じやり方で移す——そうした仕事は、形が先に分かっています。柔らかい記憶だけでは足りません
50 項目のうち 35 で勝利宣言をする。自分の宿題を採点させると甘くなる——狐が鶏小屋を採点する。多ターンと圧縮のあいだに、「X には触るな」という静かな制約が薄れていく
それが agentic laziness、self-preferential bias、goal drift です。名前より感触が大事です。仕事をする窓が、計画を覚える窓でもある。会話履歴は、並行性や安定した結果の形、落ちたあとの再開を預けるには柔らかい場所です。多くのファイルを review する、調査してから検証する、N 個のモジュールを同じやり方で移す——そうした仕事は、形が先に分かっています。柔らかい記憶だけでは足りません。
## アイデアが落ちる瞬間
もし計画がコードの中に住んだらどうでしょう。
助手は相変わらず考えます——きれいな机で、焦点の定まった一つの仕事を。**script** がループと扇状の分配とマージを持ちます。中間結果は変数と journal にあり、会話には入りません。途中で切り上げる癖は艦隊全体を止めにくくなります。自己採点の甘さは著者ではない第二の助手にぶつかります。drift も掴みにくくなります。トポロジーを、疲れた語り手が毎ターン書き換える必要がないからです。
助手は相変わらず考えます——きれいな机で、焦点の定まった一つの仕事を。**script** がループと扇状の分配とマージを持ちます。中間結果は変数と journal にあり、会話には入りません。途中で切り上げる癖は艦隊全体を止めにくくなり自己採点の甘さは著者ではない第二の助手にぶつかりdrift も掴みにくくなります。トポロジーを、疲れた語り手が毎ターン書き換える必要がないからです。
一行で言えば、workflow はオーケストレーションを「賢さ」から「構造」へ移します。モデルは各 `agent()` の中で判断し、地図は script が持ちます。
一行で言えば、**workflow はオーケストレーションを「賢さ」から「構造」へ移します。** モデルは各 `agent()` の中で判断し、地図は script が持ちます。
![Workflow Runtime Overview](images/workflow-runtime-overview.svg)
1 回の `Workflow` tool call が、その実行を始めます。進み具合は途中で小さく鳴り、最後に launch 情報と結果と task state が一つの tool result で戻ります。
## 同じ厨房への、ふたつの入口
## ふたつの入口——そして外のいとこ
Claude Code は入口について率直です。
@@ -52,7 +54,11 @@ Claude Code は入口について率直です。
ときどき、良い script はすでに `.claude/workflows/` のような場所にあります。`name``args` で呼び出します。これが **saved** の入口です。残すに値した run が、再利用できるカードになったものです。
このレッスンの外にはいとこもあります。Agent SDK や `claude -p` で先に書く **static** harness です。あらゆるエッジケースに耐える必要があるので、どうしても汎用になります。dynamic はこの布のための裁断です。形が合ったら保存します。
外にはいとこもあります。Agent SDK や `claude -p` で先に書く **static** harness です。あらゆるエッジケースに耐える必要があるので、どうしても汎用になります。dynamic はこの布のための裁断です。形が合ったら保存します。
![Static harness vs dynamic workflow](images/dynamic-vs-static.svg)
*同じ問い。左: いつも汎用レポートに終わる汎用パイプライン。右: 自分のコードを読み、自分の取引量で値段を見、devil's advocate を呼ぶ特注レシピ。*
**この章は Python の teaching runtime です。** 同じアイデアを、1 行ずつ読める形で示します。デモは名前で一つの saved workflow を登録します。概念は Claude Code の script 世界と一一対応です。「モデルは実行可能コードを渡せない」などと言いません——それは Claude Code については初めから正しくありませんでした。ここでは、完全な JavaScript インタプリタを埋め込まないだけです。
@@ -74,13 +80,19 @@ WORKFLOW_TOOL = {
}
```
## 厨房の動詞を少し
## スクリプトが話す三つの動詞
学校のバザーでケーキをたくさん焼くとします。どのテーブルも 混ぜる → 焼く → 箱詰め。助手が味見をし、レシピが順番を決めます。
`agent(...)` は、助手ひとりに一つの仕事を頼むことです。`pipeline(items, *stages)` が既定で、各ケーキが自分で段階を歩きます。だから一方が箱詰めのあいだに、もう一方はまだ混ぜていてもよい。`parallel(...)` は揃うまで待つバリアで、次の段が本当に全部の結果を要すときだけ欲しくなります。トレイ全部を味見してから採点表を書く、といった場面です。
![Workflow primitives](images/workflow-primitives.svg)
その周りに、静かな動詞もあります。`phase`ボードにいまの場所を出し、`log` は短い一声、`workflow` は小さなレシピを入れ子にし、`args` は材料リスト、`budget` は使えるオーブン分tokenす。
`agent(prompt, {schema, label, phase})`、助手ひとりに一つの仕事を頼むことです。`schema` を付ければ、答えは検証済み JSON になり——次の段が受け取れるソケットになり——最初が雑なら一回だけやり直せます。
`pipeline(items, *stages)` は多段仕事の既定です。各ケーキが自分で段階を歩き、一方が箱詰めのあいだに、もう一方はまだ混ぜていてもよい。stage 間に barrier はありません。
`parallel(thunks)` は揃うまで待つバリアです。次の段が本当に全部の結果を要すときだけ欲しくなります。トレイ全部を味見してから採点表を書く、といった場面です。
その周りに、静かな動詞もあります。`phase` はいまの場所、`log` は短い一声、入れ子の `workflow``args` は材料リスト、`budget` はオーブン分tokenです。
```python
# 各 review dimension が自分で audit → verify を歩く。
@@ -88,25 +100,34 @@ results = await ctx.pipeline(DIMENSIONS, audit, verify)
confirmed = [f for r in results if r for f in r["confirmed"]]
```
## レシピが書けるようになったら
助手が失敗しても、艦隊は優しくいられます。`parallel` の失敗した thunk はそのスロットで `null` になり、gather 自体は reject しません。`pipeline` の失敗した stage は**その item** を null にし、残りの stage を飛ばします。マージの前に注意して絞ります。
上の動詞は小麦粉と火加減です。人が何度も発明し直すのは、少数の*形*——dynamic agentic workflow のよくあるパターンです。道具箱だと思ってください。必点メニューではりません。痛みが出たときに手を伸ばし、残りは壁に掛けておきます
厨房が止まったら? 各 run には `runId` と disk 上の journal があります——助手が戻った順ではなく、あなたが**呼んだ**順のートです。resume は script を先頭から歩き、**最長の未変更プレフィックス**を再生します。最初の変更または未完了で、それ以降はすべて live。本物の JS runtime が `Date.now()``Math.random()` を禁じるのはこのためです。時計とサイコロがノートをずらします。この Python デモはそのサンドボックスまではりません——それでも script は決定的に書いてください
```text
journal: [A ok] [B ok] [C ok] [D ok]
resume: A hit → B hit → C 変更 → D は live
```
## レシピが書けるようになったら——パターンの道具箱
動詞は小麦粉と火加減です。人が何度も発明し直すのは、少数の*形*です。道具箱だと思ってください。必点メニューではありません。
![Six Workflow Patterns](images/six-workflow-patterns.svg)
*人が何度も発明する六つの形。トポロジーは script が持ち、このレッスンでは `agent` / `parallel` / `pipeline` / `phase` / journal で各形を話します。*
*人が何度も発明する六つの形。トポロジーは script が持ち、このレッスンでは `agent` / `parallel` / `pipeline` / `phase` / journal で話します。*
**Classify-And-Act。** 痛み: 万能の助手は何でもそこそこ。形: classifier がタスクを見て、専門家 A / B / C へ振り分ける。このレッスンでは、だいたい `agent({schema})` がラベルを返し、script`if`/`match` が続く `agent`(または入れ子の `workflow`を呼びます。全部が本当に同じ扱いでよいなら使わない——振り分けは儀式になります
**Classify-And-Act。** 痛み: 万能の助手は何でもそこそこ。形: classifier が見て、専門家 A / B / C へ振り分ける。ここ: `agent({schema})` がラベルを返し、script が続く `agent`(または入れ子の `workflow`へ分岐。全部が本当に同じ扱いでよいなら使わない。
**Fanout-And-Synthesize。** 痛み: 五十ファイルは一つの疲れた context に入らず、押し込めば混線する。形: 仕事を分け、多くの agent を走らせ、barrier で待ち、まとめる。各 item に自分の stage があるなら `pipeline`、次が全結果を要すなら `parallel`。まとめは gather のあとのふつうの Python。関連ファイルが三つか五つで一通しで足りるなら使わない。
**Fanout-And-Synthesize。** 痛み: 五十ファイルは一つの疲れた context に入らず、押し込めば混線する。形: 分け、多く走らせ、barrier で待ち、まとめる。ここ: 各 item に stage があるなら `pipeline`、次が全結果を要すなら `parallel`。まとめは gather のあとのふつうの Python。三つか五つの関連ファイルで足りるなら使わない。
**Adversarial Verification。** 痛み: 狐が鶏小屋を採点する。形: worker が出す。独立した verifier が反証や負荷をかける。生き残ったものだけ残る。写し方は、生産の `agent`、それから verifier `agent``parallel`できれば schema 付き)、そのあと filter。`phase`ReviewVerify” を分ける。間違えても安いなら使わない——すべてのメモに法廷は要らない
**Adversarial Verification。** 痛み: 狐が鶏小屋を採点する。形: worker が出す。独立した verifier が反証る。生き残ったものだけ残る。ここ: 生産の `agent`、それから verifier の `parallel`schema 付き)、そのあと filter。`phase` で Review と Verify。間違えても安いなら使わない。
**Generate-And-Filter。** 痛み: 欲しいのは選択肢であり、最初に賢く聞こえた案ではない。形: 多くの generator がアイデアを rubric + dedupe の filter に流し、best を残して残りを捨てる。写し方は generator の `parallel`、そのあと script 側の filterまたは schema 付きの審判 `agent`)。生成が高いとき journal / resume が効く。良い答えの空間がもともと狭いなら使わない。
**Generate-And-Filter。** 痛み: 欲しいのは選択肢であり、最初に賢く聞こえた案ではない。形: 多くの generator が rubric + dedupe の filter に流す。ここ: generator の `parallel`、そのあと script 側の filterまたは審判 `agent`)。生成が高いとき journal が効く。良い答えの空間がもともと狭いなら使わない。
**Tournament。** 痛み: 味や順位では絶対スコアがぼやける(「この名前はどれくらい良い?」)。形: ペアごとの審判、トーナメント表、勝者——比較判断は孤独な採点に勝る。写し方は、script 内で pairwise 審判 `agent``parallel` を回し、一つ残るまで続ける。鋭い rubric が一通しで勝者を決めるなら使わない。
**Tournament。** 痛み: 味や順位では絶対スコアがぼやける。形: ペアごとの審判、トーナメント表、勝者——比較判断は孤独な採点に勝る。ここ: pairwise 審判 `agent``parallel` を回し、一つ残るまで続ける。鋭い rubric が一通しで決めるなら使わない。
**Loop Until Done。** 痛み: 坑道にまだ何巡あるか分からない。形: 「新しい発見?」が yes のあいだ spawn し続け、空振りや完了条件で止まる。写し方は `while``agent`/`parallel` を包み、schema 付きの停止チェックと硬い `budget` を置く。長い掘りが止まるなら journal resume と組む。仕事量が分かっているなら固定の `pipeline` の方が単純で安全
**Loop Until Done。** 痛み: 坑道にまだ何巡あるか分からない。形: 「新しい発見?」が yes のあいだ spawn し続け、空振りで止まる。ここ: `while``agent`/`parallel` を包み、schema 付きの停止チェックと硬い `budget`。長い掘りには journal resume。仕事量が分かっているなら固定の `pipeline` の方が単純。
いくつか顔が付いたあと、道具箱は一目で収まります。
@@ -116,51 +137,10 @@ confirmed = [f for r in results if r for f in r["confirmed"]]
| Fanout-And-Synthesize | `pipeline` / `parallel` → 統合 | きれいな机がたくさん、そのあと一つの要約 |
| Adversarial Verification | 生産 → `parallel(verify)` → filter | 間違えると高い |
| Generate-And-Filter | `parallel(gens)` → rubric filter | まず選択肢、それから味 |
| Tournament | pairwise 審判 `agent` の bracket | 順位/味に鋭い物差しがない |
| Loop Until Done | `while` + 停止チェック + `budget` | どれだけ埋まっているか不明 |
| Tournament | pairwise 審判 `agent` | 順位/味に鋭い物差しがない |
| Loop Until Done | `while` + 停止 + `budget` | どれだけ埋まっているか不明 |
組み合わせはふつうです。深い調査はしばしば fanout → filter → verify → synthesize と重ねます。私たちのサンプルは、すでに二つの音の小さな和音です。
## 次の段が受け取れる答え
助手が散文で返してくると、次の stage は finding と verdict を揃えられません。`schema` を渡します。runtime は JSON を求め、確かめ、**一度だけ**やり直します。それでもだめならその call はエラーになります——失敗のとき艦隊がどう優しくいられるかは、次の話です。
```python
out = await ctx.agent(
f"この変更に {dimension} 関連の問題がないか確認してください:\n{changes}",
schema=FINDINGS_SCHEMA,
label=f"audit:{dimension}",
)
```
あなたとの会話は散文のままでよいです。パイプラインには合うソケットが要ります。
## トレイがひとつ焦げたとき
助手のオーブンが一つ失敗したくらいで、艦隊を止めてはいけません。
`parallel` では、失敗した thunk がそのスロットで `null` / `None` になり、gather 自体は reject しません。`pipeline` では、失敗した stage が**その item** を null にし、残りの stage を飛ばします。ほかの item は歩き続けます。マージの前に注意して絞ります。`if r`、JS ならよく `.filter(Boolean)` です。
```python
verdicts = await ctx.parallel([...]) # 空のスロットがありうる
confirmed = [
f for f, v in zip(findings, verdicts)
if v and v.get("isReal")
]
```
## また開けるノート
各 run には `runId` があります。`agent()` が終わるたび、disk 上の journal に一行が乗ります——助手がオーブンから戻った順ではなく、あなたが**呼んだ**順のノートです。
resume`resume_from_run_id` / `resumeFromRunId`)は script をまた先頭から走らせますが、丁寧です。呼び出し順に次の journal 行と照合し、最長の未変更プレフィックスはキャッシュから再生します。最初の変更または未完了でプレフィックスが切れ——それ以降はすべて live です。ノートの後ろに古い key が残っていても、割れ目を飛び越えて黙って hit しません。
本物の JavaScript workflow runtime が `Date.now()``Math.random()`、引数なしの `new Date()` を禁じるのも、このためです。時計とサイコロが prompt や呼び出し順を揺らすと、ノートが揃わなくなります。この Python デモはそのサンドボックスまではやりません。それでも script は決定的に書いてください。
```text
journal: [A ✓] [B ✓] [C ✓] [D ✓]
resume: A hit → B hit → C 変更 → D は live
```
組み合わせはふつうです。深い調査はしばしば fanout → filter → verify → synthesize と重ねます。トリアージでは **quarantine** を足すこともあります。信頼できない内容を読む agent は read-only のまま、trusted な actor だけが PR を開きます。私たちのサンプルは、二つの音の小さな和音です。
## `review-changes` を歩く — ひとつの composition
@@ -175,7 +155,7 @@ style ── audit ── verify ──┘
└── 各 finding: 懐疑的 verify ──┘
```
`pipeline(DIMENSIONS, audit, verify)` が各 dimension に机を渡し、correctness の雑談が security へ流れないようにします。`verify` 内の verifier agent `parallel` が敵対の和音です。ふつうのリスト filter が synthesize。`phase` が Review と Verify を印し、journal が各 `agent()` を覚えるので、止まっても audit をやり直さない。
`pipeline(DIMENSIONS, audit, verify)` が各 dimension に机を渡します。`verify` 内の verifier の `parallel` が敵対の和音です。ふつうのリスト filter が synthesize。`phase` が Review と Verify を印し、journal が各 `agent()` を覚えるので、止まっても audit をやり直さない。
三つの癖がお気に入りの席を失う感触があります。艦隊は二つの dimension で止められず、著者は審判ではなく、トポロジーは途中で漂いません。
@@ -188,13 +168,14 @@ async def sample_workflow(ctx, args):
return {"confirmed": confirmed}
```
## s15 に掛けて、置き換えない
<details>
<summary>s15 に掛けて、置き換えない</summary>
s15 は依然として host loop です。s16 が足すのは `Workflow` という tool だけです。あなた(またはモデル)が saved の名前を頼み、adapter が script を見つけて走らせます。
本番では、その run は通知付きで背景に置き、セッションは応答し続けられます。teaching CLI は `demo` `resume` を前景に置き、phase と cache hit を目で追えるようにしています。アイデアは同じで、簡略化したところははっきり言います。
本番では、その run は通知付きで背景に置き、セッションは応答し続けられます。teaching CLI は `demo` / `resume` を前景に置き、phase と cache hit を目で追えるようにしています。アイデアは同じで、簡略化したところははっきり言います。main loop は `bash``task` を借りるように、tool を一つ借ります。
main loop が workflow エンジンになるわけではありません。`bash``task` を借りるように、tool を一つ借ります。
</details>
## 宝石を回す: 計画を握っているのは誰か
@@ -230,4 +211,4 @@ Review が Verify に道を譲るのを見てください。完全な resume で
s16 はバッチの回し方です。[s17 Goal Loop](../s17_goal_loop/) は戸口で別の問いをします。止めるべきか、もう一ターンか。繰り返せるレシピに硬い「完了」も要るときは、そちらと組んでください。
<!-- translation-sync: zh@v14, en@v14, ja@v14 -->
<!-- translation-sync: zh@v15, en@v15, ja@v15 -->

View File

@@ -8,29 +8,31 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
>
> **Harness layer**: Orchestration — a multi-agent script above the single-agent loop.
>
> Trust the model; engineer the harness. Workflows are that idea at the orchestration layer.
> Trust the model. Engineer the harness. Workflows are that idea one floor up.
---
Picture cooking with a friend over text. “Chop the onions.” Wait. “Done?” Then the pan, then the salt. One dish survives that rhythm. A feast with twenty plates does not: you forget steps, repeat yourself, and if the phone dies you start over cold.
Picture cooking with a friend over text. “Chop the onions.” Wait. “Done yet?” Then the pan, then the salt. One dish survives that rhythm. A feast with twenty plates does not: you forget steps, repeat yourself, and if the phone dies you start over cold.
That is what it feels like when the model is both chef and clipboard — planning and doing inside the same chat. A **workflow** is the written recipe. The kitchen (the runtime) follows it. Helpers (subagents) taste and judge. The bowls of half-finished work sit on the counter, not in the group thread.
That is what it feels like when one model is both chef and clipboard — planning and doing inside the same chat. A **workflow** is the written recipe. The kitchen (a small runtime) follows it. Helpers (subagents) taste and judge. Half-finished bowls sit on the counter — in variables and a journal — not in the group thread.
## Why bother with another harness?
## Why another harness at all?
The default Claude Code harness is already good at coding-shaped work: change something, run it, read the error, try again. One loop, one mind, a surprising amount of craft.
The default Claude Code harness is already strong at coding-shaped work: change something, run it, read the error, try again. One loop. One mind. A surprising amount of craft.
But some jobs are a different shape — deep research, security sweeps, agent teams, a review that fans out across a whole change set. For those, people have long built a second harness on top. You can still hand-write that layer in an SDK. Or — and this is the lively part — Claude can draft a harness **for this task**, run it, and keep the good ones.
But some jobs want a **custom harness on top** — deep research, security sweeps, agent teams, a review that fans across a whole change set. You can hand-write that layer once in an SDK. Or — and this is the lively idea — Claude can draft a harness **for this task**, run it, and keep the good ones.
Same course motto, one floor up: trust the model inside each step; decide the shape of the steps yourself.
Claude Codes designers put it simply: dynamic workflows let the model write its own multi-agent harness on the fly. Same course motto, raised one floor: trust the model inside each step; decide the shape of the steps yourself.
## What goes wrong in a long chat
## What a long chat quietly does wrong
From s01 through s15, plan and action share one context window. That is wonderful when the next move depends on what you just found.
It frays when the job is long, massively parallel, rigidly structured, or needs a skeptical second opinion. Watch a long chat carefully and you will see familiar habits. It gets tired and declares victory after thirty-five of fifty review items. Asked to check its own homework, it grades kindly — the fox scoring the henhouse. And across many turns and compressions, the quiet constraint (“dont touch X”) fades until nobody remembers why it was there.
It frays when the job is long, massively parallel, rigidly structured, or needs a skeptical second opinion. Watch a long chat carefully and you will meet familiar habits before you ever learn their names.
Claude Codes designers call these agentic laziness, self-preferential bias, and goal drift. The names matter less than the feeling: the same window that does the work is also trying to remember the plan. Chat history is a soft place to keep parallelism, stable result shapes, and a way to resume after a crash. Review-many-files, research-then-verify, migrate-N-modules — those jobs already know their shape. Soft memory is not enough.
It gets tired and declares victory after thirty-five of fifty review items. Asked to check its own homework, it grades kindly — the fox scoring the henhouse. Across many turns and compressions, the quiet constraint (“dont touch X”) fades until nobody remembers why it was there.
Those are agentic laziness, self-preferential bias, and goal drift. The names matter less than the feeling: the same window that does the work is also trying to remember the plan. Chat history is a soft place to keep parallelism, stable result shapes, and a way to resume after a crash. Review-many-files, research-then-verify, migrate-N-modules — those jobs already know their shape. Soft memory is not enough.
## The idea, once it clicks
@@ -38,21 +40,25 @@ What if the plan lived in code?
Helpers still think — each at a clean desk, with one focused job. The **script** owns the loops, the fan-out, the merge. Intermediate results live in variables and a journal, not in the conversation. Laziness has a harder time stopping the fleet early. Self-checking bias meets a second helper who was not the author. Drift loses its grip because the topology is not rewritten every turn by a tired narrator.
In one line: workflows move orchestration from *intelligence* to *structure*. The model still judges inside each `agent()`; the script owns the map.
In one line: **workflows move orchestration from intelligence to structure.** The model still judges inside each `agent()`; the script owns the map.
![Workflow Runtime Overview](images/workflow-runtime-overview.svg)
One `Workflow` tool call starts that run. Progress ticks while it works; one tool result comes back with launch info, the outcome, and task state.
## Two doors into the same kitchen
## Two doors — and a cousin outside
Claude Code is straightforward about how you enter.
Claude Code is straightforward about how you enter the kitchen.
Sometimes the model writes a JavaScript orchestration script for *this* task and hands it over as `script` (or later edits `scriptPath`). That is the **dynamic** door — a harness tailored while the problem is still warm.
Sometimes the model writes a JavaScript orchestration script for *this* task and hands it over as `script` (or later edits `scriptPath`). That is the **dynamic** door — a harness cut while the problem is still warm.
Sometimes a good script has already been saved under something like `.claude/workflows/`. You call it by `name` and `args`. That is the **saved** door — the reusable residue of a run that earned its keep.
There is a cousin outside this lesson too: **static** harnesses you write ahead of time with the Agent SDK or `claude -p`. Those have to survive every edge case, so they stay generic. Dynamic ones are cut for *this* cloth; save them when the fit is right.
There is also a cousin: **static** harnesses you write ahead of time with the Agent SDK or `claude -p`. Those must survive every edge case, so they stay generic. Dynamic ones are cut for *this* cloth; save them when the fit is right.
![Static harness vs dynamic workflow](images/dynamic-vs-static.svg)
*Same question. Left: a generic pipeline that always ends in a generic report. Right: a tailor-made workflow that reads your code, prices your volume, and invites a devils advocate.*
**This chapter is a Python teaching runtime.** Same ideas, every line readable. Our demo registers one saved workflow by name; the concepts map one-to-one onto Claude Codes script world. We will not pretend “the model cannot submit executable code” — that was never true of Claude Code. We simply do not embed a full JavaScript interpreter here.
@@ -74,13 +80,19 @@ WORKFLOW_TOOL = {
}
```
## A few kitchen verbs
## Three verbs the script speaks
Imagine a school bake sale. Every table needs mix → bake → box. Helpers taste; the recipe decides order.
`agent(...)` is asking one helper to do one job. `pipeline(items, *stages)` is the default: each cake walks the stages on its own, so one can be boxing while another is still mixing. `parallel(...)` is the barrier — wait until every tray is back — and you only want that when the next step truly needs all of them together, like writing the scorecard after tasting the whole tray.
![Workflow primitives](images/workflow-primitives.svg)
Around those sit quieter verbs: `phase` to announce where you are on the board, `log` for a short shout, `workflow` to nest one smaller recipe, `args` for the ingredients list, `budget` for how many oven-minutes (tokens) you may burn.
`agent(prompt, {schema, label, phase})` asks one helper to do one job. With a `schema`, the answer comes back as validated JSON — a socket the next stage can hold — with one retry if the first reply is messy.
`pipeline(items, *stages)` is the default for multi-stage work. Each cake walks its stages alone, so one can be boxing while another is still mixing. No barrier between stages.
`parallel(thunks)` is the barrier — wait until every tray is back. Reach for it only when the next step truly needs all results together, like writing the scorecard after tasting the whole tray.
Around those sit quieter verbs: `phase` to announce where you are, `log` for a short shout, nested `workflow` one level deep, `args` for the ingredients list, `budget` for oven-minutes (tokens).
```python
# Each review dimension walks audit → verify on its own.
@@ -88,27 +100,36 @@ results = await ctx.pipeline(DIMENSIONS, audit, verify)
confirmed = [f for r in results if r for f in r["confirmed"]]
```
## Once you can write the recipe
When a helper fails, the fleet stays kind. A failing `parallel` thunk becomes `null` in that slot; the gather itself does not reject. A failing `pipeline` stage drops **that item** to null and skips its later stages; other items keep walking. Filter with care before you merge.
The verbs above are flour and heat. What people keep reinventing are a handful of *shapes* — common patterns for dynamic agentic workflows. Think of them as a toolbox, not a mandatory menu. Reach for one when its pain shows up; leave the rest on the pegboard.
And when the kitchen pauses? Every run has a `runId` and a journal on disk — a notebook ordered by the moment you *called* each helper. Resume walks the script from the top and replays the **longest unchanged prefix**. At the first changed or unfinished call, everything after runs live. That is why real JS runtimes ban `Date.now()` and `Math.random()`: clocks and dice make the notebook stop lining up. This Python demo does not fully sandbox that — write deterministic scripts anyway.
```text
journal: [A ok] [B ok] [C ok] [D ok]
resume: A hit → B hit → C changed → D runs live
```
## Once you can write the recipe — the pattern toolbox
The verbs are flour and heat. What people keep reinventing are a handful of *shapes*. Think of them as a toolbox, not a mandatory menu.
![Six Workflow Patterns](images/six-workflow-patterns.svg)
*Six shapes people keep reinventing. The script owns the topology; `agent` / `parallel` / `pipeline` / `phase` / journal are how each shape is spoken in this lesson.*
*Six shapes people keep reinventing. The script owns the topology; this lesson speaks them with `agent` / `parallel` / `pipeline` / `phase` / journal.*
**Classify-And-Act.** Pain: one generic helper is mediocre at everything. Shape: a classifier looks at the task, then routes to specialist A, B, or C. In this lesson that is usually one `agent({schema})` that returns a label, then an `if`/`match` in the script that calls the right follow-up `agent` (or a nested `workflow`). Skip it when every item truly needs the same treatment — routing is just ceremony then.
**Classify-And-Act.** Pain: one generic helper is mediocre at everything. Shape: a classifier looks, then routes to specialist A, B, or C. Here: one `agent({schema})` returns a label; the script branches to the right follow-up `agent` (or a nested `workflow`). Skip it when every item truly needs the same treatment.
**Fanout-And-Synthesize.** Pain: fifty files will not fit one tired context, and they contaminate each other if they try. Shape: split the work, run many agents, wait at a barrier, merge. Map it with `pipeline` when each item has its own stages, or `parallel` when the next step needs every result together; put the merge in ordinary Python after the gather. Skip it for three related files a single pass can hold.
**Fanout-And-Synthesize.** Pain: fifty files will not fit one tired context, and they contaminate each other if they try. Shape: split, run many, wait at a barrier, merge. Here: `pipeline` for per-item stages, or `parallel` when the next step needs every result; merge in ordinary Python after the gather. Skip it for three related files a single pass can hold.
**Adversarial Verification.** Pain: the fox grades the henhouse. Shape: a worker produces; independent verifiers try to refute or stress-test; only survivors remain. Map it with a produce `agent`, then `parallel` of verifier `agent`s (ideally `schema`d), then a filter. Phases help (“Review” then “Verify”). Skip it when the cost of a wrong answer is low — not every note needs a tribunal.
**Adversarial Verification.** Pain: the fox grades the henhouse. Shape: a worker produces; independent verifiers try to refute; only survivors remain. Here: a produce `agent`, then `parallel` of verifier `agent`s (schemad), then a filter. Phases help (“Review” then “Verify”). Skip it when a wrong answer is cheap.
**Generate-And-Filter.** Pain: you need options, not the first idea that sounded clever. Shape: many generators spill ideas into a rubric + dedupe filter; best stay, rest go. Map it with `parallel` over generators, then script-side filter (or one judge `agent` with a schema). Journal/resume matter when generation is expensive. Skip it when the space of good answers is already tiny.
**Generate-And-Filter.** Pain: you need options, not the first clever-sounding idea. Shape: many generators spill into a rubric + dedupe filter. Here: `parallel` over generators, then script-side filter (or one judge `agent`). Journal matters when generation is expensive. Skip it when the space of good answers is already tiny.
**Tournament.** Pain: absolute scores are mushy for taste and ranking (“how good is this name?”). Shape: pairwise judges, a bracket, a winner — comparative judgment beats lonely scoring. Map it with rounds of `parallel` judge `agent`s over pairs, looping in the script until one remains. Skip it when a clear rubric already picks a winner in one pass.
**Tournament.** Pain: absolute scores are mushy for taste and ranking. Shape: pairwise judges, a bracket, a winner — comparative judgment beats lonely scoring. Here: rounds of `parallel` judge `agent`s over pairs until one remains. Skip it when a clear rubric already picks a winner in one pass.
**Loop Until Done.** Pain: you do not know how many passes the mine still holds. Shape: keep spawning while “new findings?” is yes; stop on dry rounds or a done condition. Map it with a `while` over `agent`/`parallel`, a schemad stop check, and a hard `budget` so the loop cannot eat the house. Pair with journal resume when a long dig may pause. Skip it when the work has a known size — a fixed `pipeline` is simpler and safer.
**Loop Until Done.** Pain: you do not know how many passes the mine still holds. Shape: keep spawning while “new findings?” is yes; stop on dry rounds. Here: a `while` over `agent`/`parallel`, a schemad stop check, and a hard `budget`. Pair with journal resume on a long dig. Skip it when the work has a known size — a fixed `pipeline` is simpler.
After a few of those have a face, the toolbox fits in one glance:
After a few have faces, the toolbox fits in one glance:
| Pattern | Primitive sketch | Reach for it when… |
|---------|------------------|--------------------|
@@ -116,55 +137,14 @@ After a few of those have a face, the toolbox fits in one glance:
| Fanout-And-Synthesize | `pipeline` / `parallel` → merge | Many clean desks, then one summary |
| Adversarial Verification | produce → `parallel(verify)` → filter | Wrong answers are expensive |
| Generate-And-Filter | `parallel(gens)` → rubric filter | You need options, then taste |
| Tournament | pairwise judge `agent`s in a bracket | Ranking / taste without a sharp scale |
| Loop Until Done | `while` + stop check + `budget` | Unknown amount of buried work |
| Tournament | pairwise judge `agent`s | Ranking / taste without a sharp scale |
| Loop Until Done | `while` + stop + `budget` | Unknown amount of buried work |
Compositions are normal. Deep research often stacks fanout → filter → verify → synthesize. Our sample is already a small chord of two notes.
## Answers the next stage can hold
If a helper returns a poem, the next stage cannot zip findings to verdicts. Pass a `schema`. The runtime asks for JSON, checks it, and gives **one** retry. Fail again and that call errors — which brings us to how the fleet stays kind under failure.
```python
out = await ctx.agent(
f"Inspect this change for {dimension} issues:\n{changes}",
schema=FINDINGS_SCHEMA,
label=f"audit:{dimension}",
)
```
Chat with you can stay prose. A pipeline needs sockets that fit.
## When one tray burns
The fleet should not stop because one helper had a bad oven.
In `parallel`, a failing thunk becomes `null` / `None` in that slot; the gather itself does not reject. In `pipeline`, a failing stage drops **that item** to null and skips its later stages; the other items keep walking. Filter with care before you merge — `if r`, or `.filter(Boolean)` in the JS world.
```python
verdicts = await ctx.parallel([...]) # some slots may be None
confirmed = [
f for f, v in zip(findings, verdicts)
if v and v.get("isReal")
]
```
## A notebook you can reopen
Every run gets a `runId`. As each `agent()` finishes, a line lands in a journal on disk — a notebook ordered by the moment you *called* the helper, not by who wandered back from the oven first.
Resume (`resume_from_run_id` / `resumeFromRunId`) runs the script from the top again, but kindly. Call by call, in order, it matches the next journal line. The longest unchanged prefix replays from cache. At the first changed or unfinished call, the prefix breaks — and everything after runs live, even if an old key still sits further down the notebook. No silent leaps over a break.
That is also why real JavaScript workflow runtimes ban `Date.now()`, `Math.random()`, and bare `new Date()`. Clocks and dice make prompts or call order wobble, and the notebook stops lining up. This Python demo does not fully sandbox that. Write deterministic scripts anyway.
```text
journal: [A ✓] [B ✓] [C ✓] [D ✓]
resume: A hit → B hit → C changed → D runs live
```
Compositions are normal. Deep research often stacks fanout → filter → verify → synthesize. Triage sometimes adds a **quarantine**: readers of untrusted content stay read-only; only a trusted actor opens PRs. Our sample is a smaller chord of two notes.
## Walking `review-changes` — a composition
The sample is not “one pattern.” It is **Fanout-And-Synthesize** with **Adversarial Verification** inside — and a light generate-and-filter at the end when only `isReal` findings survive.
The sample is not “one pattern.” It is **Fanout-And-Synthesize** with **Adversarial Verification** inside — and a light generate-and-filter when only `isReal` findings survive.
```text
correctness ── audit ── verify ──┐
@@ -175,7 +155,7 @@ style ── audit ── verify ──┘
└── each finding: skeptical verify ──┘
```
`pipeline(DIMENSIONS, audit, verify)` gives each dimension its own desk so correctness talk does not bleed into security. Inside `verify`, `parallel` of verifier agents is the adversarial chord. Ordinary list filtering is the synthesize step. Phases mark Review then Verify; the journal remembers every `agent()` so a pause does not redo the audits.
`pipeline(DIMENSIONS, audit, verify)` gives each dimension its own desk. Inside `verify`, `parallel` of verifier agents is the adversarial chord. Ordinary list filtering is the synthesize step. Phases mark Review then Verify; the journal remembers every `agent()` so a pause does not redo the audits.
You can almost feel the three failure modes losing their favorite seats: the fleet cannot stop after two dimensions, the author is not the judge, and the topology does not drift mid-run.
@@ -188,13 +168,14 @@ async def sample_workflow(ctx, args):
return {"confirmed": confirmed}
```
## Hanging on s15 without replacing it
<details>
<summary>How this hangs on s15 without replacing it</summary>
s15 is still the host loop. s16 only adds a tool named `Workflow`. You (or the model) ask for a saved name; the adapter finds the script and runs it.
In the real product, that run can sit in the background with notifications while the session stays responsive. Our teaching CLI keeps `demo` and `resume` in the foreground so you can watch phases and cache hits without squinting. Same ideas; we say so when we simplify.
In the real product, that run can sit in the background with notifications while the session stays responsive. Our teaching CLI keeps `demo` / `resume` in the foreground so you can watch phases and cache hits. Same ideas; we say so when we simplify. The main loop borrows one tool the way it borrows `bash` or `task`.
The main loop does not become a workflow engine. It borrows one tool the way it borrows `bash` or `task`.
</details>
## Turning the gem: who holds the plan?
@@ -230,4 +211,4 @@ Watch Review give way to Verify. Watch agents flip from `done` to `cached` on a
s16 is how a batch runs. [s17 Goal Loop](../s17_goal_loop/) asks a different question at the door: should we stop, or take another turn? Pair them when a repeatable recipe also needs a hard “done.”
<!-- translation-sync: zh@v14, en@v14, ja@v14 -->
<!-- translation-sync: zh@v15, en@v15, ja@v15 -->

View File

@@ -8,29 +8,31 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
>
> **Harness 层**: 编排 — 单 agent 循环之上,再跑一套多 agent 脚本。
>
> 信任模型,工程化 harness。Workflow就是把这句话落到编排层。
> 信任模型,工程化 harness。Workflow就是把这句话往上提一层。
---
想象你跟朋友用微信一起做饭。“先切洋葱。”等回音。“切好了吗?”然后热锅、放盐。一道菜还能撑住这种节奏;二十桌宴席就不行了——步骤会丢,话会重复,手机一死还得从头来。
模型既当厨师又当记事本时,感觉就是这样:计划与动手挤在同一段对话里。**Workflow** 则是写好的菜谱。厨房runtime按谱做帮手子 agent负责尝和判断半成品放在台面上的碗里而不是塞进群聊记录
模型既当厨师又当记事本时,感觉就是这样:计划与动手挤在同一段对话里。**Workflow** 则是写好的菜谱。厨房(一个小 runtime按谱做帮手子 agent负责尝和判断半成品放在台面上的碗里——变量和 journal——而不是塞进群聊。
## 为什么还要另一层 harness
默认的 Claude Code harness 已经很擅长“写代码那种形状”的活:改一点、跑一下、看报错、再试。一个循环一颗脑袋能做出不少工艺。
默认的 Claude Code harness 已经很擅长“写代码那种形状”的活:改一点、跑一下、看报错、再试。一个循环一颗脑袋能做出不少工艺。
可有些活是另一种形状——深度调研、安全排查、agent teams、要铺开审查一整片改动。这类事,人们早就习惯在上面再搭一层定制 harness。你当然可以事先用 SDK 手写;也可以——这才是有意思的地方——让 Claude **为这次任务**起草一个 harness跑起来好用的再留下来。
可有些活需要**叠一层定制 harness**——深度调研、安全排查、agent teams、要铺开审查一整片改动。可以事先用 SDK 手写那一层;也可以——这才是有意思的地方——让 Claude **为这次任务**起草一个 harness跑起来好用的再留下来。
课程那句口号往上提一层:每一步里信任模型;步骤怎么排,由你来定结构。
Claude Code 的设计者说得很直白dynamic workflow 让模型当场为自己写下多 agent 的 harness。课程那句口号往上提一层:每一步里信任模型;步骤怎么排,由你来定结构。
## 长对话里你会看见的走偏
从 s01 到 s15计划与执行共享同一个上下文。下一步取决于刚才的发现时这很舒服。
可一旦任务变长、要大规模并行、结构又死板,或需要一个挑剔的第二意见,它就会发脆。你若耐心看一段很长的聊天,会撞见熟面孔:做到五十项里的三十五就宣布完工;让它批改自己的作业,分数总是偏甜——狐狸给鸡窝打分;多轮对话和压缩过后,那句轻轻的“别动 X”渐渐听不见了
可一旦任务变长、要大规模并行、结构又死板,或需要一个挑剔的第二意见,它就会发脆。你若耐心看一段很长的聊天,会在学会术语之前,先撞见熟面孔。
Claude Code 的设计者把这些叫做 agentic laziness、self-preferential bias、goal drift。名字不如感觉重要同一个窗口既要干活又要记住计划。对话历史太软扛不住并行、稳定的结果形状以及崩了还能续上。审查很多文件、先调研再验证、按同一方式迁移 N 个模块——这些活的形状事先就清楚。软记忆不够用
做到五十项里的三十五就宣布完工。让它批改自己的作业,分数总是偏甜——狐狸给鸡窝打分。多轮对话和压缩过后,那句轻轻的“别动 X”渐渐听不见了
这些就是 agentic laziness、self-preferential bias、goal drift。名字不如感觉重要同一个窗口既要干活又要记住计划。对话历史太软扛不住并行、稳定的结果形状以及崩了还能续上。审查很多文件、先调研再验证、按同一方式迁移 N 个模块——这些活的形状事先就清楚。软记忆不够用。
## 点子落下的那一下
@@ -38,13 +40,13 @@ Claude Code 的设计者把这些叫做 agentic laziness、self-preferential bia
帮手仍然负责想——每人一张干净桌子,一件专注的事。**脚本**掌管循环、分发和合并。中间结果待在变量和 journal 里,不进对话。想偷懒提前收工的习惯,更难叫停整支队伍;自我检查的偏心,会撞上一个不是作者本人的第二帮手;漂移也难下手,因为拓扑不再由一个疲倦的叙述者每轮改写。
一句话workflow 把编排从“靠聪明”挪到“靠结构”。模型仍在每次 `agent()` 里做判断;地图归脚本管。
一句话:**workflow 把编排从「智力」挪到「结构」。** 模型仍在每次 `agent()` 里做判断;地图归脚本管。
![Workflow Runtime 总览](images/workflow-runtime-overview.svg)
一次 `Workflow` 工具调用启动这次运行。进度在旁边轻轻响;最后一条工具结果带回启动信息、结果和任务状态。
## 同一间厨房,两扇门
## 两扇门——门外还有个表亲
Claude Code 对入口说得很直白。
@@ -52,7 +54,11 @@ Claude Code 对入口说得很直白。
有时好脚本已经进了例如 `.claude/workflows/`。你用 `name``args` 再请它出来。这是**已保存**那扇门——一次值得留下的运行,沉淀成可复用的卡片。
本课之外还有表亲:**静态** harness用 Agent SDK 或 `claude -p` 事先写好。它们得扛住所有边角,所以往往更泛。动态的是为这块布现裁的;合身了再存。
外还有表亲:**静态** harness用 Agent SDK 或 `claude -p` 事先写好。它们得扛住所有边角,所以往往更泛。动态的是为这块布现裁的;合身了再存。
![静态 harness 与动态 workflow](images/dynamic-vs-static.svg)
*同一个问题。左边:永远通向一份泛泛研究报告的通用流水线。右边:读你的代码、按你的量计价、再请来魔鬼代言人的特制菜谱。*
**这一章是 Python 教学运行时。** 同样的想法,每行都能读。演示按名字挂了一个已保存的 workflow概念和 Claude Code 的脚本世界一一对应。我们不会再说“模型不能提交可执行代码”——那从来不是 Claude Code 的真相。这里只是不嵌入完整的 JS 解释器。
@@ -74,13 +80,19 @@ WORKFLOW_TOOL = {
}
```
## 厨房里的几个动词
## 脚本会说的三个动词
想象学校义卖要烤许多蛋糕。每张桌子都是搅拌 → 烘烤 → 装箱。帮手负责尝;菜谱决定先后。
`agent(...)` 是请一个帮手做一件事。`pipeline(items, *stages)` 是默认:每块蛋糕自己走完各阶段,所以一块在装箱时,另一块可能还在搅拌。`parallel(...)` 是等齐——所有托盘都回来才往下——只有下一步真的需要全部结果时才值得,比如尝完再写评分表。
![Workflow 原语](images/workflow-primitives.svg)
旁边还有更轻的词:`phase` 在进度板上报站,`log` 喊一句短话,`workflow` 嵌一份更小的菜谱,`args` 是食材清单,`budget` 是还能烧多少烤箱分钟token
`agent(prompt, {schema, label, phase})` 是请一个帮手做一件事。带上 `schema`,答案会变成校验过的 JSON——下一阶段接得住的接口——第一次不对还给一次重试
`pipeline(items, *stages)` 是多阶段工作的默认:每块蛋糕自己走完各阶段,一块在装箱时,另一块可能还在搅拌。阶段之间没有等齐屏障。
`parallel(thunks)` 是等齐——所有托盘都回来才往下。只有下一步真的需要全部结果时才值得,比如尝完再写评分表。
旁边还有更轻的词:`phase` 报站,`log` 喊一句,嵌一层 `workflow``args` 是食材清单,`budget` 是烤箱分钟token
```python
# 每个审查维度自己走完 审计 → 验证。
@@ -88,25 +100,34 @@ results = await ctx.pipeline(DIMENSIONS, audit, verify)
confirmed = [f for r in results if r for f in r["confirmed"]]
```
## 会写菜谱之后
帮手失手时,舰队仍然温和。`parallel` 里失败的 thunk 在该槽位变成 `null`gather 本身不会拒绝;`pipeline` 里某个 stage 失败会把**那个 item** 置空并跳过后续 stage别的 item 继续往前。合并前小心过滤。
上面那些动词是面粉和火候。人们反复发明的,是少数几种*形状*——动态 agentic workflow 的常见模式。把它们想成工具箱,不是必点菜单。痛点出现再伸手;其余的留在挂板上
厨房暂停了呢?每次运行都有 `runId` 和磁盘上的 journal——按你**召唤**帮手的顺序记的笔记本。续跑从脚本开头走,回放**最长未改前缀**;碰到第一个改过或未完成的调用,之后全部实跑。所以真正的 JS 运行时禁止 `Date.now()``Math.random()`:时钟和骰子会让笔记本对不齐。这个 Python 演示不会完整沙箱那些——脚本仍写成确定性的吧
```text
journal: [A 好] [B 好] [C 好] [D 好]
续跑: A 命中 → B 命中 → C 改过 → D 实跑
```
## 会写菜谱之后——模式工具箱
动词是面粉和火候。人们反复发明的,是少数几种*形状*。把它们想成工具箱,不是必点菜单。
![六种 Workflow 模式](images/six-workflow-patterns.svg)
*人们反复发明的六种形状。脚本掌管拓扑;本课用 `agent` / `parallel` / `pipeline` / `phase` / journal 把每种形状说出来。*
*人们反复发明的六种形状。脚本掌管拓扑;本课用 `agent` / `parallel` / `pipeline` / `phase` / journal 把它们说出来。*
**Classify-And-Act分类再行动** 痛点:一个万金油帮手样样稀松。形状:分类器看一眼任务,再路由到专家 A、B 或 C。本课里多半是一次带 `schema``agent` 返回标签,脚本`if`/`match` 再叫对的后续 `agent`(或嵌一层 `workflow`)。每件东西其实都该同样处理时,就别用——路由只是仪式
**Classify-And-Act分类再行动** 痛点:一个万金油帮手样样稀松。形状:分类器看一眼,再路由到专家 A、B 或 C。本课一次带 `schema``agent` 返回标签,脚本分支到对的后续 `agent`(或嵌一层 `workflow`)。每件东西其实都该同样处理时,就别用。
**Fanout-And-Synthesize分发再汇总** 痛点:五十个文件塞不进一个疲倦的上下文,挤在一起还会串味。形状:拆开、多 agent 跑、在屏障处等齐、再合并。每件有自己阶段`pipeline`;下一步必须凑齐全部结果`parallel`;合并写在 gather 之后的普通 Python。三五个相关文件一趟就能看完时,就别用。
**Fanout-And-Synthesize分发再汇总** 痛点:五十个文件塞不进一个疲倦的上下文,挤在一起还会串味。形状:拆开、多跑、等齐、再合并。本课:每件有自己阶段用 `pipeline`;下一步必须凑齐全部结果用 `parallel`;合并写在 gather 之后的普通 Python。三五个相关文件一趟就能看完时就别用。
**Adversarial Verification对抗验证** 痛点:狐狸给鸡窝打分。形状:工人产出;独立验证者来反驳或施压;只留下幸存者。映射:一次生产用的 `agent`,再 `parallel` 一组验证 `agent`(最好带 `schema`),然后过滤。`phase` 标出 ReviewVerify。答错代价很低时就别用——不是每张便条都要法庭
**Adversarial Verification对抗验证** 痛点:狐狸给鸡窝打分。形状:工人产出;独立验证者来反驳;只留下幸存者。本课:一次生产 `agent`,再 `parallel` 一组验证 `agent`(最好带 schema然后过滤。`phase` 标出 Review 再 Verify。答错代价很低时就别用。
**Generate-And-Filter生成再过滤** 痛点:你要的是选项,不是第一个听起来机灵的念头。形状:许多生成器把想法倒进“量尺 + 去重”的过滤器;最好的留下,其余丢掉。映射`parallel` 生成,再在脚本里过滤(或一个带 schema 的裁判 `agent`)。生成很贵时journal/续跑特别有用。好答案空间本来就很小,就别用。
**Generate-And-Filter生成再过滤** 痛点:你要的是选项,不是第一个听起来机灵的念头。形状:许多生成器把想法倒进“量尺 + 去重”。本课`parallel` 生成,再在脚本里过滤(或一个裁判 `agent`)。生成很贵时 journal 特别有用。好答案空间本来就很小,就别用。
**Tournament锦标赛** 痛点:品味和排序上,绝对分数糊成一团(“这个名字有多好?”)。形状:两两比较的裁判、淘汰支架、冠军——相对判断胜过孤独打分。映射:脚本里多轮 `parallel` pairwise 裁判 `agent`,直到剩一个。清晰量尺一趟就能选出赢家时,就别用。
**Tournament锦标赛** 痛点:品味和排序上,绝对分数糊成一团。形状:两两比较、淘汰支架、冠军——相对判断胜过孤独打分。本课:脚本里多轮 pairwise 裁判 `agent`,直到剩一个。清晰量尺一趟就能选出赢家时,就别用。
**Loop Until Done接到完为止** 痛点:你不知道矿里还要挖几轮。形状:只要“还有新发现?”为是就继续派工;连续空轮或完成条件出现就停。映射`while` 包着 `agent`/`parallel`带 schema 的停止检查,再加硬性 `budget`,免得循环把家吃空。长挖可能暂停时配上 journal 续跑。工作量已知时,固定 `pipeline` 更简单也更安全
**Loop Until Done接到完为止** 痛点:你不知道矿里还要挖几轮。形状:只要“还有新发现?”为是就继续派工;连续空轮就停。本课`while` 包着 `agent`/`parallel`,带 schema 的停止检查,再加硬性 `budget`。长挖可能暂停时配上 journal。工作量已知时固定 `pipeline` 更简单。
几种有了面孔之后,工具箱一眼就能看清:
@@ -116,51 +137,10 @@ confirmed = [f for r in results if r for f in r["confirmed"]]
| Fanout-And-Synthesize | `pipeline` / `parallel` → 合并 | 许多干净桌子,再一份摘要 |
| Adversarial Verification | 产出 → `parallel(verify)` → 过滤 | 答错很贵 |
| Generate-And-Filter | `parallel(gens)` → 量尺过滤 | 先要选项,再要品味 |
| Tournament | 两两裁判 `agent` 搭支架 | 排序/品味却没有锋利刻度 |
| Loop Until Done | `while` + 停止检查 + `budget` | 埋着不知多少活 |
| Tournament | 两两裁判 `agent` | 排序/品味却没有锋利刻度 |
| Loop Until Done | `while` + 停止 + `budget` | 埋着不知多少活 |
组合是常态。深度调研常常叠成:分发 → 过滤 → 验证 → 汇总。我们的示例,已经是两个音符的一小段和弦。
## 让下一阶段接得住的答案
帮手若回来写散文,下一阶段很难把 finding 和 verdict 对齐。传入 `schema`。运行时要 JSON、做校验并给**一次**重试。再不对,这次调用报错——而舰队在失败时怎样仍然温和,下一节就说到。
```python
out = await ctx.agent(
f"检查这段变更里有没有{dimension}相关的问题:\n{changes}",
schema=FINDINGS_SCHEMA,
label=f"audit:{dimension}",
)
```
跟你聊天可以继续用自然语言。流水线需要接口对得上。
## 一个托盘糊了的时候
不能因为一个帮手烤箱失手,整支队伍停工。
`parallel` 里,失败的 thunk 在该槽位变成 `null` / `None`gather 本身不会拒绝。在 `pipeline` 里,某个 stage 失败会把**那个 item** 置成空,并跳过它后面的 stage别的 item 继续往前走。合并前小心过滤——`if r`,在 JS 里常见 `.filter(Boolean)`
```python
verdicts = await ctx.parallel([...]) # 有些格子可能是 None
confirmed = [
f for f, v in zip(findings, verdicts)
if v and v.get("isReal")
]
```
## 一本可以重开的笔记本
每次运行都有一个 `runId`。每个 `agent()` 结束,磁盘上的 journal 就多一行——按你**召唤**帮手的顺序记,而不是按他们从烤箱回来的先后。
续跑(`resume_from_run_id` / `resumeFromRunId`)仍从脚本开头走,只是更客气:按调用顺序,与下一条 journal 比对;最长未改前缀直接从缓存回放;碰到第一个改过或未完成的调用,前缀断开——之后全部实跑,即便笔记本更后面还躺着旧 key也不能跳过裂缝偷懒命中。
这也是真正的 JS workflow 运行时禁止 `Date.now()``Math.random()` 和裸 `new Date()` 的原因。时钟和骰子会让 prompt 或调用顺序晃一下,笔记本就对不齐了。这个 Python 演示不会完整沙箱那些东西。脚本仍写成确定性的吧。
```text
journal: [A ✓] [B ✓] [C ✓] [D ✓]
续跑: A 命中 → B 命中 → C 改过 → D 实跑
```
组合是常态。深度调研常常叠成:分发 → 过滤 → 验证 → 汇总。分流有时会加一层 **quarantine隔离区**:读不可信内容的 agent 只读;只有受信任的 actor 才开 PR。我们的示例,是两个音符的一小段和弦。
## 跟着 `review-changes` 走一圈——一种组合
@@ -175,7 +155,7 @@ style ── 审计 ── 验证 ──┘
└── 每条 finding怀疑式验证 ──┘
```
`pipeline(DIMENSIONS, audit, verify)` 给每个维度自己的桌子,正确性的闲聊不至于淌进安全性`verify` 里对验证 agent 做 `parallel`,就是对抗那一和弦。普通的列表过滤是汇总。`phase` 标出 Review 再 Verifyjournal 记住每次 `agent()`,暂停也不会重做审计。
`pipeline(DIMENSIONS, audit, verify)` 给每个维度自己的桌子。`verify` 里对验证 agent 做 `parallel`,就是对抗那一和弦。普通的列表过滤是汇总。`phase` 标出 Review 再 Verifyjournal 记住每次 `agent()`,暂停也不会重做审计。
那三种走偏,会感觉自己最爱的座位被撤了:舰队不能在两个维度后收工,作者不当裁判,拓扑也不会在中途漂移。
@@ -188,13 +168,14 @@ async def sample_workflow(ctx, args):
return {"confirmed": confirmed}
```
## 挂在 s15 上,并不取代它
<details>
<summary>怎样挂在 s15 上,却不取代它</summary>
s15 仍是宿主循环。s16 只多了一个名叫 `Workflow` 的工具。你(或模型)报一个已保存的名字;适配器找到脚本再跑。
在真正的产品里,这次运行可以待在后台、带着通知,会话照样能应你。教学 CLI 把 `demo` / `resume` 放在前台,好让你看清阶段和缓存命中。想法相同;简化之处我们会明说。
在真正的产品里,这次运行可以待在后台、带着通知,会话照样能应你。教学 CLI 把 `demo` / `resume` 放在前台,好让你看清阶段和缓存命中。想法相同;简化之处我们会明说。主循环多借一把工具,就像借 `bash``task`
主循环不会变成 workflow 引擎。它只是多借一把工具,就像借 `bash``task`
</details>
## 转一转这颗宝石:谁握着计划?
@@ -230,4 +211,4 @@ python s16_workflow_runtime/code.py resume # 同一 runId期待缓存命中
s16 讲一批活怎么跑。[s17 Goal Loop](../s17_goal_loop/) 在门口问另一个问题:该停,还是再来一轮?可重复的菜谱若还需要硬性的“做完”,可以和它一起用。
<!-- translation-sync: zh@v14, en@v14, ja@v14 -->
<!-- translation-sync: zh@v15, en@v15, ja@v15 -->

View File

@@ -1,12 +1,20 @@
#!/usr/bin/env python3
"""
s16: Workflow Runtime — a teaching model of Claude Code Dynamic Workflows.
s16: Workflow Runtime — teaching model of Dynamic Agentic Workflows.
Claude Code's Workflow tool accepts script / scriptPath / name / args /
resumeFromRunId. The model can write a JavaScript orchestration script for the
task (dynamic), or rerun a saved one by name. This lesson is a small Python
runtime that shows the same ideas line-by-line. The demo registers one saved
workflow by name; we do not embed a JS interpreter.
Idea: move orchestration from intelligence to structure. Subagents judge;
the script owns topology (loops, fan-out, merge). Intermediate results live
in variables + a journal, not in the host conversation.
Claude Code accepts script / scriptPath / name / args / resumeFromRunId.
Dynamic = model writes a JS harness for this task; Saved = rerun by name.
This lesson is a small Python runtime with the same ideas, line-readable.
Demo registers one saved workflow; we do not embed a JS interpreter.
Fidelity notes baked into this file:
- parallel/pipeline failures isolate to null slots (fleet continues)
- resume = longest unchanged agent() call-order prefix
- real JS runtimes ban Date.now/Math.random so journals stay aligned
Run:
python s16_workflow_runtime/code.py

View File

@@ -0,0 +1,63 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 520" font-family="system-ui, -apple-system, sans-serif" role="img" aria-labelledby="title desc">
<title id="title">Static harness vs dynamic workflow</title>
<desc id="desc">Same question, two harnesses: a generic static pipeline versus a tailor-made dynamic workflow that reads local code and runs an adversarial check.</desc>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#888"/></marker>
<marker id="ag" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#22c55e"/></marker>
</defs>
<rect width="960" height="520" rx="8" fill="#fff"/>
<text x="480" y="28" text-anchor="middle" fill="#1a1a1a" font-size="18" font-weight="700">Same question, two harnesses</text>
<rect x="220" y="48" width="520" height="36" rx="6" fill="#fafafa" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="480" y="71" text-anchor="middle" fill="#1a1a1a" font-size="12">Should we migrate our checkout service to a new provider?</text>
<line x1="350" y1="84" x2="200" y2="120" stroke="#888" stroke-width="1.3" marker-end="url(#a)"/>
<line x1="610" y1="84" x2="760" y2="120" stroke="#22c55e" stroke-width="1.5" marker-end="url(#ag)"/>
<!-- Static -->
<rect x="40" y="128" width="420" height="360" rx="8" fill="#fff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="250" y="154" text-anchor="middle" fill="#1a1a1a" font-size="14" font-weight="700">Static harness</text>
<text x="250" y="172" text-anchor="middle" fill="#888" font-size="11">written ahead - must work for every edge case</text>
<rect x="120" y="190" width="260" height="36" rx="4" fill="#fafafa" stroke="#888"/>
<text x="250" y="212" text-anchor="middle" fill="#1a1a1a" font-size="12">turn into 5 web searches</text>
<line x1="250" y1="226" x2="250" y2="244" stroke="#888" marker-end="url(#a)"/>
<rect x="120" y="246" width="260" height="36" rx="4" fill="#fafafa" stroke="#888"/>
<text x="250" y="268" text-anchor="middle" fill="#1a1a1a" font-size="12">fetch top results</text>
<line x1="250" y1="282" x2="250" y2="300" stroke="#888" marker-end="url(#a)"/>
<rect x="120" y="302" width="260" height="36" rx="4" fill="#fafafa" stroke="#888"/>
<text x="250" y="324" text-anchor="middle" fill="#1a1a1a" font-size="12">verify</text>
<line x1="250" y1="338" x2="250" y2="356" stroke="#888" marker-end="url(#a)"/>
<rect x="120" y="358" width="260" height="36" rx="4" fill="#fafafa" stroke="#888"/>
<text x="250" y="380" text-anchor="middle" fill="#1a1a1a" font-size="12">summarize</text>
<line x1="250" y1="394" x2="250" y2="412" stroke="#888" marker-end="url(#a)"/>
<rect x="120" y="414" width="260" height="40" rx="4" fill="#1a1a1a"/>
<text x="250" y="439" text-anchor="middle" fill="#fff" font-size="12" font-weight="600">a generic research report</text>
<!-- Dynamic -->
<rect x="500" y="128" width="420" height="360" rx="8" fill="#fff" stroke="#22c55e" stroke-width="1.8"/>
<text x="710" y="154" text-anchor="middle" fill="#1a1a1a" font-size="14" font-weight="700">Dynamic workflow</text>
<text x="710" y="172" text-anchor="middle" fill="#888" font-size="11">written for THIS task - then optionally saved</text>
<rect x="560" y="190" width="300" height="36" rx="4" fill="#fafafa" stroke="#1a1a1a"/>
<text x="710" y="212" text-anchor="middle" fill="#1a1a1a" font-size="12">read our billing code</text>
<rect x="560" y="234" width="88" height="24" rx="3" fill="#fff" stroke="#22c55e" stroke-dasharray="3,2"/>
<text x="604" y="250" text-anchor="middle" fill="#1a1a1a" font-size="9">billing/</text>
<rect x="656" y="234" width="88" height="24" rx="3" fill="#fff" stroke="#22c55e" stroke-dasharray="3,2"/>
<text x="700" y="250" text-anchor="middle" fill="#1a1a1a" font-size="9">webhooks/</text>
<rect x="752" y="234" width="88" height="24" rx="3" fill="#fff" stroke="#22c55e" stroke-dasharray="3,2"/>
<text x="796" y="250" text-anchor="middle" fill="#1a1a1a" font-size="9">taxes/</text>
<line x1="640" y1="258" x2="600" y2="286" stroke="#888" marker-end="url(#a)"/>
<line x1="780" y1="258" x2="820" y2="286" stroke="#888" marker-end="url(#a)"/>
<rect x="520" y="288" width="180" height="52" rx="4" fill="#fafafa" stroke="#22c55e"/>
<text x="610" y="310" text-anchor="middle" fill="#1a1a1a" font-size="10">check features vs</text>
<text x="610" y="326" text-anchor="middle" fill="#1a1a1a" font-size="10">new provider docs</text>
<rect x="720" y="288" width="180" height="52" rx="4" fill="#fafafa" stroke="#22c55e"/>
<text x="810" y="310" text-anchor="middle" fill="#1a1a1a" font-size="10">price at our</text>
<text x="810" y="326" text-anchor="middle" fill="#1a1a1a" font-size="10">transaction volume</text>
<line x1="610" y1="340" x2="670" y2="368" stroke="#888" marker-end="url(#a)"/>
<line x1="810" y1="340" x2="750" y2="368" stroke="#888" marker-end="url(#a)"/>
<rect x="560" y="370" width="300" height="40" rx="4" fill="#fff" stroke="#22c55e" stroke-dasharray="4,2"/>
<text x="710" y="395" text-anchor="middle" fill="#1a1a1a" font-size="11">devil's advocate: strongest case against</text>
<line x1="710" y1="410" x2="710" y2="428" stroke="#22c55e" marker-end="url(#ag)"/>
<rect x="560" y="430" width="300" height="40" rx="4" fill="#1a1a1a"/>
<text x="710" y="455" text-anchor="middle" fill="#fff" font-size="12" font-weight="600">a specific recommendation</text>
<text x="480" y="508" text-anchor="middle" fill="#888" font-size="10">Static aims to fit every case. Dynamic cuts cloth for this one - then save if the fit is good.</text>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -1,159 +1,108 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 720" font-family="system-ui, -apple-system, sans-serif" role="img" aria-labelledby="title description">
<title id="title">Six Workflow Patterns</title>
<desc id="description">A toolbox of six common dynamic workflow shapes: Classify-And-Act, Fanout-And-Synthesize, Adversarial Verification, Generate-And-Filter, Tournament, and Loop Until Done.</desc>
<desc id="description">Toolbox of six common dynamic workflow shapes.</desc>
<defs>
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#888888"/>
</marker>
<marker id="arr-g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#22c55e"/>
</marker>
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#888"/></marker>
<marker id="arr-g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#22c55e"/></marker>
</defs>
<rect width="960" height="720" rx="8" fill="#ffffff"/>
<rect width="960" height="720" rx="8" fill="#fff"/>
<text x="480" y="28" text-anchor="middle" fill="#1a1a1a" font-size="18" font-weight="700">Six Workflow Patterns</text>
<text x="480" y="48" text-anchor="middle" fill="#888888" font-size="11">a toolbox - not a mandatory menu. Compose with agent / parallel / pipeline</text>
<text x="480" y="48" text-anchor="middle" fill="#888" font-size="11">a toolbox - not a mandatory menu. Compose with agent / parallel / pipeline</text>
<rect x="20" y="64" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="330" y="64" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="640" y="64" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="20" y="380" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="330" y="380" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="640" y="380" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<!-- Panel frames: 2x3 -->
<!-- Row 1 -->
<rect x="20" y="64" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<rect x="330" y="64" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<rect x="640" y="64" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<!-- Row 2 -->
<rect x="20" y="380" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<rect x="330" y="380" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<rect x="640" y="380" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="170" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">1. Classify-And-Act</text>
<rect x="120" y="110" width="100" height="28" rx="4" fill="#fafafa" stroke="#1a1a1a"/><text x="170" y="128" text-anchor="middle" fill="#1a1a1a" font-size="11">task</text>
<line x1="170" y1="138" x2="170" y2="158" stroke="#888" marker-end="url(#arr)"/>
<path d="M170 162 L192 184 L170 206 L148 184 Z" fill="#fff" stroke="#22c55e"/><text x="170" y="188" text-anchor="middle" fill="#1a1a1a" font-size="9">classifier</text>
<line x1="152" y1="196" x2="90" y2="230" stroke="#888" marker-end="url(#arr)"/>
<line x1="170" y1="206" x2="170" y2="230" stroke="#22c55e" stroke-width="1.8" marker-end="url(#arr-g)"/>
<line x1="188" y1="196" x2="250" y2="230" stroke="#888" marker-end="url(#arr)"/>
<circle cx="90" cy="258" r="22" fill="#fff" stroke="#1a1a1a"/><text x="90" y="262" text-anchor="middle" fill="#1a1a1a" font-size="11">A</text>
<circle cx="170" cy="258" r="22" fill="#fff" stroke="#22c55e" stroke-width="1.8"/><text x="170" y="262" text-anchor="middle" fill="#1a1a1a" font-size="11">B</text>
<circle cx="250" cy="258" r="22" fill="#fff" stroke="#1a1a1a"/><text x="250" y="262" text-anchor="middle" fill="#1a1a1a" font-size="11">C</text>
<text x="170" y="310" text-anchor="middle" fill="#888" font-size="10">route to the right specialist</text>
<text x="170" y="330" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">agent -&gt; branch -&gt; agent</text>
<!-- ===== 1. Classify-And-Act ===== -->
<text x="170" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Classify-And-Act</text>
<rect x="120" y="108" width="100" height="28" rx="4" fill="#fafafa" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="170" y="126" text-anchor="middle" fill="#1a1a1a" font-size="11">task</text>
<line x1="170" y1="136" x2="170" y2="158" stroke="#888888" stroke-width="1.5" marker-end="url(#arr)"/>
<path d="M170 162 L190 182 L170 202 L150 182 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="170" y="186" text-anchor="middle" fill="#1a1a1a" font-size="9">classifier</text>
<line x1="150" y1="192" x2="80" y2="230" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="170" y1="202" x2="170" y2="230" stroke="#22c55e" stroke-width="1.8" marker-end="url(#arr-g)"/>
<line x1="190" y1="192" x2="260" y2="230" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<circle cx="80" cy="255" r="22" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="80" y="258" text-anchor="middle" fill="#1a1a1a" font-size="10">A</text>
<circle cx="170" cy="255" r="22" fill="#ffffff" stroke="#22c55e" stroke-width="1.8"/>
<text x="170" y="258" text-anchor="middle" fill="#1a1a1a" font-size="10">B</text>
<circle cx="260" cy="255" r="22" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="260" y="258" text-anchor="middle" fill="#1a1a1a" font-size="10">C</text>
<text x="170" y="310" text-anchor="middle" fill="#888888" font-size="10">route to the right specialist</text>
<text x="170" y="328" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">agent ' branch ' agent</text>
<!-- ===== 2. Fanout-And-Synthesize ===== -->
<text x="480" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Fanout-And-Synthesize</text>
<rect x="430" y="108" width="100" height="28" rx="4" fill="#fafafa" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="480" y="126" text-anchor="middle" fill="#1a1a1a" font-size="11">task</text>
<line x1="450" y1="136" x2="400" y2="168" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="470" y1="136" x2="450" y2="168" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="490" y1="136" x2="510" y2="168" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="510" y1="136" x2="560" y2="168" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<circle cx="400" cy="190" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="450" cy="190" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="510" cy="190" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="560" cy="190" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<line x1="400" y1="206" x2="400" y2="232" stroke="#888888" stroke-width="1.2"/>
<line x1="450" y1="206" x2="450" y2="232" stroke="#888888" stroke-width="1.2"/>
<line x1="510" y1="206" x2="510" y2="232" stroke="#888888" stroke-width="1.2"/>
<line x1="560" y1="206" x2="560" y2="232" stroke="#888888" stroke-width="1.2"/>
<text x="480" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">2. Fanout-And-Synthesize</text>
<rect x="430" y="110" width="100" height="28" rx="4" fill="#fafafa" stroke="#1a1a1a"/><text x="480" y="128" text-anchor="middle" fill="#1a1a1a" font-size="11">task</text>
<line x1="450" y1="138" x2="400" y2="168" stroke="#888" marker-end="url(#arr)"/>
<line x1="470" y1="138" x2="450" y2="168" stroke="#888" marker-end="url(#arr)"/>
<line x1="490" y1="138" x2="510" y2="168" stroke="#888" marker-end="url(#arr)"/>
<line x1="510" y1="138" x2="560" y2="168" stroke="#888" marker-end="url(#arr)"/>
<circle cx="400" cy="190" r="16" fill="#fff" stroke="#1a1a1a"/>
<circle cx="450" cy="190" r="16" fill="#fff" stroke="#1a1a1a"/>
<circle cx="510" cy="190" r="16" fill="#fff" stroke="#1a1a1a"/>
<circle cx="560" cy="190" r="16" fill="#fff" stroke="#1a1a1a"/>
<line x1="400" y1="206" x2="400" y2="232" stroke="#888"/>
<line x1="450" y1="206" x2="450" y2="232" stroke="#888"/>
<line x1="510" y1="206" x2="510" y2="232" stroke="#888"/>
<line x1="560" y1="206" x2="560" y2="232" stroke="#888"/>
<line x1="400" y1="232" x2="560" y2="232" stroke="#22c55e" stroke-width="3"/>
<text x="480" y="250" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">barrier</text>
<line x1="480" y1="252" x2="480" y2="272" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<circle cx="480" cy="298" r="22" fill="#ffffff" stroke="#22c55e" stroke-width="1.8"/>
<text x="480" y="302" text-anchor="middle" fill="#1a1a1a" font-size="9">synthesize</text>
<text x="480" y="340" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">parallel / pipeline ' merge</text>
<text x="480" y="252" text-anchor="middle" fill="#22c55e" font-size="10" font-weight="600">barrier</text>
<line x1="480" y1="256" x2="480" y2="274" stroke="#22c55e" marker-end="url(#arr-g)"/>
<circle cx="480" cy="300" r="22" fill="#fff" stroke="#22c55e" stroke-width="1.8"/><text x="480" y="304" text-anchor="middle" fill="#1a1a1a" font-size="9">synthesize</text>
<text x="480" y="340" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">parallel / pipeline -&gt; merge</text>
<!-- ===== 3. Adversarial Verification ===== -->
<text x="790" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Adversarial Verification</text>
<circle cx="720" cy="200" r="36" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.5"/>
<text x="720" y="204" text-anchor="middle" fill="#1a1a1a" font-size="11">worker</text>
<line x1="756" y1="180" x2="850" y2="140" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="756" y1="200" x2="850" y2="200" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<line x1="756" y1="220" x2="850" y2="260" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<circle cx="875" cy="140" r="20" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="875" y="144" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<circle cx="875" cy="200" r="20" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="875" y="204" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<circle cx="875" cy="260" r="20" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="875" y="264" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<text x="790" y="310" text-anchor="middle" fill="#888888" font-size="10">independents stress-test the work</text>
<text x="790" y="328" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">agent ' parallel(verifiers)</text>
<text x="790" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">3. Adversarial Verification</text>
<circle cx="720" cy="200" r="34" fill="#fff" stroke="#1a1a1a" stroke-width="1.5"/><text x="720" y="204" text-anchor="middle" fill="#1a1a1a" font-size="11">worker</text>
<line x1="754" y1="180" x2="848" y2="140" stroke="#888" marker-end="url(#arr)"/>
<line x1="754" y1="200" x2="848" y2="200" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<line x1="754" y1="220" x2="848" y2="260" stroke="#888" marker-end="url(#arr)"/>
<circle cx="872" cy="140" r="20" fill="#fff" stroke="#1a1a1a"/><text x="872" y="144" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<circle cx="872" cy="200" r="20" fill="#fff" stroke="#22c55e" stroke-width="1.5"/><text x="872" y="204" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<circle cx="872" cy="260" r="20" fill="#fff" stroke="#1a1a1a"/><text x="872" y="264" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<text x="790" y="310" text-anchor="middle" fill="#888" font-size="10">independents stress-test the work</text>
<text x="790" y="330" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">agent -&gt; parallel(verifiers)</text>
<!-- ===== 4. Generate-And-Filter ===== -->
<text x="170" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Generate-And-Filter</text>
<circle cx="70" cy="460" r="18" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="70" cy="510" r="18" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="70" cy="560" r="18" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="70" y="464" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<text x="70" y="514" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<text x="70" y="564" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<rect x="105" y="448" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="460" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="498" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="510" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="548" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="560" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<line x1="120" y1="460" x2="155" y2="510" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="120" y1="510" x2="155" y2="510" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="120" y1="560" x2="155" y2="510" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<rect x="160" y="488" width="70" height="44" rx="4" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="195" y="506" text-anchor="middle" fill="#1a1a1a" font-size="10" font-weight="600">filter</text>
<text x="195" y="520" text-anchor="middle" fill="#888888" font-size="8">rubric+dedupe</text>
<line x1="230" y1="500" x2="260" y2="470" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<line x1="230" y1="520" x2="260" y2="550" stroke="#888888" stroke-width="1.2" stroke-dasharray="3,2" marker-end="url(#arr)"/>
<rect x="262" y="452" width="40" height="28" rx="3" fill="#fafafa" stroke="#22c55e" stroke-width="1.2"/>
<text x="282" y="470" text-anchor="middle" fill="#1a1a1a" font-size="9">best</text>
<rect x="262" y="538" width="40" height="28" rx="3" fill="#fafafa" stroke="#d0d0d0" stroke-width="1.2"/>
<text x="282" y="556" text-anchor="middle" fill="#888888" font-size="8">drop</text>
<text x="170" y="620" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">parallel(gens) ' filter</text>
<text x="170" y="638" text-anchor="middle" fill="#888888" font-size="10">keep what survives the test</text>
<text x="170" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">4. Generate-And-Filter</text>
<circle cx="70" cy="460" r="18" fill="#fff" stroke="#1a1a1a"/><text x="70" y="464" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<circle cx="70" cy="510" r="18" fill="#fff" stroke="#1a1a1a"/><text x="70" y="514" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<circle cx="70" cy="560" r="18" fill="#fff" stroke="#1a1a1a"/><text x="70" y="564" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<line x1="90" y1="460" x2="150" y2="508" stroke="#888" marker-end="url(#arr)"/>
<line x1="90" y1="510" x2="150" y2="510" stroke="#888" marker-end="url(#arr)"/>
<line x1="90" y1="560" x2="150" y2="512" stroke="#888" marker-end="url(#arr)"/>
<rect x="155" y="488" width="72" height="44" rx="4" fill="#fff" stroke="#22c55e"/><text x="191" y="506" text-anchor="middle" fill="#1a1a1a" font-size="10" font-weight="600">filter</text><text x="191" y="520" text-anchor="middle" fill="#888" font-size="8">rubric+dedupe</text>
<line x1="227" y1="500" x2="255" y2="470" stroke="#22c55e" marker-end="url(#arr-g)"/>
<line x1="227" y1="520" x2="255" y2="550" stroke="#888" stroke-dasharray="3,2" marker-end="url(#arr)"/>
<rect x="258" y="454" width="42" height="26" rx="3" fill="#fafafa" stroke="#22c55e"/><text x="279" y="471" text-anchor="middle" fill="#1a1a1a" font-size="9">best</text>
<rect x="258" y="538" width="42" height="26" rx="3" fill="#fafafa" stroke="#d0d0d0"/><text x="279" y="555" text-anchor="middle" fill="#888" font-size="8">drop</text>
<text x="170" y="610" text-anchor="middle" fill="#888" font-size="10">keep what survives the test</text>
<text x="170" y="630" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">parallel(gens) -&gt; filter</text>
<!-- ===== 5. Tournament ===== -->
<text x="480" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Tournament</text>
<circle cx="380" cy="450" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="430" cy="450" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="530" cy="450" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="580" cy="450" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="380" y="454" text-anchor="middle" fill="#1a1a1a" font-size="8">a</text>
<text x="430" y="454" text-anchor="middle" fill="#1a1a1a" font-size="8">b</text>
<text x="530" y="454" text-anchor="middle" fill="#1a1a1a" font-size="8">c</text>
<text x="580" y="454" text-anchor="middle" fill="#1a1a1a" font-size="8">d</text>
<line x1="380" y1="466" x2="395" y2="495" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="430" y1="466" x2="415" y2="495" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="530" y1="466" x2="545" y2="495" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="580" y1="466" x2="565" y2="495" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<path d="M405 500 L420 515 L405 530 L390 515 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.3"/>
<path d="M555 500 L570 515 L555 530 L540 515 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.3"/>
<text x="405" y="519" text-anchor="middle" fill="#1a1a1a" font-size="7">judge</text>
<text x="555" y="519" text-anchor="middle" fill="#1a1a1a" font-size="7">judge</text>
<line x1="405" y1="530" x2="460" y2="560" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="555" y1="530" x2="500" y2="560" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<path d="M480 564 L498 582 L480 600 L462 582 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="480" y="586" text-anchor="middle" fill="#1a1a1a" font-size="8">final</text>
<line x1="480" y1="600" x2="480" y2="618" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<rect x="452" y="620" width="56" height="20" rx="3" fill="#fafafa" stroke="#22c55e" stroke-width="1.2"/>
<text x="480" y="634" text-anchor="middle" fill="#1a1a1a" font-size="9">winner</text>
<text x="480" y="662" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">pairwise agent judges</text>
<text x="480" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">5. Tournament</text>
<circle cx="380" cy="450" r="16" fill="#fff" stroke="#1a1a1a"/><text x="380" y="454" text-anchor="middle" fill="#1a1a1a" font-size="9">a</text>
<circle cx="430" cy="450" r="16" fill="#fff" stroke="#1a1a1a"/><text x="430" y="454" text-anchor="middle" fill="#1a1a1a" font-size="9">b</text>
<circle cx="530" cy="450" r="16" fill="#fff" stroke="#1a1a1a"/><text x="530" y="454" text-anchor="middle" fill="#1a1a1a" font-size="9">c</text>
<circle cx="580" cy="450" r="16" fill="#fff" stroke="#1a1a1a"/><text x="580" y="454" text-anchor="middle" fill="#1a1a1a" font-size="9">d</text>
<line x1="380" y1="466" x2="395" y2="494" stroke="#888" marker-end="url(#arr)"/>
<line x1="430" y1="466" x2="415" y2="494" stroke="#888" marker-end="url(#arr)"/>
<line x1="530" y1="466" x2="545" y2="494" stroke="#888" marker-end="url(#arr)"/>
<line x1="580" y1="466" x2="565" y2="494" stroke="#888" marker-end="url(#arr)"/>
<path d="M405 498 L420 514 L405 530 L390 514 Z" fill="#fff" stroke="#22c55e"/><text x="405" y="518" text-anchor="middle" fill="#1a1a1a" font-size="7">judge</text>
<path d="M555 498 L570 514 L555 530 L540 514 Z" fill="#fff" stroke="#22c55e"/><text x="555" y="518" text-anchor="middle" fill="#1a1a1a" font-size="7">judge</text>
<line x1="405" y1="530" x2="460" y2="558" stroke="#888" marker-end="url(#arr)"/>
<line x1="555" y1="530" x2="500" y2="558" stroke="#888" marker-end="url(#arr)"/>
<path d="M480 562 L498 580 L480 598 L462 580 Z" fill="#fff" stroke="#22c55e"/><text x="480" y="584" text-anchor="middle" fill="#1a1a1a" font-size="8">final</text>
<line x1="480" y1="598" x2="480" y2="616" stroke="#22c55e" marker-end="url(#arr-g)"/>
<rect x="452" y="618" width="56" height="20" rx="3" fill="#fafafa" stroke="#22c55e"/><text x="480" y="632" text-anchor="middle" fill="#1a1a1a" font-size="9">winner</text>
<text x="480" y="660" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">pairwise agent judges</text>
<!-- ===== 6. Loop Until Done ===== -->
<text x="790" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Loop Until Done</text>
<circle cx="790" cy="470" r="28" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.5"/>
<text x="790" y="474" text-anchor="middle" fill="#1a1a1a" font-size="11">agent</text>
<line x1="790" y1="498" x2="790" y2="525" stroke="#888888" stroke-width="1.5" marker-end="url(#arr)"/>
<path d="M790 530 L812 552 L790 574 L768 552 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="790" y="548" text-anchor="middle" fill="#1a1a1a" font-size="8">new?</text>
<text x="790" y="560" text-anchor="middle" fill="#1a1a1a" font-size="8">findings</text>
<line x1="812" y1="560" x2="870" y2="560" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<rect x="872" y="548" width="40" height="24" rx="3" fill="#1a1a1a"/>
<text x="892" y="564" text-anchor="middle" fill="#ffffff" font-size="9">done</text>
<path d="M768 548 C720 548 720 470 762 470" fill="none" stroke="#22c55e" stroke-width="1.8" marker-end="url(#arr-g)"/>
<text x="730" y="512" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">yes - </text>
<text x="730" y="524" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">spawn</text>
<text x="790" y="620" text-anchor="middle" fill="#888888" font-size="10">stop when dry rounds appear</text>
<text x="790" y="638" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">while + agent + budget</text>
<text x="790" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">6. Loop Until Done</text>
<circle cx="790" cy="470" r="28" fill="#fff" stroke="#1a1a1a" stroke-width="1.5"/><text x="790" y="474" text-anchor="middle" fill="#1a1a1a" font-size="11">agent</text>
<line x1="790" y1="498" x2="790" y2="524" stroke="#888" marker-end="url(#arr)"/>
<path d="M790 528 L814 552 L790 576 L766 552 Z" fill="#fff" stroke="#22c55e"/><text x="790" y="548" text-anchor="middle" fill="#1a1a1a" font-size="8">new?</text><text x="790" y="562" text-anchor="middle" fill="#1a1a1a" font-size="8">findings</text>
<line x1="814" y1="560" x2="868" y2="560" stroke="#888" marker-end="url(#arr)"/>
<rect x="870" y="548" width="40" height="24" rx="3" fill="#1a1a1a"/><text x="890" y="564" text-anchor="middle" fill="#fff" font-size="9">done</text>
<path d="M766 548 C720 548 720 470 762 470" fill="none" stroke="#22c55e" stroke-width="1.8" marker-end="url(#arr-g)"/>
<text x="728" y="512" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">yes -</text>
<text x="728" y="526" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">spawn</text>
<text x="790" y="620" text-anchor="middle" fill="#888" font-size="10">stop when dry rounds appear</text>
<text x="790" y="640" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">while + agent + budget</text>
<text x="480" y="702" text-anchor="middle" fill="#888888" font-size="10">Script owns the topology; agents judge inside each step.</text>
<text x="480" y="702" text-anchor="middle" fill="#888" font-size="10">Script owns the topology; agents judge inside each step.</text>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,68 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 560" font-family="system-ui, -apple-system, sans-serif" role="img" aria-labelledby="title desc">
<title id="title">Workflow primitives: agent, parallel, pipeline</title>
<desc id="desc">Three kitchen verbs of a workflow script: one helper, a barrier of many helpers, and per-item stages without a barrier.</desc>
<defs>
<marker id="p" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#888"/></marker>
<marker id="pg" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#22c55e"/></marker>
</defs>
<rect width="960" height="560" rx="8" fill="#fff"/>
<text x="480" y="28" text-anchor="middle" fill="#1a1a1a" font-size="18" font-weight="700">Three verbs the script speaks</text>
<text x="480" y="48" text-anchor="middle" fill="#888" font-size="12">agent asks a helper; parallel waits for every tray; pipeline lets each cake finish its own stages</text>
<!-- agent -->
<rect x="20" y="70" width="920" height="170" rx="8" fill="#fff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="40" y="98" fill="#1a1a1a" font-size="15" font-weight="700" font-family="monospace">agent(prompt, opts?)</text>
<text x="40" y="120" fill="#888" font-size="12">One helper. One job. Optional schema makes the answer machine-readable.</text>
<rect x="40" y="140" width="520" height="78" rx="4" fill="#111" />
<text x="56" y="164" fill="#86efac" font-size="12" font-family="monospace">const bugs = await agent("audit auth.ts", {</text>
<text x="56" y="184" fill="#e5e5e5" font-size="12" font-family="monospace"> schema: BugList, label: "audit:auth", phase: "Review"</text>
<text x="56" y="204" fill="#86efac" font-size="12" font-family="monospace">})</text>
<rect x="600" y="140" width="310" height="78" rx="4" fill="#fafafa" stroke="#22c55e"/>
<text x="755" y="170" text-anchor="middle" fill="#1a1a1a" font-size="12">returns text, or validated JSON</text>
<text x="755" y="192" text-anchor="middle" fill="#888" font-size="11">journal records each call in order</text>
<text x="755" y="210" text-anchor="middle" fill="#888" font-size="11">resume replays unchanged prefix</text>
<!-- parallel -->
<rect x="20" y="260" width="450" height="260" rx="8" fill="#fff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="40" y="288" fill="#1a1a1a" font-size="15" font-weight="700" font-family="monospace">parallel(thunks)</text>
<text x="40" y="310" fill="#888" font-size="12">Fan out. Barrier - wait for all.</text>
<circle cx="100" cy="360" r="18" fill="#fff" stroke="#1a1a1a"/>
<circle cx="170" cy="360" r="18" fill="#fff" stroke="#1a1a1a"/>
<circle cx="240" cy="360" r="18" fill="#fff" stroke="#1a1a1a"/>
<circle cx="310" cy="360" r="18" fill="#fff" stroke="#1a1a1a"/>
<line x1="100" y1="378" x2="100" y2="410" stroke="#888"/>
<line x1="170" y1="378" x2="170" y2="410" stroke="#888"/>
<line x1="240" y1="378" x2="240" y2="410" stroke="#888"/>
<line x1="310" y1="378" x2="310" y2="410" stroke="#888"/>
<line x1="100" y1="410" x2="310" y2="410" stroke="#22c55e" stroke-width="3"/>
<text x="205" y="432" text-anchor="middle" fill="#22c55e" font-size="11" font-weight="600">barrier</text>
<text x="245" y="470" text-anchor="middle" fill="#888" font-size="11" font-family="monospace">failing thunk -&gt; null slot</text>
<text x="245" y="492" text-anchor="middle" fill="#888" font-size="11">use when next step needs EVERY result</text>
<!-- pipeline -->
<rect x="490" y="260" width="450" height="260" rx="8" fill="#fff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="510" y="288" fill="#1a1a1a" font-size="15" font-weight="700" font-family="monospace">pipeline(items, *stages)</text>
<text x="510" y="310" fill="#888" font-size="12">Each item streams its own stages. No barrier.</text>
<!-- item A -->
<text x="530" y="348" fill="#1a1a1a" font-size="11">A</text>
<rect x="550" y="332" width="70" height="28" rx="3" fill="#fafafa" stroke="#1a1a1a"/>
<text x="585" y="350" text-anchor="middle" fill="#1a1a1a" font-size="10">audit</text>
<line x1="620" y1="346" x2="648" y2="346" stroke="#22c55e" marker-end="url(#pg)"/>
<rect x="650" y="332" width="70" height="28" rx="3" fill="#fafafa" stroke="#22c55e"/>
<text x="685" y="350" text-anchor="middle" fill="#1a1a1a" font-size="10">verify</text>
<line x1="720" y1="346" x2="748" y2="346" stroke="#22c55e" marker-end="url(#pg)"/>
<rect x="750" y="332" width="70" height="28" rx="3" fill="#fff" stroke="#22c55e"/>
<text x="785" y="350" text-anchor="middle" fill="#1a1a1a" font-size="10">done</text>
<!-- item B lagging -->
<text x="530" y="400" fill="#1a1a1a" font-size="11">B</text>
<rect x="550" y="384" width="70" height="28" rx="3" fill="#fafafa" stroke="#1a1a1a"/>
<text x="585" y="402" text-anchor="middle" fill="#1a1a1a" font-size="10">audit</text>
<line x1="620" y1="398" x2="648" y2="398" stroke="#888" marker-end="url(#p)"/>
<rect x="650" y="384" width="70" height="28" rx="3" fill="#fafafa" stroke="#d0d0d0" stroke-dasharray="3,2"/>
<text x="685" y="402" text-anchor="middle" fill="#888" font-size="10">verify</text>
<text x="715" y="450" text-anchor="middle" fill="#888" font-size="11" font-family="monospace">failing stage -&gt; null item</text>
<text x="715" y="472" text-anchor="middle" fill="#888" font-size="11">DEFAULT for multi-stage work</text>
<text x="715" y="494" text-anchor="middle" fill="#888" font-size="11">A can finish while B is still mixing</text>
<text x="480" y="545" text-anchor="middle" fill="#888" font-size="10">Script owns topology. Agents judge inside each call. Intermediate bowls stay in variables + journal.</text>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,63 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 520" font-family="system-ui, -apple-system, sans-serif" role="img" aria-labelledby="title desc">
<title id="title">Static harness vs dynamic workflow</title>
<desc id="desc">Same question, two harnesses: a generic static pipeline versus a tailor-made dynamic workflow that reads local code and runs an adversarial check.</desc>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#888"/></marker>
<marker id="ag" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#22c55e"/></marker>
</defs>
<rect width="960" height="520" rx="8" fill="#fff"/>
<text x="480" y="28" text-anchor="middle" fill="#1a1a1a" font-size="18" font-weight="700">Same question, two harnesses</text>
<rect x="220" y="48" width="520" height="36" rx="6" fill="#fafafa" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="480" y="71" text-anchor="middle" fill="#1a1a1a" font-size="12">Should we migrate our checkout service to a new provider?</text>
<line x1="350" y1="84" x2="200" y2="120" stroke="#888" stroke-width="1.3" marker-end="url(#a)"/>
<line x1="610" y1="84" x2="760" y2="120" stroke="#22c55e" stroke-width="1.5" marker-end="url(#ag)"/>
<!-- Static -->
<rect x="40" y="128" width="420" height="360" rx="8" fill="#fff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="250" y="154" text-anchor="middle" fill="#1a1a1a" font-size="14" font-weight="700">Static harness</text>
<text x="250" y="172" text-anchor="middle" fill="#888" font-size="11">written ahead - must work for every edge case</text>
<rect x="120" y="190" width="260" height="36" rx="4" fill="#fafafa" stroke="#888"/>
<text x="250" y="212" text-anchor="middle" fill="#1a1a1a" font-size="12">turn into 5 web searches</text>
<line x1="250" y1="226" x2="250" y2="244" stroke="#888" marker-end="url(#a)"/>
<rect x="120" y="246" width="260" height="36" rx="4" fill="#fafafa" stroke="#888"/>
<text x="250" y="268" text-anchor="middle" fill="#1a1a1a" font-size="12">fetch top results</text>
<line x1="250" y1="282" x2="250" y2="300" stroke="#888" marker-end="url(#a)"/>
<rect x="120" y="302" width="260" height="36" rx="4" fill="#fafafa" stroke="#888"/>
<text x="250" y="324" text-anchor="middle" fill="#1a1a1a" font-size="12">verify</text>
<line x1="250" y1="338" x2="250" y2="356" stroke="#888" marker-end="url(#a)"/>
<rect x="120" y="358" width="260" height="36" rx="4" fill="#fafafa" stroke="#888"/>
<text x="250" y="380" text-anchor="middle" fill="#1a1a1a" font-size="12">summarize</text>
<line x1="250" y1="394" x2="250" y2="412" stroke="#888" marker-end="url(#a)"/>
<rect x="120" y="414" width="260" height="40" rx="4" fill="#1a1a1a"/>
<text x="250" y="439" text-anchor="middle" fill="#fff" font-size="12" font-weight="600">a generic research report</text>
<!-- Dynamic -->
<rect x="500" y="128" width="420" height="360" rx="8" fill="#fff" stroke="#22c55e" stroke-width="1.8"/>
<text x="710" y="154" text-anchor="middle" fill="#1a1a1a" font-size="14" font-weight="700">Dynamic workflow</text>
<text x="710" y="172" text-anchor="middle" fill="#888" font-size="11">written for THIS task - then optionally saved</text>
<rect x="560" y="190" width="300" height="36" rx="4" fill="#fafafa" stroke="#1a1a1a"/>
<text x="710" y="212" text-anchor="middle" fill="#1a1a1a" font-size="12">read our billing code</text>
<rect x="560" y="234" width="88" height="24" rx="3" fill="#fff" stroke="#22c55e" stroke-dasharray="3,2"/>
<text x="604" y="250" text-anchor="middle" fill="#1a1a1a" font-size="9">billing/</text>
<rect x="656" y="234" width="88" height="24" rx="3" fill="#fff" stroke="#22c55e" stroke-dasharray="3,2"/>
<text x="700" y="250" text-anchor="middle" fill="#1a1a1a" font-size="9">webhooks/</text>
<rect x="752" y="234" width="88" height="24" rx="3" fill="#fff" stroke="#22c55e" stroke-dasharray="3,2"/>
<text x="796" y="250" text-anchor="middle" fill="#1a1a1a" font-size="9">taxes/</text>
<line x1="640" y1="258" x2="600" y2="286" stroke="#888" marker-end="url(#a)"/>
<line x1="780" y1="258" x2="820" y2="286" stroke="#888" marker-end="url(#a)"/>
<rect x="520" y="288" width="180" height="52" rx="4" fill="#fafafa" stroke="#22c55e"/>
<text x="610" y="310" text-anchor="middle" fill="#1a1a1a" font-size="10">check features vs</text>
<text x="610" y="326" text-anchor="middle" fill="#1a1a1a" font-size="10">new provider docs</text>
<rect x="720" y="288" width="180" height="52" rx="4" fill="#fafafa" stroke="#22c55e"/>
<text x="810" y="310" text-anchor="middle" fill="#1a1a1a" font-size="10">price at our</text>
<text x="810" y="326" text-anchor="middle" fill="#1a1a1a" font-size="10">transaction volume</text>
<line x1="610" y1="340" x2="670" y2="368" stroke="#888" marker-end="url(#a)"/>
<line x1="810" y1="340" x2="750" y2="368" stroke="#888" marker-end="url(#a)"/>
<rect x="560" y="370" width="300" height="40" rx="4" fill="#fff" stroke="#22c55e" stroke-dasharray="4,2"/>
<text x="710" y="395" text-anchor="middle" fill="#1a1a1a" font-size="11">devil's advocate: strongest case against</text>
<line x1="710" y1="410" x2="710" y2="428" stroke="#22c55e" marker-end="url(#ag)"/>
<rect x="560" y="430" width="300" height="40" rx="4" fill="#1a1a1a"/>
<text x="710" y="455" text-anchor="middle" fill="#fff" font-size="12" font-weight="600">a specific recommendation</text>
<text x="480" y="508" text-anchor="middle" fill="#888" font-size="10">Static aims to fit every case. Dynamic cuts cloth for this one - then save if the fit is good.</text>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -1,159 +1,108 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 720" font-family="system-ui, -apple-system, sans-serif" role="img" aria-labelledby="title description">
<title id="title">Six Workflow Patterns</title>
<desc id="description">A toolbox of six common dynamic workflow shapes: Classify-And-Act, Fanout-And-Synthesize, Adversarial Verification, Generate-And-Filter, Tournament, and Loop Until Done.</desc>
<desc id="description">Toolbox of six common dynamic workflow shapes.</desc>
<defs>
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#888888"/>
</marker>
<marker id="arr-g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#22c55e"/>
</marker>
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#888"/></marker>
<marker id="arr-g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#22c55e"/></marker>
</defs>
<rect width="960" height="720" rx="8" fill="#ffffff"/>
<rect width="960" height="720" rx="8" fill="#fff"/>
<text x="480" y="28" text-anchor="middle" fill="#1a1a1a" font-size="18" font-weight="700">Six Workflow Patterns</text>
<text x="480" y="48" text-anchor="middle" fill="#888888" font-size="11">a toolbox - not a mandatory menu. Compose with agent / parallel / pipeline</text>
<text x="480" y="48" text-anchor="middle" fill="#888" font-size="11">a toolbox - not a mandatory menu. Compose with agent / parallel / pipeline</text>
<rect x="20" y="64" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="330" y="64" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="640" y="64" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="20" y="380" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="330" y="380" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<rect x="640" y="380" width="300" height="300" rx="8" fill="#fff" stroke="#d0d0d0"/>
<!-- Panel frames: 2x3 -->
<!-- Row 1 -->
<rect x="20" y="64" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<rect x="330" y="64" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<rect x="640" y="64" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<!-- Row 2 -->
<rect x="20" y="380" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<rect x="330" y="380" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<rect x="640" y="380" width="300" height="300" rx="8" fill="#ffffff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="170" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">1. Classify-And-Act</text>
<rect x="120" y="110" width="100" height="28" rx="4" fill="#fafafa" stroke="#1a1a1a"/><text x="170" y="128" text-anchor="middle" fill="#1a1a1a" font-size="11">task</text>
<line x1="170" y1="138" x2="170" y2="158" stroke="#888" marker-end="url(#arr)"/>
<path d="M170 162 L192 184 L170 206 L148 184 Z" fill="#fff" stroke="#22c55e"/><text x="170" y="188" text-anchor="middle" fill="#1a1a1a" font-size="9">classifier</text>
<line x1="152" y1="196" x2="90" y2="230" stroke="#888" marker-end="url(#arr)"/>
<line x1="170" y1="206" x2="170" y2="230" stroke="#22c55e" stroke-width="1.8" marker-end="url(#arr-g)"/>
<line x1="188" y1="196" x2="250" y2="230" stroke="#888" marker-end="url(#arr)"/>
<circle cx="90" cy="258" r="22" fill="#fff" stroke="#1a1a1a"/><text x="90" y="262" text-anchor="middle" fill="#1a1a1a" font-size="11">A</text>
<circle cx="170" cy="258" r="22" fill="#fff" stroke="#22c55e" stroke-width="1.8"/><text x="170" y="262" text-anchor="middle" fill="#1a1a1a" font-size="11">B</text>
<circle cx="250" cy="258" r="22" fill="#fff" stroke="#1a1a1a"/><text x="250" y="262" text-anchor="middle" fill="#1a1a1a" font-size="11">C</text>
<text x="170" y="310" text-anchor="middle" fill="#888" font-size="10">route to the right specialist</text>
<text x="170" y="330" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">agent -&gt; branch -&gt; agent</text>
<!-- ===== 1. Classify-And-Act ===== -->
<text x="170" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Classify-And-Act</text>
<rect x="120" y="108" width="100" height="28" rx="4" fill="#fafafa" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="170" y="126" text-anchor="middle" fill="#1a1a1a" font-size="11">task</text>
<line x1="170" y1="136" x2="170" y2="158" stroke="#888888" stroke-width="1.5" marker-end="url(#arr)"/>
<path d="M170 162 L190 182 L170 202 L150 182 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="170" y="186" text-anchor="middle" fill="#1a1a1a" font-size="9">classifier</text>
<line x1="150" y1="192" x2="80" y2="230" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="170" y1="202" x2="170" y2="230" stroke="#22c55e" stroke-width="1.8" marker-end="url(#arr-g)"/>
<line x1="190" y1="192" x2="260" y2="230" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<circle cx="80" cy="255" r="22" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="80" y="258" text-anchor="middle" fill="#1a1a1a" font-size="10">A</text>
<circle cx="170" cy="255" r="22" fill="#ffffff" stroke="#22c55e" stroke-width="1.8"/>
<text x="170" y="258" text-anchor="middle" fill="#1a1a1a" font-size="10">B</text>
<circle cx="260" cy="255" r="22" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="260" y="258" text-anchor="middle" fill="#1a1a1a" font-size="10">C</text>
<text x="170" y="310" text-anchor="middle" fill="#888888" font-size="10">route to the right specialist</text>
<text x="170" y="328" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">agent ' branch ' agent</text>
<!-- ===== 2. Fanout-And-Synthesize ===== -->
<text x="480" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Fanout-And-Synthesize</text>
<rect x="430" y="108" width="100" height="28" rx="4" fill="#fafafa" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="480" y="126" text-anchor="middle" fill="#1a1a1a" font-size="11">task</text>
<line x1="450" y1="136" x2="400" y2="168" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="470" y1="136" x2="450" y2="168" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="490" y1="136" x2="510" y2="168" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="510" y1="136" x2="560" y2="168" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<circle cx="400" cy="190" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="450" cy="190" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="510" cy="190" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="560" cy="190" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<line x1="400" y1="206" x2="400" y2="232" stroke="#888888" stroke-width="1.2"/>
<line x1="450" y1="206" x2="450" y2="232" stroke="#888888" stroke-width="1.2"/>
<line x1="510" y1="206" x2="510" y2="232" stroke="#888888" stroke-width="1.2"/>
<line x1="560" y1="206" x2="560" y2="232" stroke="#888888" stroke-width="1.2"/>
<text x="480" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">2. Fanout-And-Synthesize</text>
<rect x="430" y="110" width="100" height="28" rx="4" fill="#fafafa" stroke="#1a1a1a"/><text x="480" y="128" text-anchor="middle" fill="#1a1a1a" font-size="11">task</text>
<line x1="450" y1="138" x2="400" y2="168" stroke="#888" marker-end="url(#arr)"/>
<line x1="470" y1="138" x2="450" y2="168" stroke="#888" marker-end="url(#arr)"/>
<line x1="490" y1="138" x2="510" y2="168" stroke="#888" marker-end="url(#arr)"/>
<line x1="510" y1="138" x2="560" y2="168" stroke="#888" marker-end="url(#arr)"/>
<circle cx="400" cy="190" r="16" fill="#fff" stroke="#1a1a1a"/>
<circle cx="450" cy="190" r="16" fill="#fff" stroke="#1a1a1a"/>
<circle cx="510" cy="190" r="16" fill="#fff" stroke="#1a1a1a"/>
<circle cx="560" cy="190" r="16" fill="#fff" stroke="#1a1a1a"/>
<line x1="400" y1="206" x2="400" y2="232" stroke="#888"/>
<line x1="450" y1="206" x2="450" y2="232" stroke="#888"/>
<line x1="510" y1="206" x2="510" y2="232" stroke="#888"/>
<line x1="560" y1="206" x2="560" y2="232" stroke="#888"/>
<line x1="400" y1="232" x2="560" y2="232" stroke="#22c55e" stroke-width="3"/>
<text x="480" y="250" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">barrier</text>
<line x1="480" y1="252" x2="480" y2="272" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<circle cx="480" cy="298" r="22" fill="#ffffff" stroke="#22c55e" stroke-width="1.8"/>
<text x="480" y="302" text-anchor="middle" fill="#1a1a1a" font-size="9">synthesize</text>
<text x="480" y="340" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">parallel / pipeline ' merge</text>
<text x="480" y="252" text-anchor="middle" fill="#22c55e" font-size="10" font-weight="600">barrier</text>
<line x1="480" y1="256" x2="480" y2="274" stroke="#22c55e" marker-end="url(#arr-g)"/>
<circle cx="480" cy="300" r="22" fill="#fff" stroke="#22c55e" stroke-width="1.8"/><text x="480" y="304" text-anchor="middle" fill="#1a1a1a" font-size="9">synthesize</text>
<text x="480" y="340" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">parallel / pipeline -&gt; merge</text>
<!-- ===== 3. Adversarial Verification ===== -->
<text x="790" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Adversarial Verification</text>
<circle cx="720" cy="200" r="36" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.5"/>
<text x="720" y="204" text-anchor="middle" fill="#1a1a1a" font-size="11">worker</text>
<line x1="756" y1="180" x2="850" y2="140" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="756" y1="200" x2="850" y2="200" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<line x1="756" y1="220" x2="850" y2="260" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<circle cx="875" cy="140" r="20" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="875" y="144" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<circle cx="875" cy="200" r="20" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="875" y="204" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<circle cx="875" cy="260" r="20" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="875" y="264" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<text x="790" y="310" text-anchor="middle" fill="#888888" font-size="10">independents stress-test the work</text>
<text x="790" y="328" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">agent ' parallel(verifiers)</text>
<text x="790" y="88" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">3. Adversarial Verification</text>
<circle cx="720" cy="200" r="34" fill="#fff" stroke="#1a1a1a" stroke-width="1.5"/><text x="720" y="204" text-anchor="middle" fill="#1a1a1a" font-size="11">worker</text>
<line x1="754" y1="180" x2="848" y2="140" stroke="#888" marker-end="url(#arr)"/>
<line x1="754" y1="200" x2="848" y2="200" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<line x1="754" y1="220" x2="848" y2="260" stroke="#888" marker-end="url(#arr)"/>
<circle cx="872" cy="140" r="20" fill="#fff" stroke="#1a1a1a"/><text x="872" y="144" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<circle cx="872" cy="200" r="20" fill="#fff" stroke="#22c55e" stroke-width="1.5"/><text x="872" y="204" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<circle cx="872" cy="260" r="20" fill="#fff" stroke="#1a1a1a"/><text x="872" y="264" text-anchor="middle" fill="#1a1a1a" font-size="9">verify</text>
<text x="790" y="310" text-anchor="middle" fill="#888" font-size="10">independents stress-test the work</text>
<text x="790" y="330" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">agent -&gt; parallel(verifiers)</text>
<!-- ===== 4. Generate-And-Filter ===== -->
<text x="170" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Generate-And-Filter</text>
<circle cx="70" cy="460" r="18" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="70" cy="510" r="18" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="70" cy="560" r="18" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="70" y="464" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<text x="70" y="514" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<text x="70" y="564" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<rect x="105" y="448" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="460" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="498" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="510" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="548" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<rect x="105" y="560" width="14" height="10" rx="1" fill="#fafafa" stroke="#888888"/>
<line x1="120" y1="460" x2="155" y2="510" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="120" y1="510" x2="155" y2="510" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="120" y1="560" x2="155" y2="510" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<rect x="160" y="488" width="70" height="44" rx="4" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="195" y="506" text-anchor="middle" fill="#1a1a1a" font-size="10" font-weight="600">filter</text>
<text x="195" y="520" text-anchor="middle" fill="#888888" font-size="8">rubric+dedupe</text>
<line x1="230" y1="500" x2="260" y2="470" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<line x1="230" y1="520" x2="260" y2="550" stroke="#888888" stroke-width="1.2" stroke-dasharray="3,2" marker-end="url(#arr)"/>
<rect x="262" y="452" width="40" height="28" rx="3" fill="#fafafa" stroke="#22c55e" stroke-width="1.2"/>
<text x="282" y="470" text-anchor="middle" fill="#1a1a1a" font-size="9">best</text>
<rect x="262" y="538" width="40" height="28" rx="3" fill="#fafafa" stroke="#d0d0d0" stroke-width="1.2"/>
<text x="282" y="556" text-anchor="middle" fill="#888888" font-size="8">drop</text>
<text x="170" y="620" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">parallel(gens) ' filter</text>
<text x="170" y="638" text-anchor="middle" fill="#888888" font-size="10">keep what survives the test</text>
<text x="170" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">4. Generate-And-Filter</text>
<circle cx="70" cy="460" r="18" fill="#fff" stroke="#1a1a1a"/><text x="70" y="464" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<circle cx="70" cy="510" r="18" fill="#fff" stroke="#1a1a1a"/><text x="70" y="514" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<circle cx="70" cy="560" r="18" fill="#fff" stroke="#1a1a1a"/><text x="70" y="564" text-anchor="middle" fill="#1a1a1a" font-size="8">gen</text>
<line x1="90" y1="460" x2="150" y2="508" stroke="#888" marker-end="url(#arr)"/>
<line x1="90" y1="510" x2="150" y2="510" stroke="#888" marker-end="url(#arr)"/>
<line x1="90" y1="560" x2="150" y2="512" stroke="#888" marker-end="url(#arr)"/>
<rect x="155" y="488" width="72" height="44" rx="4" fill="#fff" stroke="#22c55e"/><text x="191" y="506" text-anchor="middle" fill="#1a1a1a" font-size="10" font-weight="600">filter</text><text x="191" y="520" text-anchor="middle" fill="#888" font-size="8">rubric+dedupe</text>
<line x1="227" y1="500" x2="255" y2="470" stroke="#22c55e" marker-end="url(#arr-g)"/>
<line x1="227" y1="520" x2="255" y2="550" stroke="#888" stroke-dasharray="3,2" marker-end="url(#arr)"/>
<rect x="258" y="454" width="42" height="26" rx="3" fill="#fafafa" stroke="#22c55e"/><text x="279" y="471" text-anchor="middle" fill="#1a1a1a" font-size="9">best</text>
<rect x="258" y="538" width="42" height="26" rx="3" fill="#fafafa" stroke="#d0d0d0"/><text x="279" y="555" text-anchor="middle" fill="#888" font-size="8">drop</text>
<text x="170" y="610" text-anchor="middle" fill="#888" font-size="10">keep what survives the test</text>
<text x="170" y="630" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">parallel(gens) -&gt; filter</text>
<!-- ===== 5. Tournament ===== -->
<text x="480" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Tournament</text>
<circle cx="380" cy="450" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="430" cy="450" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="530" cy="450" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<circle cx="580" cy="450" r="16" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.2"/>
<text x="380" y="454" text-anchor="middle" fill="#1a1a1a" font-size="8">a</text>
<text x="430" y="454" text-anchor="middle" fill="#1a1a1a" font-size="8">b</text>
<text x="530" y="454" text-anchor="middle" fill="#1a1a1a" font-size="8">c</text>
<text x="580" y="454" text-anchor="middle" fill="#1a1a1a" font-size="8">d</text>
<line x1="380" y1="466" x2="395" y2="495" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="430" y1="466" x2="415" y2="495" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="530" y1="466" x2="545" y2="495" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="580" y1="466" x2="565" y2="495" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<path d="M405 500 L420 515 L405 530 L390 515 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.3"/>
<path d="M555 500 L570 515 L555 530 L540 515 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.3"/>
<text x="405" y="519" text-anchor="middle" fill="#1a1a1a" font-size="7">judge</text>
<text x="555" y="519" text-anchor="middle" fill="#1a1a1a" font-size="7">judge</text>
<line x1="405" y1="530" x2="460" y2="560" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<line x1="555" y1="530" x2="500" y2="560" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<path d="M480 564 L498 582 L480 600 L462 582 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="480" y="586" text-anchor="middle" fill="#1a1a1a" font-size="8">final</text>
<line x1="480" y1="600" x2="480" y2="618" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arr-g)"/>
<rect x="452" y="620" width="56" height="20" rx="3" fill="#fafafa" stroke="#22c55e" stroke-width="1.2"/>
<text x="480" y="634" text-anchor="middle" fill="#1a1a1a" font-size="9">winner</text>
<text x="480" y="662" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">pairwise agent judges</text>
<text x="480" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">5. Tournament</text>
<circle cx="380" cy="450" r="16" fill="#fff" stroke="#1a1a1a"/><text x="380" y="454" text-anchor="middle" fill="#1a1a1a" font-size="9">a</text>
<circle cx="430" cy="450" r="16" fill="#fff" stroke="#1a1a1a"/><text x="430" y="454" text-anchor="middle" fill="#1a1a1a" font-size="9">b</text>
<circle cx="530" cy="450" r="16" fill="#fff" stroke="#1a1a1a"/><text x="530" y="454" text-anchor="middle" fill="#1a1a1a" font-size="9">c</text>
<circle cx="580" cy="450" r="16" fill="#fff" stroke="#1a1a1a"/><text x="580" y="454" text-anchor="middle" fill="#1a1a1a" font-size="9">d</text>
<line x1="380" y1="466" x2="395" y2="494" stroke="#888" marker-end="url(#arr)"/>
<line x1="430" y1="466" x2="415" y2="494" stroke="#888" marker-end="url(#arr)"/>
<line x1="530" y1="466" x2="545" y2="494" stroke="#888" marker-end="url(#arr)"/>
<line x1="580" y1="466" x2="565" y2="494" stroke="#888" marker-end="url(#arr)"/>
<path d="M405 498 L420 514 L405 530 L390 514 Z" fill="#fff" stroke="#22c55e"/><text x="405" y="518" text-anchor="middle" fill="#1a1a1a" font-size="7">judge</text>
<path d="M555 498 L570 514 L555 530 L540 514 Z" fill="#fff" stroke="#22c55e"/><text x="555" y="518" text-anchor="middle" fill="#1a1a1a" font-size="7">judge</text>
<line x1="405" y1="530" x2="460" y2="558" stroke="#888" marker-end="url(#arr)"/>
<line x1="555" y1="530" x2="500" y2="558" stroke="#888" marker-end="url(#arr)"/>
<path d="M480 562 L498 580 L480 598 L462 580 Z" fill="#fff" stroke="#22c55e"/><text x="480" y="584" text-anchor="middle" fill="#1a1a1a" font-size="8">final</text>
<line x1="480" y1="598" x2="480" y2="616" stroke="#22c55e" marker-end="url(#arr-g)"/>
<rect x="452" y="618" width="56" height="20" rx="3" fill="#fafafa" stroke="#22c55e"/><text x="480" y="632" text-anchor="middle" fill="#1a1a1a" font-size="9">winner</text>
<text x="480" y="660" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">pairwise agent judges</text>
<!-- ===== 6. Loop Until Done ===== -->
<text x="790" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">Loop Until Done</text>
<circle cx="790" cy="470" r="28" fill="#ffffff" stroke="#1a1a1a" stroke-width="1.5"/>
<text x="790" y="474" text-anchor="middle" fill="#1a1a1a" font-size="11">agent</text>
<line x1="790" y1="498" x2="790" y2="525" stroke="#888888" stroke-width="1.5" marker-end="url(#arr)"/>
<path d="M790 530 L812 552 L790 574 L768 552 Z" fill="#ffffff" stroke="#22c55e" stroke-width="1.5"/>
<text x="790" y="548" text-anchor="middle" fill="#1a1a1a" font-size="8">new?</text>
<text x="790" y="560" text-anchor="middle" fill="#1a1a1a" font-size="8">findings</text>
<line x1="812" y1="560" x2="870" y2="560" stroke="#888888" stroke-width="1.2" marker-end="url(#arr)"/>
<rect x="872" y="548" width="40" height="24" rx="3" fill="#1a1a1a"/>
<text x="892" y="564" text-anchor="middle" fill="#ffffff" font-size="9">done</text>
<path d="M768 548 C720 548 720 470 762 470" fill="none" stroke="#22c55e" stroke-width="1.8" marker-end="url(#arr-g)"/>
<text x="730" y="512" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">yes - </text>
<text x="730" y="524" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">spawn</text>
<text x="790" y="620" text-anchor="middle" fill="#888888" font-size="10">stop when dry rounds appear</text>
<text x="790" y="638" text-anchor="middle" fill="#888888" font-size="9" font-family="monospace">while + agent + budget</text>
<text x="790" y="404" text-anchor="middle" fill="#1a1a1a" font-size="13" font-weight="700">6. Loop Until Done</text>
<circle cx="790" cy="470" r="28" fill="#fff" stroke="#1a1a1a" stroke-width="1.5"/><text x="790" y="474" text-anchor="middle" fill="#1a1a1a" font-size="11">agent</text>
<line x1="790" y1="498" x2="790" y2="524" stroke="#888" marker-end="url(#arr)"/>
<path d="M790 528 L814 552 L790 576 L766 552 Z" fill="#fff" stroke="#22c55e"/><text x="790" y="548" text-anchor="middle" fill="#1a1a1a" font-size="8">new?</text><text x="790" y="562" text-anchor="middle" fill="#1a1a1a" font-size="8">findings</text>
<line x1="814" y1="560" x2="868" y2="560" stroke="#888" marker-end="url(#arr)"/>
<rect x="870" y="548" width="40" height="24" rx="3" fill="#1a1a1a"/><text x="890" y="564" text-anchor="middle" fill="#fff" font-size="9">done</text>
<path d="M766 548 C720 548 720 470 762 470" fill="none" stroke="#22c55e" stroke-width="1.8" marker-end="url(#arr-g)"/>
<text x="728" y="512" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">yes -</text>
<text x="728" y="526" text-anchor="middle" fill="#22c55e" font-size="9" font-weight="600">spawn</text>
<text x="790" y="620" text-anchor="middle" fill="#888" font-size="10">stop when dry rounds appear</text>
<text x="790" y="640" text-anchor="middle" fill="#888" font-size="9" font-family="monospace">while + agent + budget</text>
<text x="480" y="702" text-anchor="middle" fill="#888888" font-size="10">Script owns the topology; agents judge inside each step.</text>
<text x="480" y="702" text-anchor="middle" fill="#888" font-size="10">Script owns the topology; agents judge inside each step.</text>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,68 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 560" font-family="system-ui, -apple-system, sans-serif" role="img" aria-labelledby="title desc">
<title id="title">Workflow primitives: agent, parallel, pipeline</title>
<desc id="desc">Three kitchen verbs of a workflow script: one helper, a barrier of many helpers, and per-item stages without a barrier.</desc>
<defs>
<marker id="p" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#888"/></marker>
<marker id="pg" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0 0 L10 5 L0 10 z" fill="#22c55e"/></marker>
</defs>
<rect width="960" height="560" rx="8" fill="#fff"/>
<text x="480" y="28" text-anchor="middle" fill="#1a1a1a" font-size="18" font-weight="700">Three verbs the script speaks</text>
<text x="480" y="48" text-anchor="middle" fill="#888" font-size="12">agent asks a helper; parallel waits for every tray; pipeline lets each cake finish its own stages</text>
<!-- agent -->
<rect x="20" y="70" width="920" height="170" rx="8" fill="#fff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="40" y="98" fill="#1a1a1a" font-size="15" font-weight="700" font-family="monospace">agent(prompt, opts?)</text>
<text x="40" y="120" fill="#888" font-size="12">One helper. One job. Optional schema makes the answer machine-readable.</text>
<rect x="40" y="140" width="520" height="78" rx="4" fill="#111" />
<text x="56" y="164" fill="#86efac" font-size="12" font-family="monospace">const bugs = await agent("audit auth.ts", {</text>
<text x="56" y="184" fill="#e5e5e5" font-size="12" font-family="monospace"> schema: BugList, label: "audit:auth", phase: "Review"</text>
<text x="56" y="204" fill="#86efac" font-size="12" font-family="monospace">})</text>
<rect x="600" y="140" width="310" height="78" rx="4" fill="#fafafa" stroke="#22c55e"/>
<text x="755" y="170" text-anchor="middle" fill="#1a1a1a" font-size="12">returns text, or validated JSON</text>
<text x="755" y="192" text-anchor="middle" fill="#888" font-size="11">journal records each call in order</text>
<text x="755" y="210" text-anchor="middle" fill="#888" font-size="11">resume replays unchanged prefix</text>
<!-- parallel -->
<rect x="20" y="260" width="450" height="260" rx="8" fill="#fff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="40" y="288" fill="#1a1a1a" font-size="15" font-weight="700" font-family="monospace">parallel(thunks)</text>
<text x="40" y="310" fill="#888" font-size="12">Fan out. Barrier - wait for all.</text>
<circle cx="100" cy="360" r="18" fill="#fff" stroke="#1a1a1a"/>
<circle cx="170" cy="360" r="18" fill="#fff" stroke="#1a1a1a"/>
<circle cx="240" cy="360" r="18" fill="#fff" stroke="#1a1a1a"/>
<circle cx="310" cy="360" r="18" fill="#fff" stroke="#1a1a1a"/>
<line x1="100" y1="378" x2="100" y2="410" stroke="#888"/>
<line x1="170" y1="378" x2="170" y2="410" stroke="#888"/>
<line x1="240" y1="378" x2="240" y2="410" stroke="#888"/>
<line x1="310" y1="378" x2="310" y2="410" stroke="#888"/>
<line x1="100" y1="410" x2="310" y2="410" stroke="#22c55e" stroke-width="3"/>
<text x="205" y="432" text-anchor="middle" fill="#22c55e" font-size="11" font-weight="600">barrier</text>
<text x="245" y="470" text-anchor="middle" fill="#888" font-size="11" font-family="monospace">failing thunk -&gt; null slot</text>
<text x="245" y="492" text-anchor="middle" fill="#888" font-size="11">use when next step needs EVERY result</text>
<!-- pipeline -->
<rect x="490" y="260" width="450" height="260" rx="8" fill="#fff" stroke="#d0d0d0" stroke-width="1.5"/>
<text x="510" y="288" fill="#1a1a1a" font-size="15" font-weight="700" font-family="monospace">pipeline(items, *stages)</text>
<text x="510" y="310" fill="#888" font-size="12">Each item streams its own stages. No barrier.</text>
<!-- item A -->
<text x="530" y="348" fill="#1a1a1a" font-size="11">A</text>
<rect x="550" y="332" width="70" height="28" rx="3" fill="#fafafa" stroke="#1a1a1a"/>
<text x="585" y="350" text-anchor="middle" fill="#1a1a1a" font-size="10">audit</text>
<line x1="620" y1="346" x2="648" y2="346" stroke="#22c55e" marker-end="url(#pg)"/>
<rect x="650" y="332" width="70" height="28" rx="3" fill="#fafafa" stroke="#22c55e"/>
<text x="685" y="350" text-anchor="middle" fill="#1a1a1a" font-size="10">verify</text>
<line x1="720" y1="346" x2="748" y2="346" stroke="#22c55e" marker-end="url(#pg)"/>
<rect x="750" y="332" width="70" height="28" rx="3" fill="#fff" stroke="#22c55e"/>
<text x="785" y="350" text-anchor="middle" fill="#1a1a1a" font-size="10">done</text>
<!-- item B lagging -->
<text x="530" y="400" fill="#1a1a1a" font-size="11">B</text>
<rect x="550" y="384" width="70" height="28" rx="3" fill="#fafafa" stroke="#1a1a1a"/>
<text x="585" y="402" text-anchor="middle" fill="#1a1a1a" font-size="10">audit</text>
<line x1="620" y1="398" x2="648" y2="398" stroke="#888" marker-end="url(#p)"/>
<rect x="650" y="384" width="70" height="28" rx="3" fill="#fafafa" stroke="#d0d0d0" stroke-dasharray="3,2"/>
<text x="685" y="402" text-anchor="middle" fill="#888" font-size="10">verify</text>
<text x="715" y="450" text-anchor="middle" fill="#888" font-size="11" font-family="monospace">failing stage -&gt; null item</text>
<text x="715" y="472" text-anchor="middle" fill="#888" font-size="11">DEFAULT for multi-stage work</text>
<text x="715" y="494" text-anchor="middle" fill="#888" font-size="11">A can finish while B is still mixing</text>
<text x="480" y="545" text-anchor="middle" fill="#888" font-size="10">Script owns topology. Agents judge inside each call. Intermediate bowls stay in variables + journal.</text>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB