A workflow is a JS script the model writes in the moment and hands to the
Workflow tool, which runs it in a locked-down `node:vm` and orchestrates
subagents through `agent()`/`parallel()`/`pipeline()`/`phase()`. Saving one
as a `/name` command is the secondary path; the inline script is the point.
Runtime: cross-realm value marshalling so a script cannot reach the host
`Function`, determinism guards on `Date.now()`/`Math.random()` (they would
make a resume replay diverge), a FIFO concurrency gate, and a journal that
lets an interrupted run resume from its longest unchanged prefix.
Desktop: the run shows up as a `workflow` section in the existing activity
panel — phases as headings, their agents beneath. A workflow agent is an
ordinary subagent run by the same runner, so its row opens the existing
subagent page rather than a parallel viewer of its own; that needed a
`by-agent` lookup, because these agents have no parent `Agent` tool call to
key off. Finished runs are rebuilt from the per-agent sidecars when a
session is reopened, since the live progress stream does not outlive the
process.
Resuming a stopped agent adopted the resuming tool's tool_use id, so a
follow-up sent via SendMessage re-filed the agent's tool activity and its
completion notification under the SendMessage card instead of the Agent
card that spawned it.
Persist the spawning Agent tool_use id in agent metadata and prefer it on
resume, falling back to the still-registered task. Never fall back to the
caller's own id — that is the bug itself; leaving it undefined only costs
live activity streaming, which is what pre-existing agents did anyway.
runAsyncAgentLifecycle now takes parentToolUseId as a required parameter so
a missed call site is a compile error rather than a silent regression, and
registerTask keeps the original id when a resume re-registers the task.