mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-20 12:13:38 +08:00
Polish s16 READMEs into flowing essay voice
Rewrite EN/ZH/JA for tea-conversation cadence: soft transitions, insight before jargon, fewer checklist piles — same design spine, warmer prose. Co-authored-by: Xinlu Lai <CrazyBoyM@users.noreply.github.com>
This commit is contained in:
@@ -6,62 +6,55 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
|
||||
|
||||
> *「ターンごとのチャットは、10 秒ごとにシェフへメールするようなものです。Workflow は厨房が従えるレシピです。」*
|
||||
>
|
||||
> **Harness 層**: Orchestration — single-agent loop の上で multi-agent script を実行します。
|
||||
> **Harness 層**: Orchestration — single-agent loop の上に multi-agent script を載せます。
|
||||
>
|
||||
> モデルを信頼し、harness をエンジニアリングする。Workflow は orchestration 層での harness 設計です。
|
||||
> モデルを信頼し、harness をエンジニアリングする。Workflow は、その考えを orchestration の階へ運んだものです。
|
||||
|
||||
---
|
||||
|
||||
友だちとチャットだけで料理している場面を想像してください。「玉ねぎを切って」と送り、返事を待ち、「できた?」、次は「フライパンを……」。一品ならまだよいです。二十卓の宴会では、チャット自体がボトルネックになります。手順を忘れ、同じ指示を繰り返し、スマホが落ちたら最初からやり直しです。
|
||||
友だちとチャットだけで料理しているところを想像してください。「玉ねぎを切って」。待つ。「できた?」。次はフライパン、塩。一品ならそのリズムでも持ちます。二十卓の宴会では持ちません。手順は抜け、同じ言葉が返り、スマホが落ちたら冷たいところからやり直しです。
|
||||
|
||||
ふつうの「モデルが指揮者」な会話も同じです。**Workflow** は書かれたレシピです。厨房(runtime)がそれに従い、助手(subagent)が判断し、途中の器はカウンターに置かれます —— グループチャットの中ではありません。
|
||||
モデルがシェフとクリップボードを兼ねるときも、同じ感触です。計画と実行が一つの会話に押し込まれます。**Workflow** は書かれたレシピです。厨房(runtime)がそれに従い、助手(subagent)が味見と判断をし、仕掛かりの器はカウンターに置かれます。グループスレッドの中ではありません。
|
||||
|
||||
## そもそも harness は何のため?
|
||||
## なぜ、もう一枚の harness が要るのか
|
||||
|
||||
デフォルトの Claude Code harness は、コーディング型の仕事に強いです。直す、走らせる、エラーを読む、また試す —— すべて同じループの中です。
|
||||
デフォルトの Claude Code harness は、コーディング型の仕事にもう十分強いです。直す、走らせる、エラーを読む、また試す。一つのループ、一つの頭で、かなりの手業が出ます。
|
||||
|
||||
ある種の仕事には、その上に**定制の harness** が要ります。深い調査、セキュリティ分析、agent teams、大規模な code review。SDK でその harness を先に手書きしてもよいです。あるいは —— ここが dynamic の発想ですが —— Claude に**このタスク用の harness をその場で書かせ**、走らせ、良いものを保存できます。
|
||||
ただ、形の違う仕事もあります。深い調査、セキュリティの洗い出し、agent teams、変更一式を広げて review するような仕事です。そういうとき、人は昔からその上に第二の harness を載せてきました。SDK で先に手書きしてもよい。あるいは——ここが生きているところですが——Claude に**このタスク用**の harness を書かせ、走らせ、良いものを残せます。
|
||||
|
||||
コースのモットーを一段上げるとこうなります。各ステップの中ではモデルを信頼する。ステップ同士の構造はエンジニアリングで決める。
|
||||
コースのモットーを一階上げると、こうなります。各ステップの中ではモデルを信頼する。ステップの並びは、自分で形を決める。
|
||||
|
||||
## 問題: ひとつの窓、三つの失敗
|
||||
## 長いチャットで見かける癖
|
||||
|
||||
s01 から s15 まで、モデルは**同じ** context の中で計画と実行をします。直前の発見で次が決まるタスクには向いています。長く、大規模に並行し、硬い構造が要り、あるいは敵対的な検証が要る仕事では脆くなります。
|
||||
s01 から s15 まで、計画と行動は同じ context window を共有します。次の一手が直前の発見に依るときは、とても心地よいです。
|
||||
|
||||
Claude Code の設計者は、その単一ウィンドウで起きやすい三つの失敗に名前を付けています。平たい言葉では:
|
||||
ところが仕事が長く、大規模に並行し、硬い構造を求め、あるいは疑り深い第二意見が要ると、脆くなります。長いチャットをじっと見ていると、見覚えのある癖に出会います。50 項目のうち 35 で勝利宣言をする。自分の宿題を採点させると甘くなる——狐が鶏小屋を採点する。多ターンと圧縮のあいだに、「X には触るな」という静かな制約が薄れて、なぜそれがそこにあったのか誰も覚えていない。
|
||||
|
||||
| 失敗モード | どんな感じか |
|
||||
|------------|--------------|
|
||||
| **Agentic laziness(途中で切り上げ)** | 50 項目の review のうち 35 で「完了」と言う |
|
||||
| **Self-preferential bias(自己びいき)** | 自分の発見を自分で採点すると甘くなる —— 狐が鶏小屋を採点する |
|
||||
| **Goal drift(目標の漂流)** | もともとの「X には触るな」が多ターンと圧縮のあいだに薄れる |
|
||||
Claude Code の設計者は、これらを agentic laziness、self-preferential bias、goal drift と呼びます。名前より感触が大事です。仕事をする窓が、計画を覚える窓でもある。会話履歴は、並行性や安定した結果の形、落ちたあとの再開を預けるには柔らかい場所です。多くのファイルを review する、調査してから検証する、N 個のモジュールを同じやり方で移す——そうした仕事は、形が先に分かっています。柔らかい記憶だけでは足りません。
|
||||
|
||||
会話履歴は、並行性・安定した結果の形・再開の三つを同時に預ける場所としても弱いです。多くのファイルを review する、調査してから検証する、N 個のモジュールを同じやり方で移す —— こうした仕事は**形が先に分かっている**ので、なおさらその三つが要ります。
|
||||
## アイデアが落ちる瞬間
|
||||
|
||||
## 一息でいうアイデア
|
||||
もし計画がコードの中に住んだらどうでしょう。
|
||||
|
||||
**オーケストレーションを「賢さ」から「構造」へ移します。**
|
||||
助手は相変わらず考えます——きれいな机で、焦点の定まった一つの仕事を。**script** がループと扇状の分配とマージを持ちます。中間結果は変数と journal にあり、会話には入りません。途中で切り上げる癖は、艦隊全体を止めにくくなります。自己採点の甘さは、著者ではない第二の助手にぶつかります。drift も掴みにくくなります。トポロジーを、疲れた語り手が毎ターン書き換える必要がないからです。
|
||||
|
||||
Subagent は相変わらず判断します —— それぞれきれいな context と、焦点の定まった仕事で。**script** がループ、扇状の分配、マージを持ちます。中間結果は変数(と journal)にあり、会話には入りません。分かれた助手 + script が握る制御フローが、laziness・自己チェックの偏り・drift への対抗策です。
|
||||
一行で言えば、workflow はオーケストレーションを「賢さ」から「構造」へ移します。モデルは各 `agent()` の中で判断し、地図は script が持ちます。
|
||||
|
||||

