Skip to content

Extract a shared sub-agent core behind every spawn site - #4779

Merged
georgi merged 3 commits into
mainfrom
claude/generalize-subagent-architecture-hmh00b
Aug 7, 2026
Merged

Extract a shared sub-agent core behind every spawn site#4779
georgi merged 3 commits into
mainfrom
claude/generalize-subagent-architecture-hmh00b

Conversation

@georgi

@georgi georgi commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

What

Generalizes the sub-agent concept (until now hand-rolled per spawn site, with the graph planner as one ad-hoc case) into one core module, packages/agents/src/subagent.ts, so sub-agents become reusable building blocks with streaming and CodeAct built in.

The concept, stated once: a sub-agent is an async generator of ProcessingMessage events whose return value is how the run settled. CodeAct is the default producer, but any generator with that shape — GraphPlanner.plan(), a future reviewer or researcher — streams through the same pipe and nests in the UI the same way.

The core

Primitive Does
runSubAgent(opts) One CodeAct child loop: single-step task, optional outputSchema (structured via finish(), prose otherwise), yields events, returns a SubAgentOutcome — never throws for run failures
settleStepResult(sr, {hasOutputSchema}) Unified failure detection: top-level step_result.error, the sole-key {error} payload a dying step reports, and (schemaless only) any string error property
forwardSubAgentStream(gen, opts) Drives any sub-agent generator: tags events (parent_tool_call_id, subtask_depth), tolerates a broken forwarder, honors an abort signal between rounds
enterSubAgentDepth(ctx, maxDepth) The shared recursion gate over SUBTASK_DEPTH_KEY
SubAgentTool Base class for tools that expose a sub-agent to a parent model; subclasses declare only their surface, param → run translation, and child-toolset policy

Spawn sites refactored onto it

  • RunSubtaskTool and RunSearchTool — previously two near-identical ~300-line copies of the depth guard / context copy / single-step task / executor / tagging / settlement machinery — are now thin SubAgentTool subclasses declaring only what makes each one itself (full-belt inheritance + self-stitching for recursion vs. read-only allowlist + breadth-scaled iteration budget).
  • ScriptRunner's agent() bridge calls runSubAgent directly and pushes events onto the script channel; its private copy of the executor loop and the {error}-payload detection is gone.
  • plan_workflow_graph drives the GraphPlanner generator through forwardSubAgentStream, replacing its hand-rolled tag/forward/abort loop — the proof that non-CodeAct producers ride the same pipe.

Compatibility

Tool names, input schemas, error codes (missing_prompt, max_recursion_depth_reached, subtask_failed, search_no_result, …), event tagging fields, and depth semantics are unchanged; existing tests for all four sites pass unmodified. Structured outputSchema support is now available to every tool-exposed sub-agent (previously script-mode only). One deliberate unification: a schemaless script-mode agent() result that contains a string error among other keys now settles as failure (previously only the sole-key shape did) — nothing legitimate produces that shape in prose mode.

Tests

  • New tests/subagent.test.ts covers settlement, tagging, forward-failure tolerance, abort, and the depth gate.
  • Full packages/agents suite: 150 files, 2107 passed. Downstream chat (32), websocket (2232), cli (564) all pass.
  • Root lint and typecheck pass (mobile typecheck fails only on this sandbox's missing Expo tree, unrelated).

Docs: new "Sub-Agent Core" section in packages/agents/CLAUDE.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_017YcuD3JDFqQ55kXdJicHDB


Generated by Claude Code

claude and others added 3 commits August 7, 2026 17:00
A sub-agent is now one concept stated once (packages/agents/src/subagent.ts):
an async generator of ProcessingMessages whose return value is how the run
settled. CodeAct is the default producer (runSubAgent), any generator with
that shape streams through the same pipe (forwardSubAgentStream — tagging for
UI nesting, forward-failure tolerance, between-rounds abort), recursion goes
through one gate (enterSubAgentDepth), and failure detection — the protocol
error field, the sole-key {error} payload a dying step reports, the
schemaless string-error shape — lives in one settlement function.

Before this, run_subtask and run_search each carried a full copy of the
machinery, ScriptRunner's agent() bridge duplicated the executor half, and
plan_workflow_graph re-implemented the streaming half for the GraphPlanner.
All four now sit on the core: the two tools are thin SubAgentTool subclasses
(declaring only their surface, param translation, and child toolset policy),
ScriptRunner calls runSubAgent directly, and the planner generator rides
forwardSubAgentStream. Tool names, schemas, error codes, event tagging, and
depth semantics are unchanged; structured outputSchema support is now
available to every tool-exposed sub-agent, not just script mode.

New delegation tools are a ~40-line SubAgentTool subclass instead of another
copy of the spawn/stream/settle loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017YcuD3JDFqQ55kXdJicHDB
@georgi
georgi merged commit 0a16715 into main Aug 7, 2026
24 checks passed
@georgi
georgi deleted the claude/generalize-subagent-architecture-hmh00b branch August 7, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants