Skip to content

Add spawn_agent: delegate sub-tasks to fresh model instances - #263

Closed
C-K-Loan wants to merge 5 commits into
Blaizzy:mainfrom
C-K-Loan:pr/spawn-agent-core
Closed

C-K-Loan wants to merge 5 commits into
Blaizzy:mainfrom
C-K-Loan:pr/spawn-agent-core

Conversation

@C-K-Loan

@C-K-Loan C-K-Loan commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Depends on #267 (Adopt MCP tools via a consent-gating adapter) — this branch is stacked on top of it, so the diff includes #267's commit until that one merges.

Adds spawn_agent, a tool that lets the model delegate a focused sub-task to a fresh instance of itself — a coordinator can fan work out to sub-agents instead of doing everything serially in one conversation. Each sub-agent gets its own tool-call loop with the same tools and MCP access as the coordinator, can optionally run on a different model for a call, and streams its tokens live into a nested cell in the transcript rather than jumping from a spinner straight to finished text.

How it works

Dispatch. Within a single round, any spawn_agent calls the model makes are collected rather than run inline, then dispatched together after every other tool call in that round has already resolved. All pending spawns in the batch (capped at 5 per round) run concurrently via a real TaskGroup. Each child's outcome — success, failure, or its own cancellation — is captured as a Result inside the task instead of being rethrown, so one sub-agent failing or being cancelled can't discard the others' already-settled results.

Model resolution. A sub-agent defaults to the coordinator's own model; a call can optionally request a different one explicitly. Same model as the coordinator → no reload, the request just hits the already-resident weights. A different, explicit model → the server (as it exists today) holds exactly one resident text-generation model, so a mismatch evicts whatever's loaded and loads the requested one; the coordinator's next round then evicts it right back to reload its own — the same reload cost switch_model already pays, not a new regression. That eviction decision is based on live, per-device memory (Apple's recommended GPU working-set ceiling minus what's actually active right now), not a static config value. A separate, independent PR (memory-aware model cache) lets multiple models coexist when there's room instead of always evicting on any mismatch.

Isolation. A sub-agent is a fresh, private in-memory message array (ChatAgentLoop), not a server-side session — completions are stateless and resend the full message list every request, so two sub-agents' histories never interact regardless of what model they're on.

Consent gating. A sub-agent can itself call switch_model or an MCP tool, and both still require the same human approval via the same consent-card UI as the coordinator's own tool calls — the model is never in that loop. This uses a separate implementation from the coordinator's own inline consent handling (not shared code) — reusing the coordinator's proven loop was judged higher regression risk than a focused reimplementation. Along the way, also fixes the consent card itself: it was showing switch_model's hardcoded description ("The model wants to switch to...") for every consent-gated call, including real MCP ones — now an MCP call's card names the actual tool and server (e.g. "The model wants to run search (websearch).").

Streaming UI. A sub-agent's nested transcript updates at a capped ~15fps cadence per sub-agent (the same rate as token streaming), so several concurrent sub-agents can't saturate the main thread with SwiftUI diff/layout passes. Any buffered-but-not-yet-flushed content is force-flushed the moment a sub-agent finishes, so nothing streamed right before completion is lost to the throttle window. A completed sub-agent's step cell also stays expanded instead of auto-collapsing, so its work stays visible while the coordinator continues.

Cancellation. Stopping generation (or one sub-agent's own consent being cancelled) cascades to every other still-queued tool call and every other pending spawn in the same round, rather than leaving orphaned work running.

Repetition-loop detection. A degenerate-token-repetition detector catches a model spiraling into repeating the same phrase indefinitely during streaming — a failure mode that showed up in practice once multiple sub-agents could return synthesis-worthy but low-quality results in the same round.

Blocking, by round. All spawns within one round run concurrently with each other, but the coordinator's own next turn always waits for the entire batch to settle before continuing — a tool call's result has to be back in the message history before the model can meaningfully take its next turn. See Follow-up below for a fire-and-forget alternative.

Demos

Two sub-agents running concurrently, streaming live, both step cells staying expanded after they settle:

live-stream demo

Full-quality .mp4

Two sub-agents each independently using the web-search MCP tool, own consent gating, concurrent approval, real results:

websearch demo

Full-quality .mp4

Follow-up in this draft

A sub-agent's nested step cell currently stays blank while it's reasoning/thinking, only showing anything once a tool call appears. We'll push an update to this same PR that streams a sub-agent's own thinking live, plus tighter timing between concurrent consent approvals, along with a re-recorded demo — before this comes out of draft. The current clips still accurately show the core functionality (concurrent spawn, concurrent MCP consent, real results), just not that specific UI polish yet.

Future work (not planned for this PR)

spawn_agent currently always blocks the coordinator's round until the whole batch of sub-agents in that round finishes — they run concurrently with each other, but the coordinator can't do anything else until all of them settle. A fire-and-forget mode (spawn returns immediately, coordinator's turn ends, sub-agent result gets injected back into the transcript asynchronously once done) would let the coordinator/user keep interacting while long-running sub-agents work in the background. Real design surface (persistent tracking beyond one round, async result delivery, UI for in-flight background spawns) — a genuinely separate future feature, not attempted here.

Test plan

  • Full test suite green
  • Manual verification of concurrent spawn + concurrent MCP consent gating
  • Manual verification of the insufficient-memory rejection path
  • Manual verification of per-call model override, validated before load

@C-K-Loan
C-K-Loan marked this pull request as draft August 10, 2026 23:45
@C-K-Loan
C-K-Loan force-pushed the pr/spawn-agent-core branch from ac0dbad to 62971ee Compare August 11, 2026 05:03
@C-K-Loan

Copy link
Copy Markdown
Contributor Author

Superseded by #416, which includes everything here (spawn_agent core) plus background execution, list_agents/check_agent/steer_agent, and concurrent batching. Closing in favor of that one.

@C-K-Loan C-K-Loan closed this Aug 28, 2026
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.

1 participant