Subagents: Delegate the Hard Part, Keep Working
One tool call charters a helper agent that runs as its own scheduled turn, streams into the same chat as a thread, and reports back onto the timeline it came from — with the admin shaping who may delegate to whom, the paying user deciding, and the runtime enforcing the same economic boundary as any turn.
One agent orchestrates a conversation well. Some assignments deserve more than it can give — deeper reasoning, broader knowledge, research that has to be conducted rather than recalled — or simply deserve their own pair of hands while the main thread keeps moving.
The design goal was to make delegation native to the timeline, with the same event vocabulary the agent already lives in — and to make every choice about it (who may delegate, to whom, at what spend) land with the party it belongs to: the admin shapes, the user decides, the runtime enforces.
Kickoff, never a pinned context
react.delegate takes one argument that matters: the charter
— a single self-contained assignment prompt stating the goal and what to send back. The
call returns immediately with the child conversation ref, and the parent keeps working; it
may finish its turn with helpers still running. There is no waiting primitive, because a
reasoning loop has no good way to wait — it would burn rounds idling.
What happens at the moment of delegation is deliberately small and durable:
react.delegate(charter, agent_alias?, agent_title?)
├─ fork projection ──▶ persisted as the child conversation's opening timeline
│ (working summaries + the in-progress turn, refs carrying
│ their home conversation id)
├─ fork record ──▶ parent turn's stored record: forks += {child, caption}
└─ charter event ──▶ child's event lane, carrying a task payload
= the kickoff IS the promotion: the child turn is
scheduled on the cluster like any other turn
The child is a first-class scheduled turn — same queue, same fairness, same admission and backpressure as every user turn. A user who fans out five helpers is five turns on the queue, fairly interleaved with everyone else's. If the queue declines the kickoff, the delegate call fails loudly and cleans up after itself; nothing half-spawned lingers.
The child opens with a fork of the parent's visible context: the conversation's working summaries plus the turn in progress, every conversation-scoped ref qualified with its home conversation id — so the helper reads its inheritance and can pull any artifact it names. The charter folds in as its assignment. From there it is an agent like its parent: same tools, same skills, its own round budget from configuration.
Reports ride the timeline
The helper speaks back through the parent conversation's event lane, in three event kinds:
subagent.contribution— milestones along the way: a short report plus refs, pullable withreact.pullexactly as written.subagent.converged— the final report, published only after the child's end-of-turn persistence, so every ref it carries already resolves.subagent.failed— failures are authored, never silent; a helper that dies before starting (including an economics denial) still reports why.
A live parent turn folds these into its current round — the agent sees the contribution and can act on it immediately. A parent that already finished is not left waiting: the completion event carries a task payload, so it starts a follow-up turn whose whole job is to fold the outcome, pull the deliverables, and speak to the user. Exactly-once discipline guards the seam: an event a live turn already consumed is never promoted into a second turn.
Threads in the chat
With visibility set to thread, the helper's stream arrives in the same
chat the user is watching — every emission stamped with its origin (child conversation,
forked-from turn, charter caption, helper name) and delivered on the parent conversation's
channel. The chat client multiplexes the stamp into a collapsible thread anchored
under the turn that forked it: collapsed, it shows the helper's name, the charter
caption, a live status, and contribution milestones; expanded, the full child stream
rendered by the same pipeline as the main lane — its searches, its code, its files, all
folded in by the child's own conversation identity. Several helpers under one turn —
fan-out — each get their own thread.
Reload reconstructs the same picture from the store: each turn's record carries its
forks, the child conversation carries its forked_from backref,
and the child fetches through the same conversation endpoint as any other. Live and reload
agree because both render the same source.
The default visibility is silent — deployed chat surfaces that render no
threads receive no interleaved child traffic; the parent timeline still shows the marker,
the contributions, and the outcome.
A named participant, not "you"
The delegating agent names its helper — the optional agent_title on the
call ("Science news researcher", "Pricing analyst"). That one name then carries the
helper's identity through every place the user meets it: the thread header, the in-flight
row of the announce block, and the handoff. The agent that chose the name recognizes its
own helper in each of those places without decoding conversation ids.
The subtle part is the reactive handoff. When a helper converges after the parent has
already finished, its completion starts a follow-up turn — and that turn was not initiated
by the user. So it is attributed to the helper, as a participant in the conversation: where
a user turn shows "You" and the message you typed, this turn shows the helper's
name and what it said — "Science news researcher said: I compiled the two stories into
an Excel with the trend charts." The line the helper speaks is its own
react.contribute report — its message to the delegating agent, not a slice of
the deliverable. A conversation can now have more than one voice, and each turn says plainly
whose voice it is.
Aliases, and where identity lives
The tool document the agent reads is static — identical for every user, free of model names, part of the byte-stable cached instruction. It frames when to delegate by capability need:
Delegate when the assignment needs more capability than yours — deeper reasoning, broader knowledge, work whose quality depends on conducting real research (forming queries, recognizing incomplete results, re-querying, critically analyzing, compiling) — or when a sizable self-contained piece can genuinely run alongside your own work. Do quick work yourself: a delegation costs like a full agent of the chosen strength.
Everything situational renders in the per-round announce block, in one vocabulary:
[DELEGATION]
you are: fast_agent [regular]
helper aliases (react.delegate agent_alias):
- fast_agent [regular]: quick focused work
- strong_agent [strong] (default): deep reasoning and synthesis
delegations in this conversation:
- Vendor-landscape researcher — Research the vendor landscape. — strong_agent [strong] — contributed 1
The helper is chosen by agent_alias — an enum, never a model string.
Admins map aliases to models per agent (fast_agent and
strong_agent ship as defaults); an alias nobody configured resolves to the
smartest configured one. The agent knows itself by the same alias
vocabulary, matched from its effective decision model — so "would a helper be stronger
than me?" is a direct comparison, recomputed when the user changes their model pick. Live
delegations render while they run; a completed one shows once, in the turn its outcome
folded, then lives on as ordinary timeline history.
The user pays, the user decides
Delegation raises quality and raises spend, so it follows the platform's capability model:
- The admin's per-agent config offers it (
subagents.allowed) and sets the default; the capability picker then shows a Helper agents toggle to every user, on by default, with the trade-off stated plainly: helpers can raise the quality of hard tasks; each helper runs on its own model calls, billed to your account. - A user who turns it off gets turns where the concept simply is not present — the tool leaves the catalog, the guidance leaves the instructions.
- The helper's turn passes the same economic boundary as the user's own turns — identical subject, budgets, admission; a denial fails the helper loudly back to the parent.
- Spend stays attributable: helper usage books under the child conversation with a first-class backref to the delegating turn, and when the outcome reaches the user, the usage line reads turn total X, of which helpers Y.
What made it honest
- Completions publish after persistence. The converged event is authored only when the child's timeline and workspace are saved — every ref in the final report resolves at the moment the parent (or the follow-up turn) reads it.
- No event buys iteration credit. Helper events inform a live turn; they never extend it and never start anything inside it. Promotion happens only when no turn is live — one event, one consumer, provably once.
- The catalog is the authority. Delegation is opt-in, so its tool appears only where it is on — and the instruction set now says so generically: configuration can add reactive tools beyond the core set, and every catalog section opens with a derived roster (count + ids) the model verifies completeness against.