Files
analysis_claude_code/s16_workflow_runtime/README.md

5.8 KiB

s16: Workflow Runtime — put the plan in code

English · 中文 · 日本語

s15s16s17

Don't keep the plan only in chat. The script owns order; the model owns each judgment.

Harness layer: orchestration — a multi-agent script on top of the single agent loop.

Problem

You already know how to let a model read files, edit code, and read errors in one loop. Some jobs, though, have an order you already know: review by dimension, then adversarial checks, then merge. If that order lives only in chat, the model stops halfway and calls it done, grades its own homework too kindly, and after a few compressions even "don't touch X" disappears.

Soft conversation can't carry parallelism, stable result shapes, or crash-and-resume. You don't need a chattier model. You need orchestration written down.

Solution

  your chat ──► Workflow(...) ──► one result back
                    │
                    ▼
            script: agent / pipeline / parallel
                    │
                    ▼
              vars + journal (keep intermediates here, not in the thread)

Subagents still think; the script owns loops, fan-out, and merge. Intermediates live in variables and a journal, not the host dialogue.

One line: move orchestration from intelligence to structure.

static harness vs dynamic workflow

Left: a generic fixed pipeline. Right: a harness cut for this task.

Claude Code has two doors: dynamic — the model writes JS for this task (script / scriptPath); saved — rerun a good script with name + args. Outside sits static SDK / claude -p orchestration. This lesson is a Python teaching runtime (no JS VM): same ideas, demo on the saved door. In the product the model can submit scripts — we just don't run JS here.

How it works

1. Three verbs

  agent      one helper, one job (optional schema → JSON you can pass on)
  pipeline   each item walks stages on its own (default; no barrier)
  parallel   wait for every result (barrier; use sparingly)

On failure: a parallel slot becomes null; pipeline drops that item. The fleet does not sink. Filter before you merge.

2. Resume from a notebook, not chat memory

The journal records agent() calls in invocation order. Resume replays the longest unchanged prefix; after the first change, everything runs live. Real JS runtimes ban Date.now() / Math.random() so the notebook can match — keep teaching scripts deterministic too.

  journal  [A] [B] [C] [D]
  resume    hit  hit  ✂ live

3. One sample: fan-out + adversarial

review-changes is not "one pattern". It is Fanout with Adversarial inside: pipeline(audit, verify) per dimension, then parallel verifiers, keep only findings that still stand.

  correctness ── audit ── verify ──┐
  security    ── audit ── verify ──┤── confirmed
  performance ── audit ── verify ──┤
  style       ── audit ── verify ──┘
# from code.py — the shape is the point
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 can't stop early, authors don't referee themselves, and topology isn't rewritten by a tired chat turn.

Six common shapes (pattern toolbox)

Six workflow patterns

Pattern In plain words Primitives
Classify-And-Act Sort, then hand off agent → branch → agent
Fanout-And-Synthesize Split, then merge pipeline / parallel → synthesize
Adversarial Verification Don't let the fox grade the henhouse produce → parallel(verify) → filter
Generate-And-Filter Many drafts, then a ruler parallel(gens) → filter
Tournament Pairwise to a winner judge agent
Loop Until Done Keep going while "anything new?" while + stop + budget

review-changes ≈ Fanout + Adversarial. Research stacks often go fan-out → filter → verify → synthesize.

Dynamic / saved / static & official primitives
# teaching sketch
Workflow({ "name": "review-changes", "args": { "changes": "..." } })
# Claude Code also accepts: script | scriptPath | resumeFromRunId

Workflow primitives

Untrusted input: quarantine reads

The agent that reads tickets should not also hold the keys to open a PR. Readers only read → summary; the trusted side acts on the summary.

  backlog → [quarantine: read / dedupe / summarize] → [trusted: act]

quarantine triage

Who owns the plan? s06 is one-shot dispatch, s13 is teammates with a mailbox, s15 is one chat loop, s16 is script + journal, s17 asks at the door whether the whole goal is done. For ordinary file edits, s15 or one s06 is often enough. Workflows cost tokens and coordination — reach for them when structure must outlive a single conversation.

Try it

python s16_workflow_runtime/code.py demo
python s16_workflow_runtime/code.py resume

First run: watch Review → Verify. Second run on the same id: expect mostly cached (ideally agents=0 tokens=0). For the full host loop, run code.py with no args.

s15 is still the loop; this chapter only adds a Workflow tool. s17 asks a different question: should we stop?