Skip to content

feat: agent sessions can spawn and orchestrate other sessions - #1

Merged
roughcoder merged 2 commits into
mainfrom
feat/session-spawning
Aug 11, 2026
Merged

feat: agent sessions can spawn and orchestrate other sessions#1
roughcoder merged 2 commits into
mainfrom
feat/session-spawning

Conversation

@roughcoder

@roughcoder roughcoder commented Aug 11, 2026

Copy link
Copy Markdown

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 sessions toolkit to the per-thread t3-code MCP server, so any agent session can, from inside its own turn:

  • list_session_providers — enumerate spawnable provider instances and models
  • spawn_session — create a new thread (own git worktree by default, linked via spawnedByThreadId) that starts working immediately
  • send_to_session / read_session / stop_session — scoped to threads the caller spawned
  • post_report — post a completion report (status, markdown summary, artifacts)

A new SessionSpawnReactor watches 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.ts into a shared ThreadTurnBootstrap service; 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-sdk to 0.3.226 (new SDK message types handled explicitly in ClaudeAdapter), and fixes an MCP compatibility bug — an empty tool parameter struct encodes to a typeless anyOf schema, which makes Claude Code silently drop the whole server's toolset.

Model options

list_session_providers returns each model's option descriptors (reasoning effort, context size, boolean flags) with their valid choices, and spawn_session accepts an options array 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 with reasoningEffort=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

  • Focused suites green (decider/projector/reactor/persistence/client-runtime, 77+ tests incl. new decider.reports.test.ts); contracts, server, client-runtime, and web all typecheck clean.
  • Live end-to-end in the web app: a Codex parent spawned a Codex child into its own worktree; the child did the work, posted a success report with artifacts, and the parent was woken with the report and summarized it. Cross-client checked with Claude (tools load and list_session_providers returns all five providers). Settings toggle verified live: flipping it off made a running session's next tool call fail with disabled_in_settings; flipping back on restored it.
  • Contract changes are additive (optional fields + decoding defaults) so existing clients keep decoding; mobile renders spawned threads as ordinary threads (banner/report-card affordances are web-only for now — mobile decision pending per adapter/provider checklist).

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

roughcoder and others added 2 commits August 11, 2026 16:05
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
roughcoder merged commit 577c251 into main Aug 11, 2026
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.
@roughcoder
roughcoder deleted the feat/session-spawning branch August 18, 2026 00:34
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