|
||||
|
||||
1 回の `Workflow` tool call が、その script 実行を始めます。実行中に lifecycle / progress event が出て、最後に launch 情報・result・task state を含む tool result が返ります。
|
||||
1 回の `Workflow` tool call が、その実行を始めます。進み具合は途中で小さく鳴り、最後に launch 情報と結果と task state が一つの tool result で戻ります。
|
||||
|
||||
## ふたつの入口 — dynamic と static
|
||||
## 同じ厨房への、ふたつの入口
|
||||
|
||||
Claude Code は同じ厨房への入口を二つ開いています。
|
||||
Claude Code は入口について率直です。
|
||||
|
||||
| 入口 | 渡すもの | いつ使うか |
|
||||
|------|----------|------------|
|
||||
| **Dynamic** | オーケストレーション用の JavaScript(`script`、あとから `scriptPath`) | モデルが**このタスク用**にレシピを書く |
|
||||
| **Saved** | `name` + `args` | 良いレシピを例えば `.claude/workflows/` に保存し、名前で再実行する |
|
||||
ときどきモデルは、*この*タスク用の JavaScript オーケストレーションを書き、`script` として渡します(あとから `scriptPath` を編集することもあります)。これが **dynamic** の入口です。問題がまだ熱いうちに、合わせた harness を裁断します。
|
||||
|
||||
厨房は同じです。Dynamic は「今レシピを書く」、Saved は「カード箱から引く」—— 良い dynamic run の残した、再利用できる残りです。
|
||||
ときどき、良い script はすでに `.claude/workflows/` のような場所にあります。`name` と `args` で呼び出します。これが **saved** の入口です。残すに値した run が、再利用できるカードになったものです。
|
||||
|
||||
このレッスンの外にはいとこもあります。**static** harness(あらかじめ書く Agent SDK / `claude -p` の编排)です。static はあらゆるエッジケース向けなので、どうしても汎用になります。dynamic は*この*タスク向けの特注です。形が合ったら saved にします。
|
||||
このレッスンの外にはいとこもあります。Agent SDK や `claude -p` で先に書く **static** harness です。あらゆるエッジケースに耐える必要があるので、どうしても汎用になります。dynamic はこの布のための裁断です。形が合ったら保存します。
|
||||
|
||||
**このレッスンは Python の teaching runtime です。** 同じアイデアを、1 行ずつ読める形で示します。デモは名前で saved workflow を登録します。概念は Claude Code の script 世界と 1:1 です。「モデルは実行可能コードを渡せない」と Claude Code について主張するのは誤りでした。ここでは単に、完全な JS インタプリタを埋め込まないだけです。
|
||||
**この章は Python の teaching runtime です。** 同じアイデアを、1 行ずつ読める形で示します。デモは名前で一つの saved workflow を登録します。概念は Claude Code の script 世界と一一対応です。「モデルは実行可能コードを渡せない」などと言いません——それは Claude Code については初めから正しくありませんでした。ここでは、完全な JavaScript インタプリタを埋め込まないだけです。
|
||||
|
||||
```python
|
||||
# Teaching adapter: saved の入口(name + args)。
|
||||
@@ -81,44 +74,35 @@ WORKFLOW_TOOL = {
|
||||
}
|
||||
```
|
||||
|
||||
## プリミティブを学校のバザーで
|
||||
## 厨房の動詞を少し
|
||||
|
||||
学校のバザーでたくさんのケーキを焼くとします。各テーブルは 混ぜる → 焼く → 箱詰め。助手が味見と判断をし、レシピが順番を決めます。
|
||||
学校のバザーでケーキをたくさん焼くとします。どのテーブルも 混ぜる → 焼く → 箱詰め。助手が味見をし、レシピが順番を決めます。
|
||||
|
||||
| Primitive | 厨房での意味 |
|
||||
|-----------|--------------|
|
||||
| `agent(prompt, {schema, label, phase})` | 助手ひとりに一つの仕事を頼む |
|
||||
| `pipeline(items, *stages)` | **既定。** 各ケーキが自分で混ぜ→焼き→箱詰めを通る。A が箱詰め中でも、B はまだ混ぜているかもしれない |
|
||||
| `parallel(thunks)` | **すべての**トレイが戻るまで待つ — 次の段が本当に全部の結果を必要とするときだけ |
|
||||
| `phase(title)` | 進捗ボードに「今は焼き工程」と出す |
|
||||
| `log(message)` | 短いステータスを一声 |
|
||||
| `workflow(name, args)` | 小さなレシピを呼ぶ(ネストは 1 段) |
|
||||
| `args` | この run に渡す材料リスト |
|
||||
| `budget` | 使える「オーブン分」(token) |
|
||||
`agent(...)` は、助手ひとりに一つの仕事を頼むことです。`pipeline(items, *stages)` が既定で、各ケーキが自分で段階を歩きます。だから一方が箱詰めのあいだに、もう一方はまだ混ぜていてもよい。`parallel(...)` は揃うまで待つバリアで、次の段が本当に全部の結果を要すときだけ欲しくなります。トレイ全部を味見してから採点表を書く、といった場面です。
|
||||
|
||||
既定は `pipeline` です。次の段が直前の結果をすべてまとめて必要とするときだけ `parallel` を使います —— 全トレイを味見してから採点表を書く、といった場合です。
|
||||
その周りに、静かな動詞もあります。`phase` はボードにいまの場所を出し、`log` は短い一声、`workflow` は小さなレシピを入れ子にし、`args` は材料リスト、`budget` は使えるオーブン分(token)です。
|
||||
|
||||
```python
|
||||
# 各 dimension が独立して audit → verify を通る(stage 間に barrier なし)。
|
||||
# 各 review dimension が自分で audit → verify を歩く。
|
||||
results = await ctx.pipeline(DIMENSIONS, audit, verify)
|
||||
confirmed = [f for r in results if r for f in r["confirmed"]]
|
||||
```
|
||||
|
||||
## 味のあるパターン(一覧の投げ売りではない)
|
||||
## パターンは、元が取れるときだけ
|
||||
|
||||
パターンはレシピのスタイルだと思ってください。サンプル `review-changes` が頼るのは主に三つです。
|
||||
カタログを暗記する必要はありません。サンプルが何をしているかに気づけば、手元に三つのスタイルがあります。
|
||||
|
||||
| パターン | 平たい意味 | サンプルでは |
|
||||
|----------|------------|--------------|
|
||||
| **Fan-out-and-synthesize** | 仕事を分け、きれいな机で進め、あとでまとめる | 4 つの dimension が `pipeline` で audit し、確認リストへ |
|
||||
| **Adversarial verification** | 別の助手が前の成果をあえて疑う | 各 finding が verify agent を通ってから残る |
|
||||
| **Generate-and-filter** | 候補を出し、検査を通ったものだけ残す | findings 入り → `isReal` だけ出し |
|
||||
変更を review の各 dimension へ**広げ**、きれいな机で進め、一つの確認リストへ**まとめ**ます。fan-out-and-synthesize です。かけらが一つの騒がしい context で混線するときに効きます。
|
||||
|
||||
同じ道具箱には、あとで出会うスタイルもあります。**classify-and-act**(種類で振り分け)、**tournament**(競わせて勝者を選ぶ)、**loop-until-done**(新しいものがなくなるまで回す)。コストに見合う、より明確で安全な結果が取れるときだけ使います。
|
||||
verify の内側では、第二の助手が各 finding をあえて疑います。adversarial verification——自分の宿題に甘くならないための、構造からの答えです。
|
||||
|
||||
## 答えを機械が読める形に
|
||||
残るのは、生成したものへのフィルタです。Generate-and-filter。候補が入り、通ったものだけが出ます。
|
||||
|
||||
助手が散文で返してくると、次の stage は finding と verdict を reliably に対応づけられません。`schema` を渡します。runtime は JSON を求め、検証し、だめなら**1 回だけ**再試行します。それでもだめならその call はエラーになります(下の null 分離を参照)。
|
||||
同じ道具箱には classify-and-act、tournament、loop-until-done もあり、あとで出会います。余分なコストが、安くは手に入らない明瞭さや安全を買うときだけ、スタイルを借りてください。
|
||||
|
||||
## 次の段が受け取れる答え
|
||||
|
||||
助手が散文で返してくると、次の stage は finding と verdict を揃えられません。`schema` を渡します。runtime は JSON を求め、確かめ、**一度だけ**やり直します。それでもだめならその call はエラーになります——失敗のとき艦隊がどう優しくいられるかは、次の話です。
|
||||
|
||||
```python
|
||||
out = await ctx.agent(
|
||||
@@ -126,60 +110,49 @@ out = await ctx.agent(
|
||||
schema=FINDINGS_SCHEMA,
|
||||
label=f"audit:{dimension}",
|
||||
)
|
||||
# out は "findings" を持つ dict であり、段落ではない
|
||||
```
|
||||
|
||||
あなたとの会話は自然言語でよいです。パイプラインには合うソケットが必要です。
|
||||
あなたとの会話は散文のままでよいです。パイプラインには合うソケットが要ります。
|
||||
|
||||
## 助手がひとり失敗したとき
|
||||
## トレイがひとつ焦げたとき
|
||||
|
||||
トレイがひとつ焦げても、艦隊全体を止めてはいけません。
|
||||
助手のオーブンが一つ失敗したくらいで、艦隊を止めてはいけません。
|
||||
|
||||
- **`parallel`**: 失敗した thunk はそのスロットで `null` / `None` になります。gather 自体は reject しません。
|
||||
- **`pipeline`**: 失敗した stage は**その item** を `null` / `None` にし、残りの stage をスキップします。他の item は進み続けます。
|
||||
|
||||
マージ前に注意して絞り込みます。よくあるのは `if r` / `.filter(Boolean)` です。
|
||||
`parallel` では、失敗した thunk がそのスロットで `null` / `None` になり、gather 自体は reject しません。`pipeline` では、失敗した stage が**その item** を null にし、残りの stage を飛ばします。ほかの item は歩き続けます。マージの前に注意して絞ります。`if r`、JS ならよく `.filter(Boolean)` です。
|
||||
|
||||
```python
|
||||
verdicts = await ctx.parallel([...]) # いくつかは None かもしれない
|
||||
verdicts = await ctx.parallel([...]) # 空のスロットがありうる
|
||||
confirmed = [
|
||||
f for f, v in zip(findings, verdicts)
|
||||
if v and v.get("isReal")
|
||||
]
|
||||
```
|
||||
|
||||
## Journal と resume
|
||||
## また開けるノート
|
||||
|
||||
各 run には `runId` があります。`agent()` が終わるたびに、runtime は disk 上の journal へ 1 行追記します。ノートだと思ってください。助手がオーブンから戻った順ではなく、あなたが**呼んだ**順です。
|
||||
各 run には `runId` があります。`agent()` が終わるたび、disk 上の journal に一行が乗ります——助手がオーブンから戻った順ではなく、あなたが**呼んだ**順のノートです。
|
||||
|
||||
resume(`resume_from_run_id` / `resumeFromRunId`)では script をまた先頭から走らせますが:
|
||||
resume(`resume_from_run_id` / `resumeFromRunId`)は script をまた先頭から走らせますが、丁寧です。呼び出し順に次の journal 行と照合し、最長の未変更プレフィックスはキャッシュから再生します。最初の変更または未完了でプレフィックスが切れ——それ以降はすべて live です。ノートの後ろに古い key が残っていても、割れ目を飛び越えて黙って hit しません。
|
||||
|
||||
1. 呼び出し順で、各 `agent()` を次の journal 行と照合します。
|
||||
2. **最長の未変更プレフィックス** → cache hit(即座に再生)。
|
||||
3. **最初の**変更または未完了 call でプレフィックスが切れます。
|
||||
4. **それ以降はすべて live** — journal の後ろに古い key が残っていても、黙って hit しません。
|
||||
|
||||
本物の JS workflow runtime が `Date.now()` / `Math.random()` / 引数なしの `new Date()` を禁じるのはこのためです。非決定的な時計や乱数は prompt や呼び出し順を変え、ノートが合わなくなります。この Python デモは完全なサンドボックスではありません —— それでも script は決定的に書いてください。
|
||||
本物の 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(古い D への silent hit なし)
|
||||
resume: A hit → B hit → C 変更 → D は live
|
||||
```
|
||||
|
||||
## サンプルを歩く: `review-changes`
|
||||
## `review-changes` を歩く
|
||||
|
||||
4 つの review dimension が同じ 2 段階の道を通ります —— fan-out、敵対的 verify、filter。
|
||||
四つの dimension が同じ二段の道を共有します——広げ、敵対的に確かめ、残ったものを残す。
|
||||
|
||||
```text
|
||||
correctness ── audit ── verify ──┐
|
||||
security ── audit ── verify ──┤── 確認済み finding を統合
|
||||
security ── audit ── verify ──┤── 確認済みの finding
|
||||
performance ── audit ── verify ──┤
|
||||
style ── audit ── verify ──┘
|
||||
```
|
||||
|
||||
1. **Review** — 各 dimension の auditor が構造化 findings を返します(きれいな机 → 混線が減る)。
|
||||
2. **Verify** — 各 finding を敵対的チェッカーへ(verify stage 内で `parallel`)。書いた本人が審判を兼ねない。
|
||||
3. 本物とされたものだけ残し、severity で並べます。
|
||||
Review は各 auditor を自分の机に置き、correctness の雑談が security へ流れ込まないようにします。Verify は各 finding を、著者ではない懐疑者へ渡します。本物だけが残り、severity で並びます。三つの癖が、お気に入りの席を失う感触が、そこにあります。
|
||||
|
||||
```python
|
||||
async def sample_workflow(ctx, args):
|
||||
@@ -190,64 +163,46 @@ async def sample_workflow(ctx, args):
|
||||
return {"confirmed": confirmed}
|
||||
```
|
||||
|
||||
## s15 へのつなぎ方
|
||||
## s15 に掛けて、置き換えない
|
||||
|
||||
s15 は依然として host loop です。s16 が足すのは一つの tool、`Workflow` だけです。モデル(またはあなた)が saved name を渡し、adapter が registry を解決して script を走らせます。
|
||||
s15 は依然として host loop です。s16 が足すのは `Workflow` という tool だけです。あなた(またはモデル)が saved の名前を頼み、adapter が script を見つけて走らせます。
|
||||
|
||||
| | Claude Code / Pi(製品) | この teaching CLI |
|
||||
|--|--------------------------|-------------------|
|
||||
| Script 言語 | サンドボックス内の JavaScript | 読める Python 関数 |
|
||||
| Dynamic 入口 | モデルが `script` を書く / `scriptPath` を編集 | 文書で説明。デモは saved の `name` |
|
||||
| 実行中の host | バックグラウンド + 通知でセッションが応答し続ける | 観察しやすいよう `demo` / `resume` は前景 |
|
||||
| アイデア | 同じ primitives、journal、prefix resume | teaching model — 簡略化は明示する |
|
||||
本番では、その run は通知付きで背景に置き、セッションは応答し続けられます。teaching CLI は `demo` と `resume` を前景に置き、phase と cache hit を目で追えるようにしています。アイデアは同じで、簡略化したところははっきり言います。
|
||||
|
||||
main loop が workflow エンジンになるわけではありません。`bash` や `task` を借りるのと同じく、tool をひとつ借ります。
|
||||
main loop が workflow エンジンになるわけではありません。`bash` や `task` を借りるように、tool を一つ借ります。
|
||||
|
||||
## 近所: 計画を握っているのは誰か
|
||||
## 宝石を回す: 計画を握っているのは誰か
|
||||
|
||||
Workflow は「agent を増やす」ことではありません。**トポロジーを誰が持つか**を変えます。
|
||||
近所を見ると、同じものが別の面を見せます。役に立つ問いは「agent は何人か?」ではなく、**トポロジーを誰が持つか**、仕掛かりの器はどこに置かれるか、です。
|
||||
|
||||
| 近所 | 計画を握るもの | 中間結果の置き場 | 向いている用途 |
|
||||
|------|----------------|------------------|----------------|
|
||||
| [s06 Subagent](../s06_subagent/) | モデル、一度きり | 最終 summary 以外は捨てる | 汚い子タスクを隔離 |
|
||||
| [s13 Agent Teams](../s13_agent_teams/) | Lead モデルがターンごと + mailbox | 共有タスク / メッセージ | 長時間の同僚、人間らしい協働 |
|
||||
| [s15 Integrated Harness](../s15_integrated_harness/) | 一つのループ内のモデル | 会話 `messages[]` | 積み上げ型の coding agent |
|
||||
| **s16 Workflow** | **Script** | **Script 変数 + journal** | 既知 / 大規模な構造化 fan-out + verify |
|
||||
| [s17 Goal Loop](../s17_goal_loop/) | 停止境界の evaluator | 会話を証拠にする | 「ゴール全体は終わったか?」 |
|
||||
| [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 |
|
||||
| [s17 Goal Loop](../s17_goal_loop/) | 停止時の evaluator | 会話を証拠に | 「ゴール全体は終わったか?」 |
|
||||
|
||||
より安い代替もしばしば勝ちます。skill / prompt を軟らかい計画にする、短い multi-agent チャット、手書きの static SDK orchestrator、あるいは単に大きな一回のモデルターン。単一 context より長く構造を保ちたいときに workflow へ手を伸ばします —— 審査員パネルが聞こえがいいからではありません。
|
||||
より安い道もしばしば勝ちます。skill を軟らかい計画にする、短い multi-agent の会話、手書きの static orchestrator、あるいは大きな一回のモデルターン。構造が単一の context より長く生きねばならないときに、workflow へ手を伸ばします。審査員パネルが聞こえがいいからではありません。
|
||||
|
||||
## Workflow を*使わない*とき
|
||||
## 棚に戻しておくとき
|
||||
|
||||
Workflow は token と調整コストがかかります。ふつうのコーディングの大半は、5 人の reviewer パネルを**必要としません**。
|
||||
Workflow は token と調整のコストを使います。ふつうのコーディングの大半は、五人の reviewer を必要としません。
|
||||
|
||||
聞いてください。この仕事は本当にもっと計算と定制 harness が要るか? ふつうの s15 の一ターン(や一つの s06 subagent)で足りるなら、そこで止めます。抑制も設計思想の一部です —— 並行と専門化は、そのコストを回収しなければなりません。
|
||||
回す前に聞いてください。この仕事は本当にもっと計算と定制 harness を欲しがっているか。ふつうの s15 の一ターン——あるいは一つの誠実な s06 subagent——で足りるなら、そこで止めます。抑制も思想の一部です。並行と専門化は、自分の席を自分で稼がねばなりません。
|
||||
|
||||
## 試してみる
|
||||
|
||||
```bash
|
||||
python s16_workflow_runtime/code.py # s15 host + Workflow tool(real API)
|
||||
python s16_workflow_runtime/code.py demo # 固定 fixture: phase と agent を観察
|
||||
python s16_workflow_runtime/code.py resume # 同じ runId。prefix はすべて cache hit になるはず
|
||||
python s16_workflow_runtime/code.py # s15 host + Workflow(real API)
|
||||
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` と出ます。ノートが「温め直しは要らない」と言っている感じです。
|
||||
|
||||
- `workflow_phase` が Review、続いて Verify
|
||||
- 各 `workflow_agent` が初回は `done`、完全 resume では `cached`
|
||||
- 末尾の短い confirmed リスト。全 hit の resume は `agents=0 tokens=0`
|
||||
## 次へ
|
||||
|
||||
## s15 との対比 → 次は s17
|
||||
s16 はバッチの回し方です。[s17 Goal Loop](../s17_goal_loop/) は戸口で別の問いをします。止めるべきか、もう一ターンか。繰り返せるレシピに硬い「完了」も要るときは、そちらと組んでください。
|
||||
|
||||
| | s15 Integrated Harness | s16 Workflow Runtime |
|
||||
|--|------------------------|----------------------|
|
||||
| loop | 1 つ、モデル駆動 | 同じ loop。1 つの tool が script を実行 |
|
||||
| 次の step を決めるもの | モデルが毎ラウンド | script がバッチの形を持つ |
|
||||
| multi-agent | 一度きりの subagent | script 化・再開可能な `agent()` |
|
||||
| 失敗 / resume | 会話メモリ頼り | null 分離 + journal prefix |
|
||||
|
||||
**s16 = バッチの回し方。s17 = ゴール全体が終わったかどうか。**
|
||||
|
||||
[s17 Goal Loop](../s17_goal_loop/) は独立した評価器に聞きます。止めるべきか、もう一ターンか。繰り返せる workflow に硬い完了条件も要るときは、そちらと組み合わせます。
|
||||
|
||||
<!-- translation-sync: zh@v12, en@v12, ja@v12 -->
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
|
||||
@@ -6,65 +6,58 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
|
||||
|
||||
> *"Chatting turn-by-turn is like texting the chef every ten seconds. A workflow is a recipe the kitchen can follow."*
|
||||
>
|
||||
> **Harness layer**: Orchestration — run a multi-agent script above the single-agent loop.
|
||||
> **Harness layer**: Orchestration — a multi-agent script above the single-agent loop.
|
||||
>
|
||||
> Trust the model; engineer the harness. Workflows are harness engineering at the orchestration layer.
|
||||
> Trust the model; engineer the harness. Workflows are that idea at the orchestration layer.
|
||||
|
||||
---
|
||||
|
||||
Imagine you are cooking with a friend over text. You send “chop the onions,” wait, ask “are they done?”, then “now the pan…”. It works for one dish. For a feast with twenty dishes, that chat becomes the bottleneck: you forget steps, repeat yourself, and if the phone dies you start over.
|
||||
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.
|
||||
|
||||
That is ordinary model-as-orchestrator chatting. A **workflow** is the written recipe: the kitchen (runtime) follows it, helpers (subagents) do judgment, and intermediate bowls sit on the counter — not in the group chat.
|
||||
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.
|
||||
|
||||
## Why a harness at all?
|
||||
## Why bother with another harness?
|
||||
|
||||
The default Claude Code harness is excellent at coding-shaped work: edit, run, read the error, try again — all in one loop.
|
||||
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.
|
||||
|
||||
Some jobs need a **custom harness on top**: deep research, security analysis, agent teams, large code review. You could hand-write that harness once in an SDK. Or — and this is the dynamic idea — Claude can **write a harness for this task on the fly**, run it, and optionally save the good ones.
|
||||
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.
|
||||
|
||||
Course motto, one layer up: trust the model inside each step; engineer the structure around the steps.
|
||||
Same course motto, one floor up: trust the model inside each step; decide the shape of the steps yourself.
|
||||
|
||||
## The problem: one window, three ways to fail
|
||||
## What goes wrong in a long chat
|
||||
|
||||
From s01 through s15, the model plans and executes in the **same** context. Great when the next move depends on what you just found. Weak when the job is long, massively parallel, rigidly structured, or adversarial.
|
||||
From s01 through s15, plan and action share one context window. That is wonderful when the next move depends on what you just found.
|
||||
|
||||
Claude Code’s designers name three failure modes that show up in that single window. In plain language:
|
||||
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 (“don’t touch X”) fades until nobody remembers why it was there.
|
||||
|
||||
| Failure mode | What it feels like |
|
||||
|--------------|--------------------|
|
||||
| **Agentic laziness** | Stops halfway through a fifty-item review and says “done” after thirty-five |
|
||||
| **Self-preferential bias** | Likes its own findings when asked to check itself — the fox grading the henhouse |
|
||||
| **Goal drift** | The original “don’t touch X” fades across many turns and compressions |
|
||||
Claude Code’s 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.
|
||||
|
||||
Chat history is also a weak place to store parallelism, stable result shapes, and resume. You need those for review-many-files, research-then-verify, migrate-N-modules — jobs whose **shape** is already known.
|
||||
## The idea, once it clicks
|
||||
|
||||
## The idea in one breath
|
||||
What if the plan lived in code?
|
||||
|
||||
**Move orchestration from intelligence to structure.**
|
||||
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.
|
||||
|
||||
Subagents still think — each in a clean context with a focused job. The **script** owns loops, fan-out, and merge. Intermediate results live in variables (and a journal), not in the conversation. Separate helpers + script-owned control flow is how you fight laziness, self-checking bias, and drift.
|
||||
In one line: workflows move orchestration from *intelligence* to *structure*. The model still judges inside each `agent()`; the script owns the map.
|
||||
|
||||

|
||||
|
||||
One `Workflow` tool call starts that scripted run. Lifecycle and progress events fire while it works; one tool result comes back with launch info, the result, and task state.
|
||||
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 — and dynamic vs static
|
||||
## Two doors into the same kitchen
|
||||
|
||||
Claude Code opens two doors into the same kitchen:
|
||||
Claude Code is straightforward about how you enter.
|
||||
|
||||
| Door | What you pass | When |
|
||||
|------|----------------|------|
|
||||
| **Dynamic** | A JavaScript orchestration script (`script`, or later `scriptPath`) | The model writes a recipe for *this* task |
|
||||
| **Saved** | `name` + `args` | A good recipe lives under e.g. `.claude/workflows/` and you rerun it |
|
||||
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.
|
||||
|
||||
Same kitchen. Dynamic is “write the recipe now.” Saved is “pull the card from the box” — the reusable residue of a good dynamic run.
|
||||
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 also a cousin outside this lesson: **static** harnesses (Agent SDK / `claude -p` orchestrations you write ahead of time). Static ones must work for every edge case, so they stay generic. Dynamic ones are tailor-made for *this* task; save them when the cut fits well.
|
||||
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.
|
||||
|
||||
**This lesson is a Python teaching runtime.** Same ideas, every line readable. Our demo registers a saved workflow by name; concepts map 1:1 to Claude Code’s script world. We do **not** claim “the model cannot submit executable code” — that was wrong for Claude Code. We simply skip embedding a full JS interpreter here.
|
||||
**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 Code’s 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.
|
||||
|
||||
```python
|
||||
# Teaching adapter: saved door (name + args).
|
||||
# Teaching adapter: the saved door (name + args).
|
||||
# Claude Code also accepts script / scriptPath / resumeFromRunId.
|
||||
WORKFLOW_TOOL = {
|
||||
"name": "Workflow",
|
||||
@@ -81,44 +74,35 @@ WORKFLOW_TOOL = {
|
||||
}
|
||||
```
|
||||
|
||||
## Primitives, taught with a kitchen story
|
||||
## A few kitchen verbs
|
||||
|
||||
You are running a school bake sale. Each table needs mix → bake → box. Helpers taste and judge; the recipe decides the order.
|
||||
Imagine a school bake sale. Every table needs mix → bake → box. Helpers taste; the recipe decides order.
|
||||
|
||||
| Primitive | Kitchen meaning |
|
||||
|-----------|-----------------|
|
||||
| `agent(prompt, {schema, label, phase})` | Ask one helper to do one job |
|
||||
| `pipeline(items, *stages)` | **Default.** Each cake goes through mix→bake→box on its own. Cake A can be boxing while cake B is still mixing |
|
||||
| `parallel(thunks)` | Wait until **every** tray comes back — only when the next step needs all of them together |
|
||||
| `phase(title)` | Announce “we’re in baking now” on the progress board |
|
||||
| `log(message)` | Shout a short status line |
|
||||
| `workflow(name, args)` | Call a smaller recipe (one level deep) |
|
||||
| `args` | The ingredients list passed into this run |
|
||||
| `budget` | How many “oven minutes” (tokens) you may spend |
|
||||
`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.
|
||||
|
||||
Default to `pipeline`. Reach for `parallel` only when the next step truly needs every prior result at once — like tasting all trays before writing the scorecard.
|
||||
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.
|
||||
|
||||
```python
|
||||
# Each dimension walks audit → verify on its own (no barrier between stages).
|
||||
# Each review dimension walks audit → verify on its own.
|
||||
results = await ctx.pipeline(DIMENSIONS, audit, verify)
|
||||
confirmed = [f for r in results if r for f in r["confirmed"]]
|
||||
```
|
||||
|
||||
## Patterns with taste (not a laundry list)
|
||||
## Patterns, when they earn their keep
|
||||
|
||||
Think of patterns as recipe styles. Our sample `review-changes` leans on three:
|
||||
You do not need a catalog. Notice what our sample already does, and you have three styles in hand.
|
||||
|
||||
| Pattern | Plain meaning | In the sample |
|
||||
|---------|---------------|---------------|
|
||||
| **Fan-out-and-synthesize** | Split the work, give each piece a clean desk, then merge | Four dimensions audit in a `pipeline`, then one confirmed list |
|
||||
| **Adversarial verification** | A second helper tries to knock the first one’s work down | Each finding faces a verify agent before it counts |
|
||||
| **Generate-and-filter** | Produce candidates, keep only what survives a test | Findings in → only `isReal` out |
|
||||
It **fans out** the change across review dimensions, each on a clean desk, then **synthesizes** one confirmed list. That is fan-out-and-synthesize — useful when pieces would contaminate each other in one crowded context.
|
||||
|
||||
Same toolbox, other styles you will meet later: **classify-and-act** (route by type), **tournament** (compete, then pick a winner), **loop-until-done** (keep going until nothing new appears). Use a pattern only when its cost earns a clearer or safer result.
|
||||
Inside verify, a second helper tries to knock each finding down. That is adversarial verification — the structural answer to grading your own homework.
|
||||
|
||||
## Make answers machine-readable
|
||||
What survives is a filter over what was generated. Generate-and-filter: candidates in, only the ones that pass out.
|
||||
|
||||
If a helper returns a poem, the next stage cannot reliably zip findings to verdicts. Pass a `schema`: the runtime asks for JSON, validates it, and retries **once**. Fail again and that call errors (see null-isolation below).
|
||||
The same toolbox holds other cuts you will meet later — classify-and-act, tournament, loop-until-done. Borrow a style only when the extra cost buys clarity or safety you could not get cheaper.
|
||||
|
||||
## 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(
|
||||
@@ -126,60 +110,49 @@ out = await ctx.agent(
|
||||
schema=FINDINGS_SCHEMA,
|
||||
label=f"audit:{dimension}",
|
||||
)
|
||||
# out is a dict with "findings", not a paragraph
|
||||
```
|
||||
|
||||
Free-form prose is fine for chatting with you. Pipelines need sockets that fit.
|
||||
Chat with you can stay prose. A pipeline needs sockets that fit.
|
||||
|
||||
## When one helper fails
|
||||
## When one tray burns
|
||||
|
||||
A fleet should not stop because one tray burned.
|
||||
The fleet should not stop because one helper had a bad oven.
|
||||
|
||||
- **`parallel`**: a failing thunk becomes `null` / `None` in that slot; the gather itself does not reject.
|
||||
- **`pipeline`**: a failing stage drops **that item** to `null` / `None` and skips its remaining stages; other items keep going.
|
||||
|
||||
Filter with care — usually `if r` / `.filter(Boolean)` — before you merge.
|
||||
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 entries may be None
|
||||
verdicts = await ctx.parallel([...]) # some slots may be None
|
||||
confirmed = [
|
||||
f for f, v in zip(findings, verdicts)
|
||||
if v and v.get("isReal")
|
||||
]
|
||||
```
|
||||
|
||||
## Journal + resume
|
||||
## A notebook you can reopen
|
||||
|
||||
Every run gets a `runId`. As each `agent()` finishes, the runtime appends a line to a journal on disk. Think of a notebook that lists helpers in the order you *called* them, not the order they wandered back from the oven.
|
||||
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.
|
||||
|
||||
On resume (`resume_from_run_id` / `resumeFromRunId`), the script runs from the top again, but:
|
||||
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.
|
||||
|
||||
1. Compare each `agent()` call, in call order, to the next journal line.
|
||||
2. **Longest unchanged prefix** → cache hits (instant replay).
|
||||
3. At the **first** changed or unfinished call, the prefix breaks.
|
||||
4. **Everything after that runs live** — even if an old key still sits later in the journal.
|
||||
|
||||
That is why real JS workflow runtimes ban `Date.now()`, `Math.random()`, and bare `new Date()`: nondeterministic clocks and dice change prompts or call order, and the notebook no longer matches. This Python demo does not fully sandbox that — still write deterministic scripts.
|
||||
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 (no silent hit on old D)
|
||||
resume: A hit → B hit → C changed → D runs live
|
||||
```
|
||||
|
||||
## Walk the sample: `review-changes`
|
||||
## Walking `review-changes`
|
||||
|
||||
Four review dimensions walk the same two-stage path — fan-out, then adversarial verify, then filter:
|
||||
Four dimensions share one two-stage path — fan out, verify adversarially, keep what survives:
|
||||
|
||||
```text
|
||||
correctness ── audit ── verify ──┐
|
||||
security ── audit ── verify ──┤── merge confirmed findings
|
||||
security ── audit ── verify ──┤── confirmed findings
|
||||
performance ── audit ── verify ──┤
|
||||
style ── audit ── verify ──┘
|
||||
```
|
||||
|
||||
1. **Review** — each dimension’s auditor returns structured findings (clean desks → less cross-contamination).
|
||||
2. **Verify** — each finding gets an adversarial checker (`parallel` inside the verify stage) so the author is not also the judge.
|
||||
3. Keep only findings marked real; sort by severity.
|
||||
Review puts each auditor at its own desk so correctness talk does not bleed into security talk. Verify hands every finding to a skeptic who was not the author. Only the real ones remain, sorted by severity. You can almost feel the three failure modes losing their favorite seats.
|
||||
|
||||
```python
|
||||
async def sample_workflow(ctx, args):
|
||||
@@ -190,64 +163,46 @@ async def sample_workflow(ctx, args):
|
||||
return {"confirmed": confirmed}
|
||||
```
|
||||
|
||||
## How this plugs into s15
|
||||
## Hanging on s15 without replacing it
|
||||
|
||||
s15 is still the host loop. s16 adds one tool: `Workflow`. The model (or you) asks for a saved name; the adapter resolves the registry and runs the script.
|
||||
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.
|
||||
|
||||
| | Claude Code / Pi (product) | This teaching CLI |
|
||||
|--|----------------------------|-------------------|
|
||||
| Script language | JavaScript in a sandbox | Python functions you can read |
|
||||
| Dynamic door | Model writes `script` / edits `scriptPath` | Explained in docs; demo uses saved `name` |
|
||||
| Host while running | Background + notification; session stays responsive | `demo` / `resume` run in the foreground for clarity |
|
||||
| Ideas | Same primitives, journal, prefix resume | Teaching model — precise where 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` and `resume` in the foreground so you can watch phases and cache hits without squinting. Same ideas; we say so when we simplify.
|
||||
|
||||
The main loop does not become a workflow engine. It 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`.
|
||||
|
||||
## Neighbors: who holds the plan?
|
||||
## Turning the gem: who holds the plan?
|
||||
|
||||
Workflows are not “more agents.” They change **who owns the topology**.
|
||||
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.
|
||||
|
||||
| Neighbor | Who holds the plan | Where intermediate results live | Best for |
|
||||
|----------|--------------------|---------------------------------|----------|
|
||||
| [s06 Subagent](../s06_subagent/) | Model, one-shot | Discarded except final summary | Isolate one dirty subtask |
|
||||
| [s13 Agent Teams](../s13_agent_teams/) | Lead model turn-by-turn + mailbox | Shared tasks / messages | Long-running peers, human-like collaboration |
|
||||
| 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** | **Script variables + journal** | Known / large structured fan-out + verify |
|
||||
| [s17 Goal Loop](../s17_goal_loop/) | Evaluator at the stop boundary | Conversation as evidence | “Is the whole goal done?” |
|
||||
| **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?” |
|
||||
|
||||
Cheaper alternatives still win often: a skill or prompt as a soft plan, a short multi-agent chat, a hand-written static SDK orchestrator, or simply one bigger model turn. Reach for a workflow when structure must outlast a single context — not because panels sound 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 the structure must outlast a single context — not because a panel of reviewers sounds impressive.
|
||||
|
||||
## When *not* to use a workflow
|
||||
## And when to leave it on the shelf
|
||||
|
||||
Workflows cost tokens and coordination. Most ordinary coding does **not** need a panel of five reviewers.
|
||||
Workflows spend tokens and coordination. Most ordinary coding does not need five reviewers.
|
||||
|
||||
Ask: does this job really need more compute and a custom harness? If a normal s15 turn (or one s06 subagent) is enough, stop there. Restraint is part of the design thought — parallelism and specialization have to earn their keep.
|
||||
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.
|
||||
|
||||
## Try it
|
||||
|
||||
```bash
|
||||
python s16_workflow_runtime/code.py # s15 host + Workflow tool (real API)
|
||||
python s16_workflow_runtime/code.py demo # fixed fixture: watch phases + agents
|
||||
python s16_workflow_runtime/code.py resume # same runId; prefix should be all cache hits
|
||||
python s16_workflow_runtime/code.py # s15 host + Workflow (real API)
|
||||
python s16_workflow_runtime/code.py demo # fixed fixture; watch phases
|
||||
python s16_workflow_runtime/code.py resume # same runId; expect cache hits
|
||||
```
|
||||
|
||||
What to watch for:
|
||||
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.
|
||||
|
||||
- `workflow_phase` lines for Review, then Verify
|
||||
- each `workflow_agent` flip from `done` (first run) to `cached` (full resume)
|
||||
- a short confirmed list at the end; full resume shows `agents=0 tokens=0`
|
||||
## Next
|
||||
|
||||
## Relative to s15 → next is s17
|
||||
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.”
|
||||
|
||||
| | s15 Integrated Harness | s16 Workflow Runtime |
|
||||
|--|------------------------|----------------------|
|
||||
| Loop | One model-driven loop | Same loop; one tool runs a script |
|
||||
| Who decides the next step | Model, each round | Script owns the batch shape |
|
||||
| Multi-agent | One-shot subagents | Scripted, resumable `agent()` calls |
|
||||
| Failure / resume | Conversation memory | Null-isolation + journal prefix |
|
||||
|
||||
**s16 = how a batch runs. s17 = whether the whole goal is done.**
|
||||
|
||||
[s17 Goal Loop](../s17_goal_loop/) asks an independent evaluator: should we stop, or take another turn? Pair them when a repeatable workflow also needs a hard completion check.
|
||||
|
||||
<!-- translation-sync: zh@v12, en@v12, ja@v12 -->
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
|
||||
@@ -6,62 +6,55 @@ s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](..
|
||||
|
||||
> *“一轮轮聊天,像每隔十秒给厨师发一条短信。Workflow 是厨房能照着做的菜谱。”*
|
||||
>
|
||||
> **Harness 层**: 编排 — 在单 agent 循环之上,跑一套多 agent 脚本。
|
||||
> **Harness 层**: 编排 — 单 agent 循环之上,再跑一套多 agent 脚本。
|
||||
>
|
||||
> 信任模型,工程化 harness。Workflow 就是编排层上的 harness 工程。
|
||||
> 信任模型,工程化 harness。Workflow,就是把这句话落到编排层。
|
||||
|
||||
---
|
||||
|
||||
想象你和朋友用微信一起做饭。你发“先切洋葱”,等他回,再问“切好了吗?”,然后“热锅……”。一道菜还行;要办二十桌宴席,聊天就成了瓶颈:步骤记丢、反复叮嘱,手机一死还得从头来。
|
||||
想象你跟朋友用微信一起做饭。“先切洋葱。”等回音。“切好了吗?”然后热锅、放盐。一道菜还能撑住这种节奏;二十桌宴席就不行了——步骤会丢,话会重复,手机一死还得从头来。
|
||||
|
||||
普通“模型当总指挥”的对话就是这样。**Workflow** 是写好的菜谱:厨房(runtime)按谱做,帮手(子 agent)负责判断,中间结果放在台面上的碗里 —— 不塞进群聊记录。
|
||||
模型既当厨师又当记事本时,感觉就是这样:计划与动手挤在同一段对话里。**Workflow** 则是写好的菜谱。厨房(runtime)按谱做,帮手(子 agent)负责尝和判断,半成品放在台面上的碗里,而不是塞进群聊记录。
|
||||
|
||||
## 为什么需要 harness?
|
||||
## 为什么还要另一层 harness?
|
||||
|
||||
默认的 Claude Code harness 很擅长“写代码那种形状”的工作:改、跑、看报错、再试 —— 都在同一个循环里。
|
||||
默认的 Claude Code harness 已经很擅长“写代码那种形状”的活:改一点、跑一下、看报错、再试。一个循环、一颗脑袋,能做出不少工艺。
|
||||
|
||||
有些活需要**叠一层定制 harness**:深度调研、安全分析、agent teams、大规模 code review。你可以事先用 SDK 手写那层 harness;也可以 —— 这就是动态的想法 —— 让 Claude **为这次任务现场写一个 harness**,跑完,好用的再存下来。
|
||||
可有些活是另一种形状——深度调研、安全排查、agent teams、要铺开审查一整片改动。这类事,人们早就习惯在上面再搭一层定制 harness。你当然可以事先用 SDK 手写;也可以——这才是有意思的地方——让 Claude **为这次任务**起草一个 harness,跑起来,好用的再留下来。
|
||||
|
||||
课程的口号往上提一层:每一步里信任模型;步骤之间的结构,靠工程来定。
|
||||
课程那句口号往上提一层:每一步里信任模型;步骤怎么排,由你来定结构。
|
||||
|
||||
## 问题:一个窗口,三种走偏
|
||||
## 长对话里你会看见的走偏
|
||||
|
||||
从 s01 到 s15,模型在**同一个**上下文里既规划又执行。当“下一步取决于刚才发现了什么”时,这很合适。当任务又长、又要大规模并行、又要求死板结构、或需要对抗验证时,就会变脆。
|
||||
从 s01 到 s15,计划与执行共享同一个上下文。下一步取决于刚才的发现时,这很舒服。
|
||||
|
||||
Claude Code 的设计者给单窗口里常见的三种失败起了名字。用大白话说:
|
||||
可一旦任务变长、要大规模并行、结构又死板,或需要一个挑剔的第二意见,它就会发脆。你若耐心看一段很长的聊天,会撞见熟面孔:做到五十项里的三十五就宣布完工;让它批改自己的作业,分数总是偏甜——狐狸给鸡窝打分;多轮对话和压缩过后,那句轻轻的“别动 X”渐渐听不见了。
|
||||
|
||||
| 失败模式 | 感觉起来像什么 |
|
||||
|----------|----------------|
|
||||
| **Agentic laziness(偷懒收工)** | 五十项审查做到三十五,就说“做完了” |
|
||||
| **Self-preferential bias(自我偏爱)** | 让它检查自己的结论时,总觉得自己更对 —— 狐狸给鸡窝打分 |
|
||||
| **Goal drift(目标漂移)** | 原来的“别动 X”在多轮对话和压缩之后渐渐淡掉 |
|
||||
Claude Code 的设计者把这些叫做 agentic laziness、self-preferential bias、goal drift。名字不如感觉重要:同一个窗口既要干活,又要记住计划。对话历史太软,扛不住并行、稳定的结果形状,以及崩了还能续上。审查很多文件、先调研再验证、按同一方式迁移 N 个模块——这些活的形状事先就清楚。软记忆不够用。
|
||||
|
||||
对话历史也很难同时扛住并行、稳定的结果形状、以及续跑。审查很多文件、先调研再验证、按同一方式迁移 N 个模块 —— 这些活的**形状**事先就知道,更需要那三样。
|
||||
## 点子落下的那一下
|
||||
|
||||
## 一句话说清想法
|
||||
假如计划住在代码里呢?
|
||||
|
||||
**把编排从“靠聪明”挪到“靠结构”。**
|
||||
帮手仍然负责想——每人一张干净桌子,一件专注的事。**脚本**掌管循环、分发和合并。中间结果待在变量和 journal 里,不进对话。想偷懒提前收工的习惯,更难叫停整支队伍;自我检查的偏心,会撞上一个不是作者本人的第二帮手;漂移也难下手,因为拓扑不再由一个疲倦的叙述者每轮改写。
|
||||
|
||||
子 agent 仍然负责判断 —— 各自干净的上下文、专注的目标。**脚本**负责循环、分发和合并。中间结果存在变量(和 journal)里,不进对话。分开的帮手 + 脚本掌握的控制流,就是对抗偷懒、自我检查偏差和漂移的办法。
|
||||
一句话:workflow 把编排从“靠聪明”挪到“靠结构”。模型仍在每次 `agent()` 里做判断;地图归脚本管。
|
||||
|
||||

|
||||
|
||||
一次 `Workflow` 工具调用启动这次脚本运行。运行中会发出生命周期和进度事件;最后一条工具结果带回启动信息、结果和任务状态。
|
||||
一次 `Workflow` 工具调用启动这次运行。进度在旁边轻轻响;最后一条工具结果带回启动信息、结果和任务状态。
|
||||
|
||||
## 两扇门 — 以及动态 vs 静态
|
||||
## 同一间厨房,两扇门
|
||||
|
||||
Claude Code 用两扇门走进同一间厨房:
|
||||
Claude Code 对入口说得很直白。
|
||||
|
||||
| 门 | 你传什么 | 什么时候用 |
|
||||
|----|----------|------------|
|
||||
| **动态(Dynamic)** | 一段编排用的 JavaScript(`script`,或之后的 `scriptPath`) | 模型为**这次任务**现写菜谱 |
|
||||
| **已保存(Saved)** | `name` + `args` | 好用的菜谱放进例如 `.claude/workflows/`,按名字再跑 |
|
||||
有时模型为*这次*任务写一段编排用的 JavaScript,以 `script` 交出来(或之后改 `scriptPath`)。这是**动态**那扇门——问题还热着,就裁出一件合身的 harness。
|
||||
|
||||
同一间厨房。动态是“现在写菜谱”;已保存是“从卡片盒里抽一张”—— 一次漂亮动态运行留下来的可复用残渣。
|
||||
有时好脚本已经进了例如 `.claude/workflows/`。你用 `name` 和 `args` 再请它出来。这是**已保存**那扇门——一次值得留下的运行,沉淀成可复用的卡片。
|
||||
|
||||
本课之外还有表亲:**静态** harness(事先写好的 Agent SDK / `claude -p` 编排)。静态的要覆盖所有边角,所以往往更泛用。动态的是为*这次*任务量身定做;合身了再存成 saved。
|
||||
本课之外还有表亲:**静态** harness,用 Agent SDK 或 `claude -p` 事先写好。它们得扛住所有边角,所以往往更泛。动态的是为这块布现裁的;合身了再存。
|
||||
|
||||
**本课是一个 Python 教学运行时。** 同样的想法,每行你都能读懂。演示按名字注册一个已保存的 workflow;概念和 Claude Code 的脚本世界一一对应。我们**不会**再说“模型不能提交可执行代码”——那是对 Claude Code 的误述。这里只是不嵌入完整的 JS 解释器。
|
||||
**这一章是 Python 教学运行时。** 同样的想法,每行都能读。演示按名字挂了一个已保存的 workflow;概念和 Claude Code 的脚本世界一一对应。我们不会再说“模型不能提交可执行代码”——那从来不是 Claude Code 的真相。这里只是不嵌入完整的 JS 解释器。
|
||||
|
||||
```python
|
||||
# 教学适配器:已保存这扇门(name + args)。
|
||||
@@ -81,44 +74,35 @@ WORKFLOW_TOOL = {
|
||||
}
|
||||
```
|
||||
|
||||
## 原语:用一次义卖来讲
|
||||
## 厨房里的几个动词
|
||||
|
||||
学校义卖要烤很多蛋糕。每张桌子都要:搅拌 → 烘烤 → 装箱。帮手负责尝和判断;菜谱决定顺序。
|
||||
想象学校义卖要烤许多蛋糕。每张桌子都是搅拌 → 烘烤 → 装箱。帮手负责尝;菜谱决定先后。
|
||||
|
||||
| 原语 | 在厨房里的意思 |
|
||||
|------|----------------|
|
||||
| `agent(prompt, {schema, label, phase})` | 请一个帮手做一件事 |
|
||||
| `pipeline(items, *stages)` | **默认。** 每块蛋糕自己走完搅拌→烘烤→装箱。A 在装箱时,B 可能还在搅拌 |
|
||||
| `parallel(thunks)` | 等**所有**托盘都回来 —— 只有下一步真的需要全部结果时才用 |
|
||||
| `phase(title)` | 在进度板上宣布“现在进入烘烤” |
|
||||
| `log(message)` | 喊一句短状态 |
|
||||
| `workflow(name, args)` | 套用一份更小的菜谱(只嵌一层) |
|
||||
| `args` | 这次运行的“食材清单” |
|
||||
| `budget` | 还能烧多少“烤箱分钟”(token) |
|
||||
`agent(...)` 是请一个帮手做一件事。`pipeline(items, *stages)` 是默认:每块蛋糕自己走完各阶段,所以一块在装箱时,另一块可能还在搅拌。`parallel(...)` 是等齐——所有托盘都回来才往下——只有下一步真的需要全部结果时才值得,比如尝完再写评分表。
|
||||
|
||||
默认用 `pipeline`。只有下一步必须凑齐上一阶段全部结果时,才用 `parallel` —— 比如要先尝完所有托盘再写评分表。
|
||||
旁边还有更轻的词:`phase` 在进度板上报站,`log` 喊一句短话,`workflow` 嵌一份更小的菜谱,`args` 是食材清单,`budget` 是还能烧多少烤箱分钟(token)。
|
||||
|
||||
```python
|
||||
# 每个审查维度独立走 审计 → 验证(阶段之间不等齐)。
|
||||
# 每个审查维度自己走完 审计 → 验证。
|
||||
results = await ctx.pipeline(DIMENSIONS, audit, verify)
|
||||
confirmed = [f for r in results if r for f in r["confirmed"]]
|
||||
```
|
||||
|
||||
## 有品味的模式(不是清单倾销)
|
||||
## 模式:用得着才拿
|
||||
|
||||
把模式想成菜谱风格。示例 `review-changes` 主要用了三种:
|
||||
不必背目录。看清示例在干什么,手里就有三种风格。
|
||||
|
||||
| 模式 | 大白话 | 在示例里 |
|
||||
|------|--------|----------|
|
||||
| **Fan-out-and-synthesize(分发再汇总)** | 拆开干,每人一张干净桌子,再合并 | 四个维度在 `pipeline` 里审计,最后合成确认列表 |
|
||||
| **Adversarial verification(对抗验证)** | 第二个帮手专门来挑刺 | 每条 finding 先过 verify agent 才作数 |
|
||||
| **Generate-and-filter(生成再过滤)** | 先产出候选,只留通过检验的 | findings 进来 → 只留 `isReal` |
|
||||
它把改动**分发**到各个审查维度,每人一张干净桌子,再**汇总**成一份确认列表——fan-out-and-synthesize。碎片若挤在同一个嘈杂上下文里会互相串味时,这一招值钱。
|
||||
|
||||
同一工具箱里还有别的风格,以后会遇到:**classify-and-act**(按类型分流)、**tournament**(比武再选冠军)、**loop-until-done**(直到没有新发现再停)。只有当额外成本能换来更清楚或更稳妥的结果时,才上模式。
|
||||
验证阶段里,第二个帮手专门来挑每条 finding 的刺——adversarial verification,结构上回答“别给自己的作业打高分”。
|
||||
|
||||
## 让答案机器能读
|
||||
留下来的,是对生成物做过滤。Generate-and-filter:候选进来,过关的留下。
|
||||
|
||||
如果帮手回来写散文,下一阶段就很难把 finding 和 verdict 一一对应。传入 `schema`:运行时要求 JSON、做校验,不对就**重试一次**。再不对,这次调用报错(见下面的空值隔离)。
|
||||
同一工具箱里还有 classify-and-act、tournament、loop-until-done,以后都会遇见。只有额外成本能买到更清楚或更稳妥的结果时,才去借一种风格。
|
||||
|
||||
## 让下一阶段接得住的答案
|
||||
|
||||
帮手若回来写散文,下一阶段很难把 finding 和 verdict 对齐。传入 `schema`。运行时要 JSON、做校验,并给**一次**重试。再不对,这次调用报错——而舰队在失败时怎样仍然温和,下一节就说到。
|
||||
|
||||
```python
|
||||
out = await ctx.agent(
|
||||
@@ -126,60 +110,49 @@ out = await ctx.agent(
|
||||
schema=FINDINGS_SCHEMA,
|
||||
label=f"audit:{dimension}",
|
||||
)
|
||||
# out 是带 "findings" 的字典,不是一段话
|
||||
```
|
||||
|
||||
跟你聊天可以用自然语言;流水线需要接口对得上。
|
||||
跟你聊天可以继续用自然语言。流水线需要接口对得上。
|
||||
|
||||
## 一个帮手失败时
|
||||
## 一个托盘糊了的时候
|
||||
|
||||
不能因为一个托盘糊了,整支队伍停工。
|
||||
不能因为一个帮手烤箱失手,整支队伍停工。
|
||||
|
||||
- **`parallel`**:失败的 thunk 在该槽位变成 `null` / `None`;整个 gather 不会因此拒绝。
|
||||
- **`pipeline`**:某个 stage 失败时,**该 item** 变成 `null` / `None`,并跳过它后面的 stage;其他 item 继续。
|
||||
|
||||
合并前要小心过滤 —— 常见写法是 `if r` / `.filter(Boolean)`。
|
||||
在 `parallel` 里,失败的 thunk 在该槽位变成 `null` / `None`,gather 本身不会拒绝。在 `pipeline` 里,某个 stage 失败会把**那个 item** 置成空,并跳过它后面的 stage;别的 item 继续往前走。合并前小心过滤——`if r`,在 JS 里常见 `.filter(Boolean)`。
|
||||
|
||||
```python
|
||||
verdicts = await ctx.parallel([...]) # 有些位置可能是 None
|
||||
verdicts = await ctx.parallel([...]) # 有些格子可能是 None
|
||||
confirmed = [
|
||||
f for f, v in zip(findings, verdicts)
|
||||
if v and v.get("isReal")
|
||||
]
|
||||
```
|
||||
|
||||
## Journal 与续跑
|
||||
## 一本可以重开的笔记本
|
||||
|
||||
每次运行都有一个 `runId`。每个 `agent()` 结束后,运行时往磁盘上的 journal 追加一行。把它想成笔记本:按你**召唤**帮手的顺序记,而不是按他们从烤箱回来的先后。
|
||||
每次运行都有一个 `runId`。每个 `agent()` 结束,磁盘上的 journal 就多一行——按你**召唤**帮手的顺序记,而不是按他们从烤箱回来的先后。
|
||||
|
||||
续跑时(`resume_from_run_id` / `resumeFromRunId`),脚本仍从开头执行,但是:
|
||||
续跑(`resume_from_run_id` / `resumeFromRunId`)仍从脚本开头走,只是更客气:按调用顺序,与下一条 journal 比对;最长未改前缀直接从缓存回放;碰到第一个改过或未完成的调用,前缀断开——之后全部实跑,即便笔记本更后面还躺着旧 key,也不能跳过裂缝偷懒命中。
|
||||
|
||||
1. 按调用顺序,把每次 `agent()` 和下一条 journal 记录比对。
|
||||
2. **最长未改前缀** → 缓存命中(直接回放)。
|
||||
3. 遇到**第一个**改过或未完成的调用,前缀断开。
|
||||
4. **之后全部实跑** —— 即使 journal 更后面还躺着旧 key,也不能偷懒命中。
|
||||
|
||||
所以真正的 JS workflow 运行时会禁止 `Date.now()`、`Math.random()` 和裸的 `new Date()`:不确定的时钟和骰子会改 prompt 或调用顺序,笔记本就对不上了。这个 Python 演示不会完整沙箱这些 —— 但脚本仍应写成确定性的。
|
||||
这也是真正的 JS workflow 运行时禁止 `Date.now()`、`Math.random()` 和裸 `new Date()` 的原因。时钟和骰子会让 prompt 或调用顺序晃一下,笔记本就对不齐了。这个 Python 演示不会完整沙箱那些东西。脚本仍写成确定性的吧。
|
||||
|
||||
```text
|
||||
journal: [A ✓] [B ✓] [C ✓] [D ✓]
|
||||
续跑: A 命中 → B 命中 → C 改过 → D 实跑(不会悄悄命中旧的 D)
|
||||
续跑: A 命中 → B 命中 → C 改过 → D 实跑
|
||||
```
|
||||
|
||||
## 跟着示例走:`review-changes`
|
||||
## 跟着 `review-changes` 走一圈
|
||||
|
||||
四个审查维度走同一条两阶段路径 —— 先分发,再对抗验证,再过滤:
|
||||
四个维度共用一条两阶段路径——先铺开,再对抗验证,留下活下来的:
|
||||
|
||||
```text
|
||||
correctness ── 审计 ── 验证 ──┐
|
||||
security ── 审计 ── 验证 ──┤── 合并确认过的问题
|
||||
security ── 审计 ── 验证 ──┤── 确认过的问题
|
||||
performance ── 审计 ── 验证 ──┤
|
||||
style ── 审计 ── 验证 ──┘
|
||||
```
|
||||
|
||||
1. **Review** — 每个维度的审计员返回结构化 findings(干净桌子 → 少串味)。
|
||||
2. **Verify** — 每条 finding 交给对抗性检查(在 verify 阶段里用 `parallel`),作者不当裁判。
|
||||
3. 只保留被标成真实的问题,再按严重程度排序。
|
||||
Review 让每个审计员坐自己的桌子,正确性的闲聊不至于淌进安全性。Verify 把每条 finding 交给不是作者的怀疑者。只留下真的,再按严重程度排好。那三种走偏,会感觉自己最爱的座位被撤了。
|
||||
|
||||
```python
|
||||
async def sample_workflow(ctx, args):
|
||||
@@ -190,64 +163,46 @@ async def sample_workflow(ctx, args):
|
||||
return {"confirmed": confirmed}
|
||||
```
|
||||
|
||||
## 怎样接到 s15
|
||||
## 挂在 s15 上,并不取代它
|
||||
|
||||
s15 仍是宿主循环。s16 只多一个工具:`Workflow`。模型(或你)给出已保存的名字;适配器查 registry,再跑脚本。
|
||||
s15 仍是宿主循环。s16 只多了一个名叫 `Workflow` 的工具。你(或模型)报一个已保存的名字;适配器找到脚本再跑。
|
||||
|
||||
| | Claude Code / Pi(产品) | 本课教学 CLI |
|
||||
|--|--------------------------|--------------|
|
||||
| 脚本语言 | 沙箱里的 JavaScript | 可读的 Python 函数 |
|
||||
| 动态门 | 模型写 `script` / 改 `scriptPath` | 文档说明;演示走已保存的 `name` |
|
||||
| 运行时宿主 | 后台 + 通知,会话保持可响应 | `demo` / `resume` 前台跑,方便观察 |
|
||||
| 想法 | 同一套原语、journal、前缀续跑 | 教学模型 —— 简化处会说清楚 |
|
||||
在真正的产品里,这次运行可以待在后台、带着通知,会话照样能应你。教学 CLI 把 `demo` / `resume` 放在前台,好让你看清阶段和缓存命中。想法相同;简化之处我们会明说。
|
||||
|
||||
主循环不会变成 workflow 引擎。它只是多借一把工具,就像借 `bash` 或 `task` 一样。
|
||||
主循环不会变成 workflow 引擎。它只是多借一把工具,就像借 `bash` 或 `task`。
|
||||
|
||||
## 邻居们:谁握着计划?
|
||||
## 转一转这颗宝石:谁握着计划?
|
||||
|
||||
Workflow 不是“多派几个 agent”。它改的是**谁拥有拓扑结构**。
|
||||
看看邻居,同一件东西会露出新的面。有用的问题不是“几个 agent?”,而是**谁拥有拓扑**,半成品的碗放在哪。
|
||||
|
||||
| 邻居 | 谁握着计划 | 中间结果住哪 | 最适合 |
|
||||
|------|------------|--------------|--------|
|
||||
| [s06 子 Agent](../s06_subagent/) | 模型,一次性 | 除最终摘要外丢掉 | 隔离一个脏的子任务 |
|
||||
| [s13 Agent Teams](../s13_agent_teams/) | Lead 模型逐轮 + 邮箱 | 共享任务 / 消息 | 长跑同伴、偏人类协作 |
|
||||
| [s06 子 Agent](../s06_subagent/) | 模型,一次性 | 多半丢掉 | 隔离一个脏的子任务 |
|
||||
| [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/) | 停止边界上的判断器 | 对话当证据 | “整个目标做完了吗?” |
|
||||
| **s16 Workflow** | **脚本** | **变量 + journal** | 结构化分发与验证 |
|
||||
| [s17 Goal Loop](../s17_goal_loop/) | 停止时的判断器 | 对话当证据 | “整个目标做完了吗?” |
|
||||
|
||||
更便宜的替代方案经常就够用:skill / prompt 当软计划、一小段多 agent 闲聊、手写静态 SDK 编排,或者干脆更大的单轮模型调用。当结构必须比单个上下文活得更久时,再伸手去拿 workflow —— 不是因为“专家团”听起来很酷。
|
||||
更便宜的路经常就够:skill 当软计划、一小段多 agent 闲聊、手写静态编排,或更大的单轮模型调用。当结构必须比单个上下文活得更久,再伸手去拿 workflow——不是因为“专家团”听起来很酷。
|
||||
|
||||
## 什么时候*别*用 workflow
|
||||
## 什么时候先放回架子上
|
||||
|
||||
Workflow 要花 token,也有协调成本。大多数普通写代码的活,**不需要**五人评审团。
|
||||
Workflow 要花 token,也有协调成本。大多数普通写代码,并不需要五人评审团。
|
||||
|
||||
问问自己:这件事真的需要更多算力和定制 harness 吗?如果普通的 s15 一轮(或一个 s06 子 agent)就够,就停在那儿。克制也是设计思想的一部分 —— 并行和分工必须赚回自己的成本。
|
||||
动手前问一句:这活真的想要更多算力和一层定制 harness 吗?若普通的 s15 一轮——或一个老实的 s06 子 agent——就够,就停在那儿。克制也是思想的一部分:并行和分工得赚回自己的位置。
|
||||
|
||||
## 试一下
|
||||
|
||||
```bash
|
||||
python s16_workflow_runtime/code.py # s15 宿主 + Workflow 工具(真实 API)
|
||||
python s16_workflow_runtime/code.py demo # 固定数据:观察阶段和 agent
|
||||
python s16_workflow_runtime/code.py resume # 同一个 runId;前缀应全部缓存命中
|
||||
python s16_workflow_runtime/code.py # s15 宿主 + Workflow(真实 API)
|
||||
python s16_workflow_runtime/code.py demo # 固定数据;看阶段
|
||||
python s16_workflow_runtime/code.py resume # 同一 runId;期待缓存命中
|
||||
```
|
||||
|
||||
留意这些:
|
||||
看 Review 让给 Verify;看完整续跑时 agent 从 `done` 翻成 `cached`。结尾是一份短短的确认列表——干净续跑会显示 `agents=0 tokens=0`,像笔记本在说:没有什么需要重新加热。
|
||||
|
||||
- `workflow_phase`:先 Review,再 Verify
|
||||
- 每个 `workflow_agent`:第一次是 `done`,完整续跑变成 `cached`
|
||||
- 结尾有一份简短的确认列表;全命中续跑显示 `agents=0 tokens=0`
|
||||
## 接下来
|
||||
|
||||
## 相对 s15 → 下一站 s17
|
||||
s16 讲一批活怎么跑。[s17 Goal Loop](../s17_goal_loop/) 在门口问另一个问题:该停,还是再来一轮?可重复的菜谱若还需要硬性的“做完”,可以和它一起用。
|
||||
|
||||
| | s15 Agent Harness 集成 | s16 Workflow Runtime |
|
||||
|--|------------------------|----------------------|
|
||||
| 循环 | 单个、模型驱动 | 同一循环;一个工具跑脚本 |
|
||||
| 谁决定下一步 | 模型逐轮决定 | 脚本规定整批形状 |
|
||||
| 多 agent | 一次性子 agent | 可脚本化、可续跑的 `agent()` |
|
||||
| 失败 / 续跑 | 靠对话记忆 | 空值隔离 + journal 前缀 |
|
||||
|
||||
**s16 = 一批活怎么跑。s17 = 整个目标算不算做完。**
|
||||
|
||||
[s17 Goal Loop](../s17_goal_loop/) 会问一个独立判断器:该停,还是再来一轮?可重复的 workflow 若还需要硬性完成条件,可以和它配对。
|
||||
|
||||
<!-- translation-sync: zh@v12, en@v12, ja@v12 -->
|
||||
<!-- translation-sync: zh@v13, en@v13, ja@v13 -->
|
||||
|
||||
Reference in New Issue
Block a user