How a workflow grows across several chat turns without redoing the work that is already done
Every run saves its script to a file and returns a runId. Next turn, I edit that file and call
Workflow({scriptPath, resumeFromRunId}). Steps at the front that did not change come back
from the cache, instantly and for free. Only the new step runs for real.
One catch: the cache works on the longest unchanged prefix. Change an early step, and that step plus everything after it runs again. So add new work at the end.
01The four pieces
Think of a recipe card plus a box of finished dishes. You can add a line to the card; the dishes already cooked stay in the box.
recipeThe script file
The JavaScript that says which agents to run. Each run saves it as a file in this session's folder; the tool result prints the path. I edit it with Edit, then pass it back as scriptPath.
receiptThe run ID
A name for one run, like wf_…. It points to that run's saved results. You pass it as resumeFromRunId.
dish boxThe journal
journal.jsonl in the run's folder records what each agent() call returned. Resume reads it. I also read it when a result looks empty or odd.
order noteargs
Input handed to the script. It is part of the cache key: same script + same args = full cache hit. Use it to pass a result into a different workflow.
02The whole flow, turn by turn
Example: review a branch, then fix what was found, then double-check the fixes. Three chat turns, one growing script.
- I call
Workflow({script})with phases Review → Verify. - The tool returns right away with a
runIdand the savedscriptPath. The run continues in the background. - When it ends, a notification wakes me. I read the result and report the confirmed findings to you.
- I
Editthe saved script and append a Fix phase after Verify. It uses the confirmed findings already in the script. - I call
Workflow({scriptPath, resumeFromRunId: "run A"}). - Review and Verify did not change, so they replay from the journal in no time. Only Fix runs.
- I append a Check phase and resume from the latest run, run B.
- Everything before Check is cached; Check runs live.
- I stop the running workflow first with
TaskStop. - Then I edit the script and resume from that run's ID. Agents that already finished come from the cache.
In one line: each turn = edit the same file, add at the end, resume from the newest run ID. The script grows; the cost only covers the new part.
03What the script looks like
Turn 2's edit, simplified. Green lines are the append; nothing above them changes.
export const meta = { name: 'review-then-fix', description: 'Review branch changes, verify, then fix confirmed findings', phases: [{ title: 'Review' }, { title: 'Verify' }, { title: 'Fix' }], } const found = await pipeline(DIMENSIONS, d => agent(d.prompt, { phase: 'Review', schema: FINDINGS })) const confirmed = await verifyAll(found) // unchanged → replayed from the journal // --- appended in turn 2 --- const fixes = await parallel(confirmed.map(f => () => agent(`Fix ${f.title} in ${f.file}`, { phase: 'Fix', isolation: 'worktree' }))) return { confirmed, fixes }
Two small rules: the meta block must be a plain literal, and the script cannot use
Date.now(), Math.random(), or new Date(). Those would give a different answer on every replay and break the cache.
Pass a timestamp in through args instead.
04The cache rule: longest unchanged prefix
Resume walks the agent() calls in order. It reuses them until it meets the first one that is new or different. From there on, everything runs live.
| You change… | Effect on resume |
|---|---|
Nothing (same script, same args) | ✓ 100% cached |
| Add steps after the last one | ✓ old steps cached, new ones live |
An early agent() prompt or its options | ✗ that step and all later steps rerun |
args | ✗ any step whose prompt uses them reruns |
Easy to get wrong. It is tempting to think "any unchanged call is cached." In fact, only the unchanged calls before the first change are cached. That is why appending is the safe habit.
05Other ways to continue
Resume is the default. Pick another path when the next job is not really "more of the same run."
| Situation | What I call | Why |
|---|---|---|
| Next job builds on this run's data | Workflow({scriptPath, resumeFromRunId}) | Reuses finished agents; one growing script. |
| Want a clean rerun of the edited script | Workflow({scriptPath}) | No cache; everything runs fresh. |
| Next job is a different kind of work | New Workflow({script, args}) | Feed the last result in via args; each workflow stays small and readable. |
| One script should call another | workflow(name or {scriptPath}, args) inside the script | Runs the child inline. Only one level deep. |
| Reuse it in later sessions | Save under .claude/workflows/, call by name | Resume can't cross sessions; a saved workflow can be rerun anywhere. |
For big tasks, the guidance is to run phases as separate workflows in sequence—understand → design → implement → review—so I read each result before choosing the next step. That keeps you in the loop between phases.
06Limits and gotchas
- Same session only. A new session cannot resume an old run.
- Stop before you resume a run that is still going (
TaskStop). - Nesting is one level. A child workflow can't call another.
- Opt-in. I only start a workflow when you ask (e.g. "use a workflow").
- Add new work at the end of the script.
- Don't reword early prompts unless you want them to rerun.
- Odd or empty result? Check
journal.jsonlbefore guessing. - Mention the run ("extend the last workflow…") so I know which run ID to resume.
What to tell me: "Extend the last workflow with <new step> and resume." I'll append to the saved script, resume from the newest run ID, and only the new step will cost anything.