Workflow resumeFromRunId re-executes successful agent() calls (not just failed ones) when script uses pipeline()/parallel()

Open 💬 3 comments Opened Jul 5, 2026 by 229amini

What happened

Ran a workflow: pipeline(8 categories, genStage, verifyStage, assembleStage), each stage fanning out via parallel() internally (9 subtopic agent() calls per category in genStage, 1 agent() call per category in verifyStage). 72/80 agent() calls succeeded, 8 failed (session rate limit). The workflow completed successfully overall because the script had its own fallback logic for failed verify calls.

Resumed with Workflow({scriptPath, resumeFromRunId}), script unchanged, expecting only the 8 failed calls to re-run — per the docs: "the longest unchanged prefix of agent() calls returns cached results instantly; the first edited/new call and everything after it runs live."

Instead, ~70 of the 72 already-successful calls re-executed live (new agentIds, materially different output text since generation isn't deterministic), burning ~2.4M additional tokens on top of the original ~2.7M.

Root cause (as far as I can tell from journal.jsonl)

Confirmed via the run's journal.jsonl: across both runs there were 151 total started events, but only ~80 unique keys matched between run 1 and run 2 — the rest were logged as brand-new keys/agentIds even though the script and every prompt were byte-identical between runs.

This points to the resume cache being position-in-sequence based (a literal "prefix" of the call log) rather than pure content/hash-addressed. parallel()/pipeline() — which the tool's own documentation recommends as the default pattern ("DEFAULT TO pipeline()") — have non-deterministic completion/call order across runs, since it depends on model/network latency for each concurrent agent. So the actual sequence in which agent() calls get logged differs run-to-run, the "prefix" diverges almost immediately, and everything after that point in the log re-executes live instead of being served from cache — even calls with an identical (prompt, opts) key that succeeded last time.

Impact

This silently doubles cost for what should be the cheapest, most common recovery action — "some calls failed (rate limit/transient error), resume to retry just those." Any workflow using the tool's own recommended parallel()/pipeline() pattern is exposed to this, and there's no warning that resume is unsafe/expensive in that shape.

Suggested fix

  • Make the resume cache purely content/key-addressed (hash of prompt + opts), independent of the order calls were logged/started in, so a matching key hits cache regardless of position.
  • Failing that, at minimum: warn (or refuse) resume when the script contains parallel()/pipeline() calls, since call order isn't guaranteed deterministic and the "prefix" guarantee can't hold.

Happy to share the script and journal.jsonl if useful for repro.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