[FEATURE] Persistent & forkable agent handles inside Workflow scripts (token/compute reuse + deterministic orchestration)

Open 💬 1 comment Opened Jun 14, 2026 by ManufactoryOfCode

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Problem

Inside a Workflow script (.claude/workflows/*.js), every agent() call spawns a
fresh, stateless subagent that only returns its final text/structured output.
There is no way to keep an agent alive across calls, hold a reference to it, or
branch it. This makes it impossible to build genuinely efficient end-to-end
automated workflows: any context an agent built up (e.g. reading the same source
files, loading the task base, building a mental model) is thrown away after each
agent() call and must be re-read from scratch on the next one
— burning tokens,
compute, and wall-clock time on repeated identical work.

Current state (what already exists, but not in workflows)

The capability already exists in the model-driven Agent layer, just not in the
deterministic Workflow scripting API:

  • Persistence + reuse: the Agent tool returns an agentId; SendMessage

continues that agent with full conversation context retained (all prior tool
calls, results, reasoning).

  • Forking: subagent_type: "fork" / --fork-session / CLAUDE_CODE_FORK_SUBAGENT

branch a conversation's history (a copy that then diverges).

So the underlying primitives — persistent reuse and fork — are implemented. They are
simply not exposed to the Workflow runtime, where agent() is fire-and-forget.

Why this matters more than just token savings: determinism

This loop is already achievable with skills (e.g. a model-driven orchestrator
that spawns implementer + reviewer and shuttles messages). The problem is that
model-driven orchestration is unreliable: orchestrating agents bend or forget
their rules mid-run, drift from the intended process, skip gates, or improvise.

A Workflow script is deterministic JavaScript control flow — the loop, the gate,
the reuse, the iteration cap all execute exactly as written, every time. Exposing
persistent/forkable agent handles in workflows is what lets the agents' work be
driven by deterministic orchestration instead of a model that might quietly do
something else. That combination — deterministic control + context-preserving agents
— is the missing piece for trustworthy automated pipelines.

Why this isn't a duplicate

Related but distinct issues address session/conversation-level branching or nesting,
not the Workflow scripting API:

  • #32631 (Conversation Branching: fork/merge/tree) — session-level
  • #12629 (Session Branching / Conversation Forking) — notes --fork-session can't be

triggered from a running session

  • #17668 (MCP context isolation for forked agent/skill contexts)
  • #61993 (sub-agents can't spawn sub-agents)
  • #4182 (nested tasks + token accounting)

None expose persistent/forkable agent handles inside .claude/workflows scripts,
which is the gap here.

Motivation / impact

Enables building fully automated, efficient, and deterministic workflows in Claude
Code
that save tokens, compute, and time by loading shared context once and
reusing/branching it — instead of every agent() re-reading the same code and
re-deriving the same state — while replacing unreliable model-driven orchestration
with guaranteed control flow.

Underlying principle: don't burn expensive AI on cheap deterministic decisions

Anywhere a step is a simple, deterministic decision (a loop, a gate, a branch,
"reuse this agent vs. spawn a fresh one"), it should be handled by code, not by an
expensive model call. Spending model tokens/compute on control flow that a few lines
of JavaScript can decide is pure waste. Persistent/forkable agent handles are exactly
what let the deterministic parts stay deterministic (and nearly free) while the
model is spent only where genuine reasoning is needed.

These savings look tiny per step, but they compound: a workflow makes the same kind of
decision thousands of times, and a thousand times "nothing" still adds up to a real
cost. Eliminating wasted re-reads and needless re-spawns at every iteration is what
makes large automated pipelines economically viable rather than quietly expensive.

Proposed Solution

Proposal

Wire these existing primitives into the Workflow scripting API as first-class handles:

// pre-spawn and hold a reference (does NOT return final text immediately)
const base = await spawn("Load and study the story and the shared materials for the
                          overall change; build a working model. Await instructions.", opts)

// reuse with retained context (equivalent of SendMessage) — no re-loading the base
const a = await base.send("Now implement subtask 1.")

// cheap branch from a loaded state: each fork starts from "story + shared context
// already loaded", without the unnecessary context of sibling subtasks
const subtasks = await Promise.all(
  STORY.subtasks.map(st => base.fork().send(`Your part: ${st.context}. Implement it.`))
)

Key points:

  • spawn() returns a handle, not a terminal result.
  • handle.send(work) reuses the agent with its accumulated context (SendMessage semantics).
  • handle.fork() clones the agent in its current loaded state — the core

token-saver: load expensive context once, then branch N cheap workers off it
instead of re-establishing that state N times.

  • Should compose with existing parallel() / pipeline().

Alternative Solutions

_No response_

Priority

Critical - Blocking my work

Feature Category

CLI commands and flags

Use Case Example

Concrete example: deterministic implement-feature workflow

A real workflow this unlocks — an implement-and-review loop where the implementer is
reused across review-fix iterations:

// 1. spawn the implementer once, tell it what to load for this task
const impl = await spawn("You implement task T. Read these files / spec; build a
                          working model. Await the concrete task.", opts)

// 2. give it the actual task
let result = await impl.send("Implement task T per the spec.")

// 3. review/fix loop — SAME implementer fixes its own mistakes
for (let i = 0; i < MAX_ITERS; i++) {
  const review = await agent(`Review this change: ${result.diff}`, {schema: VERDICT})
  if (review.pass) break

  // implementer already knows WHAT it did, WHY, and WHERE — it does NOT re-study
  // the whole context. It just looks at the reviewer's findings and fixes them.
  result = await impl.send(`Review failed. Fix these findings: ${review.findings}`)
}

Today, on a fresh agent() per iteration, the fix step would re-read the spec and
all the source files just to re-establish what it already knew — multiplied by every
failed review. With a reused handle, the fix iteration only costs the reviewer's
findings plus the edit.

Concrete example: fork the loaded base across subtasks

The fork primitive shines when one expensive context is shared by many independent
units of work:

// 1. build the base ONCE: load the story and the shared materials for the whole change
const base = await spawn("Load the story and all shared materials for this change.
                          Build a working model. Await your subtask.", opts)

// 2. fork the base per subtask; each fork inherits "story + shared context already
//    loaded" and only receives its own specific additional context
const results = await Promise.all(
  STORY.subtasks.map(st =>
    base.fork().send(`Your part of the change: ${st.context}. Implement it.`)
  )
)

Result: the story and shared groundwork are read and understood once. Without
fork, every subtask agent would re-load the whole base from scratch — paying that
cost N times in tokens, compute, and time. With fork, each subtask pays only for its
own delta.

Additional Context

_No response_

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