feat: agent sessions can spawn and orchestrate other sessions - #1
Merged
Conversation
A running agent session can now create sibling sessions on any configured provider, exchange messages with them, and receive their completion reports without polling. New sessions MCP toolkit (list_session_providers, spawn_session, send_to_session, read_session, stop_session, post_report) on the per-thread t3-code server; spawned threads get their own worktree by default and carry a spawnedByThreadId link. A SessionSpawnReactor starts a turn on the spawning thread when a child posts its report or hits a provider error. Reports persist as a projection and render as cards; child threads show a Spawned-by banner. Guardrails: direct-children-only scoping, 8-child / 3-deep caps, child permission mode capped at the parent's, and a Settings toggle (enableSessionOrchestration) that applies to running sessions immediately. Also extracts the thread bootstrap macro from ws.ts into a shared ThreadTurnBootstrap service, bumps @anthropic-ai/claude-agent-sdk to 0.3.226 (new SDK message types handled in ClaudeAdapter), and fixes an MCP gotcha where an empty tool parameter struct made Claude Code drop the entire server's toolset. Built with Claude Fable 5 on Claude Code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
list_session_providers now returns each model's option descriptors
(reasoning effort, context size, …) and spawn_session takes an optional
options array of {id, value} selections, validated against the chosen
model's descriptors before being carried into the thread's model selection.
Verified live: a Claude session spawned a Codex child with
reasoningEffort=low; the selection persisted on the spawned thread and the
completion report woke the parent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roughcoder
added a commit
that referenced
this pull request
Aug 11, 2026
ProjectionSnapshotQuery: the session-spawning feature added `spawnedByThreadId` to the projected thread, but neither expected snapshot in this test was updated. Both assertions needed it - deepEqual aborts on the first mismatch, so fixing the snapshot.threads case simply unmasked the shellSnapshot one. Same class of regression as the `reports` field, and same cause: PR #1 merged without CI ever executing. imageCompression: give the give-up-path case a 90s timeout. It is the only case where nothing fits the budget, so it runs 3 dimension scales x (probe + 4 quality steps) = 15 encodes, each a base64 round-trip through jsdom's FileReader. That takes ~8s on its own and overruns the 15s default once the rest of the suite is competing for CPU. An earlier attempt to fix this by shrinking the stubbed encode from 8MB to 2MB did not work - the test passes in isolation even at 8MB, so volume was never the driver, contention is. The smaller stub is kept because there is no reason to do more work than the assertion needs, but the timeout is what actually fixes it. The product path is bounded at 15 encodes and returns correctly; this is a slow test, not a hanging implementation.
roughcoder
added a commit
that referenced
this pull request
Aug 11, 2026
Renames the fork's user-visible identity from T3 Code to Phoenix and gives it a distinct OS-level identity, so it installs and runs alongside upstream T3 Code rather than colliding with it. Runtime identity diverges, source identity does not: state dir (~/.phoenix), env var (PHOENIX_HOME), desktop userData, product name, URL scheme (phoenix://), bundle ID (com.goodbird.phoenix), Linux desktop entry and WM class, systemd unit, ports (3873, 13873/5833), CLI binary and MCP server id all change. The @t3tools package scope, t3-prefixed paths and internal symbols stay as upstream has them so merges stay cheap. See docs/internals/branding.md. Notably, the legacy-userData adoption step is removed entirely - keeping it would have pointed two live applications at one directory - and PHOENIX_HOME deliberately has no T3CODE_HOME fallback, unlike every other variable, because the base dir holds the SQLite database and auth state. Also reworks CI for the fork. Upstream targets Blacksmith runners, which a fork without a Blacksmith installation queues indefinitely rather than failing, so no check on this repository had ever executed. Points ci.yml at GitHub-hosted runners, disables the nine workflows that need T3's infrastructure (GitHub-side, so the files stay byte-identical to upstream), and adds phoenix-build.yml to produce an unsigned macOS DMG per merge with a GitHub pre-release and Slack notification. That first real CI run surfaced three defects that had been invisible on main, all from PR #1 landing with zero executed checks: a missing `reports` field in a mobile test fixture, a missing `spawnedByThreadId` in two ProjectionSnapshotQuery assertions, and an upstream image-compression test that starves under parallel load. All three are fixed here. Attribution to T3 Code is added to the README and Settings -> About; the original MIT copyright is untouched.
This was referenced Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A session cannot delegate: splitting work across providers means the user manually creating threads and shuttling context between them. There is no way for a long-running "manager" chat to run multiple flows and find out when they finish.
What this does
Adds a
sessionstoolkit to the per-threadt3-codeMCP server, so any agent session can, from inside its own turn:list_session_providers— enumerate spawnable provider instances and modelsspawn_session— create a new thread (own git worktree by default, linked viaspawnedByThreadId) that starts working immediatelysend_to_session/read_session/stop_session— scoped to threads the caller spawnedpost_report— post a completion report (status, markdown summary, artifacts)A new
SessionSpawnReactorwatches domain events: when a spawned thread posts its report (or its provider session errors), it starts a turn on the spawning thread carrying the result — the parent is woken instead of polling. Spawned sessions automatically get report instructions appended to their first message so the contract holds on every provider.Supporting changes: the thread bootstrap macro (create + worktree + setup script + rollback) is extracted from
ws.tsinto a sharedThreadTurnBootstrapservice; reports persist as a projection (migrations 041/042); web UI renders a "Spawned by" banner on child threads and report cards in the timeline; Settings → General gains a Session orchestration toggle (enableSessionOrchestration, checked per tool call so it applies to running sessions instantly).Guardrails: mutating tools reach direct children only; max 8 children per thread, 3 levels deep; a child's permission mode is capped at its parent's; spawned threads participate in normal archive/settle session cleanup.
Also: bumps
@anthropic-ai/claude-agent-sdkto 0.3.226 (new SDK message types handled explicitly inClaudeAdapter), and fixes an MCP compatibility bug — an empty tool parameter struct encodes to a typelessanyOfschema, which makes Claude Code silently drop the whole server's toolset.Model options
list_session_providersreturns each model's option descriptors (reasoning effort, context size, boolean flags) with their valid choices, andspawn_sessionaccepts anoptionsarray of{id, value}selections — validated against the chosen model's descriptors and carried into the spawned thread's model selection. Verified live: a Claude session spawned a Codex child withreasoningEffort=low, the selection persisted on the child thread, and the completion report woke the parent (cross-provider both directions: Codex→Claude and Claude→Codex).Verification
decider.reports.test.ts); contracts, server, client-runtime, and web all typecheck clean.list_session_providersreturns all five providers). Settings toggle verified live: flipping it off made a running session's next tool call fail withdisabled_in_settings; flipping back on restored it.Screenshots of the report card, spawned-by banner, parent wake-up, and settings toggle to be attached in comments.
Built with Claude Fable 5 on Claude Code.
🤖 Generated with Claude Code