Craft s16 READMEs for magnetic GitHub reading

Add critical-path figures with captions, ASCII topology sketches,
labeled teaching-code vs code.py snippets, and details collapses for
optional depth. Recompress official PNGs; keep essay spine visible.

Co-authored-by: Xinlu Lai <CrazyBoyM@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-08-12 14:02:15 +00:00
parent 02c93c8616
commit 13302bbc33
No known key found for this signature in database
11 changed files with 403 additions and 257 deletions

View file

@ -22,156 +22,205 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
ただ、ある仕事にはその上に**定制の harness** が要ります。深い調査、セキュリティの洗い出し、agent teams、変更一式を広げて review するような仕事です。SDK で先に手書きしてもよい。あるいは——ここが生きているところですが——Claude に**このタスク用**の harness を書かせ、走らせ、良いものを残せます。
Claude Code の設計者は率直です。dynamic workflow は、モデルがその場で自分用の multi-agent harness を書けるようにします。コースのモットーを一階上げると、こうなります。各ステップの中ではモデルを信頼する。ステップの並びは、自分で形を決める。
コースのモットーを一階上げると、こうなります。各ステップの中ではモデルを信頼する。ステップの並びは、自分で形を決める。
## 長いチャットが静かにやりがちなこと
s01 から s15 まで、計画と行動は同じ context window を共有します。次の一手が直前の発見に依るときは、とても心地よいです。
s01 から s15 まで、計画と行動は同じ context window を共有します。次の一手が直前の発見に依るときは心地よいです。仕事が長く、大規模に並行し、硬い構造を求め、あるいは疑り深い第二意見が要ると、脆くなります。
ところが仕事が長く、大規模に並行し、硬い構造を求め、あるいは疑り深い第二意見が要ると、脆くなります。長いチャットをじっと見ていると、用語を覚える前に、見覚えのある癖に出会います。
長いチャットをじっと見ていると、用語を覚える前に癖に出会います。50 項目のうち 35 で勝利宣言をする。自分の宿題を採点させると甘くなる——狐が鶏小屋を採点する。多ターンと圧縮のあいだに、「X には触るな」が薄れていく。
50 項目のうち 35 で勝利宣言をする。自分の宿題を採点させると甘くなる——狐が鶏小屋を採点する。多ターンと圧縮のあいだに、「X には触るな」という静かな制約が薄れていく。
それが agentic laziness、self-preferential bias、goal drift です。名前より感触が大事です。仕事をする窓が、計画を覚える窓でもある。会話履歴は、並行性や安定した結果の形、落ちたあとの再開を預けるには柔らかい場所です。多くのファイルを review する、調査してから検証する、N 個のモジュールを同じやり方で移す——そうした仕事は、形が先に分かっています。柔らかい記憶だけでは足りません。
それが agentic laziness、self-preferential bias、goal drift です。名前より感触が大事です。仕事をする窓が、計画を覚える窓でもある。柔らかい会話メモリは、並行性や安定した結果の形、再開を預けるには弱い場所です。
## アイデアが落ちる瞬間
もし計画がコードの中に住んだらどうでしょう。
助手は相変わらず考えます——きれいな机で、焦点の定まった一つの仕事を。**script** がループと扇状の分配とマージを持ちます。中間結果は変数と journal にあり、会話には入りません。途中で切り上げる癖は艦隊全体を止めにくくなり、自己採点の甘さは著者ではない第二の助手にぶつかり、drift も掴みにくくなります。トポロジーを、疲れた語り手が毎ターン書き換える必要がないからです。
助手は相変わらず考えます——きれいな机で。**script** がループと扇状の分配とマージを持ちます。中間結果は変数と journal にあり、会話には入りません。途中で切り上げる癖は艦隊を止めにくくなり、自己採点の甘さは著者ではない第二の助手にぶつかり、drift も掴みにくくなります。
一行で言えば、**workflow はオーケストレーションを「賢さ」から「構造」へ移します。** モデルは各 `agent()` の中で判断し、地図は script が持ちます。
**Workflow はオーケストレーションを「賢さ」から「構造」へ移します。** モデルは各 `agent()` の中で判断し、地図は script が持ちます。
```text
messages[] ──► Workflow(...) ──► tool_result { launched, result, task }
┌───────────────┐
│ script が │
│ topology を持つ│
└───────┬───────┘
│ agent / parallel / pipeline
変数 + journal
```
1 回の `Workflow` tool call が、その実行を始めます。進み具合は途中で小さく鳴り、レシピが終わると一つの tool result が戻ります。
<details>
<summary>Runtime 概要図(任意)</summary>
![Workflow Runtime Overview](images/workflow-runtime-overview.svg)
1 回の `Workflow` tool call が、その実行を始めます。進み具合は途中で小さく鳴り、最後に launch 情報と結果と task state が一つの tool result で戻ります。
</details>
## ふたつの入口——そして外のいとこ
Claude Code は入口について率直です。
ときどきモデルは、*この*タスク用の JavaScript オーケストレーションを書き、`script` として渡します(あとから `scriptPath` を編集することもあります)。これが **dynamic** の入口です。問題がまだ熱いうちに、合わせた harness を裁断します。
**Dynamic** — モデルが*この*タスク用の JavaScript オーケストレーションを書く(`script`、あとから `scriptPath`)。問題がまだ熱いうちに裁断する harness
ときどき、良い script はすでに `.claude/workflows/` のような場所にあります。`name``args` で呼び出します。これが **saved** の入口です。残すに値した run が、再利用できるカードになったものです。
**Saved** — 良い script がすでに `.claude/workflows/` のような場所にある。`name` + `args` で呼び出す。残すに値した run が、再利用できるカードになったものです。
外にはいとこもあります。Agent SDK や `claude -p` で先に書く **static** harness です。あらゆるエッジケースに耐える必要があるので、どうしても汎用になります。dynamic はこの布のための裁断です。形が合ったら保存します。
外にはいとこもあります。**static** harness を Agent SDK や `claude -p` で先に書くやり方です。あらゆるエッジケースに耐える必要があるので、どうしても汎用になります。dynamic はこの布のための裁断です。形が合ったら保存します。
![Static harness vs dynamic workflow](images/dynamic-vs-static.png)
*Claude Code の設計エッセイより。同じ問い、ふたつの harness。左 — 固定の検索→検証→要約で、汎用レポートに終わる。右 — billing コードを読み、分岐し、devil's advocate を呼んでから具体的な推奨を出す特注レシピ。*
*同じ問い、ふたつの harness。左: 固定の検索→検証→要約 → 汎用レポート。右: billing コードを読み、分岐し、devil's advocate を呼ぶ → 具体的な推奨。*
**この章は Python の teaching runtime です。** 同じアイデアを、1 行ずつ読める形で示します。デモは名前で一つの saved workflow を登録します。概念は Claude Code の script 世界と一一対応です。「モデルは実行可能コードを渡せない」などと言いません——それは Claude Code については初めから正しくありませんでした。ここでは、完全な JavaScript インタプリタを埋め込まないだけです。
**この章は Python の teaching runtime です。** 同じアイデアを、1 行ずつ読める形で示します。デモは名前で一つの saved workflow を登録します。概念は Claude Code の script 世界と 1:1 です。「モデルは実行可能コードを渡せない」などと言いません——それは Claude Code については初めから正しくありませんでした。ここでは JS インタプリタを埋め込まないだけです。
```python
# Teaching adapter: saved の入口name + args
# Claude Code は script / scriptPath / resumeFromRunId も受け付ける。
WORKFLOW_TOOL = {
"name": "Workflow",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"args": {"type": "object"},
"resume_from_run_id": {"type": "string"},
"resumeFromRunId": {"type": "string"},
},
"required": ["name"],
},
}
# teaching sketch — saved の入口(完全な Claude Code schema ではない)
Workflow({ "name": "review-changes", "args": { "changes": "..." } })
# Claude Code は他にも受け付ける: script | scriptPath | resumeFromRunId
```
## スクリプトが話す三つの動詞
学校のバザーでケーキをたくさん焼くとします。どのテーブルも 混ぜる → 焼く → 箱詰め。助手が味見をし、レシピが順番を決めます。
学校のバザー。どのテーブルも 混ぜる → 焼く → 箱詰め。助手が味見をし、レシピが順番を決めます。
![Workflow primitives: agent, parallel, pipeline](images/workflow-primitives.png)
```text
agent 助手ひとり、仕事ひとつ
pipeline 各ケーキが自分で stage を歩く (既定 — barrier なし)
parallel すべてのトレイが戻るまで待つ barrier — 控えめに)
```
*公式のプリミティブ・カード。ひとつの `agent` と、多くを走らせる二つのやり方 — `parallel`barrier`pipeline`(各 item が自分の stage を流れる)。*
`agent(prompt, opts?)` は助手ひとりに頼みます。`schema` があれば検証済み JSON が返り——次の段が受け取れるソケットになり——最初が雑なら一回だけやり直せます。
`agent(prompt, opts?)` は、助手ひとりに一つの仕事を頼むことです。`schema` を付ければ、答えは検証済み JSON になり——次の段が受け取れるソケットになり——最初が雑なら一回だけやり直せます。本物の Claude Code では `model``isolation`worktree / remote`agentType` も選べます。この teaching runtime は表面を小さく保ち、1 行ずつ読めるようにしています。
`pipeline(items, *stages)` は多段仕事の既定です。各ケーキが自分で段階を歩き、一方が箱詰めのあいだに、もう一方はまだ混ぜていてもよい。stage 間に barrier はありません。
`parallel(thunks)` は揃うまで待つバリアです。次の段が本当に全部の結果を要すときだけ欲しくなります。トレイ全部を味見してから採点表を書く、といった場面です。
その周りに、静かな動詞もあります。`phase` はいまの場所、`log` は短い一声、入れ子の `workflow``args` は材料リスト、`budget` はオーブン分tokenです。
`pipeline` はケーキ A が箱詰めのあいだに、ケーキ B がまだ混ぜていてもよい形です。`parallel` は次の段が本当に全部の結果を要すときだけ——トレイ全部を味見してから採点表を書く、といった場面です。
```python
# 各 review dimension が自分で audit → verify を歩く。
# teaching sketch — 形だけ(実行可能な sample は code.py
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 を飛ばします。マージの前に注意して絞ります。
助手が失敗しても艦隊は優しくいられます。`parallel` の失敗はそのスロットで `null``pipeline` の失敗は**その item** を落として残りの stage を飛ばします。マージの前に絞ります。
厨房が止まったら? 各 run には `runId` と disk 上の journal があります——助手が戻った順ではなく、あなたが**呼んだ**順のートです。resume は script を先頭から歩き、**最長の未変更プレフィックス**を再生します。最初の変更または未完了で、それ以降はすべて live。本物の JS runtime が `Date.now()``Math.random()` を禁じるのはこのためです。時計とサイコロがノートをずらします。この Python デモはそのサンドボックスまではやりません——それでも script は決定的に書いてください。
厨房が止まったら? disk 上の journal が*呼び出し順*で各 call を覚えます。resume は**最長の未変更プレフィックス**を再生し、最初の変更から先は 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
journal [A] [B] [C] [D]
resume hit hit ✂ live ← プレフィックスは C で切れる
```
<details>
<summary>公式プリミティブ・カード + 静かな動詞</summary>
![Workflow primitives](images/workflow-primitives.png)
*公式カード: `agent`、そして `parallel`barrier`pipeline`streaming stages。Claude Code には `model` / `isolation` / `agentType` もあります。teaching runtime は表面を小さく保ちます。*
静かな動詞: `phase``log`、入れ子の `workflow`1 段)、`args``budget`
</details>
## レシピが書けるようになったら——パターンの道具箱
動詞は小麦粉と火加減です。人が何度も発明し直すのは、少数の*形*です。道具箱だと思ってください。必点メニューではありません。
動詞は小麦粉と火加減です。人が何度も発明し直すのは、少数の*形*——道具箱であり、必点メニューではありません。
![Six Workflow Patterns](images/six-workflow-patterns.png)
*公式の六パターン格子 — 道具箱であり、必点メニューではない。トポロジーは script が持ち、このレッスンでは `agent` / `parallel` / `pipeline` / `phase` / journal で各形を話します。*
*公式の六パターン格子。トポロジーは script が持ち、このレッスンでは `agent` / `parallel` / `pipeline` / journal で各形を話します。*
**Classify-And-Act。** 痛み: 万能の助手は何でもそこそこ。形: classifier が見て、専門家 A / B / C へ振り分ける。ここ: `agent({schema})` がラベルを返し、script が続く `agent`(または入れ子の `workflow`)へ分岐。全部が本当に同じ扱いでよいなら使わない。
このあとのサンプルのために、いちばん大事な三つを先に感じてください——名前はあとからでよいです
**Fanout-And-Synthesize。** 痛み: 五十ファイルは一つの疲れた context に入らず、押し込めば混線する。形: 分け、多く走らせ、barrier で待ち、まとめる。ここ: 各 item に stage があるなら `pipeline`、次が全結果を要すなら `parallel`。まとめは gather のあとのふつうの Python。三つか五つの関連ファイルで足りるなら使わない。
**Fanout-And-Synthesize** — 五十ファイルは一つの疲れた context に入りません。分け、多く走らせ、barrier でまとめます
**Adversarial Verification。** 痛み: 狐が鶏小屋を採点する。形: worker が出す。独立した verifier が反証する。生き残ったものだけ残る。ここ: 生産の `agent`、それから verifier の `parallel`schema 付き)、そのあと filter。`phase` で Review と Verify。間違えても安いなら使わない。
```text
task ──► ● ● ● ● ══barrier══► synthesize
```
**Generate-And-Filter。** 痛み: 欲しいのは選択肢であり、最初に賢く聞こえた案ではない。形: 多くの generator が rubric + dedupe の filter に流す。ここ: generator の `parallel`、そのあと script 側の filterまたは審判 `agent`)。生成が高いとき journal が効く。良い答えの空間がもともと狭いなら使わない。
**Adversarial Verification** — 狐が鶏小屋を採点してはいけません。worker が出し、独立した verifier が突き、立っているものだけ残します
**Tournament。** 痛み: 味や順位では絶対スコアがぼやける。形: ペアごとの審判、トーナメント表、勝者——比較判断は孤独な採点に勝る。ここ: pairwise 審判 `agent``parallel` を回し、一つ残るまで続ける。鋭い rubric が一通しで決めるなら使わない。
```text
worker ──► verifier
├──► verifier
└──► verifier → まだ立つものだけ
```
**Loop Until Done。** 痛み: 坑道にまだ何巡あるか分からない。形: 「新しい発見?」が yes のあいだ spawn し続け、空振りで止まる。ここ: `while``agent`/`parallel` を包み、schema 付きの停止チェックと硬い `budget`。長い掘りには journal resume。仕事量が分かっているなら固定の `pipeline` の方が単純。
**Generate-And-Filter** — 欲しいのは選択肢であり、最初に賢く聞こえた案ではありません。多くの generator、そのあと rubricと dedupe
いくつか顔が付いたあと、道具箱は一目で収まります。
同じ道具箱には **Classify-And-Act**(専門家へ振り分け)、**Tournament**(ペア審判で勝者)、**Loop Until Done**(「新しい発見?」が yes のあいだ spawn、硬い `budget` 付き)もあります。コストが明瞭さや安全を買うときだけ、スタイルを借ります。
| パターン | プリミティブの素描 | 手を伸ばすとき |
|----------|--------------------|----------------|
| Classify-And-Act | `agent` → 分岐 → `agent` | 項目ごとに違う専門家が要る |
| Fanout-And-Synthesize | `pipeline` / `parallel` → 統合 | きれいな机がたくさん、そのあと一つの要約 |
| Adversarial Verification | 生産 → `parallel(verify)` → filter | 間違えると高い |
| Generate-And-Filter | `parallel(gens)` → rubric filter | まず選択肢、それから味 |
| Tournament | pairwise 審判 `agent` | 順位/味に鋭い物差しがない |
| Loop Until Done | `while` + 停止 + `budget` | どれだけ埋まっているか不明 |
<details>
<summary>各パターンをこのレッスンのプリミティブへ写す</summary>
組み合わせはふつうです。深い調査はしばしば fanout → filter → verify → synthesize と重ねます。私たちのサンプルは、二つの音の小さな和音です。
| パターン | プリミティブの素描 | 使わないとき |
|----------|--------------------|--------------|
| Classify-And-Act | `agent` → 分岐 → `agent` | 全部が同じ扱いでよい |
| Fanout-And-Synthesize | `pipeline` / `parallel` → 統合 | 一通しでもう足りる |
| Adversarial Verification | 生産 → `parallel(verify)` → filter | 間違えても安い |
| Generate-And-Filter | `parallel(gens)` → filter | 良い答えの空間がもともと狭い |
| Tournament | pairwise 審判 `agent` | 鋭い rubric が一通しで決める |
| Loop Until Done | `while` + 停止 + `budget` | 仕事量が分かっている |
```python
# teaching sketch — 分類してから動く
kind = await ctx.agent("この ticket を分類", schema=KIND)
if kind["type"] == "billing":
return await ctx.agent("billing を処理…")
```
組み合わせはふつうです。深い調査はしばしば fanout → filter → verify → synthesize と重ねます。
</details>
### 信頼できない入力に workflow が出会うとき
道具箱のそばにもう一つ残しておきたい形があります。**quarantine triage** です。サポートチケット、bug 報告、ユーザーフィードバックは信頼できません。それらを*読む* agent に、PR を開ける鍵まで持たせたくはありません。
サポートチケットやユーザーフィードバックは信頼できません。それらを*読む* agent に、PR を開ける鍵まで持たせたくありません。エアロックを残します。reader は read-only のまま、構造化 summary だけを渡し、trusted な actor は summary にだけ作用する——生本文には触れない。
```text
backlog (untrusted)
┌─ QUARANTINE (read-only) ─┐
│ readers → dedupe → summary │
└────────────┬───────────────┘
┌─ TRUSTED (high privilege) ─┐
│ actor → fix / escalate │
└─────────────────────────────┘
```
<details>
<summary>公式 quarantine 図</summary>
![Quarantine triage](images/quarantine-triage.png)
*Reader は read-only の quarantine に留まり、分類と dedupe をし、構造化 summary だけを渡します。高権限ツールは trusted 側に住み — summary にだけ作用し、生本文には触れません。バックログが眠らないなら `/loop` と組んでもよいです。*
*Reader は quarantine で分類と dedupe をし、高権限ツールは trusted 側に住みます。バックログが眠らないなら `/loop` と組んでもよいです。*
このレッスンのプリミティブでは、それも script と agent です。低権限 reader `agent``pipeline``parallel`、変数に入った構造化 summary、それから書く側の別 actor `agent`(または入れ子の `workflow`)。面白いのはエアロック — 誰が生テキストを見てよいか、です。
</details>
## `review-changes` を歩く — ひとつの composition
サンプルは「一つのパターン」ではありません。**Fanout-And-Synthesize** の中に **Adversarial Verification** が入り、終わりで軽く generate-and-filter がかかる——`isReal` の finding だけが残ります。
サンプルは「一つのパターン」ではありません。**Fanout-And-Synthesize** の中に **Adversarial Verification** が入り、終わりで軽く filter がかかる——`isReal` の finding だけが残ります。
```text
correctness ── audit ── verify ──┐
security ── audit ── verify ──┤── 確認済みの finding
performance ── audit ── verify ──┤
style ── audit ── verify ──┘
fanout synthesize
└── 各 finding: 懐疑的 verify ──┘
correctness ── audit ── verify ──┐
security ── audit ── verify ──┤── confirmed
performance ── audit ── verify ──┤
style ── audit ── verify ──┘
fanout ▲ synthesize
└── 各 finding 懐疑的 verify
```
`pipeline(DIMENSIONS, audit, verify)` が各 dimension に机を渡します。`verify` 内の verifier の `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 で止められず、著者は審判ではなく、トポロジーは途中で漂いません。
三つの癖が席を失う感触があります。艦隊は二つの dimension で止められず、著者は審判ではなく、トポロジーは途中で漂いません。
```python
# code.py より — 実行可能な sample短縮
async def sample_workflow(ctx, args):
ctx.phase("Review")
results = await ctx.pipeline(DIMENSIONS, audit, verify)
@ -181,33 +230,33 @@ async def sample_workflow(ctx, args):
```
<details>
<summary>s15 に掛けて、置き換えない</summary>
<summary>s15 に掛けて(置き換えない)</summary>
s15 は依然として host loop です。s16 が足すのは `Workflow` という tool だけです。あなた(またはモデル)が saved の名前を頼み、adapter が script を見つけて走らせます。
s15 は依然として host loop です。s16 が足すのは `Workflow` tool だけです。あなた(またはモデル)が saved の名前を頼み、adapter が script を走らせます。
本番では、その run は通知付きで背景に置き、セッションは応答し続けられます。teaching CLI は `demo` / `resume` を前景に置き、phase と cache hit を目で追えるようにしています。アイデアは同じで、簡略化したところははっきり言います。main loop は `bash``task` を借りるように、tool を一つ借ります。
本番では背景+通知で置けます。teaching CLI は `demo` / `resume` を前景に置き、phase と cache hit を追いやすくしています。アイデアは同じで、簡略化は明示します。
</details>
## 宝石を回す: 計画を握っているのは誰か
近所を見ると、同じものが別の面を見せます。役に立つ問いは「agent は何人か?」ではなく、**トポロジーを誰が持つか**、仕掛かりの器はどこに置かれるか、です。
役に立つ問いは「agent は何人か?」ではなく、**トポロジーを誰が持つか**、仕掛かりの器はどこに置かれるか、です。
| 近所 | 計画を握るもの | 中間結果の置き場 | 向いている用途 |
|------|----------------|------------------|----------------|
| [s06 Subagent](../s06_subagent/) | モデル、一度きり | ほとんど捨てる | 汚い子タスクの隔離 |
| [s13 Agent Teams](../s13_agent_teams/) | Lead がターンごと + mailbox | 共有タスク / メッセージ | 長時間の同僚 |
| [s15 Integrated Harness](../s15_integrated_harness/) | 一つのループ内のモデル | 会話 `messages[]` | 積み上げ型 coding agent |
| **s16 Workflow** | **Script** | **変数 + journal** | 構造化した fan-out verify |
| [s13 Agent Teams](../s13_agent_teams/) | Lead + mailbox | 共有タスク / メッセージ | 長時間の同僚 |
| [s15 Integrated Harness](../s15_integrated_harness/) | 一つのループ内のモデル | `messages[]` | 積み上げ型 coding agent |
| **s16 Workflow** | **Script** | **変数 + journal** | 構造化した fan-out + verify |
| [s17 Goal Loop](../s17_goal_loop/) | 停止時の evaluator | 会話を証拠に | 「ゴール全体は終わったか?」 |
より安い道もしばしば勝ちます。skill を軟らかい計画にする、短い multi-agent の会話、手書きの static orchestrator、あるいは大きな一回のモデルターン。構造が単一の context より長く生きねばならないときに、workflow へ手を伸ばします。審査員パネルが聞こえがいいからではありません。
より安い道もしばしば勝ちます。skill を軟らかい計画にする、短い multi-agent の会話、手書きの static orchestrator、あるいは大きな一回のモデルターン。構造が単一の context より長く生きねばならないときに、workflow へ手を伸ばします。
## 棚に戻しておくとき
Workflow は token と調整のコストを使います。ふつうのコーディングの大半は、五人の reviewer を必要としません。
回す前に聞いてください。この仕事は本当にもっと計算と定制 harness を欲しがっているか。ふつうの s15 の一ターン——あるいは一つの誠実な s06 subagent——で足りるなら、そこで止めます。抑制も思想の一部です。並行と専門化は、自分の席を自分で稼がねばなりません。
この仕事が本当にもっと計算と定制 harness を欲しがっているか、聞いてください。ふつうの s15 の一ターン——あるいは一つの誠実な s06 subagent——で足りるなら、そこで止めます。抑制も設計思想の一部です。
## 試してみる
@ -217,10 +266,10 @@ python s16_workflow_runtime/code.py demo # 固定 fixture。phase を見る
python s16_workflow_runtime/code.py resume # 同じ runId。cache hit を期待
```
Review が Verify に道を譲るのを見てください。完全な resume で agent が `done` から `cached` へ翻るのを見てください。終わりには短い確認リストがあり——きれいな resume では `agents=0 tokens=0` と出ます。ノートが「温め直しは要らない」と言っている感じです。
Review が Verify に道を譲るのを見てください。完全な resume では agent が `cached` になり、`agents=0 tokens=0` と出るはずです——ノートが「温め直しは要らない」と言っている感じです。
## 次へ
s16 はバッチの回し方です。[s17 Goal Loop](../s17_goal_loop/) は戸口で別の問いをします。止めるべきか、もう一ターンか。繰り返せるレシピに硬い「完了」も要るときは、そちらと組んでください。
s16 はバッチの回し方です。[s17 Goal Loop](../s17_goal_loop/) は戸口で別の問いをします。止めるべきか、もう一ターンか。
<!-- translation-sync: zh@v16, en@v16, ja@v16 -->
<!-- translation-sync: zh@v17, en@v17, ja@v17 -->

