docs(s16): ideal March-feel chapter + maintainer readability standard (v19)

This commit is contained in:
Xinlu Lai
2026-08-12 22:52:20 +08:00
parent 87d484251a
commit cb6062258a
4 changed files with 386 additions and 339 deletions

View File

@@ -1,104 +1,65 @@
# s16: Workflow Runtime — 把菜谱写进代码
# s16: Workflow Runtime — 把编排写进代码
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](../s17_goal_loop/)
[s15](../s15_integrated_harness/) → `s16` → [s17](../s17_goal_loop/)
> Workflow = 写进代码的编排。脚本管拓扑,模型管每一步判断。
> *计划别只活在嘴上。* 脚本管先后,模型管每一步判断。
>
> **Harness 层**: 编排 — 单 agent 循环之上再跑多 agent 脚本。
>
> 信任模型,工程化 harness。Workflow 把这句话往上提一层。
---
> **Harness 层**: 编排 — 单 agent 循环之上再跑一套多 agent 脚本。
## 问题
长任务里,计划和动手挤在同一段 chat做到一半就宣布完工自己批改自己偏甜、压缩几轮后约束悄悄丢了。并行、稳定结果形状、崩了续跑——软对话记忆扛不住
你已经会让模型在一个循环里读文件、改代码、看报错。可有些活你其实**早就知道先后顺序**:先分维度审,再找人挑刺,最后汇总。若顺序只靠聊天记住,模型会做到一半完工自己批改自己偏甜,压几轮上下文后连「别动 X」都丢了
一轮轮催进度,像每隔十秒给厨师发短信。**Workflow** 是厨房能照着做的菜谱
软对话扛不住并行、稳定结果形状,也扛不住崩了接着跑。你需要的不是更会聊天的模型,是一份**写下来的编排**
## 想法
帮手(子 agent仍负责想**脚本**管循环、分发、合并。中间结果进变量和 journal不进对话。
**编排从「智力」挪到「结构」。**
## 解决方案
```text
messages[] ──► Workflow(...) ──► tool_result
脚本管拓扑agent / parallel / pipeline
变量 + journal
你的对话 ──► Workflow(...) ──► 一条结果回来
脚本agent / pipeline / parallel
变量 + journal(半成品放这儿,不塞群聊)
```
一次 `Workflow` 工具调用开跑;菜谱做完,一条结果回来
帮手(子 agent仍负责想**脚本**管循环、分发、合并。中间结果进变量和 journal不进主对话
<details>
<summary>运行时总览图</summary>
一句话:**编排从「智力」挪到「结构」。**
![Workflow Runtime 总览](images/workflow-runtime-overview.svg)
![静态 harness vs 动态 workflow](images/dynamic-vs-static.png)
</details>
*左:通吃的固定流水线。右:为这次任务现裁的编排。*
## 两扇门
Claude Code 里有两扇门:**动态**——模型为这次任务写 JS`script` / `scriptPath`**已保存**——好脚本用 `name` + `args` 再跑。门外还有用 SDK 事先写死的静态编排。本章是 **Python 教学 runtime**(不嵌 JS思想对齐演示走「已保存」门。模型在产品里本来就能交脚本——我们只是不在这里跑 JS。
- **动态**:模型为*这次*任务写 JS 编排(`script` / `scriptPath`)。
- **已保存**:好脚本进 `.claude/workflows/`,用 `name` + `args` 再调。
- **静态**门外表亲SDK / `claude -p` 事先写好,偏通用。
## 工作原理
![静态 vs 动态](images/dynamic-vs-static.png)
*左:固定流水线 → 泛报告。右:按你的代码现裁 → 具体建议。*
本章是 **Python 教学 runtime**(不嵌 JS VM。概念对齐 Claude Code演示走「已保存」门。模型在产品里本来就能交可执行脚本——我们只是不在这里跑 JS。
```python
# 教学示意 — 不是完整 schema
Workflow({ "name": "review-changes", "args": { "changes": "..." } })
# Claude Code 还接受script | scriptPath | resumeFromRunId
```
## 三个动词
**1. 三个动词**
```text
agent 一个帮手,一件事(可带 schema → 校验 JSON
pipeline 每个 item 自己走阶段(默认,不等齐)
parallel 等再往下(屏障,少用)
agent 一个帮手,一件事(可带 schema,拿到能往下传的 JSON
pipeline 每个 item 自己走完各阶段(默认,不等齐)
parallel 等所有结果齐了再往下(屏障,少用)
```
失败时舰队继续`parallel` 槽位变 `null``pipeline` 丢掉那个 item 及其后续 stage。合并前先过滤。
谁失手`parallel` 那个槽变成 `null``pipeline` 丢掉那个 item。舰队不整船沉。合并前先过滤。
续跑journal 按召唤顺序记;回放**最长未改前缀**,第一个改动之后全实跑。真 JS 运行时禁 `Date.now()` / `Math.random()`;本 demo 不完整沙箱——脚本仍写成确定性的。
**2. 续跑靠本子,不靠聊天记忆**
journal 按 `agent()` **召唤顺序**记账。续跑回放最长未改前缀;碰到第一处改动,后面全实跑。真 JS 运行时禁 `Date.now()` / `Math.random()`,免得本子对不齐——教学脚本也请写成确定性的。
```text
journal [A] [B] [C] [D]
续跑 命中 命中 ✂ 实跑
```
<details>
<summary>官方原语卡片 + 更轻的动词</summary>
**3. 一个样本:分发 + 对抗**
![Workflow 原语](images/workflow-primitives.png)
*`agent``parallel`屏障vs `pipeline`流式阶段。Claude Code 还有 `model` / `isolation` / `agentType`;教学面更小。*
更轻:`phase``log`、嵌一层 `workflow``args``budget`
</details>
## 两种形状 + 一个样本
先摸两种(完整六模式见下方折叠):
```text
Fanout task ──► ● ● ● ● ══屏障══► synthesize
Adversarial worker ──► verifier×N → 只留站得住的
```
样本 `review-changes` = **Fanout** 里嵌 **Adversarial**:多维度 `pipeline(audit, verify)``verify``parallel` 挑刺,过滤后只留 `isReal`
`review-changes` 不是「一种模式」,是 **Fanout** 里嵌 **Adversarial**:多维度 `pipeline(audit, verify)`,验证里再 `parallel` 挑刺,只留站得住的 finding。
```text
correctness ── 审计 ── 验证 ──┐
@@ -108,7 +69,7 @@ Workflow({ "name": "review-changes", "args": { "changes": "..." } })
```
```python
# 来自 code.py节选
# code.py 节选 — 形状就这些
async def sample_workflow(ctx, args):
ctx.phase("Review")
results = await ctx.pipeline(DIMENSIONS, audit, verify)
@@ -116,71 +77,63 @@ async def sample_workflow(ctx, args):
return {"confirmed": confirmed}
```
舰队不能早停作者不当裁判拓扑不靠 chat 每轮改写。
舰队不能早停作者不当裁判拓扑不靠 chat 每轮改写。
<details>
<summary>六种模式网格 + 原语对照</summary>
<summary>六种常见形状(模式库)</summary>
![六种 Workflow 模式](images/six-workflow-patterns.png)
| 模式 | 原语速写 | 什么时候别用 |
|------|----------|--------------|
| 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("处理账单…")
```
`review-changes` ≈ Fanout + Adversarial。研究类常叠分发 → 过滤 → 验证 → 汇总。
</details>
<details>
<summary>不可信输入隔离分流quarantine</summary>
<summary>动态 / 已保存 / 静态 & 官方原语图</summary>
读工单的 agent 不该同时握开 PR 的钥匙。读者只读 → 结构化摘要;受信任 actor 只看摘要行动。
```python
# 教学示意
Workflow({ "name": "review-changes", "args": { "changes": "..." } })
# Claude Code 还接受script | scriptPath | resumeFromRunId
```
![Workflow 原语](images/workflow-primitives.png)
</details>
<details>
<summary>不可信输入时:隔离读写</summary>
读工单的人,不该同时握着开 PR 的钥匙。读者只读 → 摘要;受信任的一侧只看摘要行动。
```text
积压(不可信)→ [隔离区: readers → 去重 摘要] → [受信任: actor]
积压 → [隔离区: 读 / 去重 / 摘要] → [受信任: 行动]
```
![隔离分流](images/quarantine-triage.png)
*高权限工具住在受信任一侧。积压睡不着时可配 `/loop`。*
</details>
<details>
<summary>怎样挂在 s15 上</summary>
谁握计划s06 一次性派工s13 邮箱同伴s15 单循环聊天,**s16 是脚本 + journal**s17 在门口问整件事做完没有。普通改几个文件s15 或一个 s06 往往够。Workflow 贵在 token 和协调——**结构必须比单次对话活得更久**时再用。
s15 仍是宿主循环s16 只多一个 `Workflow` 工具。产品里可后台跑;教学 CLI 用前台 `demo` / `resume` 看阶段和缓存。
</details>
## 邻居 & 何时别用
谁握计划s06 一次性委派、s13 邮箱同伴、s15 单循环、**s16 脚本 + journal**、s17 门口问「整目标做完了吗」。
普通改代码s15 一轮或一个 s06 往往够。Workflow 要 token 和协调——结构必须比单个上下文活得更久时再用。
## 试一下
## 试一试
```bash
python s16_workflow_runtime/code.py # s15 宿主 + Workflow真实 API
python s16_workflow_runtime/code.py demo # 固定数据;看阶段
python s16_workflow_runtime/code.py resume # 同一 runId期待缓存命中
python s16_workflow_runtime/code.py demo
python s16_workflow_runtime/code.py resume
```
完整续跑应看到 `agents=0 tokens=0`
第一次看 Review → Verify第二次同一 runagent 应大量 `cached`(理想情况 `agents=0 tokens=0`)。想挂进完整宿主:不加参数直接跑 `code.py`
## 接下来
s15 还是那个循环;这里只是多了一个 `Workflow` 工具。[s17](../s17_goal_loop/) 问另一个问题:该停了吗?
s16 讲一批活怎么跑。[s17 Goal Loop](../s17_goal_loop/) 问:该停,还是再来一轮?
<!-- translation-sync: zh@v18, en@v18, ja@v18 -->
<!-- translation-sync: zh@v19, en@v19, ja@v19 -->