# s16: Workflow Runtime — Put the Recipe in Code
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → ... → s14 → [s15](../s15_integrated_harness/) → `s16` → [s17](../s17_goal_loop/)
> Workflow = orchestration written as code. Script owns topology; the model judges each step.
>
> **Harness layer**: Orchestration — a multi-agent script above the single-agent loop.
>
> Trust the model. Engineer the harness. Workflows take that one floor up.
---
## Problem
On long jobs, plan and action share one chat: stop early, grade your own homework kindly, lose quiet constraints after compressions. Soft chat memory is a weak place for parallelism, stable result shapes, and resume.
Nudging turn-by-turn is like texting the chef every ten seconds. A **workflow** is a recipe the kitchen can follow.
## Idea
Helpers (subagents) still think. The **script** owns loops, fan-out, and merge. Intermediates live in variables and a journal — not the conversation.
**Orchestration moves from intelligence to structure.**
```text
messages[] ──► Workflow(...) ──► tool_result
│
▼
script owns: agent / parallel / pipeline
│
▼
variables + journal
```
One `Workflow` tool call starts the run; one result comes back when it finishes.
Runtime overview diagram

## Two doors
- **Dynamic**: model writes a JS orchestration script for *this* task (`script` / `scriptPath`).
- **Saved**: good script under `.claude/workflows/`; call by `name` + `args`.
- **Static** (cousin outside): Agent SDK / `claude -p` written ahead — usually more generic.

*Left: fixed pipeline → generic report. Right: cut for your code → a specific recommendation.*
This chapter is a **Python teaching runtime** (no JS VM). Concepts map to Claude Code; the demo uses the Saved door. In the product the model can submit executable scripts — we just skip embedding a JS interpreter here.
```python
# teaching sketch — not the full schema
Workflow({ "name": "review-changes", "args": { "changes": "..." } })
# Claude Code also accepts: script | scriptPath | resumeFromRunId
```
## Three verbs
```text
agent one helper, one job (optional schema → validated JSON)
pipeline each item walks stages alone (default — no barrier)
parallel wait for every tray (barrier — use sparingly)
```
On failure the fleet continues: `parallel` → `null` in that slot; `pipeline` drops **that item** and its later stages. Filter before merge.
Resume: journal records calls in invocation order; replay the **longest unchanged prefix**, then run live. Real JS runtimes ban `Date.now()` / `Math.random()`. This demo does not fully sandbox that — write deterministic scripts anyway.
```text
journal [A] [B] [C] [D]
resume hit hit ✂ live
```
Official primitive card + quieter verbs

*`agent`; `parallel` (barrier) vs `pipeline` (streaming stages). Claude Code also has `model` / `isolation` / `agentType`; teaching surface is smaller.*
Quieter: `phase`, `log`, nested `workflow`, `args`, `budget`.
## Two shapes + one sample
Feel two first (full six-pattern grid in the fold below):
```text
Fanout task ──► ● ● ● ● ══barrier══► synthesize
Adversarial worker ──► verifier×N → keep what still stands
```
Sample `review-changes` = **Fanout** with **Adversarial** inside: `pipeline(audit, verify)` per dimension; `parallel` verifiers; keep only `isReal`.
```text
correctness ── audit ── verify ──┐
security ── audit ── verify ──┤── confirmed
performance ── audit ── verify ──┤
style ── audit ── verify ──┘
```
```python
# from code.py (abbreviated)
async def sample_workflow(ctx, args):
ctx.phase("Review")
results = await ctx.pipeline(DIMENSIONS, audit, verify)
confirmed = [f for r in results if r for f in r["confirmed"]]
return {"confirmed": confirmed}
```
The fleet cannot stop early, the author is not the judge, and topology is not rewritten every chat turn.
Six-pattern grid + primitive map

| Pattern | Primitive sketch | Skip when… |
|---------|------------------|------------|
| Classify-And-Act | `agent` → branch → `agent` | Same treatment for every item |
| Fanout-And-Synthesize | `pipeline` / `parallel` → merge | One pass already fits |
| Adversarial Verification | produce → `parallel(verify)` → filter | A wrong answer is cheap |
| Generate-And-Filter | `parallel(gens)` → filter | Answer space is already tiny |
| Tournament | pairwise judge `agent`s | A clear rubric picks a winner |
| Loop Until Done | `while` + stop + `budget` | Work size is known |
```python
# teaching sketch
kind = await ctx.agent("classify this ticket", schema=KIND)
if kind["type"] == "billing":
return await ctx.agent("handle billing…")
```
Untrusted input: quarantine
The agent that *reads* tickets should not also hold PR keys. Readers stay read-only → structured summary; a trusted actor acts on the summary only.
```text
backlog (untrusted) → [quarantine: readers → dedupe → summary] → [trusted: actor]
```

*High-privilege tools stay on the trusted side. Pair with `/loop` if the backlog never sleeps.*
How this hangs on s15
s15 stays the host loop; s16 only adds a `Workflow` tool. Product runs can be background; the teaching CLI keeps `demo` / `resume` in the foreground for phases and cache hits.
## Neighbors & when not
Who holds the plan? s06 one-shot delegate, s13 mailbox peers, s15 one loop, **s16 script + journal**, s17 asks “is the whole goal done?”
Ordinary coding: one s15 turn or one honest s06 often wins. Workflows cost tokens and coordination — reach for them when structure must outlast a single context.
## Try it
```bash
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
```
A full resume should show `agents=0 tokens=0`.
## Next
s16 is how a batch runs. [s17 Goal Loop](../s17_goal_loop/) asks: stop, or take another turn?