View file

@ -22,156 +22,205 @@ The default Claude Code harness is already strong at coding-shaped work: change
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.
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.
Same course motto, raised one floor: trust the model inside each step; decide the shape of the steps yourself.
## 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.
From s01 through s15, plan and action share one context window. 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.
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.
Watch a long chat carefully and you will meet the habits before you learn their names. 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 “dont touch X” fades until nobody remembers why it was there.
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.
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. Soft chat memory is a weak place to keep parallelism, stable result shapes, and resume.
## The idea, once it clicks
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.
Helpers still think — each at a clean desk. The **script** owns loops, fan-out, and merge. Intermediate results live in variables and a journal, not in the conversation. Laziness struggles to stop 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.
**Workflows move orchestration from intelligence to structure.** The model still judges inside each `agent()`; the script owns the map.
```text
messages[] ──► Workflow(...) ──► tool_result { launched, result, task }
┌───────────────┐
│ script owns │
│ the topology │
└───────┬───────┘
│ agent / parallel / pipeline
variables + journal
```
One `Workflow` tool call starts that run. Progress ticks while it works; one tool result comes back when the recipe finishes.
<details>
<summary>Runtime overview diagram (optional)</summary>
![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.
</details>
## Two doors — and a cousin outside
Claude Code is straightforward about how you enter the kitchen.
Claude Code is straightforward about how you enter.
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.
**Dynamic** — the model writes a JavaScript orchestration script for *this* task (`script`, later `scriptPath`). 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.
**Saved** — a good script already lives under something like `.claude/workflows/`. You call it by `name` + `args`. The reusable residue of a run that earned its keep.
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.
Outside sits a cousin: **static** harnesses you write ahead 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.png)
*From Claude Codes design essay: same question, two harnesses. Left — a fixed search→verify→summarize pipeline that ends in a generic report. Right — a tailor-made workflow that reads your billing code, branches, and invites a devils advocate before recommending.*
*Same question, two harnesses. Left: fixed search→verify→summarize → a generic report. Right: read your billing code, branch, invite a devils advocate → a specific recommendation.*
**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.
**This chapter is a Python teaching runtime.** Same ideas, every line readable. The demo registers one saved workflow by name; concepts map 1:1 to Claude Codes script world. We will not pretend “the model cannot submit executable code” — that was never true of Claude Code. We simply skip embedding a JS interpreter.
```python
# Teaching adapter: the saved door (name + args).
# Claude Code also accepts script / scriptPath / resumeFromRunId.
WORKFLOW_TOOL = {
"name": "Workflow",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"args": {"type": "object"},
"resume_from_run_id": {"type": "string"},
"resumeFromRunId": {"type": "string"},
},
"required": ["name"],
},
}
# teaching sketch — saved door (not the full Claude Code schema)
Workflow({ "name": "review-changes", "args": { "changes": "..." } })
# Claude Code also accepts: script | scriptPath | resumeFromRunId
```
## Three verbs the script speaks
Imagine a school bake sale. Every table needs mix → bake → box. Helpers taste; the recipe decides order.
School bake sale. Every table: mix → bake → box. Helpers taste; the recipe decides order.
![Workflow primitives: agent, parallel, pipeline](images/workflow-primitives.png)
```text
agent one helper, one job
pipeline each cake walks stages alone (default — no barrier)
parallel wait until EVERY tray is back (barrier — use sparingly)
```
*Official primitive card: one `agent`, then the two ways to run many — `parallel` (barrier) vs `pipeline` (each item streams its stages).*
`agent(prompt, opts?)` asks one helper. With `schema`, you get validated JSON — a socket the next stage can hold — and one retry if the first reply is messy.
`agent(prompt, opts?)` 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. Real Claude Code also lets you pick `model`, `isolation` (worktree / remote), and `agentType`; this teaching runtime keeps the surface smaller so every line stays readable.
`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).
`pipeline` lets cake A box while cake B is still mixing. `parallel` is for when the next step truly needs all results together — tasting every tray before the scorecard.
```python
# Each review dimension walks audit → verify on its own.
# teaching sketch — shape only (see code.py for the runnable sample)
results = await ctx.pipeline(DIMENSIONS, audit, verify)
confirmed = [f for r in results if r for f in r["confirmed"]]
```
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.
When a helper fails, the fleet stays kind: a `parallel` miss becomes `null` in that slot; a `pipeline` miss drops **that item** and skips its later stages. Filter before you merge.
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.
When the kitchen pauses, a journal on disk remembers calls in *invocation* order. Resume replays the **longest unchanged prefix**; at the first change, everything after runs live. Real JS runtimes ban `Date.now()` / `Math.random()` so the notebook stays aligned. 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
journal [A] [B] [C] [D]
resume hit hit ✂ live ← prefix breaks at C
```
<details>
<summary>Official primitive card + quieter verbs</summary>
![Workflow primitives](images/workflow-primitives.png)
*Official card: `agent`, then `parallel` (barrier) vs `pipeline` (streaming stages). Claude Code also exposes `model` / `isolation` / `agentType`; our teaching runtime keeps a smaller surface.*
Quieter verbs: `phase`, `log`, nested `workflow` (one level), `args`, `budget`.
</details>
## 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.
The verbs are flour and heat. What people keep reinventing are a handful of *shapes* a toolbox, not a mandatory menu.
![Six Workflow Patterns](images/six-workflow-patterns.png)
*The official six-pattern grid — a toolbox, not a mandatory menu. The script owns the topology; this lesson speaks each shape with `agent` / `parallel` / `pipeline` / `phase` / journal.*
*Official six-pattern grid. Script owns topology; this lesson speaks each shape with `agent` / `parallel` / `pipeline` / journal.*
**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.
Three shapes matter most for the sample ahead — feel them before the names pile up.
**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.
**Fanout-And-Synthesize** — fifty files will not fit one tired context. Split, run many, merge at a barrier.
**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.
```text
task ──► ● ● ● ● ══barrier══► synthesize
```
**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.
**Adversarial Verification** — the fox must not grade the henhouse. A worker produces; independent verifiers try to knock it down; only survivors remain.
**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.
```text
worker ──► verifier
├──► verifier
└──► verifier → keep what still stands
```
**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.
**Generate-And-Filter** — you need options, not the first clever-sounding idea. Many generators, then a rubric (and dedupe).
After a few have faces, the toolbox fits in one glance:
The same toolbox holds **Classify-And-Act** (route to a specialist), **Tournament** (pairwise judges to a winner), and **Loop Until Done** (keep spawning while “new findings?” is yes, with a hard `budget`). Borrow a style only when its cost buys clarity or safety.
| Pattern | Primitive sketch | Reach for it when… |
|---------|------------------|--------------------|
| Classify-And-Act | `agent` → branch → `agent` | Items need different specialists |
| 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 | Ranking / taste without a sharp scale |
| Loop Until Done | `while` + stop + `budget` | Unknown amount of buried work |
<details>
<summary>How each pattern maps to this lessons primitives</summary>
Compositions are normal. Deep research often stacks fanout → filter → verify → synthesize. Our sample is a smaller chord of two notes.
| Pattern | Primitive sketch | Skip when… |
|---------|------------------|------------|
| Classify-And-Act | `agent` → branch → `agent` | Every item needs the same treatment |
| Fanout-And-Synthesize | `pipeline` / `parallel` → merge | A single pass already fits |
| Adversarial Verification | produce → `parallel(verify)` → filter | A wrong answer is cheap |
| Generate-And-Filter | `parallel(gens)` → filter | The answer space is already tiny |
| Tournament | pairwise judge `agent`s | A clear rubric picks a winner in one pass |
| Loop Until Done | `while` + stop + `budget` | The work has a known size |
```python
# teaching sketch — classify then act
kind = await ctx.agent("classify this ticket", schema=KIND)
if kind["type"] == "billing":
return await ctx.agent("handle billing…")
```
Compositions are normal: deep research often stacks fanout → filter → verify → synthesize.
</details>
### When workflows meet untrusted input
One more shape is worth keeping near the toolbox: **quarantine triage**. Support tickets, bug reports, and user feedback are untrusted. You do not want the agent that *reads* them to also hold the keys that open a PR.
Support tickets and user feedback are untrusted. The agent that *reads* them should not also hold the keys that open a PR. Keep an airlock: readers stay read-only, pass only a structured summary; a trusted actor acts on the summary — never the raw text.
```text
backlog (untrusted)
┌─ QUARANTINE (read-only) ─┐
│ readers → dedupe → summary │
└────────────┬───────────────┘
┌─ TRUSTED (high privilege) ─┐
│ actor → fix / escalate │
└─────────────────────────────┘
```
<details>
<summary>Official quarantine figure</summary>
![Quarantine triage](images/quarantine-triage.png)
*Readers stay in a read-only quarantine, classify and dedupe, and pass only a structured summary across. High-privilege tools live on the trusted side — they act on summaries, never on raw content. Pair with `/loop` if the backlog never sleeps.*
*Readers classify and dedupe in quarantine; high-privilege tools live on the trusted side. Pair with `/loop` if the backlog never sleeps.*
In this lessons primitives that is still just scripts and agents: a `pipeline` or `parallel` of low-privilege reader `agent`s, a structured summary in a variable, then a separate actor `agent` (or nested `workflow`) that may write. The interesting part is the airlock — who is allowed to see the raw text.
</details>
## 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 when only `isReal` findings survive.
The sample is not “one pattern.” It is **Fanout-And-Synthesize** with **Adversarial Verification** inside — and a light filter when only `isReal` findings survive.
```text
correctness ── audit ── verify ──┐
security ── audit ── verify ──┤── confirmed findings
performance ── audit ── verify ──┤
style ── audit ── verify ──┘
fanout synthesize
└── each finding: skeptical verify ──┘
correctness ── audit ── verify ──┐
security ── audit ── verify ──┤── confirmed
performance ── audit ── verify ──┤
style ── audit ── verify ──┘
fanout ▲ synthesize
└── skeptical verify per finding
```
`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.
`pipeline(DIMENSIONS, audit, verify)` gives each dimension its own desk. Inside `verify`, `parallel` of verifier agents is the adversarial chord. List filtering is the synthesize step. Phases mark Review → 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.
You can almost feel the three failure modes losing their seats: the fleet cannot stop after two dimensions, the author is not the judge, and the topology does not drift mid-run.
```python
# from code.py — runnable sample (abbreviated)
async def sample_workflow(ctx, args):
ctx.phase("Review")
results = await ctx.pipeline(DIMENSIONS, audit, verify)
@ -181,33 +230,33 @@ async def sample_workflow(ctx, args):
```
<details>
<summary>How this hangs on s15 without replacing it</summary>
<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.
s15 is still the host loop. s16 only adds a `Workflow` tool. You (or the model) ask for a saved name; the adapter runs the script.
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`.
In the product, the run can sit in the background with notifications. Our teaching CLI keeps `demo` / `resume` in the foreground so phases and cache hits are easy to watch. Same ideas; we say so when we simplify.
</details>
## Turning the gem: who holds the plan?
Look at the neighbors and the same object shows a new face. The useful question is not “how many agents?” but **who owns the topology**, and where the half-finished bowls live.
The useful question is not “how many agents?” but **who owns the topology**, and where the half-finished bowls live.
| Neighbor | Who holds the plan | Where intermediates live | Best for |
|----------|--------------------|--------------------------|----------|
| [s06 Subagent](../s06_subagent/) | Model, one-shot | Mostly discarded | One dirty subtask, isolated |
| [s13 Agent Teams](../s13_agent_teams/) | Lead, turn by turn + mailbox | Shared tasks / messages | Long-running peers |
| [s15 Integrated Harness](../s15_integrated_harness/) | Model in one loop | Conversation `messages[]` | Cumulative coding agent |
| **s16 Workflow** | **Script** | **Variables + journal** | Structured fan-out and verify |
| [s17 Goal Loop](../s17_goal_loop/) | Evaluator at stop time | Conversation as evidence | “Is the whole goal done?” |
| [s06 Subagent](../s06_subagent/) | Model, one-shot | Mostly discarded | One dirty subtask |
| [s13 Agent Teams](../s13_agent_teams/) | Lead + mailbox | Shared tasks / messages | Long-running peers |
| [s15 Integrated Harness](../s15_integrated_harness/) | Model in one loop | `messages[]` | Cumulative coding agent |
| **s16 Workflow** | **Script** | **Variables + journal** | Structured fan-out + verify |
| [s17 Goal Loop](../s17_goal_loop/) | Evaluator at stop | Conversation as evidence | “Is the whole goal done?” |
Cheaper paths still win often: a skill as a soft plan, a short multi-agent chat, a hand-written static orchestrator, or one larger model turn. Reach for a workflow when the structure must outlast a single context — not because a panel of reviewers sounds impressive.
Cheaper paths still win often: a skill as a soft plan, a short multi-agent chat, a hand-written static orchestrator, or one larger model turn. Reach for a workflow when structure must outlast a single context — not because a panel sounds impressive.
## And when to leave it on the shelf
Workflows spend tokens and coordination. Most ordinary coding does not need five reviewers.
Before you spin one up, ask whether the job truly wants more compute and a custom harness. If a normal s15 turn — or one honest s06 subagent — will do, stop there. Restraint is part of the thought: parallelism and specialization have to earn their keep.
Ask whether the job truly wants more compute and a custom harness. If a normal s15 turn — or one honest s06 subagent — will do, stop there. Restraint is part of the design thought.
## Try it
@ -217,10 +266,10 @@ python s16_workflow_runtime/code.py demo # fixed fixture; watch phases
python s16_workflow_runtime/code.py resume # same runId; expect cache hits
```
Watch Review give way to Verify. Watch agents flip from `done` to `cached` on a full resume. At the end, a short confirmed list — and on a clean resume, `agents=0 tokens=0`, which is the notebook saying: nothing needed reheating.
Watch Review give way to Verify. On a full resume, agents flip to `cached` and you should see `agents=0 tokens=0` — the notebook saying nothing needed reheating.
## Next
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.”
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?
<!-- translation-sync: zh@v16, en@v16, ja@v16 -->
<!-- translation-sync: zh@v17, en@v17, ja@v17 -->

View file

@ -14,7 +14,7 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
想象你跟朋友用微信一起做饭。“先切洋葱。”等回音。“切好了吗?”然后热锅、放盐。一道菜还能撑住这种节奏;二十桌宴席就不行了——步骤会丢,话会重复,手机一死还得从头来。
模型既当厨师又当记事本时,感觉就是这样:计划与动手挤在同一段对话里。**Workflow** 是写好的菜谱。厨房(一个小 runtime按谱做帮手子 agent负责尝和判断半成品放在台面上的碗里——变量和 journal——而不是塞进群聊。
模型既当厨师又当记事本时,感觉就是这样:计划与动手挤在同一段对话里。**Workflow** 是写好的菜谱。厨房(一个小 runtime按谱做帮手子 agent负责尝和判断半成品放在台面上的碗里——变量和 journal——而不是塞进群聊。
## 为什么还要另一层 harness
@ -22,156 +22,204 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
可有些活需要**叠一层定制 harness**——深度调研、安全排查、agent teams、要铺开审查一整片改动。你可以事先用 SDK 手写那一层;也可以——这才是有意思的地方——让 Claude **为这次任务**起草一个 harness跑起来好用的再留下来。
Claude Code 的设计者说得很直白dynamic workflow 让模型当场为自己写下多 agent 的 harness。课程那句口号往上提一层:每一步里信任模型;步骤怎么排,由你来定结构。
课程那句口号往上提一层:每一步里信任模型;步骤怎么排,由你来定结构。
## 长对话里你会看见的走偏
从 s01 到 s15计划与执行共享同一个上下文。下一步取决于刚才的发现时这很舒服。
从 s01 到 s15计划与执行共享同一个上下文。下一步取决于刚才的发现时这很舒服。可一旦任务变长、要大规模并行、结构又死板,或需要一个挑剔的第二意见,它就会发脆。
可一旦任务变长、要大规模并行、结构又死板,或需要一个挑剔的第二意见,它就会发脆。你若耐心看一段很长的聊天,会在学会术语之前先撞见熟面孔。
耐心看一段很长的聊天,会在学会术语之前先撞见熟面孔。做到五十项里的三十五就宣布完工。让它批改自己的作业,分数总是偏甜——狐狸给鸡窝打分。多轮对话和压缩过后,那句轻轻的“别动 X”渐渐听不见了。
做到五十项里的三十五就宣布完工。让它批改自己的作业,分数总是偏甜——狐狸给鸡窝打分。多轮对话和压缩过后,那句轻轻的“别动 X”渐渐听不见了。
这些就是 agentic laziness、self-preferential bias、goal drift。名字不如感觉重要同一个窗口既要干活又要记住计划。对话历史太软扛不住并行、稳定的结果形状以及崩了还能续上。审查很多文件、先调研再验证、按同一方式迁移 N 个模块——这些活的形状事先就清楚。软记忆不够用。
这些就是 agentic laziness、self-preferential bias、goal drift。名字不如感觉重要同一个窗口既要干活又要记住计划。软软的对话记忆很难扛住并行、稳定的结果形状以及崩了还能续上。
## 点子落下的那一下
假如计划住在代码里呢?
帮手仍然负责想——每人一张干净桌子,一件专注的事。**脚本**掌管循环、分发和合并。中间结果待在变量和 journal 里,不进对话。想偷懒提前收工的习惯,更难叫停整支队伍;自我检查的偏心,会撞上一个不是作者本人的第二帮手;漂移也难下手,因为拓扑不再由一个疲倦的叙述者每轮改写。
帮手仍然负责想——每人一张干净桌子。**脚本**掌管循环、分发和合并。中间结果待在变量和 journal 里,不进对话。想偷懒提前收工的习惯,更难叫停整支队伍;自我检查的偏心,会撞上一个不是作者本人的第二帮手;漂移也难下手,因为拓扑不再由一个疲倦的叙述者每轮改写。
一句话:**workflow 把编排从「智力」挪到「结构」。** 模型仍在每次 `agent()` 里做判断;地图归脚本管。
**Workflow 把编排从「智力」挪到「结构」。** 模型仍在每次 `agent()` 里做判断;地图归脚本管。
```text
messages[] ──► Workflow(...) ──► tool_result { launched, result, task }
┌───────────────┐
│ 脚本掌管拓扑 │
└───────┬───────┘
│ agent / parallel / pipeline
变量 + journal
```
一次 `Workflow` 工具调用启动这次运行。进度在旁边轻轻响;菜谱做完,一条工具结果回来。
<details>
<summary>运行时总览图(可选)</summary>
![Workflow Runtime 总览](images/workflow-runtime-overview.svg)
一次 `Workflow` 工具调用启动这次运行。进度在旁边轻轻响;最后一条工具结果带回启动信息、结果和任务状态。
</details>
## 两扇门——门外还有个表亲
Claude Code 对入口说得很直白。
有时模型为*这次*任务写一段编排用的 JavaScript`script` 交出来(或之后改 `scriptPath`)。这是**动态**那扇门——问题还热着,就裁出一件合身的 harness。
**动态**——模型为*这次*任务写一段编排用的 JavaScript`script`,之后可改 `scriptPath`)。问题还热着,就裁出一件合身的 harness。
有时好脚本已经进了例如 `.claude/workflows/`。你用 `name``args` 再请它出来。这是**已保存**那扇门——一次值得留下的运行,沉淀成可复用的卡片。
**已保存**——好脚本已经进了例如 `.claude/workflows/`。你用 `name` + `args` 再请它出来。一次值得留下的运行,沉淀成可复用的卡片。
门外还有表亲:**静态** harness用 Agent SDK 或 `claude -p` 事先写好。它们得扛住所有边角,所以往往更泛。动态的是为这块布现裁的;合身了再存。
![静态 harness 与动态 workflow](images/dynamic-vs-static.png)
*来自 Claude Code 设计文:同一个问题,两套 harness。左边——固定的搜索→验证→摘要终点是一份泛泛的研究报告。右边——读你的 billing 代码、分叉、再请来魔鬼代言人,最后给出具体建议。*
*同一个问题,两套 harness。左边固定的搜索→验证→摘要终点是泛泛的报告。右边读你的 billing 代码、分叉、请来魔鬼代言人,最后给出具体建议。*
**这一章是 Python 教学运行时。** 同样的想法,每行都能读。演示按名字挂了一个已保存的 workflow概念和 Claude Code 的脚本世界一一对应。我们不会再说“模型不能提交可执行代码”——那从来不是 Claude Code 的真相。这里只是不嵌入完整的 JS 解释器。
**这一章是 Python 教学运行时。** 同样的想法,每行都能读。演示按名字挂了一个已保存的 workflow概念和 Claude Code 的脚本世界一一对应。我们不会再说“模型不能提交可执行代码”——那从来不是 Claude Code 的真相。这里只是不嵌入 JS 解释器。
```python
# 教学适配器已保存这扇门name + args
# Claude Code 还接受 script / scriptPath / resumeFromRunId。
WORKFLOW_TOOL = {
"name": "Workflow",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"args": {"type": "object"},
"resume_from_run_id": {"type": "string"},
"resumeFromRunId": {"type": "string"},
},
"required": ["name"],
},
}
# 教学示意 — 已保存这扇门(不是完整 Claude Code schema
Workflow({ "name": "review-changes", "args": { "changes": "..." } })
# Claude Code 还接受script | scriptPath | resumeFromRunId
```
## 脚本会说的三个动词
想象学校义卖要烤许多蛋糕。每张桌子都是搅拌 → 烘烤 → 装箱。帮手负责尝;菜谱决定先后。
学校义卖。每张桌子:搅拌 → 烘烤 → 装箱。帮手负责尝;菜谱决定先后。
![Workflow 原语agent、parallel、pipeline](images/workflow-primitives.png)
```text
agent 一个帮手,一件事
pipeline 每块蛋糕自己走完各阶段 (默认 — 不等齐)
parallel 等所有托盘都回来再往下 (屏障 — 少用)
```
*官方原语卡片:一个 `agent`,以及两种“跑很多”的方式——`parallel`(等齐屏障)与 `pipeline`(每个 item 自己流过各阶段)。*
`agent(prompt, opts?)` 请一个帮手。带上 `schema`,答案变成校验过的 JSON——下一阶段接得住的接口——第一次不对还给一次重试。
`agent(prompt, opts?)` 是请一个帮手做一件事。带上 `schema`,答案会变成校验过的 JSON——下一阶段接得住的接口——第一次不对还给一次重试。真正的 Claude Code 还可以选 `model``isolation`worktree / remote`agentType`;本课教学运行时把表面收小一点,好让每一行都读得完。
`pipeline(items, *stages)` 是多阶段工作的默认:每块蛋糕自己走完各阶段,一块在装箱时,另一块可能还在搅拌。阶段之间没有等齐屏障。
`parallel(thunks)` 是等齐——所有托盘都回来才往下。只有下一步真的需要全部结果时才值得,比如尝完再写评分表。
旁边还有更轻的词:`phase` 报站,`log` 喊一句,嵌一层 `workflow``args` 是食材清单,`budget` 是烤箱分钟token
`pipeline` 让蛋糕 A 装箱时,蛋糕 B 还可以在搅拌。`parallel` 只在下一步真的需要全部结果时才值得——比如尝完所有托盘再写评分表。
```python
# 每个审查维度自己走完 审计 → 验证。
# 教学示意 — 只看形状(可运行样本在 code.py
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 继续往前。合并前小心过滤。
帮手失手时,舰队仍然温和`parallel` 的失败在该槽位变成 `null``pipeline` 的失败会丢掉**那个 item** 并跳过后续 stage。合并前先过滤。
厨房暂停了呢?每次运行都有 `runId` 和磁盘上的 journal——按你**召唤**帮手的顺序记的笔记本。续跑从脚本开头走,回放**最长未改前缀**;碰到第一个改过或未完成的调用,之后全部实跑。所以真正的 JS 运行时禁止 `Date.now()``Math.random()`:时钟和骰子会让笔记本对不齐。这个 Python 演示不会完整沙箱那些——脚本仍写成确定性的吧。
厨房暂停了呢?磁盘上的 journal 按**召唤顺序**记着每次调用。续跑回放**最长未改前缀**;碰到第一个改动,之后全部实跑。真正的 JS 运行时禁止 `Date.now()` / `Math.random()`,好让笔记本对得齐。这个 Python 演示不会完整沙箱那些——脚本仍写成确定性的吧。
```text
journal: [A 好] [B 好] [C 好] [D 好]
续跑: A 命中 → B 命中 → C 改过 → D 实跑
journal [A] [B] [C] [D]
续跑 命中 命中 ✂ 实跑 ← 前缀在 C 断开
```
<details>
<summary>官方原语卡片 + 更轻的动词</summary>
![Workflow 原语](images/workflow-primitives.png)
*官方卡片:`agent`,以及 `parallel`(屏障)与 `pipeline`流式阶段。Claude Code 还有 `model` / `isolation` / `agentType`;教学运行时把表面收小一点。*
更轻的动词:`phase``log`、嵌一层 `workflow``args``budget`
</details>
## 会写菜谱之后——模式工具箱
动词是面粉和火候。人们反复发明的,是少数几种*形状*。把它们想成工具箱,不是必点菜单。
动词是面粉和火候。人们反复发明的,是少数几种*形状*——工具箱,不是必点菜单。
![六种 Workflow 模式](images/six-workflow-patterns.png)
*官方六模式网格——工具箱,不是必点菜单。脚本掌管拓扑;本课用 `agent` / `parallel` / `pipeline` / `phase` / journal 把每种形状说出来。*
*官方六模式网格。脚本掌管拓扑;本课用 `agent` / `parallel` / `pipeline` / journal 把每种形状说出来。*
**Classify-And-Act分类再行动。** 痛点:一个万金油帮手样样稀松。形状:分类器看一眼,再路由到专家 A、B 或 C。本课一次带 `schema``agent` 返回标签,脚本分支到对的后续 `agent`(或嵌一层 `workflow`)。每件东西其实都该同样处理时,就别用。
对后面的示例,先摸清三种最要紧的形状——名字可以后到
**Fanout-And-Synthesize分发再汇总。** 痛点:五十个文件塞不进一个疲倦的上下文,挤在一起还会串味。形状:拆开、多跑、等齐、再合并。本课:每件有自己阶段用 `pipeline`;下一步必须凑齐全部结果用 `parallel`;合并写在 gather 之后的普通 Python。三五个相关文件一趟就能看完时就别用。
**Fanout-And-Synthesize分发再汇总**——五十个文件塞不进一个疲倦的上下文。拆开、多跑、在屏障处合并
**Adversarial Verification对抗验证。** 痛点:狐狸给鸡窝打分。形状:工人产出;独立验证者来反驳;只留下幸存者。本课:一次生产 `agent`,再 `parallel` 一组验证 `agent`(最好带 schema然后过滤。`phase` 标出 Review 再 Verify。答错代价很低时就别用。
```text
task ──► ● ● ● ● ══屏障══► synthesize
```
**Generate-And-Filter生成再过滤。** 痛点:你要的是选项,不是第一个听起来机灵的念头。形状:许多生成器把想法倒进“量尺 + 去重”。本课:`parallel` 生成,再在脚本里过滤(或一个裁判 `agent`)。生成很贵时 journal 特别有用。好答案空间本来就很小,就别用。
**Adversarial Verification对抗验证**——狐狸不该给鸡窝打分。工人产出;独立验证者来挑刺;只留下还站得住的
**Tournament锦标赛。** 痛点:品味和排序上,绝对分数糊成一团。形状:两两比较、淘汰支架、冠军——相对判断胜过孤独打分。本课:脚本里多轮 pairwise 裁判 `agent`,直到剩一个。清晰量尺一趟就能选出赢家时,就别用。
```text
worker ──► verifier
├──► verifier
└──► verifier → 留下仍然成立的
```
**Loop Until Done接到完为止。** 痛点:你不知道矿里还要挖几轮。形状:只要“还有新发现?”为是就继续派工;连续空轮就停。本课:`while` 包着 `agent`/`parallel`,带 schema 的停止检查,再加硬性 `budget`。长挖可能暂停时配上 journal。工作量已知时固定 `pipeline` 更简单。
**Generate-And-Filter生成再过滤**——你要的是选项,不是第一个听起来机灵的念头。许多生成器,再加一把量尺(和去重)
几种有了面孔之后,工具箱一眼就能看清:
同一工具箱里还有 **Classify-And-Act**(路由到专家)、**Tournament**(两两比较出冠军)、**Loop Until Done**(“还有新发现?”为是就继续派,并加上硬性 `budget`)。只有额外成本能买到更清楚或更稳妥的结果时,才去借一种风格。
| 模式 | 原语速写 | 什么时候伸手 |
<details>
<summary>每种模式如何落到本课原语</summary>
| 模式 | 原语速写 | 什么时候别用 |
|------|----------|--------------|
| Classify-And-Act | `agent` → 分支 → `agent` | 条目需要不同专家 |
| Fanout-And-Synthesize | `pipeline` / `parallel` → 合并 | 许多干净桌子,再一份摘要 |
| Adversarial Verification | 产出 → `parallel(verify)` → 过滤 | 答错很 |
| Generate-And-Filter | `parallel(gens)`量尺过滤 | 先要选项,再要品味 |
| Tournament | 两两裁判 `agent` | 排序/品味却没有锋利刻度 |
| Loop Until Done | `while` + 停止 + `budget` | 埋着不知多少活 |
| Classify-And-Act | `agent` → 分支 → `agent` | 每件东西其实都该同样处理 |
| Fanout-And-Synthesize | `pipeline` / `parallel` → 合并 | 一趟已经装得下 |
| Adversarial Verification | 产出 → `parallel(verify)` → 过滤 | 答错很便宜 |
| Generate-And-Filter | `parallel(gens)`过滤 | 好答案空间本来就很小 |
| Tournament | 两两裁判 `agent` | 清晰量尺一趟就能选出赢家 |
| Loop Until Done | `while` + 停止 + `budget` | 工作量已知 |
组合是常态。深度调研常常叠成:分发 → 过滤 → 验证 → 汇总。我们的示例,是两个音符的一小段和弦。
```python
# 教学示意 — 先分类再行动
kind = await ctx.agent("给这张工单分类", schema=KIND)
if kind["type"] == "billing":
return await ctx.agent("处理账单…")
```
组合是常态:深度调研常常叠成 分发 → 过滤 → 验证 → 汇总。
</details>
### 当 workflow 碰上不可信输入
工具箱旁边还值得留一个形状:**quarantine triage隔离分流**。工单、bug 报告、用户反馈都是不可信的。你不会希望*读*它们的 agent同时也握着能开 PR 的钥匙。
工单和用户反馈是不可信的。*读*它们的 agent不该同时握着能开 PR 的钥匙。留一道气闸:读者只读,只递结构化摘要;受信任的 actor 根据摘要行动——从不碰原始正文。
```text
积压(不可信)
┌─ 隔离区(只读) ────────┐
│ readers → 去重 → 摘要 │
└────────────┬────────────┘
┌─ 受信任(高权限) ──────┐
│ actor → 修复 / 升级人工 │
└─────────────────────────┘
```
<details>
<summary>官方隔离分流图</summary>
![隔离分流](images/quarantine-triage.png)
*读者留在只读的隔离区里,分类、去重,只把结构化摘要递过去。高权限工具住在受信任一侧——它们只根据摘要行动,从不碰原始正文。积压永远睡不着时,可以和 `/loop` 配对。*
*读者在隔离区里分类、去重;高权限工具住在受信任一侧。积压永远睡不着时,可以和 `/loop` 配对。*
落到本课原语,仍是脚本和 agent一串低权限 reader `agent``pipeline``parallel`,摘要进变量,再交给另一个 actor `agent`(或嵌一层 `workflow`)去写。真正值钱的是气闸——谁被允许看见原始文本。
</details>
## 跟着 `review-changes` 走一圈——一种组合
示例不是“一种模式”。它是 **Fanout-And-Synthesize**,里面嵌着 **Adversarial Verification**——结尾再轻轻做一层 generate-and-filter只留下 `isReal` 的 finding。
示例不是“一种模式”。它是 **Fanout-And-Synthesize**,里面嵌着 **Adversarial Verification**——结尾再轻轻过滤,只留下 `isReal` 的 finding。
```text
correctness ── 审计 ── 验证 ──┐
security ── 审计 ── 验证 ──┤── 确认过的问题
performance ── 审计 ── 验证 ──┤
style ── 审计 ── 验证 ──┘
分发fanout 汇总synthesize
└── 每条 finding怀疑式验证 ──┘
correctness ── 审计 ── 验证 ──┐
security ── 审计 ── 验证 ──┤── 确认列表
performance ── 审计 ── 验证 ──┤
style ── 审计 ── 验证 ──┘
分发 ▲ 汇总
└── 每条 finding 的怀疑式验证
```
`pipeline(DIMENSIONS, audit, verify)` 给每个维度自己的桌子。`verify` 里对验证 agent 做 `parallel`,就是对抗那一和弦。普通的列表过滤是汇总。`phase` 标出 Review Verifyjournal 记住每次 `agent()`,暂停也不会重做审计。
`pipeline(DIMENSIONS, audit, verify)` 给每个维度自己的桌子。`verify` 里对验证 agent 做 `parallel`,就是对抗那一和弦。列表过滤是汇总。`phase` 标出 Review Verifyjournal 记住每次 `agent()`,暂停也不会重做审计。
那三种走偏,会感觉自己最爱的座位被撤了:舰队不能在两个维度后收工,作者不当裁判,拓扑也不会在中途漂移。
```python
# 来自 code.py — 可运行样本(节选)
async def sample_workflow(ctx, args):
ctx.phase("Review")
results = await ctx.pipeline(DIMENSIONS, audit, verify)
@ -181,23 +229,23 @@ async def sample_workflow(ctx, args):
```
<details>
<summary>怎样挂在 s15 上,却不取代它</summary>
<summary>怎样挂在 s15 上(却不取代它)</summary>
s15 仍是宿主循环。s16 只多了一个名叫 `Workflow` 工具。你(或模型)报一个已保存的名字;适配器找到脚本再跑。
s15 仍是宿主循环。s16 只多了一个 `Workflow` 工具。你(或模型)报一个已保存的名字;适配器跑脚本
真正的产品里,这次运行可以待在后台、带着通知,会话照样能应你。教学 CLI 把 `demo` / `resume` 放在前台,好让你看清阶段和缓存命中。想法相同;简化之处我们会明说。主循环多借一把工具,就像借 `bash``task`
在产品里,这次运行可以待在后台、带着通知。教学 CLI 把 `demo` / `resume` 放在前台,好让阶段和缓存命中容易看见。想法相同;简化之处我们会明说。
</details>
## 转一转这颗宝石:谁握着计划?
看看邻居,同一件东西会露出新的面。有用的问题不是“几个 agent而是**谁拥有拓扑**,半成品的碗放在哪。
有用的问题不是“几个 agent而是**谁拥有拓扑**,半成品的碗放在哪。
| 邻居 | 谁握着计划 | 中间结果住哪 | 最适合 |
|------|------------|--------------|--------|
| [s06 子 Agent](../s06_subagent/) | 模型,一次性 | 多半丢掉 | 隔离一个脏的子任务 |
| [s13 Agent Teams](../s13_agent_teams/) | Lead 逐轮 + 邮箱 | 共享任务 / 消息 | 长跑的同伴 |
| [s15 Agent Harness 集成](../s15_integrated_harness/) | 模型在一个循环里 | 对话 `messages[]` | 累积型 coding agent |
| [s13 Agent Teams](../s13_agent_teams/) | Lead + 邮箱 | 共享任务 / 消息 | 长跑的同伴 |
| [s15 Agent Harness 集成](../s15_integrated_harness/) | 模型在一个循环里 | `messages[]` | 累积型 coding agent |
| **s16 Workflow** | **脚本** | **变量 + journal** | 结构化分发与验证 |
| [s17 Goal Loop](../s17_goal_loop/) | 停止时的判断器 | 对话当证据 | “整个目标做完了吗?” |
@ -207,7 +255,7 @@ s15 仍是宿主循环。s16 只多了一个名叫 `Workflow` 的工具。你(
Workflow 要花 token也有协调成本。大多数普通写代码并不需要五人评审团。
动手前问一句:这活真的想要更多算力和一层定制 harness 吗?若普通的 s15 一轮——或一个老实的 s06 子 agent——就够就停在那儿。克制也是思想的一部分:并行和分工得赚回自己的位置
问问这活是否真的想要更多算力和一层定制 harness。若普通的 s15 一轮——或一个老实的 s06 子 agent——就够就停在那儿。克制也是设计思想的一部分。
## 试一下
@ -217,10 +265,10 @@ python s16_workflow_runtime/code.py demo # 固定数据;看阶段
python s16_workflow_runtime/code.py resume # 同一 runId期待缓存命中
```
看 Review 让给 Verify;看完整续跑时 agent 从 `done` 翻成 `cached`。结尾是一份短短的确认列表——干净续跑会显示 `agents=0 tokens=0`,像笔记本在说:没有什么需要重新加热。
看 Review 让给 Verify。完整续跑时 agent 翻成 `cached`,并应看到 `agents=0 tokens=0`——笔记本在说:没有什么需要重新加热。
## 接下来
s16 讲一批活怎么跑。[s17 Goal Loop](../s17_goal_loop/) 在门口问另一个问题:该停,还是再来一轮?可重复的菜谱若还需要硬性的“做完”,可以和它一起用。
s16 讲一批活怎么跑。[s17 Goal Loop](../s17_goal_loop/) 在门口问另一个问题:该停,还是再来一轮?
<!-- translation-sync: zh@v16, en@v16, ja@v16 -->
<!-- translation-sync: zh@v17, en@v17, ja@v17 -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 181 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 181 KiB

Before After
Before After