[Feature]: Provider-agnostic orchestration ("ultracode for every model") — a fork-owned MCP toolkit that lets any adapter fan out into real T3 threads #43

Description

@radroid

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I am describing a concrete problem or use case, not just a vague idea.

Area

apps/server

Problem or use case

What "ultracode" is, for a reader who has never used it

"Ultracode" is shorthand for a cluster of behaviours people expect from a high-effort coding agent, none of which are a single feature:

  • Parallel fan-out. The agent decomposes a task and runs several workers at once (explore three call sites, write tests while another writes the implementation) instead of doing everything serially in one context.
  • Structured output. Workers return machine-readable results (a verdict, a file list, a JSON summary) rather than prose the parent has to re-read and re-parse.
  • Adversarial verification. A second agent is asked to break the first one's work — find the bug, find the missing test — rather than to agree with it.
  • Judge panels / best-of-N. The same task is attempted N ways and a separate adjudicator picks a winner or merges the outputs.
  • Loop-until-dry. The agent keeps iterating (fix → run tests → fix) until a stopping condition is met, rather than stopping after one pass.

What T3 Code actually has today

In this repo, ultracode is only a Claude effort level, not any of the above. It is a dropdown value that normalises to --effort xhigh plus a settings.ultracode: true flag handed to the Claude Agent SDK:

  • apps/server/src/provider/Layers/ClaudeProvider.ts:75 (and :106, :142) — { value: "ultracode", label: "Ultracode" }, listed only for Claude models
  • apps/server/src/provider/Layers/ClaudeProvider.ts:406if (effort === "ultracode") { return "xhigh"; }
  • apps/server/src/provider/Layers/ClaudeAdapter.ts:3510 / :3521const ultracode = isClaudeUltracodeEffort(effort)...(ultracode ? { ultracode: true } : {})

So every one of the five ultracode behaviours, when it happens at all, happens inside the Claude CLI process, where T3 cannot inspect, interrupt, checkpoint, or steer it. That is the direct cause of "ultracode does not always work well in T3 Code": T3 renders provider-internal fan-out as flat, opaque rows.

  • Subagent activity is a display heuristic, not a model: tool names containing agent/task/subagent get bucketed into the canonical item type collab_agent_tool_call, titled "Subagent task" (ClaudeAdapter.ts:596, :865). The same heuristic exists in OpenCodeAdapter.ts and CodexAdapter.ts and is absent from CursorAdapter.ts and GrokAdapter.ts.
  • task.started / task.progress / task.completed exist in the canonical union (packages/contracts/src/providerRuntime.ts:177) and are ingested (apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts), but only ClaudeAdapter.ts ever emits them — verified: grep -rl 'task\.started' apps/server/src packages/contracts/src returns only ClaudeAdapter.ts, ProviderRuntimeIngestion.ts, and the contracts file. They are read-only telemetry about work T3 does not own.

Why this cannot be fixed inside the adapters

ProviderAdapterCapabilities has exactly one field, and all five adapters set it to the same value — the declarative capability surface differentiates nothing today:

// apps/server/src/provider/Services/ProviderAdapter.ts:28exportinterfaceProviderAdapterCapabilities{readonlysessionModelSwitch: ProviderSessionModelSwitchMode;}

ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703 — all sessionModelSwitch: "in-session".

There is no spawn/subagent primitive anywhere in ProviderAdapterShape, and there is no parent/child thread concept in T3's own domain: grep -rn parentThreadId apps/server/src packages/contracts/src returns nothing (the identifier exists only in the vendored Codex app-server generated schema). OrchestrationThread (packages/contracts/src/orchestration.ts:352) has no parent field.

Building fan-out inside adapters would mean five implementations against five unrelated protocols (Claude Agent SDK, Codex app-server JSON-RPC, ACP ×2, OpenCode HTTP), and two of them (Cursor, Grok) have no subagent concept at all. The repo already documents what happens when capability is under-declared: the web app hardcodes a per-driver allowlist because there is no flag to read —

// apps/web/src/outbox/composerSteering.logic.ts:13*Thecatchisthatthereisnosteeringcapabilityflaganywhereinthe*contracts,sosupport is implicitperadapterandhastobeassertedhere.// :43constSTEERABLE_PROVIDER_DRIVER_KINDS: ReadonlySet<string>=newSet([...]);

Proposed solution

Shape: one orchestrator thread + N child threads, driven entirely above the adapter layer

The orchestrator is an ordinary T3 thread on any provider. Its children are ordinary T3 threads. The only new machinery is a fork-owned MCP toolkit the model calls, plus a fork-owned store that tracks runs. Nothing touches ProviderAdapterShape, and no provider-native subagent feature is used or required.

Why this is provider-agnostic for free

Every one of the five adapters already mounts a T3-hosted MCP server into its provider session, with a per-thread bearer credential issued by T3:

  • apps/server/src/provider/Layers/ClaudeAdapter.ts:3523
  • apps/server/src/provider/Layers/CursorAdapter.ts:534
  • apps/server/src/provider/Layers/CodexAdapter.ts:1397
  • apps/server/src/provider/Layers/GrokAdapter.ts:572
  • apps/server/src/provider/Layers/OpenCodeAdapter.ts:1217

all calling McpProviderSession.readMcpProviderSession(input.threadId). The invocation scope carries the calling thread's id, so a tool handler natively knows who the parent is:

// apps/server/src/mcp/McpInvocationContext.ts:12exportinterfaceMcpInvocationScope{readonlyenvironmentId: EnvironmentId;readonlythreadId: ThreadId;readonlyproviderSessionId: string;readonlyproviderInstanceId: ProviderInstanceId;readonlycapabilities: ReadonlySet<McpCapability>;readonlyissuedAt: number;}

Adding tools here means every provider gets them on the same day, with zero adapter edits.

New files (all fork-owned, zero upstream conflict)

apps/server/src/t3x/orchestrate/
tools.ts # Effect Tool.make definitions — copy apps/server/src/mcp/toolkits/preview/tools.ts
handlers.ts # copy apps/server/src/mcp/toolkits/preview/handlers.ts
RunStore.ts # JSON persistence in stateDir, modelled on t3x-auto-resume.json
Runner.ts # dispatches thread.create + thread.turn.start, watches for completion
index.ts # exports OrchestrateToolkitRegistrationLive + OrchestrateLayerLive
apps/server/src/t3x/mcp/index.ts # single aggregator for all future fork MCP toolkits

apps/server/src/mcp/toolkits/preview/ is a complete, shipping template (tools.ts, handlers.ts, plus tests for both) — it is currently the only toolkit (ls apps/server/src/mcp/toolkitspreview).

The three tools (v1)

ToolBehaviour
subagent_spawnTakes { tasks: [{ label, prompt, providerInstanceId?, modelId?, runtimeMode? }] }, max 4. Creates one child thread per task, dispatches thread.turn.start with only the task prompt (no parent history), returns { runId, children: [{ childThreadId, label }] }immediately. Never blocks.
subagent_status{ runId } → per-child { label, childThreadId, phase } where phase comes from projectThreadAwareness. Cheap to poll.
subagent_result{ runId } or { childThreadId } → final assistant message text for each completed child, plus its phase.

Async-return + poll is deliberate: an MCP handler that blocks for minutes holds the parent's tool call open against adapter and CLI tool timeouts. Polling burns orchestrator turns but cannot deadlock.

Dispatch: reuse the proven fork precedent verbatim

AutoResumeReactor is already a server-side autonomous dispatcher that synthesises turns indistinguishable from a keystroke:

// apps/server/src/t3x/autoResume/Reactor.ts:111commandId: CommandId.make(`t3x-auto-resume:${commandUuid}`),

and it already writes its own audit trail with thread.activity.append (Reactor.ts:75; command declared at packages/contracts/src/orchestration.ts:865).

The orchestrator does the same: engine.dispatch({ type: "thread.create", ... }) then engine.dispatch({ type: "thread.turn.start", ... }) with commandId prefixed t3x-orchestrate:, and appends a breadcrumb to the parent thread for every spawn, phase change, and result read. That trail is provider-agnostic and shows up on Cursor/Grok/OpenCode identically — which is exactly why the design must not build on task.* (Claude-only, ClaudeAdapter.ts is the sole emitter).

Completion detection: already solved, do not reinvent

// packages/shared/src/agentAwareness.ts:53exportfunctionprojectThreadAwareness(...)// :77functionresolveThreadAwarenessPhase(thread): AgentAwarenessPhase|null

This reads only shell-projection fields — never provider internals — and is already consumed server-side by the fork's Web Push seam (apps/server/src/t3x/webPush/attention.ts). It returns waiting_for_approval / completed / null, which is precisely the "is this child done, stuck on an approval, or still working" distinction the orchestrator needs.

Do not gate on session.status alone. The existing code documents settled/teardown races, and fork issue #38 records the hazard that synthetic turns deadlock status-gated logic.

Result collection

OrchestrationLatestTurn (packages/contracts/src/orchestration.ts:335) carries assistantMessageId: Schema.NullOr(MessageId) at :341, and ProjectionSnapshotQuery.getThreadDetailById (apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168) returns the full thread including messages. subagent_result is a projection read, not a provider call.

Registration seam (the only upstream edit)

The MCP server builds its toolkit layer in apps/server/src/mcp/McpHttpServer.ts:206-225:

constPreviewStandardToolkitRegistrationLive=McpServer.toolkit(PreviewStandardToolkit).pipe(Layer.provide(PreviewStandardToolkitHandlersLive),);exportconstPreviewToolkitRegistrationLive=Layer.mergeAll(...);exportconstlayer=PreviewToolkitRegistrationLive.pipe(Layer.provideMerge(McpTransportLive));

Add one merged layer sourced from apps/server/src/t3x/mcp/index.ts — the same aggregator discipline apps/server/src/t3x/index.ts:71/:100 already enforces for T3xLayerLive / T3xRoutesLive. Every future fork toolkit then costs zero additional upstream churn.

Deliberately avoid the capability gate in v1.McpCapability is a closed union with one member (apps/server/src/mcp/McpInvocationContext.ts:10 — export type McpCapability = "preview";) and it is issued from a hardcoded set (apps/server/src/mcp/McpSessionRegistry.ts:131 — capabilities: new Set(["preview"])). Extending both would add two more ledger rows for no v1 benefit. Gate the toolkit on a fork-local setting inside handlers.ts instead, and revisit if v2 needs per-thread scoping.

What degrades, per adapter, and how

Because the design uses no provider-native subagent feature, the capability does not degrade — the presentation and reliability do:

ConcernClaudeCodexOpenCodeCursorGrok
Can call the tools (MCP mounted)yesyesyesyesyes
Spawn tool call renders as "Subagent task"yes (ClaudeAdapter.ts:596)yesyesno — generic tool rowno — generic tool row
Native task.* telemetryyesnononono
Structured judge verdictsnative --json-schemanative --output-schemaprompt-instructedprompt-instructedprompt-instructed
Steering the orchestrator mid-runyesno (excluded from STEERABLE_PROVIDER_DRIVER_KINDS)yesyesyes

Mitigations: emit the fan-out trail via thread.activity.append so it is legible everywhere (not task.*); accept prose results in v1 and defer structured verdicts; document that Codex orchestrators cannot be steered mid-run.

Fan-out cap and why it is low

Session startup is serialized. ensureSessionForThread (ProviderCommandReactor.ts:684) runs inside buildSendTurnRequestForThread, which runs inside a single-item-at-a-timeDrainableWorker (ProviderCommandReactor.ts:1323). Spawning N children means N sequential CLI process spawns before any of them runs. The turn itself is forked (ProviderCommandReactor.ts:1117-1119 — .pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped)), so once started they do run concurrently. Cap v1 at 4 and make the cap a fork-local constant.

Separately, ProviderSessionReaper.ts:17 sets DEFAULT_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 — a parked child session is reaped after 30 minutes, invisibly to a polling orchestrator. subagent_status must report a reaped child as a distinct phase, not silently as "still working".

Restart durability

RunStore.ts persists { runId, parentThreadId, children: [{ childThreadId, label, phase }] } to stateDir, mirroring t3x-auto-resume.json. On boot, reconcile every non-terminal run against getThreadDetailById. Without this a server restart orphans every child thread with no trace.

Why this matters

Today "high-effort agent" in T3 Code means one Claude-only dropdown value that changes a flag on one provider's CLI, and whatever fan-out happens is invisible and unsteerable because it lives inside that CLI's process. This inverts that: each unit of parallel work becomes a first-class T3 thread, which means it inherits everything T3 already built — the activity timeline, approvals, checkpoints, per-turn diffs, interrupt, Web Push, auto-resume, mobile — with no per-feature work.

It is genuinely model-agnostic rather than nominally so: the orchestrator can be a Grok thread spawning Codex children, because the only interface it touches is OrchestrationEngineService.dispatch. That is a capability no single provider gives you, and it is the one thing a wrapper app is uniquely positioned to build.

It also removes a class of "why did nothing happen" confusion: a stalled child shows up as a thread with a phase and an approval card, not as a silent gap inside another process.

Smallest useful scope

v1 = async spawn + poll + collect, on shared cwd, no UI.

Ships:

  • apps/server/src/t3x/orchestrate/ with subagent_spawn, subagent_status, subagent_result
  • one aggregator seam line in apps/server/src/mcp/McpHttpServer.ts via apps/server/src/t3x/mcp/index.ts
  • fan-out cap 4, fork-local constant
  • RunStore JSON persistence + boot reconciliation
  • thread.activity.append breadcrumbs on the parent for spawn / phase-change / result
  • completion via projectThreadAwareness, results via getThreadDetailById + latestTurn.assistantMessageId
  • tests mirroring apps/server/src/mcp/toolkits/preview/{tools,handlers}.test.ts

Explicitly out of scope for v1 (each is a follow-up issue):

  • Git worktrees per child. This is the big one. Worktree bootstrap is not in the engine — it is implemented inline in the WebSocket transport (apps/server/src/ws.ts:750 dispatchBootstrapTurnStart, :891thread.create, :908gitWorkflow.createWorktree, :940runSetupProgram()). A server-side dispatcher calling engine.dispatch directly gets none of it. ws.ts measured churn is 41 commits in the 60 days before merge-base 64bf01619 — the highest of any candidate file. So v1 children share the parent's cwd, which makes it safe for read-heavy fan-out (parallel investigation, review, test authoring in disjoint directories) and unsafe for parallel edits to the same files. State that limit in the tool description so the model does not attempt it.
  • Judge panels / best-of-N adjudication. Needs structured verdicts; see alternatives.
  • UI grouping. With no parent link in OrchestrationThreadShell, 4 children land flat in the shell snapshot and each completion fires the needs-attention / Web Push coordinator.
  • Extending ProviderAdapterCapabilities.
  • Generic structured output in TextGenerationService.

Alternatives considered

1. Wait for upstream orchestrator-v2. PR pingdotgg#3638 already merged delegate_task / task_status / task_cancel / create_threads / t3_thread_* as orchestrator MCP tools — but into the stack branch t3code/codex-turn-mapping, not main. Its gate, pingdotgg#2829, is still open against main. Verified: merge commit dac514e6b is not an ancestor of upstream/main, origin/main, or fork HEAD. This is the strongest alternative and the honest reason to keep v1 tiny — the whole toolkit is three tools in fork-owned files and is cheap to delete when v2 lands. Naming should deliberately not collide with delegate_task/task_status so both can coexist during a transition.

2. Cherry-pick the v2 toolkit onto today's main.git show dac514e6b:apps/server/src/mcp/toolkits/orchestrator/tools.ts is real, reviewed code. But it targets the v2 orchestrator's data model, so porting it means porting or stubbing that model — a much larger change than three fork-local tools, and it creates the "parallel paths" hazard the fork has already been bitten by (a fork path that duplicates an upstream capability silently bypasses new upstream guards).

3. A declarative t3x reactor instead of model-callable tools. Model AutoResumeReactor directly: a server-side supervisor that runs a user-configured plan (stages, providers, prompts) without the model deciding anything. Better for deterministic verification pipelines and judge panels; worse for the "ultracode" experience the user asked for, where the model authors its own fan-out. They share ~80% of the machinery (RunStore, Runner, awareness polling) so (a) can be added on top of (b) later. Picking the MCP toolkit first is the choice that matches the request.

4. createSdkMcpServer from the Claude Agent SDK. Exported and typed but unused anywhere in the repo. Strictly worse here: Claude-only, requires a ClaudeAdapter.ts edit (churn 12), and defeats the entire point of the issue.

5. Extend TextGenerationService with a generic generateStructured(schema, prompt) for judge verdicts. The interface is closed at four git-flavoured operations (apps/server/src/textGeneration/TextGeneration.ts:77-82), so this means five implementations, and only Claude and Codex get a hard schema constraint — Cursor/Grok/OpenCode scrape prompt-instructed JSON with extractJsonObject. Cheaper: run the judge as its own child thread and parse its final assistant message with the same lenient extractor those three adapters already use. Loses per-call model/effort control.

6. Do nothing and document that ultracode is Claude-only. Legitimate. The counter is that ultracode's value is mostly the orchestration pattern, not the effort flag, and that pattern is provider-independent.

Risks or tradeoffs

Seam cost (measured against merge-base 64bf01619, 60-day window)

docs/t3x/SEAMS.md:5 records 34 upstream-owned files, +1616 / -187 lines, and :21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." This feature adds one row.

FileEditLinesChurnRisk
apps/server/src/mcp/McpHttpServer.tsone merged layer sourced from t3x/mcp/index.ts~36~18

Measured with git log --oneline --since="@$((MBTS-60*86400))" 64bf01619 -- <path>. For contrast, the paths this design deliberately avoids: apps/server/src/ws.ts = 41, apps/server/src/server.ts = 29, packages/contracts/src/orchestration.ts = 12, apps/server/src/provider/Layers/ClaudeAdapter.ts = 12, apps/web/src/components/ChatView.tsx = 63 (already the fork's worst row at risk 11466, SEAMS.md:41). Fork-owned apps/server/src/t3x/ has churn 0.

Rejected-but-tempting rows and their cost: McpSessionRegistry.ts (churn 7) + McpInvocationContext.ts (churn 3) for a capability gate — two rows for a v1 that has no per-thread scoping requirement. ProviderAdapter.ts has churn 0 and is cheap in isolation, but extending it forces edits to all five adapters (ClaudeAdapter.ts churn 12 among them).

The ledger must be updated in the same commit — header totals plus the new row — per the self-reference rule at SEAMS.md:22-28, and the t3x/mcp/index.ts aggregator should be documented there as the reason no further MCP rows will follow.

Parallel-paths hazard

This is the fork's known failure mode: a fork path that duplicates an upstream capability silently bypasses new upstream guards. Upstream is actively building the same thing (pingdotgg#3138, pingdotgg#4649, PRs pingdotgg#4663 / pingdotgg#5219 / pingdotgg#3638). Concretely: use distinct tool names from delegate_task/task_status, keep the entire implementation deletable in one rm -rf apps/server/src/t3x/orchestrate/, and re-check the SEAMS table every sync.

Machine and cost risks

  • Fan-out thrashes a 16GB Mac. Each child is a full provider CLI process, and startup is serialized through the DrainableWorker. Cap 4, and expect the first child to start well before the fourth.
  • No budget ceiling exists. Nothing in T3 bounds a run's token spend. A model that spawns 4 children, polls, and re-spawns can burn a lot unattended. v1 should hard-cap total spawns per parent thread.
  • The 30-minute idle reaper (ProviderSessionReaper.ts:17) silently kills parked children.

UNVERIFIED — do not present these as settled

  1. Do Cursor and Grok's ACP clients actually expose T3's MCP tools to the model? The mcpServers wiring is verified present (CursorAdapter.ts:544, GrokAdapter.ts:582), but nobody has confirmed the tool reaches the model's tool list on those providers. Experiment: start a thread on each of the five providers and ask the model to call mcp__t3-code__preview_status; a tool result on all five proves the channel end-to-end before any orchestration code is written. Do this first — it is the load-bearing assumption of the whole design.
  2. Does a child thread created via engine.dispatch({type:"thread.create"}) without the ws.ts bootstrap path actually start a working session? All existing evidence is that AutoResumeReactor dispatches thread.turn.start to threads that already exist. Experiment: dispatch thread.create then thread.turn.start from a scratch Effect script and confirm turn.completed.
  3. Whether the reaper's 30-minute teardown produces a distinguishable phase from projectThreadAwareness, or looks identical to "completed". Experiment: let a thread idle past 30 minutes and diff the projected phase.
  4. What settings.ultracode: true actually does inside the Claude binary. This repo only sets the flag (ClaudeAdapter.ts:3521); the semantics are undocumented here. The claim "ultracode means parallel fan-out" is inferred from observed behaviour, not from this codebase. Experiment:strings the bundled Claude platform binary for ultracode and read the surrounding tool-registry code.
  5. Whether 4 concurrent children is survivable on this machine. Nobody has measured it.

Examples or references

Upstream issues this overlaps (this is NOT a clean-slate feature)

File:line evidence (all verified in this checkout at main)

  • Capabilities: apps/server/src/provider/Services/ProviderAdapter.ts:26-32; identical values at ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703
  • Five drivers: apps/server/src/provider/builtInDrivers.ts:47
  • MCP mounted by every adapter: ClaudeAdapter.ts:3523, CursorAdapter.ts:534, CodexAdapter.ts:1397, GrokAdapter.ts:572, OpenCodeAdapter.ts:1217
  • MCP scope carries parent threadId: apps/server/src/mcp/McpInvocationContext.ts:10-19; issuance apps/server/src/mcp/McpSessionRegistry.ts:131
  • Toolkit registration point: apps/server/src/mcp/McpHttpServer.ts:206-225; only toolkit today: apps/server/src/mcp/toolkits/preview/ (tools.ts, handlers.ts, tools.test.ts, handlers.test.ts)
  • Fork aggregators: apps/server/src/t3x/index.ts:71 (T3xLayerLive), :100 (T3xRoutesLive); existing seams autoResume/, webPush/
  • Autonomous dispatch precedent: apps/server/src/t3x/autoResume/Reactor.ts:75 (thread.activity.append), :111 (commandId)
  • Bootstrap trapped in the transport: apps/server/src/ws.ts:750, :891, :908, :940, :961
  • Serialized startup / forked turn: apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:684, :1117-1119, :1323
  • Idle reaper: apps/server/src/provider/Layers/ProviderSessionReaper.ts:17
  • Completion phase: packages/shared/src/agentAwareness.ts:53, :77
  • Result addressing: packages/contracts/src/orchestration.ts:335, :341; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168
  • Bootstrap schema (unused by engine): packages/contracts/src/orchestration.ts:677, :701, :720
  • thread.activity.append command: packages/contracts/src/orchestration.ts:865
  • Ultracode today: ClaudeProvider.ts:75, :406, :425; ClaudeAdapter.ts:3510, :3521
  • Closed structured-output interface: apps/server/src/textGeneration/TextGeneration.ts:77-82
  • Steering allowlist precedent: apps/web/src/outbox/composerSteering.logic.ts:13, :43, :51
  • Seam ledger: docs/t3x/SEAMS.md:5, :21, :22-28, :41

Duplicate search performed before filing

Searched all 1,615 upstream pingdotgg/t3code issues (open + closed — count confirmed against gh api search/issuestotal_count) by dumping the full title corpus locally and grepping ~80 term variants, plus body-level gh search issues per concept (orchestration, delegation, subagent, parallel agents, swarm, judge, voting, best of n, consensus, agent team, teammate, ultracode, fan-out, pipeline, workflow), plus a gh search prs sweep. Also searched all 21 radroid/t3code fork issues. Upstream Discussions are disabled (hasDiscussionsEnabled: false), so issues and PRs are the complete surface.

Result: this is not a duplicate, but it is a strong overlap and the body says so explicitly.

Found and cross-referenced above: pingdotgg#3138 (Orchestration/Delegation — closest, owns the goal statement), pingdotgg#4649 (configurable pipelines — owns the staged-workflow half), pingdotgg#5218 / pingdotgg#4456 / pingdotgg#1740 / pingdotgg#538 / pingdotgg#2921 (subagent UI, nested threads, CLI orchestration — all the presentation half this issue defers), and in-flight PRs pingdotgg#4663, pingdotgg#5219.

Most decisive finding: PR pingdotgg#3638 is MERGED — but into the stack branch t3code/codex-turn-mapping, gated behind still-open pingdotgg#2829. Verified git merge-base --is-ancestor dac514e6b upstream/main → NOT an ancestor of upstream/main, origin/main, or fork HEAD, and no scheduled_tasks/orchestrator toolkit files are tracked in this checkout (ls apps/server/src/mcp/toolkitspreview only). So the capability exists upstream on a branch but is not reachable from main today, which is the justification for filing this on the fork rather than upstream.

Zero hits in either repo for judge panels, best-of-N, adjudication, voting, consensus, or swarm — that slice is genuinely unclaimed, and is called out as a deferred follow-up rather than folded into v1.

No fork issue duplicates this. The only adjacent fork issue is #38 (Loop Watch), which is about supervising a single long-running thread and explicitly rules cron out of scope; it is referenced for its session.status hazard note, not as an overlap.

Contribution

  • I would be open to helping implement this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      [Feature]: Provider-agnostic orchestration ("ultracode for every model") — a fork-owned MCP toolkit that lets any adapter fan out into real T3 threads #43

      Description

      @radroid

      Before submitting

      • I searched existing issues and did not find a duplicate.
      • I am describing a concrete problem or use case, not just a vague idea.

      Area

      apps/server

      Problem or use case

      What "ultracode" is, for a reader who has never used it

      "Ultracode" is shorthand for a cluster of behaviours people expect from a high-effort coding agent, none of which are a single feature:

      • Parallel fan-out. The agent decomposes a task and runs several workers at once (explore three call sites, write tests while another writes the implementation) instead of doing everything serially in one context.
      • Structured output. Workers return machine-readable results (a verdict, a file list, a JSON summary) rather than prose the parent has to re-read and re-parse.
      • Adversarial verification. A second agent is asked to break the first one's work — find the bug, find the missing test — rather than to agree with it.
      • Judge panels / best-of-N. The same task is attempted N ways and a separate adjudicator picks a winner or merges the outputs.
      • Loop-until-dry. The agent keeps iterating (fix → run tests → fix) until a stopping condition is met, rather than stopping after one pass.

      What T3 Code actually has today

      In this repo, ultracode is only a Claude effort level, not any of the above. It is a dropdown value that normalises to --effort xhigh plus a settings.ultracode: true flag handed to the Claude Agent SDK:

      • apps/server/src/provider/Layers/ClaudeProvider.ts:75 (and :106, :142) — { value: "ultracode", label: "Ultracode" }, listed only for Claude models
      • apps/server/src/provider/Layers/ClaudeProvider.ts:406if (effort === "ultracode") { return "xhigh"; }
      • apps/server/src/provider/Layers/ClaudeAdapter.ts:3510 / :3521const ultracode = isClaudeUltracodeEffort(effort)...(ultracode ? { ultracode: true } : {})

      So every one of the five ultracode behaviours, when it happens at all, happens inside the Claude CLI process, where T3 cannot inspect, interrupt, checkpoint, or steer it. That is the direct cause of "ultracode does not always work well in T3 Code": T3 renders provider-internal fan-out as flat, opaque rows.

      • Subagent activity is a display heuristic, not a model: tool names containing agent/task/subagent get bucketed into the canonical item type collab_agent_tool_call, titled "Subagent task" (ClaudeAdapter.ts:596, :865). The same heuristic exists in OpenCodeAdapter.ts and CodexAdapter.ts and is absent from CursorAdapter.ts and GrokAdapter.ts.
      • task.started / task.progress / task.completed exist in the canonical union (packages/contracts/src/providerRuntime.ts:177) and are ingested (apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts), but only ClaudeAdapter.ts ever emits them — verified: grep -rl 'task\.started' apps/server/src packages/contracts/src returns only ClaudeAdapter.ts, ProviderRuntimeIngestion.ts, and the contracts file. They are read-only telemetry about work T3 does not own.

      Why this cannot be fixed inside the adapters

      ProviderAdapterCapabilities has exactly one field, and all five adapters set it to the same value — the declarative capability surface differentiates nothing today:

      // apps/server/src/provider/Services/ProviderAdapter.ts:28exportinterfaceProviderAdapterCapabilities{readonlysessionModelSwitch: ProviderSessionModelSwitchMode;}

      ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703 — all sessionModelSwitch: "in-session".

      There is no spawn/subagent primitive anywhere in ProviderAdapterShape, and there is no parent/child thread concept in T3's own domain: grep -rn parentThreadId apps/server/src packages/contracts/src returns nothing (the identifier exists only in the vendored Codex app-server generated schema). OrchestrationThread (packages/contracts/src/orchestration.ts:352) has no parent field.

      Building fan-out inside adapters would mean five implementations against five unrelated protocols (Claude Agent SDK, Codex app-server JSON-RPC, ACP ×2, OpenCode HTTP), and two of them (Cursor, Grok) have no subagent concept at all. The repo already documents what happens when capability is under-declared: the web app hardcodes a per-driver allowlist because there is no flag to read —

      // apps/web/src/outbox/composerSteering.logic.ts:13*Thecatchisthatthereisnosteeringcapabilityflaganywhereinthe*contracts,sosupport is implicitperadapterandhastobeassertedhere.// :43constSTEERABLE_PROVIDER_DRIVER_KINDS: ReadonlySet<string>=newSet([...]);

      Proposed solution

      Shape: one orchestrator thread + N child threads, driven entirely above the adapter layer

      The orchestrator is an ordinary T3 thread on any provider. Its children are ordinary T3 threads. The only new machinery is a fork-owned MCP toolkit the model calls, plus a fork-owned store that tracks runs. Nothing touches ProviderAdapterShape, and no provider-native subagent feature is used or required.

      Why this is provider-agnostic for free

      Every one of the five adapters already mounts a T3-hosted MCP server into its provider session, with a per-thread bearer credential issued by T3:

      • apps/server/src/provider/Layers/ClaudeAdapter.ts:3523
      • apps/server/src/provider/Layers/CursorAdapter.ts:534
      • apps/server/src/provider/Layers/CodexAdapter.ts:1397
      • apps/server/src/provider/Layers/GrokAdapter.ts:572
      • apps/server/src/provider/Layers/OpenCodeAdapter.ts:1217

      all calling McpProviderSession.readMcpProviderSession(input.threadId). The invocation scope carries the calling thread's id, so a tool handler natively knows who the parent is:

      // apps/server/src/mcp/McpInvocationContext.ts:12exportinterfaceMcpInvocationScope{readonlyenvironmentId: EnvironmentId;readonlythreadId: ThreadId;readonlyproviderSessionId: string;readonlyproviderInstanceId: ProviderInstanceId;readonlycapabilities: ReadonlySet<McpCapability>;readonlyissuedAt: number;}

      Adding tools here means every provider gets them on the same day, with zero adapter edits.

      New files (all fork-owned, zero upstream conflict)

      apps/server/src/t3x/orchestrate/
      tools.ts # Effect Tool.make definitions — copy apps/server/src/mcp/toolkits/preview/tools.ts
      handlers.ts # copy apps/server/src/mcp/toolkits/preview/handlers.ts
      RunStore.ts # JSON persistence in stateDir, modelled on t3x-auto-resume.json
      Runner.ts # dispatches thread.create + thread.turn.start, watches for completion
      index.ts # exports OrchestrateToolkitRegistrationLive + OrchestrateLayerLive
      apps/server/src/t3x/mcp/index.ts # single aggregator for all future fork MCP toolkits
      

      apps/server/src/mcp/toolkits/preview/ is a complete, shipping template (tools.ts, handlers.ts, plus tests for both) — it is currently the only toolkit (ls apps/server/src/mcp/toolkitspreview).

      The three tools (v1)

      ToolBehaviour
      subagent_spawnTakes { tasks: [{ label, prompt, providerInstanceId?, modelId?, runtimeMode? }] }, max 4. Creates one child thread per task, dispatches thread.turn.start with only the task prompt (no parent history), returns { runId, children: [{ childThreadId, label }] }immediately. Never blocks.
      subagent_status{ runId } → per-child { label, childThreadId, phase } where phase comes from projectThreadAwareness. Cheap to poll.
      subagent_result{ runId } or { childThreadId } → final assistant message text for each completed child, plus its phase.

      Async-return + poll is deliberate: an MCP handler that blocks for minutes holds the parent's tool call open against adapter and CLI tool timeouts. Polling burns orchestrator turns but cannot deadlock.

      Dispatch: reuse the proven fork precedent verbatim

      AutoResumeReactor is already a server-side autonomous dispatcher that synthesises turns indistinguishable from a keystroke:

      // apps/server/src/t3x/autoResume/Reactor.ts:111commandId: CommandId.make(`t3x-auto-resume:${commandUuid}`),

      and it already writes its own audit trail with thread.activity.append (Reactor.ts:75; command declared at packages/contracts/src/orchestration.ts:865).

      The orchestrator does the same: engine.dispatch({ type: "thread.create", ... }) then engine.dispatch({ type: "thread.turn.start", ... }) with commandId prefixed t3x-orchestrate:, and appends a breadcrumb to the parent thread for every spawn, phase change, and result read. That trail is provider-agnostic and shows up on Cursor/Grok/OpenCode identically — which is exactly why the design must not build on task.* (Claude-only, ClaudeAdapter.ts is the sole emitter).

      Completion detection: already solved, do not reinvent

      // packages/shared/src/agentAwareness.ts:53exportfunctionprojectThreadAwareness(...)// :77functionresolveThreadAwarenessPhase(thread): AgentAwarenessPhase|null

      This reads only shell-projection fields — never provider internals — and is already consumed server-side by the fork's Web Push seam (apps/server/src/t3x/webPush/attention.ts). It returns waiting_for_approval / completed / null, which is precisely the "is this child done, stuck on an approval, or still working" distinction the orchestrator needs.

      Do not gate on session.status alone. The existing code documents settled/teardown races, and fork issue #38 records the hazard that synthetic turns deadlock status-gated logic.

      Result collection

      OrchestrationLatestTurn (packages/contracts/src/orchestration.ts:335) carries assistantMessageId: Schema.NullOr(MessageId) at :341, and ProjectionSnapshotQuery.getThreadDetailById (apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168) returns the full thread including messages. subagent_result is a projection read, not a provider call.

      Registration seam (the only upstream edit)

      The MCP server builds its toolkit layer in apps/server/src/mcp/McpHttpServer.ts:206-225:

      constPreviewStandardToolkitRegistrationLive=McpServer.toolkit(PreviewStandardToolkit).pipe(Layer.provide(PreviewStandardToolkitHandlersLive),);exportconstPreviewToolkitRegistrationLive=Layer.mergeAll(...);exportconstlayer=PreviewToolkitRegistrationLive.pipe(Layer.provideMerge(McpTransportLive));

      Add one merged layer sourced from apps/server/src/t3x/mcp/index.ts — the same aggregator discipline apps/server/src/t3x/index.ts:71/:100 already enforces for T3xLayerLive / T3xRoutesLive. Every future fork toolkit then costs zero additional upstream churn.

      Deliberately avoid the capability gate in v1.McpCapability is a closed union with one member (apps/server/src/mcp/McpInvocationContext.ts:10 — export type McpCapability = "preview";) and it is issued from a hardcoded set (apps/server/src/mcp/McpSessionRegistry.ts:131 — capabilities: new Set(["preview"])). Extending both would add two more ledger rows for no v1 benefit. Gate the toolkit on a fork-local setting inside handlers.ts instead, and revisit if v2 needs per-thread scoping.

      What degrades, per adapter, and how

      Because the design uses no provider-native subagent feature, the capability does not degrade — the presentation and reliability do:

      ConcernClaudeCodexOpenCodeCursorGrok
      Can call the tools (MCP mounted)yesyesyesyesyes
      Spawn tool call renders as "Subagent task"yes (ClaudeAdapter.ts:596)yesyesno — generic tool rowno — generic tool row
      Native task.* telemetryyesnononono
      Structured judge verdictsnative --json-schemanative --output-schemaprompt-instructedprompt-instructedprompt-instructed
      Steering the orchestrator mid-runyesno (excluded from STEERABLE_PROVIDER_DRIVER_KINDS)yesyesyes

      Mitigations: emit the fan-out trail via thread.activity.append so it is legible everywhere (not task.*); accept prose results in v1 and defer structured verdicts; document that Codex orchestrators cannot be steered mid-run.

      Fan-out cap and why it is low

      Session startup is serialized. ensureSessionForThread (ProviderCommandReactor.ts:684) runs inside buildSendTurnRequestForThread, which runs inside a single-item-at-a-timeDrainableWorker (ProviderCommandReactor.ts:1323). Spawning N children means N sequential CLI process spawns before any of them runs. The turn itself is forked (ProviderCommandReactor.ts:1117-1119 — .pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped)), so once started they do run concurrently. Cap v1 at 4 and make the cap a fork-local constant.

      Separately, ProviderSessionReaper.ts:17 sets DEFAULT_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 — a parked child session is reaped after 30 minutes, invisibly to a polling orchestrator. subagent_status must report a reaped child as a distinct phase, not silently as "still working".

      Restart durability

      RunStore.ts persists { runId, parentThreadId, children: [{ childThreadId, label, phase }] } to stateDir, mirroring t3x-auto-resume.json. On boot, reconcile every non-terminal run against getThreadDetailById. Without this a server restart orphans every child thread with no trace.

      Why this matters

      Today "high-effort agent" in T3 Code means one Claude-only dropdown value that changes a flag on one provider's CLI, and whatever fan-out happens is invisible and unsteerable because it lives inside that CLI's process. This inverts that: each unit of parallel work becomes a first-class T3 thread, which means it inherits everything T3 already built — the activity timeline, approvals, checkpoints, per-turn diffs, interrupt, Web Push, auto-resume, mobile — with no per-feature work.

      It is genuinely model-agnostic rather than nominally so: the orchestrator can be a Grok thread spawning Codex children, because the only interface it touches is OrchestrationEngineService.dispatch. That is a capability no single provider gives you, and it is the one thing a wrapper app is uniquely positioned to build.

      It also removes a class of "why did nothing happen" confusion: a stalled child shows up as a thread with a phase and an approval card, not as a silent gap inside another process.

      Smallest useful scope

      v1 = async spawn + poll + collect, on shared cwd, no UI.

      Ships:

      • apps/server/src/t3x/orchestrate/ with subagent_spawn, subagent_status, subagent_result
      • one aggregator seam line in apps/server/src/mcp/McpHttpServer.ts via apps/server/src/t3x/mcp/index.ts
      • fan-out cap 4, fork-local constant
      • RunStore JSON persistence + boot reconciliation
      • thread.activity.append breadcrumbs on the parent for spawn / phase-change / result
      • completion via projectThreadAwareness, results via getThreadDetailById + latestTurn.assistantMessageId
      • tests mirroring apps/server/src/mcp/toolkits/preview/{tools,handlers}.test.ts

      Explicitly out of scope for v1 (each is a follow-up issue):

      • Git worktrees per child. This is the big one. Worktree bootstrap is not in the engine — it is implemented inline in the WebSocket transport (apps/server/src/ws.ts:750 dispatchBootstrapTurnStart, :891thread.create, :908gitWorkflow.createWorktree, :940runSetupProgram()). A server-side dispatcher calling engine.dispatch directly gets none of it. ws.ts measured churn is 41 commits in the 60 days before merge-base 64bf01619 — the highest of any candidate file. So v1 children share the parent's cwd, which makes it safe for read-heavy fan-out (parallel investigation, review, test authoring in disjoint directories) and unsafe for parallel edits to the same files. State that limit in the tool description so the model does not attempt it.
      • Judge panels / best-of-N adjudication. Needs structured verdicts; see alternatives.
      • UI grouping. With no parent link in OrchestrationThreadShell, 4 children land flat in the shell snapshot and each completion fires the needs-attention / Web Push coordinator.
      • Extending ProviderAdapterCapabilities.
      • Generic structured output in TextGenerationService.

      Alternatives considered

      1. Wait for upstream orchestrator-v2. PR pingdotgg#3638 already merged delegate_task / task_status / task_cancel / create_threads / t3_thread_* as orchestrator MCP tools — but into the stack branch t3code/codex-turn-mapping, not main. Its gate, pingdotgg#2829, is still open against main. Verified: merge commit dac514e6b is not an ancestor of upstream/main, origin/main, or fork HEAD. This is the strongest alternative and the honest reason to keep v1 tiny — the whole toolkit is three tools in fork-owned files and is cheap to delete when v2 lands. Naming should deliberately not collide with delegate_task/task_status so both can coexist during a transition.

      2. Cherry-pick the v2 toolkit onto today's main.git show dac514e6b:apps/server/src/mcp/toolkits/orchestrator/tools.ts is real, reviewed code. But it targets the v2 orchestrator's data model, so porting it means porting or stubbing that model — a much larger change than three fork-local tools, and it creates the "parallel paths" hazard the fork has already been bitten by (a fork path that duplicates an upstream capability silently bypasses new upstream guards).

      3. A declarative t3x reactor instead of model-callable tools. Model AutoResumeReactor directly: a server-side supervisor that runs a user-configured plan (stages, providers, prompts) without the model deciding anything. Better for deterministic verification pipelines and judge panels; worse for the "ultracode" experience the user asked for, where the model authors its own fan-out. They share ~80% of the machinery (RunStore, Runner, awareness polling) so (a) can be added on top of (b) later. Picking the MCP toolkit first is the choice that matches the request.

      4. createSdkMcpServer from the Claude Agent SDK. Exported and typed but unused anywhere in the repo. Strictly worse here: Claude-only, requires a ClaudeAdapter.ts edit (churn 12), and defeats the entire point of the issue.

      5. Extend TextGenerationService with a generic generateStructured(schema, prompt) for judge verdicts. The interface is closed at four git-flavoured operations (apps/server/src/textGeneration/TextGeneration.ts:77-82), so this means five implementations, and only Claude and Codex get a hard schema constraint — Cursor/Grok/OpenCode scrape prompt-instructed JSON with extractJsonObject. Cheaper: run the judge as its own child thread and parse its final assistant message with the same lenient extractor those three adapters already use. Loses per-call model/effort control.

      6. Do nothing and document that ultracode is Claude-only. Legitimate. The counter is that ultracode's value is mostly the orchestration pattern, not the effort flag, and that pattern is provider-independent.

      Risks or tradeoffs

      Seam cost (measured against merge-base 64bf01619, 60-day window)

      docs/t3x/SEAMS.md:5 records 34 upstream-owned files, +1616 / -187 lines, and :21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." This feature adds one row.

      FileEditLinesChurnRisk
      apps/server/src/mcp/McpHttpServer.tsone merged layer sourced from t3x/mcp/index.ts~36~18

      Measured with git log --oneline --since="@$((MBTS-60*86400))" 64bf01619 -- <path>. For contrast, the paths this design deliberately avoids: apps/server/src/ws.ts = 41, apps/server/src/server.ts = 29, packages/contracts/src/orchestration.ts = 12, apps/server/src/provider/Layers/ClaudeAdapter.ts = 12, apps/web/src/components/ChatView.tsx = 63 (already the fork's worst row at risk 11466, SEAMS.md:41). Fork-owned apps/server/src/t3x/ has churn 0.

      Rejected-but-tempting rows and their cost: McpSessionRegistry.ts (churn 7) + McpInvocationContext.ts (churn 3) for a capability gate — two rows for a v1 that has no per-thread scoping requirement. ProviderAdapter.ts has churn 0 and is cheap in isolation, but extending it forces edits to all five adapters (ClaudeAdapter.ts churn 12 among them).

      The ledger must be updated in the same commit — header totals plus the new row — per the self-reference rule at SEAMS.md:22-28, and the t3x/mcp/index.ts aggregator should be documented there as the reason no further MCP rows will follow.

      Parallel-paths hazard

      This is the fork's known failure mode: a fork path that duplicates an upstream capability silently bypasses new upstream guards. Upstream is actively building the same thing (pingdotgg#3138, pingdotgg#4649, PRs pingdotgg#4663 / pingdotgg#5219 / pingdotgg#3638). Concretely: use distinct tool names from delegate_task/task_status, keep the entire implementation deletable in one rm -rf apps/server/src/t3x/orchestrate/, and re-check the SEAMS table every sync.

      Machine and cost risks

      • Fan-out thrashes a 16GB Mac. Each child is a full provider CLI process, and startup is serialized through the DrainableWorker. Cap 4, and expect the first child to start well before the fourth.
      • No budget ceiling exists. Nothing in T3 bounds a run's token spend. A model that spawns 4 children, polls, and re-spawns can burn a lot unattended. v1 should hard-cap total spawns per parent thread.
      • The 30-minute idle reaper (ProviderSessionReaper.ts:17) silently kills parked children.

      UNVERIFIED — do not present these as settled

      1. Do Cursor and Grok's ACP clients actually expose T3's MCP tools to the model? The mcpServers wiring is verified present (CursorAdapter.ts:544, GrokAdapter.ts:582), but nobody has confirmed the tool reaches the model's tool list on those providers. Experiment: start a thread on each of the five providers and ask the model to call mcp__t3-code__preview_status; a tool result on all five proves the channel end-to-end before any orchestration code is written. Do this first — it is the load-bearing assumption of the whole design.
      2. Does a child thread created via engine.dispatch({type:"thread.create"}) without the ws.ts bootstrap path actually start a working session? All existing evidence is that AutoResumeReactor dispatches thread.turn.start to threads that already exist. Experiment: dispatch thread.create then thread.turn.start from a scratch Effect script and confirm turn.completed.
      3. Whether the reaper's 30-minute teardown produces a distinguishable phase from projectThreadAwareness, or looks identical to "completed". Experiment: let a thread idle past 30 minutes and diff the projected phase.
      4. What settings.ultracode: true actually does inside the Claude binary. This repo only sets the flag (ClaudeAdapter.ts:3521); the semantics are undocumented here. The claim "ultracode means parallel fan-out" is inferred from observed behaviour, not from this codebase. Experiment:strings the bundled Claude platform binary for ultracode and read the surrounding tool-registry code.
      5. Whether 4 concurrent children is survivable on this machine. Nobody has measured it.

      Examples or references

      Upstream issues this overlaps (this is NOT a clean-slate feature)

      File:line evidence (all verified in this checkout at main)

      • Capabilities: apps/server/src/provider/Services/ProviderAdapter.ts:26-32; identical values at ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703
      • Five drivers: apps/server/src/provider/builtInDrivers.ts:47
      • MCP mounted by every adapter: ClaudeAdapter.ts:3523, CursorAdapter.ts:534, CodexAdapter.ts:1397, GrokAdapter.ts:572, OpenCodeAdapter.ts:1217
      • MCP scope carries parent threadId: apps/server/src/mcp/McpInvocationContext.ts:10-19; issuance apps/server/src/mcp/McpSessionRegistry.ts:131
      • Toolkit registration point: apps/server/src/mcp/McpHttpServer.ts:206-225; only toolkit today: apps/server/src/mcp/toolkits/preview/ (tools.ts, handlers.ts, tools.test.ts, handlers.test.ts)
      • Fork aggregators: apps/server/src/t3x/index.ts:71 (T3xLayerLive), :100 (T3xRoutesLive); existing seams autoResume/, webPush/
      • Autonomous dispatch precedent: apps/server/src/t3x/autoResume/Reactor.ts:75 (thread.activity.append), :111 (commandId)
      • Bootstrap trapped in the transport: apps/server/src/ws.ts:750, :891, :908, :940, :961
      • Serialized startup / forked turn: apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:684, :1117-1119, :1323
      • Idle reaper: apps/server/src/provider/Layers/ProviderSessionReaper.ts:17
      • Completion phase: packages/shared/src/agentAwareness.ts:53, :77
      • Result addressing: packages/contracts/src/orchestration.ts:335, :341; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168
      • Bootstrap schema (unused by engine): packages/contracts/src/orchestration.ts:677, :701, :720
      • thread.activity.append command: packages/contracts/src/orchestration.ts:865
      • Ultracode today: ClaudeProvider.ts:75, :406, :425; ClaudeAdapter.ts:3510, :3521
      • Closed structured-output interface: apps/server/src/textGeneration/TextGeneration.ts:77-82
      • Steering allowlist precedent: apps/web/src/outbox/composerSteering.logic.ts:13, :43, :51
      • Seam ledger: docs/t3x/SEAMS.md:5, :21, :22-28, :41

      Duplicate search performed before filing

      Searched all 1,615 upstream pingdotgg/t3code issues (open + closed — count confirmed against gh api search/issuestotal_count) by dumping the full title corpus locally and grepping ~80 term variants, plus body-level gh search issues per concept (orchestration, delegation, subagent, parallel agents, swarm, judge, voting, best of n, consensus, agent team, teammate, ultracode, fan-out, pipeline, workflow), plus a gh search prs sweep. Also searched all 21 radroid/t3code fork issues. Upstream Discussions are disabled (hasDiscussionsEnabled: false), so issues and PRs are the complete surface.

      Result: this is not a duplicate, but it is a strong overlap and the body says so explicitly.

      Found and cross-referenced above: pingdotgg#3138 (Orchestration/Delegation — closest, owns the goal statement), pingdotgg#4649 (configurable pipelines — owns the staged-workflow half), pingdotgg#5218 / pingdotgg#4456 / pingdotgg#1740 / pingdotgg#538 / pingdotgg#2921 (subagent UI, nested threads, CLI orchestration — all the presentation half this issue defers), and in-flight PRs pingdotgg#4663, pingdotgg#5219.

      Most decisive finding: PR pingdotgg#3638 is MERGED — but into the stack branch t3code/codex-turn-mapping, gated behind still-open pingdotgg#2829. Verified git merge-base --is-ancestor dac514e6b upstream/main → NOT an ancestor of upstream/main, origin/main, or fork HEAD, and no scheduled_tasks/orchestrator toolkit files are tracked in this checkout (ls apps/server/src/mcp/toolkitspreview only). So the capability exists upstream on a branch but is not reachable from main today, which is the justification for filing this on the fork rather than upstream.

      Zero hits in either repo for judge panels, best-of-N, adjudication, voting, consensus, or swarm — that slice is genuinely unclaimed, and is called out as a deferred follow-up rather than folded into v1.

      No fork issue duplicates this. The only adjacent fork issue is #38 (Loop Watch), which is about supervising a single long-running thread and explicitly rules cron out of scope; it is referenced for its session.status hazard note, not as an overlap.

      Contribution

      • I would be open to helping implement this.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        enhancementNew feature or request

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          [Feature]: Provider-agnostic orchestration ("ultracode for every model") — a fork-owned MCP toolkit that lets any adapter fan out into real T3 threads #43

          Description

          @radroid

          Before submitting

          • I searched existing issues and did not find a duplicate.
          • I am describing a concrete problem or use case, not just a vague idea.

          Area

          apps/server

          Problem or use case

          What "ultracode" is, for a reader who has never used it

          "Ultracode" is shorthand for a cluster of behaviours people expect from a high-effort coding agent, none of which are a single feature:

          • Parallel fan-out. The agent decomposes a task and runs several workers at once (explore three call sites, write tests while another writes the implementation) instead of doing everything serially in one context.
          • Structured output. Workers return machine-readable results (a verdict, a file list, a JSON summary) rather than prose the parent has to re-read and re-parse.
          • Adversarial verification. A second agent is asked to break the first one's work — find the bug, find the missing test — rather than to agree with it.
          • Judge panels / best-of-N. The same task is attempted N ways and a separate adjudicator picks a winner or merges the outputs.
          • Loop-until-dry. The agent keeps iterating (fix → run tests → fix) until a stopping condition is met, rather than stopping after one pass.

          What T3 Code actually has today

          In this repo, ultracode is only a Claude effort level, not any of the above. It is a dropdown value that normalises to --effort xhigh plus a settings.ultracode: true flag handed to the Claude Agent SDK:

          • apps/server/src/provider/Layers/ClaudeProvider.ts:75 (and :106, :142) — { value: "ultracode", label: "Ultracode" }, listed only for Claude models
          • apps/server/src/provider/Layers/ClaudeProvider.ts:406if (effort === "ultracode") { return "xhigh"; }
          • apps/server/src/provider/Layers/ClaudeAdapter.ts:3510 / :3521const ultracode = isClaudeUltracodeEffort(effort)...(ultracode ? { ultracode: true } : {})

          So every one of the five ultracode behaviours, when it happens at all, happens inside the Claude CLI process, where T3 cannot inspect, interrupt, checkpoint, or steer it. That is the direct cause of "ultracode does not always work well in T3 Code": T3 renders provider-internal fan-out as flat, opaque rows.

          • Subagent activity is a display heuristic, not a model: tool names containing agent/task/subagent get bucketed into the canonical item type collab_agent_tool_call, titled "Subagent task" (ClaudeAdapter.ts:596, :865). The same heuristic exists in OpenCodeAdapter.ts and CodexAdapter.ts and is absent from CursorAdapter.ts and GrokAdapter.ts.
          • task.started / task.progress / task.completed exist in the canonical union (packages/contracts/src/providerRuntime.ts:177) and are ingested (apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts), but only ClaudeAdapter.ts ever emits them — verified: grep -rl 'task\.started' apps/server/src packages/contracts/src returns only ClaudeAdapter.ts, ProviderRuntimeIngestion.ts, and the contracts file. They are read-only telemetry about work T3 does not own.

          Why this cannot be fixed inside the adapters

          ProviderAdapterCapabilities has exactly one field, and all five adapters set it to the same value — the declarative capability surface differentiates nothing today:

          // apps/server/src/provider/Services/ProviderAdapter.ts:28exportinterfaceProviderAdapterCapabilities{readonlysessionModelSwitch: ProviderSessionModelSwitchMode;}

          ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703 — all sessionModelSwitch: "in-session".

          There is no spawn/subagent primitive anywhere in ProviderAdapterShape, and there is no parent/child thread concept in T3's own domain: grep -rn parentThreadId apps/server/src packages/contracts/src returns nothing (the identifier exists only in the vendored Codex app-server generated schema). OrchestrationThread (packages/contracts/src/orchestration.ts:352) has no parent field.

          Building fan-out inside adapters would mean five implementations against five unrelated protocols (Claude Agent SDK, Codex app-server JSON-RPC, ACP ×2, OpenCode HTTP), and two of them (Cursor, Grok) have no subagent concept at all. The repo already documents what happens when capability is under-declared: the web app hardcodes a per-driver allowlist because there is no flag to read —

          // apps/web/src/outbox/composerSteering.logic.ts:13*Thecatchisthatthereisnosteeringcapabilityflaganywhereinthe*contracts,sosupport is implicitperadapterandhastobeassertedhere.// :43constSTEERABLE_PROVIDER_DRIVER_KINDS: ReadonlySet<string>=newSet([...]);

          Proposed solution

          Shape: one orchestrator thread + N child threads, driven entirely above the adapter layer

          The orchestrator is an ordinary T3 thread on any provider. Its children are ordinary T3 threads. The only new machinery is a fork-owned MCP toolkit the model calls, plus a fork-owned store that tracks runs. Nothing touches ProviderAdapterShape, and no provider-native subagent feature is used or required.

          Why this is provider-agnostic for free

          Every one of the five adapters already mounts a T3-hosted MCP server into its provider session, with a per-thread bearer credential issued by T3:

          • apps/server/src/provider/Layers/ClaudeAdapter.ts:3523
          • apps/server/src/provider/Layers/CursorAdapter.ts:534
          • apps/server/src/provider/Layers/CodexAdapter.ts:1397
          • apps/server/src/provider/Layers/GrokAdapter.ts:572
          • apps/server/src/provider/Layers/OpenCodeAdapter.ts:1217

          all calling McpProviderSession.readMcpProviderSession(input.threadId). The invocation scope carries the calling thread's id, so a tool handler natively knows who the parent is:

          // apps/server/src/mcp/McpInvocationContext.ts:12exportinterfaceMcpInvocationScope{readonlyenvironmentId: EnvironmentId;readonlythreadId: ThreadId;readonlyproviderSessionId: string;readonlyproviderInstanceId: ProviderInstanceId;readonlycapabilities: ReadonlySet<McpCapability>;readonlyissuedAt: number;}

          Adding tools here means every provider gets them on the same day, with zero adapter edits.

          New files (all fork-owned, zero upstream conflict)

          apps/server/src/t3x/orchestrate/
          tools.ts # Effect Tool.make definitions — copy apps/server/src/mcp/toolkits/preview/tools.ts
          handlers.ts # copy apps/server/src/mcp/toolkits/preview/handlers.ts
          RunStore.ts # JSON persistence in stateDir, modelled on t3x-auto-resume.json
          Runner.ts # dispatches thread.create + thread.turn.start, watches for completion
          index.ts # exports OrchestrateToolkitRegistrationLive + OrchestrateLayerLive
          apps/server/src/t3x/mcp/index.ts # single aggregator for all future fork MCP toolkits
          

          apps/server/src/mcp/toolkits/preview/ is a complete, shipping template (tools.ts, handlers.ts, plus tests for both) — it is currently the only toolkit (ls apps/server/src/mcp/toolkitspreview).

          The three tools (v1)

          ToolBehaviour
          subagent_spawnTakes { tasks: [{ label, prompt, providerInstanceId?, modelId?, runtimeMode? }] }, max 4. Creates one child thread per task, dispatches thread.turn.start with only the task prompt (no parent history), returns { runId, children: [{ childThreadId, label }] }immediately. Never blocks.
          subagent_status{ runId } → per-child { label, childThreadId, phase } where phase comes from projectThreadAwareness. Cheap to poll.
          subagent_result{ runId } or { childThreadId } → final assistant message text for each completed child, plus its phase.

          Async-return + poll is deliberate: an MCP handler that blocks for minutes holds the parent's tool call open against adapter and CLI tool timeouts. Polling burns orchestrator turns but cannot deadlock.

          Dispatch: reuse the proven fork precedent verbatim

          AutoResumeReactor is already a server-side autonomous dispatcher that synthesises turns indistinguishable from a keystroke:

          // apps/server/src/t3x/autoResume/Reactor.ts:111commandId: CommandId.make(`t3x-auto-resume:${commandUuid}`),

          and it already writes its own audit trail with thread.activity.append (Reactor.ts:75; command declared at packages/contracts/src/orchestration.ts:865).

          The orchestrator does the same: engine.dispatch({ type: "thread.create", ... }) then engine.dispatch({ type: "thread.turn.start", ... }) with commandId prefixed t3x-orchestrate:, and appends a breadcrumb to the parent thread for every spawn, phase change, and result read. That trail is provider-agnostic and shows up on Cursor/Grok/OpenCode identically — which is exactly why the design must not build on task.* (Claude-only, ClaudeAdapter.ts is the sole emitter).

          Completion detection: already solved, do not reinvent

          // packages/shared/src/agentAwareness.ts:53exportfunctionprojectThreadAwareness(...)// :77functionresolveThreadAwarenessPhase(thread): AgentAwarenessPhase|null

          This reads only shell-projection fields — never provider internals — and is already consumed server-side by the fork's Web Push seam (apps/server/src/t3x/webPush/attention.ts). It returns waiting_for_approval / completed / null, which is precisely the "is this child done, stuck on an approval, or still working" distinction the orchestrator needs.

          Do not gate on session.status alone. The existing code documents settled/teardown races, and fork issue #38 records the hazard that synthetic turns deadlock status-gated logic.

          Result collection

          OrchestrationLatestTurn (packages/contracts/src/orchestration.ts:335) carries assistantMessageId: Schema.NullOr(MessageId) at :341, and ProjectionSnapshotQuery.getThreadDetailById (apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168) returns the full thread including messages. subagent_result is a projection read, not a provider call.

          Registration seam (the only upstream edit)

          The MCP server builds its toolkit layer in apps/server/src/mcp/McpHttpServer.ts:206-225:

          constPreviewStandardToolkitRegistrationLive=McpServer.toolkit(PreviewStandardToolkit).pipe(Layer.provide(PreviewStandardToolkitHandlersLive),);exportconstPreviewToolkitRegistrationLive=Layer.mergeAll(...);exportconstlayer=PreviewToolkitRegistrationLive.pipe(Layer.provideMerge(McpTransportLive));

          Add one merged layer sourced from apps/server/src/t3x/mcp/index.ts — the same aggregator discipline apps/server/src/t3x/index.ts:71/:100 already enforces for T3xLayerLive / T3xRoutesLive. Every future fork toolkit then costs zero additional upstream churn.

          Deliberately avoid the capability gate in v1.McpCapability is a closed union with one member (apps/server/src/mcp/McpInvocationContext.ts:10 — export type McpCapability = "preview";) and it is issued from a hardcoded set (apps/server/src/mcp/McpSessionRegistry.ts:131 — capabilities: new Set(["preview"])). Extending both would add two more ledger rows for no v1 benefit. Gate the toolkit on a fork-local setting inside handlers.ts instead, and revisit if v2 needs per-thread scoping.

          What degrades, per adapter, and how

          Because the design uses no provider-native subagent feature, the capability does not degrade — the presentation and reliability do:

          ConcernClaudeCodexOpenCodeCursorGrok
          Can call the tools (MCP mounted)yesyesyesyesyes
          Spawn tool call renders as "Subagent task"yes (ClaudeAdapter.ts:596)yesyesno — generic tool rowno — generic tool row
          Native task.* telemetryyesnononono
          Structured judge verdictsnative --json-schemanative --output-schemaprompt-instructedprompt-instructedprompt-instructed
          Steering the orchestrator mid-runyesno (excluded from STEERABLE_PROVIDER_DRIVER_KINDS)yesyesyes

          Mitigations: emit the fan-out trail via thread.activity.append so it is legible everywhere (not task.*); accept prose results in v1 and defer structured verdicts; document that Codex orchestrators cannot be steered mid-run.

          Fan-out cap and why it is low

          Session startup is serialized. ensureSessionForThread (ProviderCommandReactor.ts:684) runs inside buildSendTurnRequestForThread, which runs inside a single-item-at-a-timeDrainableWorker (ProviderCommandReactor.ts:1323). Spawning N children means N sequential CLI process spawns before any of them runs. The turn itself is forked (ProviderCommandReactor.ts:1117-1119 — .pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped)), so once started they do run concurrently. Cap v1 at 4 and make the cap a fork-local constant.

          Separately, ProviderSessionReaper.ts:17 sets DEFAULT_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 — a parked child session is reaped after 30 minutes, invisibly to a polling orchestrator. subagent_status must report a reaped child as a distinct phase, not silently as "still working".

          Restart durability

          RunStore.ts persists { runId, parentThreadId, children: [{ childThreadId, label, phase }] } to stateDir, mirroring t3x-auto-resume.json. On boot, reconcile every non-terminal run against getThreadDetailById. Without this a server restart orphans every child thread with no trace.

          Why this matters

          Today "high-effort agent" in T3 Code means one Claude-only dropdown value that changes a flag on one provider's CLI, and whatever fan-out happens is invisible and unsteerable because it lives inside that CLI's process. This inverts that: each unit of parallel work becomes a first-class T3 thread, which means it inherits everything T3 already built — the activity timeline, approvals, checkpoints, per-turn diffs, interrupt, Web Push, auto-resume, mobile — with no per-feature work.

          It is genuinely model-agnostic rather than nominally so: the orchestrator can be a Grok thread spawning Codex children, because the only interface it touches is OrchestrationEngineService.dispatch. That is a capability no single provider gives you, and it is the one thing a wrapper app is uniquely positioned to build.

          It also removes a class of "why did nothing happen" confusion: a stalled child shows up as a thread with a phase and an approval card, not as a silent gap inside another process.

          Smallest useful scope

          v1 = async spawn + poll + collect, on shared cwd, no UI.

          Ships:

          • apps/server/src/t3x/orchestrate/ with subagent_spawn, subagent_status, subagent_result
          • one aggregator seam line in apps/server/src/mcp/McpHttpServer.ts via apps/server/src/t3x/mcp/index.ts
          • fan-out cap 4, fork-local constant
          • RunStore JSON persistence + boot reconciliation
          • thread.activity.append breadcrumbs on the parent for spawn / phase-change / result
          • completion via projectThreadAwareness, results via getThreadDetailById + latestTurn.assistantMessageId
          • tests mirroring apps/server/src/mcp/toolkits/preview/{tools,handlers}.test.ts

          Explicitly out of scope for v1 (each is a follow-up issue):

          • Git worktrees per child. This is the big one. Worktree bootstrap is not in the engine — it is implemented inline in the WebSocket transport (apps/server/src/ws.ts:750 dispatchBootstrapTurnStart, :891thread.create, :908gitWorkflow.createWorktree, :940runSetupProgram()). A server-side dispatcher calling engine.dispatch directly gets none of it. ws.ts measured churn is 41 commits in the 60 days before merge-base 64bf01619 — the highest of any candidate file. So v1 children share the parent's cwd, which makes it safe for read-heavy fan-out (parallel investigation, review, test authoring in disjoint directories) and unsafe for parallel edits to the same files. State that limit in the tool description so the model does not attempt it.
          • Judge panels / best-of-N adjudication. Needs structured verdicts; see alternatives.
          • UI grouping. With no parent link in OrchestrationThreadShell, 4 children land flat in the shell snapshot and each completion fires the needs-attention / Web Push coordinator.
          • Extending ProviderAdapterCapabilities.
          • Generic structured output in TextGenerationService.

          Alternatives considered

          1. Wait for upstream orchestrator-v2. PR pingdotgg#3638 already merged delegate_task / task_status / task_cancel / create_threads / t3_thread_* as orchestrator MCP tools — but into the stack branch t3code/codex-turn-mapping, not main. Its gate, pingdotgg#2829, is still open against main. Verified: merge commit dac514e6b is not an ancestor of upstream/main, origin/main, or fork HEAD. This is the strongest alternative and the honest reason to keep v1 tiny — the whole toolkit is three tools in fork-owned files and is cheap to delete when v2 lands. Naming should deliberately not collide with delegate_task/task_status so both can coexist during a transition.

          2. Cherry-pick the v2 toolkit onto today's main.git show dac514e6b:apps/server/src/mcp/toolkits/orchestrator/tools.ts is real, reviewed code. But it targets the v2 orchestrator's data model, so porting it means porting or stubbing that model — a much larger change than three fork-local tools, and it creates the "parallel paths" hazard the fork has already been bitten by (a fork path that duplicates an upstream capability silently bypasses new upstream guards).

          3. A declarative t3x reactor instead of model-callable tools. Model AutoResumeReactor directly: a server-side supervisor that runs a user-configured plan (stages, providers, prompts) without the model deciding anything. Better for deterministic verification pipelines and judge panels; worse for the "ultracode" experience the user asked for, where the model authors its own fan-out. They share ~80% of the machinery (RunStore, Runner, awareness polling) so (a) can be added on top of (b) later. Picking the MCP toolkit first is the choice that matches the request.

          4. createSdkMcpServer from the Claude Agent SDK. Exported and typed but unused anywhere in the repo. Strictly worse here: Claude-only, requires a ClaudeAdapter.ts edit (churn 12), and defeats the entire point of the issue.

          5. Extend TextGenerationService with a generic generateStructured(schema, prompt) for judge verdicts. The interface is closed at four git-flavoured operations (apps/server/src/textGeneration/TextGeneration.ts:77-82), so this means five implementations, and only Claude and Codex get a hard schema constraint — Cursor/Grok/OpenCode scrape prompt-instructed JSON with extractJsonObject. Cheaper: run the judge as its own child thread and parse its final assistant message with the same lenient extractor those three adapters already use. Loses per-call model/effort control.

          6. Do nothing and document that ultracode is Claude-only. Legitimate. The counter is that ultracode's value is mostly the orchestration pattern, not the effort flag, and that pattern is provider-independent.

          Risks or tradeoffs

          Seam cost (measured against merge-base 64bf01619, 60-day window)

          docs/t3x/SEAMS.md:5 records 34 upstream-owned files, +1616 / -187 lines, and :21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." This feature adds one row.

          FileEditLinesChurnRisk
          apps/server/src/mcp/McpHttpServer.tsone merged layer sourced from t3x/mcp/index.ts~36~18

          Measured with git log --oneline --since="@$((MBTS-60*86400))" 64bf01619 -- <path>. For contrast, the paths this design deliberately avoids: apps/server/src/ws.ts = 41, apps/server/src/server.ts = 29, packages/contracts/src/orchestration.ts = 12, apps/server/src/provider/Layers/ClaudeAdapter.ts = 12, apps/web/src/components/ChatView.tsx = 63 (already the fork's worst row at risk 11466, SEAMS.md:41). Fork-owned apps/server/src/t3x/ has churn 0.

          Rejected-but-tempting rows and their cost: McpSessionRegistry.ts (churn 7) + McpInvocationContext.ts (churn 3) for a capability gate — two rows for a v1 that has no per-thread scoping requirement. ProviderAdapter.ts has churn 0 and is cheap in isolation, but extending it forces edits to all five adapters (ClaudeAdapter.ts churn 12 among them).

          The ledger must be updated in the same commit — header totals plus the new row — per the self-reference rule at SEAMS.md:22-28, and the t3x/mcp/index.ts aggregator should be documented there as the reason no further MCP rows will follow.

          Parallel-paths hazard

          This is the fork's known failure mode: a fork path that duplicates an upstream capability silently bypasses new upstream guards. Upstream is actively building the same thing (pingdotgg#3138, pingdotgg#4649, PRs pingdotgg#4663 / pingdotgg#5219 / pingdotgg#3638). Concretely: use distinct tool names from delegate_task/task_status, keep the entire implementation deletable in one rm -rf apps/server/src/t3x/orchestrate/, and re-check the SEAMS table every sync.

          Machine and cost risks

          • Fan-out thrashes a 16GB Mac. Each child is a full provider CLI process, and startup is serialized through the DrainableWorker. Cap 4, and expect the first child to start well before the fourth.
          • No budget ceiling exists. Nothing in T3 bounds a run's token spend. A model that spawns 4 children, polls, and re-spawns can burn a lot unattended. v1 should hard-cap total spawns per parent thread.
          • The 30-minute idle reaper (ProviderSessionReaper.ts:17) silently kills parked children.

          UNVERIFIED — do not present these as settled

          1. Do Cursor and Grok's ACP clients actually expose T3's MCP tools to the model? The mcpServers wiring is verified present (CursorAdapter.ts:544, GrokAdapter.ts:582), but nobody has confirmed the tool reaches the model's tool list on those providers. Experiment: start a thread on each of the five providers and ask the model to call mcp__t3-code__preview_status; a tool result on all five proves the channel end-to-end before any orchestration code is written. Do this first — it is the load-bearing assumption of the whole design.
          2. Does a child thread created via engine.dispatch({type:"thread.create"}) without the ws.ts bootstrap path actually start a working session? All existing evidence is that AutoResumeReactor dispatches thread.turn.start to threads that already exist. Experiment: dispatch thread.create then thread.turn.start from a scratch Effect script and confirm turn.completed.
          3. Whether the reaper's 30-minute teardown produces a distinguishable phase from projectThreadAwareness, or looks identical to "completed". Experiment: let a thread idle past 30 minutes and diff the projected phase.
          4. What settings.ultracode: true actually does inside the Claude binary. This repo only sets the flag (ClaudeAdapter.ts:3521); the semantics are undocumented here. The claim "ultracode means parallel fan-out" is inferred from observed behaviour, not from this codebase. Experiment:strings the bundled Claude platform binary for ultracode and read the surrounding tool-registry code.
          5. Whether 4 concurrent children is survivable on this machine. Nobody has measured it.

          Examples or references

          Upstream issues this overlaps (this is NOT a clean-slate feature)

          File:line evidence (all verified in this checkout at main)

          • Capabilities: apps/server/src/provider/Services/ProviderAdapter.ts:26-32; identical values at ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703
          • Five drivers: apps/server/src/provider/builtInDrivers.ts:47
          • MCP mounted by every adapter: ClaudeAdapter.ts:3523, CursorAdapter.ts:534, CodexAdapter.ts:1397, GrokAdapter.ts:572, OpenCodeAdapter.ts:1217
          • MCP scope carries parent threadId: apps/server/src/mcp/McpInvocationContext.ts:10-19; issuance apps/server/src/mcp/McpSessionRegistry.ts:131
          • Toolkit registration point: apps/server/src/mcp/McpHttpServer.ts:206-225; only toolkit today: apps/server/src/mcp/toolkits/preview/ (tools.ts, handlers.ts, tools.test.ts, handlers.test.ts)
          • Fork aggregators: apps/server/src/t3x/index.ts:71 (T3xLayerLive), :100 (T3xRoutesLive); existing seams autoResume/, webPush/
          • Autonomous dispatch precedent: apps/server/src/t3x/autoResume/Reactor.ts:75 (thread.activity.append), :111 (commandId)
          • Bootstrap trapped in the transport: apps/server/src/ws.ts:750, :891, :908, :940, :961
          • Serialized startup / forked turn: apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:684, :1117-1119, :1323
          • Idle reaper: apps/server/src/provider/Layers/ProviderSessionReaper.ts:17
          • Completion phase: packages/shared/src/agentAwareness.ts:53, :77
          • Result addressing: packages/contracts/src/orchestration.ts:335, :341; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168
          • Bootstrap schema (unused by engine): packages/contracts/src/orchestration.ts:677, :701, :720
          • thread.activity.append command: packages/contracts/src/orchestration.ts:865
          • Ultracode today: ClaudeProvider.ts:75, :406, :425; ClaudeAdapter.ts:3510, :3521
          • Closed structured-output interface: apps/server/src/textGeneration/TextGeneration.ts:77-82
          • Steering allowlist precedent: apps/web/src/outbox/composerSteering.logic.ts:13, :43, :51
          • Seam ledger: docs/t3x/SEAMS.md:5, :21, :22-28, :41

          Duplicate search performed before filing

          Searched all 1,615 upstream pingdotgg/t3code issues (open + closed — count confirmed against gh api search/issuestotal_count) by dumping the full title corpus locally and grepping ~80 term variants, plus body-level gh search issues per concept (orchestration, delegation, subagent, parallel agents, swarm, judge, voting, best of n, consensus, agent team, teammate, ultracode, fan-out, pipeline, workflow), plus a gh search prs sweep. Also searched all 21 radroid/t3code fork issues. Upstream Discussions are disabled (hasDiscussionsEnabled: false), so issues and PRs are the complete surface.

          Result: this is not a duplicate, but it is a strong overlap and the body says so explicitly.

          Found and cross-referenced above: pingdotgg#3138 (Orchestration/Delegation — closest, owns the goal statement), pingdotgg#4649 (configurable pipelines — owns the staged-workflow half), pingdotgg#5218 / pingdotgg#4456 / pingdotgg#1740 / pingdotgg#538 / pingdotgg#2921 (subagent UI, nested threads, CLI orchestration — all the presentation half this issue defers), and in-flight PRs pingdotgg#4663, pingdotgg#5219.

          Most decisive finding: PR pingdotgg#3638 is MERGED — but into the stack branch t3code/codex-turn-mapping, gated behind still-open pingdotgg#2829. Verified git merge-base --is-ancestor dac514e6b upstream/main → NOT an ancestor of upstream/main, origin/main, or fork HEAD, and no scheduled_tasks/orchestrator toolkit files are tracked in this checkout (ls apps/server/src/mcp/toolkitspreview only). So the capability exists upstream on a branch but is not reachable from main today, which is the justification for filing this on the fork rather than upstream.

          Zero hits in either repo for judge panels, best-of-N, adjudication, voting, consensus, or swarm — that slice is genuinely unclaimed, and is called out as a deferred follow-up rather than folded into v1.

          No fork issue duplicates this. The only adjacent fork issue is #38 (Loop Watch), which is about supervising a single long-running thread and explicitly rules cron out of scope; it is referenced for its session.status hazard note, not as an overlap.

          Contribution

          • I would be open to helping implement this.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            enhancementNew feature or request

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              [Feature]: Provider-agnostic orchestration ("ultracode for every model") — a fork-owned MCP toolkit that lets any adapter fan out into real T3 threads #43

              Description

              @radroid

              Before submitting

              • I searched existing issues and did not find a duplicate.
              • I am describing a concrete problem or use case, not just a vague idea.

              Area

              apps/server

              Problem or use case

              What "ultracode" is, for a reader who has never used it

              "Ultracode" is shorthand for a cluster of behaviours people expect from a high-effort coding agent, none of which are a single feature:

              • Parallel fan-out. The agent decomposes a task and runs several workers at once (explore three call sites, write tests while another writes the implementation) instead of doing everything serially in one context.
              • Structured output. Workers return machine-readable results (a verdict, a file list, a JSON summary) rather than prose the parent has to re-read and re-parse.
              • Adversarial verification. A second agent is asked to break the first one's work — find the bug, find the missing test — rather than to agree with it.
              • Judge panels / best-of-N. The same task is attempted N ways and a separate adjudicator picks a winner or merges the outputs.
              • Loop-until-dry. The agent keeps iterating (fix → run tests → fix) until a stopping condition is met, rather than stopping after one pass.

              What T3 Code actually has today

              In this repo, ultracode is only a Claude effort level, not any of the above. It is a dropdown value that normalises to --effort xhigh plus a settings.ultracode: true flag handed to the Claude Agent SDK:

              • apps/server/src/provider/Layers/ClaudeProvider.ts:75 (and :106, :142) — { value: "ultracode", label: "Ultracode" }, listed only for Claude models
              • apps/server/src/provider/Layers/ClaudeProvider.ts:406if (effort === "ultracode") { return "xhigh"; }
              • apps/server/src/provider/Layers/ClaudeAdapter.ts:3510 / :3521const ultracode = isClaudeUltracodeEffort(effort)...(ultracode ? { ultracode: true } : {})

              So every one of the five ultracode behaviours, when it happens at all, happens inside the Claude CLI process, where T3 cannot inspect, interrupt, checkpoint, or steer it. That is the direct cause of "ultracode does not always work well in T3 Code": T3 renders provider-internal fan-out as flat, opaque rows.

              • Subagent activity is a display heuristic, not a model: tool names containing agent/task/subagent get bucketed into the canonical item type collab_agent_tool_call, titled "Subagent task" (ClaudeAdapter.ts:596, :865). The same heuristic exists in OpenCodeAdapter.ts and CodexAdapter.ts and is absent from CursorAdapter.ts and GrokAdapter.ts.
              • task.started / task.progress / task.completed exist in the canonical union (packages/contracts/src/providerRuntime.ts:177) and are ingested (apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts), but only ClaudeAdapter.ts ever emits them — verified: grep -rl 'task\.started' apps/server/src packages/contracts/src returns only ClaudeAdapter.ts, ProviderRuntimeIngestion.ts, and the contracts file. They are read-only telemetry about work T3 does not own.

              Why this cannot be fixed inside the adapters

              ProviderAdapterCapabilities has exactly one field, and all five adapters set it to the same value — the declarative capability surface differentiates nothing today:

              // apps/server/src/provider/Services/ProviderAdapter.ts:28exportinterfaceProviderAdapterCapabilities{readonlysessionModelSwitch: ProviderSessionModelSwitchMode;}

              ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703 — all sessionModelSwitch: "in-session".

              There is no spawn/subagent primitive anywhere in ProviderAdapterShape, and there is no parent/child thread concept in T3's own domain: grep -rn parentThreadId apps/server/src packages/contracts/src returns nothing (the identifier exists only in the vendored Codex app-server generated schema). OrchestrationThread (packages/contracts/src/orchestration.ts:352) has no parent field.

              Building fan-out inside adapters would mean five implementations against five unrelated protocols (Claude Agent SDK, Codex app-server JSON-RPC, ACP ×2, OpenCode HTTP), and two of them (Cursor, Grok) have no subagent concept at all. The repo already documents what happens when capability is under-declared: the web app hardcodes a per-driver allowlist because there is no flag to read —

              // apps/web/src/outbox/composerSteering.logic.ts:13*Thecatchisthatthereisnosteeringcapabilityflaganywhereinthe*contracts,sosupport is implicitperadapterandhastobeassertedhere.// :43constSTEERABLE_PROVIDER_DRIVER_KINDS: ReadonlySet<string>=newSet([...]);

              Proposed solution

              Shape: one orchestrator thread + N child threads, driven entirely above the adapter layer

              The orchestrator is an ordinary T3 thread on any provider. Its children are ordinary T3 threads. The only new machinery is a fork-owned MCP toolkit the model calls, plus a fork-owned store that tracks runs. Nothing touches ProviderAdapterShape, and no provider-native subagent feature is used or required.

              Why this is provider-agnostic for free

              Every one of the five adapters already mounts a T3-hosted MCP server into its provider session, with a per-thread bearer credential issued by T3:

              • apps/server/src/provider/Layers/ClaudeAdapter.ts:3523
              • apps/server/src/provider/Layers/CursorAdapter.ts:534
              • apps/server/src/provider/Layers/CodexAdapter.ts:1397
              • apps/server/src/provider/Layers/GrokAdapter.ts:572
              • apps/server/src/provider/Layers/OpenCodeAdapter.ts:1217

              all calling McpProviderSession.readMcpProviderSession(input.threadId). The invocation scope carries the calling thread's id, so a tool handler natively knows who the parent is:

              // apps/server/src/mcp/McpInvocationContext.ts:12exportinterfaceMcpInvocationScope{readonlyenvironmentId: EnvironmentId;readonlythreadId: ThreadId;readonlyproviderSessionId: string;readonlyproviderInstanceId: ProviderInstanceId;readonlycapabilities: ReadonlySet<McpCapability>;readonlyissuedAt: number;}

              Adding tools here means every provider gets them on the same day, with zero adapter edits.

              New files (all fork-owned, zero upstream conflict)

              apps/server/src/t3x/orchestrate/
              tools.ts # Effect Tool.make definitions — copy apps/server/src/mcp/toolkits/preview/tools.ts
              handlers.ts # copy apps/server/src/mcp/toolkits/preview/handlers.ts
              RunStore.ts # JSON persistence in stateDir, modelled on t3x-auto-resume.json
              Runner.ts # dispatches thread.create + thread.turn.start, watches for completion
              index.ts # exports OrchestrateToolkitRegistrationLive + OrchestrateLayerLive
              apps/server/src/t3x/mcp/index.ts # single aggregator for all future fork MCP toolkits
              

              apps/server/src/mcp/toolkits/preview/ is a complete, shipping template (tools.ts, handlers.ts, plus tests for both) — it is currently the only toolkit (ls apps/server/src/mcp/toolkitspreview).

              The three tools (v1)

              ToolBehaviour
              subagent_spawnTakes { tasks: [{ label, prompt, providerInstanceId?, modelId?, runtimeMode? }] }, max 4. Creates one child thread per task, dispatches thread.turn.start with only the task prompt (no parent history), returns { runId, children: [{ childThreadId, label }] }immediately. Never blocks.
              subagent_status{ runId } → per-child { label, childThreadId, phase } where phase comes from projectThreadAwareness. Cheap to poll.
              subagent_result{ runId } or { childThreadId } → final assistant message text for each completed child, plus its phase.

              Async-return + poll is deliberate: an MCP handler that blocks for minutes holds the parent's tool call open against adapter and CLI tool timeouts. Polling burns orchestrator turns but cannot deadlock.

              Dispatch: reuse the proven fork precedent verbatim

              AutoResumeReactor is already a server-side autonomous dispatcher that synthesises turns indistinguishable from a keystroke:

              // apps/server/src/t3x/autoResume/Reactor.ts:111commandId: CommandId.make(`t3x-auto-resume:${commandUuid}`),

              and it already writes its own audit trail with thread.activity.append (Reactor.ts:75; command declared at packages/contracts/src/orchestration.ts:865).

              The orchestrator does the same: engine.dispatch({ type: "thread.create", ... }) then engine.dispatch({ type: "thread.turn.start", ... }) with commandId prefixed t3x-orchestrate:, and appends a breadcrumb to the parent thread for every spawn, phase change, and result read. That trail is provider-agnostic and shows up on Cursor/Grok/OpenCode identically — which is exactly why the design must not build on task.* (Claude-only, ClaudeAdapter.ts is the sole emitter).

              Completion detection: already solved, do not reinvent

              // packages/shared/src/agentAwareness.ts:53exportfunctionprojectThreadAwareness(...)// :77functionresolveThreadAwarenessPhase(thread): AgentAwarenessPhase|null

              This reads only shell-projection fields — never provider internals — and is already consumed server-side by the fork's Web Push seam (apps/server/src/t3x/webPush/attention.ts). It returns waiting_for_approval / completed / null, which is precisely the "is this child done, stuck on an approval, or still working" distinction the orchestrator needs.

              Do not gate on session.status alone. The existing code documents settled/teardown races, and fork issue #38 records the hazard that synthetic turns deadlock status-gated logic.

              Result collection

              OrchestrationLatestTurn (packages/contracts/src/orchestration.ts:335) carries assistantMessageId: Schema.NullOr(MessageId) at :341, and ProjectionSnapshotQuery.getThreadDetailById (apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168) returns the full thread including messages. subagent_result is a projection read, not a provider call.

              Registration seam (the only upstream edit)

              The MCP server builds its toolkit layer in apps/server/src/mcp/McpHttpServer.ts:206-225:

              constPreviewStandardToolkitRegistrationLive=McpServer.toolkit(PreviewStandardToolkit).pipe(Layer.provide(PreviewStandardToolkitHandlersLive),);exportconstPreviewToolkitRegistrationLive=Layer.mergeAll(...);exportconstlayer=PreviewToolkitRegistrationLive.pipe(Layer.provideMerge(McpTransportLive));

              Add one merged layer sourced from apps/server/src/t3x/mcp/index.ts — the same aggregator discipline apps/server/src/t3x/index.ts:71/:100 already enforces for T3xLayerLive / T3xRoutesLive. Every future fork toolkit then costs zero additional upstream churn.

              Deliberately avoid the capability gate in v1.McpCapability is a closed union with one member (apps/server/src/mcp/McpInvocationContext.ts:10 — export type McpCapability = "preview";) and it is issued from a hardcoded set (apps/server/src/mcp/McpSessionRegistry.ts:131 — capabilities: new Set(["preview"])). Extending both would add two more ledger rows for no v1 benefit. Gate the toolkit on a fork-local setting inside handlers.ts instead, and revisit if v2 needs per-thread scoping.

              What degrades, per adapter, and how

              Because the design uses no provider-native subagent feature, the capability does not degrade — the presentation and reliability do:

              ConcernClaudeCodexOpenCodeCursorGrok
              Can call the tools (MCP mounted)yesyesyesyesyes
              Spawn tool call renders as "Subagent task"yes (ClaudeAdapter.ts:596)yesyesno — generic tool rowno — generic tool row
              Native task.* telemetryyesnononono
              Structured judge verdictsnative --json-schemanative --output-schemaprompt-instructedprompt-instructedprompt-instructed
              Steering the orchestrator mid-runyesno (excluded from STEERABLE_PROVIDER_DRIVER_KINDS)yesyesyes

              Mitigations: emit the fan-out trail via thread.activity.append so it is legible everywhere (not task.*); accept prose results in v1 and defer structured verdicts; document that Codex orchestrators cannot be steered mid-run.

              Fan-out cap and why it is low

              Session startup is serialized. ensureSessionForThread (ProviderCommandReactor.ts:684) runs inside buildSendTurnRequestForThread, which runs inside a single-item-at-a-timeDrainableWorker (ProviderCommandReactor.ts:1323). Spawning N children means N sequential CLI process spawns before any of them runs. The turn itself is forked (ProviderCommandReactor.ts:1117-1119 — .pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped)), so once started they do run concurrently. Cap v1 at 4 and make the cap a fork-local constant.

              Separately, ProviderSessionReaper.ts:17 sets DEFAULT_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 — a parked child session is reaped after 30 minutes, invisibly to a polling orchestrator. subagent_status must report a reaped child as a distinct phase, not silently as "still working".

              Restart durability

              RunStore.ts persists { runId, parentThreadId, children: [{ childThreadId, label, phase }] } to stateDir, mirroring t3x-auto-resume.json. On boot, reconcile every non-terminal run against getThreadDetailById. Without this a server restart orphans every child thread with no trace.

              Why this matters

              Today "high-effort agent" in T3 Code means one Claude-only dropdown value that changes a flag on one provider's CLI, and whatever fan-out happens is invisible and unsteerable because it lives inside that CLI's process. This inverts that: each unit of parallel work becomes a first-class T3 thread, which means it inherits everything T3 already built — the activity timeline, approvals, checkpoints, per-turn diffs, interrupt, Web Push, auto-resume, mobile — with no per-feature work.

              It is genuinely model-agnostic rather than nominally so: the orchestrator can be a Grok thread spawning Codex children, because the only interface it touches is OrchestrationEngineService.dispatch. That is a capability no single provider gives you, and it is the one thing a wrapper app is uniquely positioned to build.

              It also removes a class of "why did nothing happen" confusion: a stalled child shows up as a thread with a phase and an approval card, not as a silent gap inside another process.

              Smallest useful scope

              v1 = async spawn + poll + collect, on shared cwd, no UI.

              Ships:

              • apps/server/src/t3x/orchestrate/ with subagent_spawn, subagent_status, subagent_result
              • one aggregator seam line in apps/server/src/mcp/McpHttpServer.ts via apps/server/src/t3x/mcp/index.ts
              • fan-out cap 4, fork-local constant
              • RunStore JSON persistence + boot reconciliation
              • thread.activity.append breadcrumbs on the parent for spawn / phase-change / result
              • completion via projectThreadAwareness, results via getThreadDetailById + latestTurn.assistantMessageId
              • tests mirroring apps/server/src/mcp/toolkits/preview/{tools,handlers}.test.ts

              Explicitly out of scope for v1 (each is a follow-up issue):

              • Git worktrees per child. This is the big one. Worktree bootstrap is not in the engine — it is implemented inline in the WebSocket transport (apps/server/src/ws.ts:750 dispatchBootstrapTurnStart, :891thread.create, :908gitWorkflow.createWorktree, :940runSetupProgram()). A server-side dispatcher calling engine.dispatch directly gets none of it. ws.ts measured churn is 41 commits in the 60 days before merge-base 64bf01619 — the highest of any candidate file. So v1 children share the parent's cwd, which makes it safe for read-heavy fan-out (parallel investigation, review, test authoring in disjoint directories) and unsafe for parallel edits to the same files. State that limit in the tool description so the model does not attempt it.
              • Judge panels / best-of-N adjudication. Needs structured verdicts; see alternatives.
              • UI grouping. With no parent link in OrchestrationThreadShell, 4 children land flat in the shell snapshot and each completion fires the needs-attention / Web Push coordinator.
              • Extending ProviderAdapterCapabilities.
              • Generic structured output in TextGenerationService.

              Alternatives considered

              1. Wait for upstream orchestrator-v2. PR pingdotgg#3638 already merged delegate_task / task_status / task_cancel / create_threads / t3_thread_* as orchestrator MCP tools — but into the stack branch t3code/codex-turn-mapping, not main. Its gate, pingdotgg#2829, is still open against main. Verified: merge commit dac514e6b is not an ancestor of upstream/main, origin/main, or fork HEAD. This is the strongest alternative and the honest reason to keep v1 tiny — the whole toolkit is three tools in fork-owned files and is cheap to delete when v2 lands. Naming should deliberately not collide with delegate_task/task_status so both can coexist during a transition.

              2. Cherry-pick the v2 toolkit onto today's main.git show dac514e6b:apps/server/src/mcp/toolkits/orchestrator/tools.ts is real, reviewed code. But it targets the v2 orchestrator's data model, so porting it means porting or stubbing that model — a much larger change than three fork-local tools, and it creates the "parallel paths" hazard the fork has already been bitten by (a fork path that duplicates an upstream capability silently bypasses new upstream guards).

              3. A declarative t3x reactor instead of model-callable tools. Model AutoResumeReactor directly: a server-side supervisor that runs a user-configured plan (stages, providers, prompts) without the model deciding anything. Better for deterministic verification pipelines and judge panels; worse for the "ultracode" experience the user asked for, where the model authors its own fan-out. They share ~80% of the machinery (RunStore, Runner, awareness polling) so (a) can be added on top of (b) later. Picking the MCP toolkit first is the choice that matches the request.

              4. createSdkMcpServer from the Claude Agent SDK. Exported and typed but unused anywhere in the repo. Strictly worse here: Claude-only, requires a ClaudeAdapter.ts edit (churn 12), and defeats the entire point of the issue.

              5. Extend TextGenerationService with a generic generateStructured(schema, prompt) for judge verdicts. The interface is closed at four git-flavoured operations (apps/server/src/textGeneration/TextGeneration.ts:77-82), so this means five implementations, and only Claude and Codex get a hard schema constraint — Cursor/Grok/OpenCode scrape prompt-instructed JSON with extractJsonObject. Cheaper: run the judge as its own child thread and parse its final assistant message with the same lenient extractor those three adapters already use. Loses per-call model/effort control.

              6. Do nothing and document that ultracode is Claude-only. Legitimate. The counter is that ultracode's value is mostly the orchestration pattern, not the effort flag, and that pattern is provider-independent.

              Risks or tradeoffs

              Seam cost (measured against merge-base 64bf01619, 60-day window)

              docs/t3x/SEAMS.md:5 records 34 upstream-owned files, +1616 / -187 lines, and :21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." This feature adds one row.

              FileEditLinesChurnRisk
              apps/server/src/mcp/McpHttpServer.tsone merged layer sourced from t3x/mcp/index.ts~36~18

              Measured with git log --oneline --since="@$((MBTS-60*86400))" 64bf01619 -- <path>. For contrast, the paths this design deliberately avoids: apps/server/src/ws.ts = 41, apps/server/src/server.ts = 29, packages/contracts/src/orchestration.ts = 12, apps/server/src/provider/Layers/ClaudeAdapter.ts = 12, apps/web/src/components/ChatView.tsx = 63 (already the fork's worst row at risk 11466, SEAMS.md:41). Fork-owned apps/server/src/t3x/ has churn 0.

              Rejected-but-tempting rows and their cost: McpSessionRegistry.ts (churn 7) + McpInvocationContext.ts (churn 3) for a capability gate — two rows for a v1 that has no per-thread scoping requirement. ProviderAdapter.ts has churn 0 and is cheap in isolation, but extending it forces edits to all five adapters (ClaudeAdapter.ts churn 12 among them).

              The ledger must be updated in the same commit — header totals plus the new row — per the self-reference rule at SEAMS.md:22-28, and the t3x/mcp/index.ts aggregator should be documented there as the reason no further MCP rows will follow.

              Parallel-paths hazard

              This is the fork's known failure mode: a fork path that duplicates an upstream capability silently bypasses new upstream guards. Upstream is actively building the same thing (pingdotgg#3138, pingdotgg#4649, PRs pingdotgg#4663 / pingdotgg#5219 / pingdotgg#3638). Concretely: use distinct tool names from delegate_task/task_status, keep the entire implementation deletable in one rm -rf apps/server/src/t3x/orchestrate/, and re-check the SEAMS table every sync.

              Machine and cost risks

              • Fan-out thrashes a 16GB Mac. Each child is a full provider CLI process, and startup is serialized through the DrainableWorker. Cap 4, and expect the first child to start well before the fourth.
              • No budget ceiling exists. Nothing in T3 bounds a run's token spend. A model that spawns 4 children, polls, and re-spawns can burn a lot unattended. v1 should hard-cap total spawns per parent thread.
              • The 30-minute idle reaper (ProviderSessionReaper.ts:17) silently kills parked children.

              UNVERIFIED — do not present these as settled

              1. Do Cursor and Grok's ACP clients actually expose T3's MCP tools to the model? The mcpServers wiring is verified present (CursorAdapter.ts:544, GrokAdapter.ts:582), but nobody has confirmed the tool reaches the model's tool list on those providers. Experiment: start a thread on each of the five providers and ask the model to call mcp__t3-code__preview_status; a tool result on all five proves the channel end-to-end before any orchestration code is written. Do this first — it is the load-bearing assumption of the whole design.
              2. Does a child thread created via engine.dispatch({type:"thread.create"}) without the ws.ts bootstrap path actually start a working session? All existing evidence is that AutoResumeReactor dispatches thread.turn.start to threads that already exist. Experiment: dispatch thread.create then thread.turn.start from a scratch Effect script and confirm turn.completed.
              3. Whether the reaper's 30-minute teardown produces a distinguishable phase from projectThreadAwareness, or looks identical to "completed". Experiment: let a thread idle past 30 minutes and diff the projected phase.
              4. What settings.ultracode: true actually does inside the Claude binary. This repo only sets the flag (ClaudeAdapter.ts:3521); the semantics are undocumented here. The claim "ultracode means parallel fan-out" is inferred from observed behaviour, not from this codebase. Experiment:strings the bundled Claude platform binary for ultracode and read the surrounding tool-registry code.
              5. Whether 4 concurrent children is survivable on this machine. Nobody has measured it.

              Examples or references

              Upstream issues this overlaps (this is NOT a clean-slate feature)

              File:line evidence (all verified in this checkout at main)

              • Capabilities: apps/server/src/provider/Services/ProviderAdapter.ts:26-32; identical values at ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703
              • Five drivers: apps/server/src/provider/builtInDrivers.ts:47
              • MCP mounted by every adapter: ClaudeAdapter.ts:3523, CursorAdapter.ts:534, CodexAdapter.ts:1397, GrokAdapter.ts:572, OpenCodeAdapter.ts:1217
              • MCP scope carries parent threadId: apps/server/src/mcp/McpInvocationContext.ts:10-19; issuance apps/server/src/mcp/McpSessionRegistry.ts:131
              • Toolkit registration point: apps/server/src/mcp/McpHttpServer.ts:206-225; only toolkit today: apps/server/src/mcp/toolkits/preview/ (tools.ts, handlers.ts, tools.test.ts, handlers.test.ts)
              • Fork aggregators: apps/server/src/t3x/index.ts:71 (T3xLayerLive), :100 (T3xRoutesLive); existing seams autoResume/, webPush/
              • Autonomous dispatch precedent: apps/server/src/t3x/autoResume/Reactor.ts:75 (thread.activity.append), :111 (commandId)
              • Bootstrap trapped in the transport: apps/server/src/ws.ts:750, :891, :908, :940, :961
              • Serialized startup / forked turn: apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:684, :1117-1119, :1323
              • Idle reaper: apps/server/src/provider/Layers/ProviderSessionReaper.ts:17
              • Completion phase: packages/shared/src/agentAwareness.ts:53, :77
              • Result addressing: packages/contracts/src/orchestration.ts:335, :341; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168
              • Bootstrap schema (unused by engine): packages/contracts/src/orchestration.ts:677, :701, :720
              • thread.activity.append command: packages/contracts/src/orchestration.ts:865
              • Ultracode today: ClaudeProvider.ts:75, :406, :425; ClaudeAdapter.ts:3510, :3521
              • Closed structured-output interface: apps/server/src/textGeneration/TextGeneration.ts:77-82
              • Steering allowlist precedent: apps/web/src/outbox/composerSteering.logic.ts:13, :43, :51
              • Seam ledger: docs/t3x/SEAMS.md:5, :21, :22-28, :41

              Duplicate search performed before filing

              Searched all 1,615 upstream pingdotgg/t3code issues (open + closed — count confirmed against gh api search/issuestotal_count) by dumping the full title corpus locally and grepping ~80 term variants, plus body-level gh search issues per concept (orchestration, delegation, subagent, parallel agents, swarm, judge, voting, best of n, consensus, agent team, teammate, ultracode, fan-out, pipeline, workflow), plus a gh search prs sweep. Also searched all 21 radroid/t3code fork issues. Upstream Discussions are disabled (hasDiscussionsEnabled: false), so issues and PRs are the complete surface.

              Result: this is not a duplicate, but it is a strong overlap and the body says so explicitly.

              Found and cross-referenced above: pingdotgg#3138 (Orchestration/Delegation — closest, owns the goal statement), pingdotgg#4649 (configurable pipelines — owns the staged-workflow half), pingdotgg#5218 / pingdotgg#4456 / pingdotgg#1740 / pingdotgg#538 / pingdotgg#2921 (subagent UI, nested threads, CLI orchestration — all the presentation half this issue defers), and in-flight PRs pingdotgg#4663, pingdotgg#5219.

              Most decisive finding: PR pingdotgg#3638 is MERGED — but into the stack branch t3code/codex-turn-mapping, gated behind still-open pingdotgg#2829. Verified git merge-base --is-ancestor dac514e6b upstream/main → NOT an ancestor of upstream/main, origin/main, or fork HEAD, and no scheduled_tasks/orchestrator toolkit files are tracked in this checkout (ls apps/server/src/mcp/toolkitspreview only). So the capability exists upstream on a branch but is not reachable from main today, which is the justification for filing this on the fork rather than upstream.

              Zero hits in either repo for judge panels, best-of-N, adjudication, voting, consensus, or swarm — that slice is genuinely unclaimed, and is called out as a deferred follow-up rather than folded into v1.

              No fork issue duplicates this. The only adjacent fork issue is #38 (Loop Watch), which is about supervising a single long-running thread and explicitly rules cron out of scope; it is referenced for its session.status hazard note, not as an overlap.

              Contribution

              • I would be open to helping implement this.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                enhancementNew feature or request

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  [Feature]: Provider-agnostic orchestration ("ultracode for every model") — a fork-owned MCP toolkit that lets any adapter fan out into real T3 threads #43

                  Description

                  @radroid

                  Before submitting

                  • I searched existing issues and did not find a duplicate.
                  • I am describing a concrete problem or use case, not just a vague idea.

                  Area

                  apps/server

                  Problem or use case

                  What "ultracode" is, for a reader who has never used it

                  "Ultracode" is shorthand for a cluster of behaviours people expect from a high-effort coding agent, none of which are a single feature:

                  • Parallel fan-out. The agent decomposes a task and runs several workers at once (explore three call sites, write tests while another writes the implementation) instead of doing everything serially in one context.
                  • Structured output. Workers return machine-readable results (a verdict, a file list, a JSON summary) rather than prose the parent has to re-read and re-parse.
                  • Adversarial verification. A second agent is asked to break the first one's work — find the bug, find the missing test — rather than to agree with it.
                  • Judge panels / best-of-N. The same task is attempted N ways and a separate adjudicator picks a winner or merges the outputs.
                  • Loop-until-dry. The agent keeps iterating (fix → run tests → fix) until a stopping condition is met, rather than stopping after one pass.

                  What T3 Code actually has today

                  In this repo, ultracode is only a Claude effort level, not any of the above. It is a dropdown value that normalises to --effort xhigh plus a settings.ultracode: true flag handed to the Claude Agent SDK:

                  • apps/server/src/provider/Layers/ClaudeProvider.ts:75 (and :106, :142) — { value: "ultracode", label: "Ultracode" }, listed only for Claude models
                  • apps/server/src/provider/Layers/ClaudeProvider.ts:406if (effort === "ultracode") { return "xhigh"; }
                  • apps/server/src/provider/Layers/ClaudeAdapter.ts:3510 / :3521const ultracode = isClaudeUltracodeEffort(effort)...(ultracode ? { ultracode: true } : {})

                  So every one of the five ultracode behaviours, when it happens at all, happens inside the Claude CLI process, where T3 cannot inspect, interrupt, checkpoint, or steer it. That is the direct cause of "ultracode does not always work well in T3 Code": T3 renders provider-internal fan-out as flat, opaque rows.

                  • Subagent activity is a display heuristic, not a model: tool names containing agent/task/subagent get bucketed into the canonical item type collab_agent_tool_call, titled "Subagent task" (ClaudeAdapter.ts:596, :865). The same heuristic exists in OpenCodeAdapter.ts and CodexAdapter.ts and is absent from CursorAdapter.ts and GrokAdapter.ts.
                  • task.started / task.progress / task.completed exist in the canonical union (packages/contracts/src/providerRuntime.ts:177) and are ingested (apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts), but only ClaudeAdapter.ts ever emits them — verified: grep -rl 'task\.started' apps/server/src packages/contracts/src returns only ClaudeAdapter.ts, ProviderRuntimeIngestion.ts, and the contracts file. They are read-only telemetry about work T3 does not own.

                  Why this cannot be fixed inside the adapters

                  ProviderAdapterCapabilities has exactly one field, and all five adapters set it to the same value — the declarative capability surface differentiates nothing today:

                  // apps/server/src/provider/Services/ProviderAdapter.ts:28exportinterfaceProviderAdapterCapabilities{readonlysessionModelSwitch: ProviderSessionModelSwitchMode;}

                  ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703 — all sessionModelSwitch: "in-session".

                  There is no spawn/subagent primitive anywhere in ProviderAdapterShape, and there is no parent/child thread concept in T3's own domain: grep -rn parentThreadId apps/server/src packages/contracts/src returns nothing (the identifier exists only in the vendored Codex app-server generated schema). OrchestrationThread (packages/contracts/src/orchestration.ts:352) has no parent field.

                  Building fan-out inside adapters would mean five implementations against five unrelated protocols (Claude Agent SDK, Codex app-server JSON-RPC, ACP ×2, OpenCode HTTP), and two of them (Cursor, Grok) have no subagent concept at all. The repo already documents what happens when capability is under-declared: the web app hardcodes a per-driver allowlist because there is no flag to read —

                  // apps/web/src/outbox/composerSteering.logic.ts:13*Thecatchisthatthereisnosteeringcapabilityflaganywhereinthe*contracts,sosupport is implicitperadapterandhastobeassertedhere.// :43constSTEERABLE_PROVIDER_DRIVER_KINDS: ReadonlySet<string>=newSet([...]);

                  Proposed solution

                  Shape: one orchestrator thread + N child threads, driven entirely above the adapter layer

                  The orchestrator is an ordinary T3 thread on any provider. Its children are ordinary T3 threads. The only new machinery is a fork-owned MCP toolkit the model calls, plus a fork-owned store that tracks runs. Nothing touches ProviderAdapterShape, and no provider-native subagent feature is used or required.

                  Why this is provider-agnostic for free

                  Every one of the five adapters already mounts a T3-hosted MCP server into its provider session, with a per-thread bearer credential issued by T3:

                  • apps/server/src/provider/Layers/ClaudeAdapter.ts:3523
                  • apps/server/src/provider/Layers/CursorAdapter.ts:534
                  • apps/server/src/provider/Layers/CodexAdapter.ts:1397
                  • apps/server/src/provider/Layers/GrokAdapter.ts:572
                  • apps/server/src/provider/Layers/OpenCodeAdapter.ts:1217

                  all calling McpProviderSession.readMcpProviderSession(input.threadId). The invocation scope carries the calling thread's id, so a tool handler natively knows who the parent is:

                  // apps/server/src/mcp/McpInvocationContext.ts:12exportinterfaceMcpInvocationScope{readonlyenvironmentId: EnvironmentId;readonlythreadId: ThreadId;readonlyproviderSessionId: string;readonlyproviderInstanceId: ProviderInstanceId;readonlycapabilities: ReadonlySet<McpCapability>;readonlyissuedAt: number;}

                  Adding tools here means every provider gets them on the same day, with zero adapter edits.

                  New files (all fork-owned, zero upstream conflict)

                  apps/server/src/t3x/orchestrate/
                  tools.ts # Effect Tool.make definitions — copy apps/server/src/mcp/toolkits/preview/tools.ts
                  handlers.ts # copy apps/server/src/mcp/toolkits/preview/handlers.ts
                  RunStore.ts # JSON persistence in stateDir, modelled on t3x-auto-resume.json
                  Runner.ts # dispatches thread.create + thread.turn.start, watches for completion
                  index.ts # exports OrchestrateToolkitRegistrationLive + OrchestrateLayerLive
                  apps/server/src/t3x/mcp/index.ts # single aggregator for all future fork MCP toolkits
                  

                  apps/server/src/mcp/toolkits/preview/ is a complete, shipping template (tools.ts, handlers.ts, plus tests for both) — it is currently the only toolkit (ls apps/server/src/mcp/toolkitspreview).

                  The three tools (v1)

                  ToolBehaviour
                  subagent_spawnTakes { tasks: [{ label, prompt, providerInstanceId?, modelId?, runtimeMode? }] }, max 4. Creates one child thread per task, dispatches thread.turn.start with only the task prompt (no parent history), returns { runId, children: [{ childThreadId, label }] }immediately. Never blocks.
                  subagent_status{ runId } → per-child { label, childThreadId, phase } where phase comes from projectThreadAwareness. Cheap to poll.
                  subagent_result{ runId } or { childThreadId } → final assistant message text for each completed child, plus its phase.

                  Async-return + poll is deliberate: an MCP handler that blocks for minutes holds the parent's tool call open against adapter and CLI tool timeouts. Polling burns orchestrator turns but cannot deadlock.

                  Dispatch: reuse the proven fork precedent verbatim

                  AutoResumeReactor is already a server-side autonomous dispatcher that synthesises turns indistinguishable from a keystroke:

                  // apps/server/src/t3x/autoResume/Reactor.ts:111commandId: CommandId.make(`t3x-auto-resume:${commandUuid}`),

                  and it already writes its own audit trail with thread.activity.append (Reactor.ts:75; command declared at packages/contracts/src/orchestration.ts:865).

                  The orchestrator does the same: engine.dispatch({ type: "thread.create", ... }) then engine.dispatch({ type: "thread.turn.start", ... }) with commandId prefixed t3x-orchestrate:, and appends a breadcrumb to the parent thread for every spawn, phase change, and result read. That trail is provider-agnostic and shows up on Cursor/Grok/OpenCode identically — which is exactly why the design must not build on task.* (Claude-only, ClaudeAdapter.ts is the sole emitter).

                  Completion detection: already solved, do not reinvent

                  // packages/shared/src/agentAwareness.ts:53exportfunctionprojectThreadAwareness(...)// :77functionresolveThreadAwarenessPhase(thread): AgentAwarenessPhase|null

                  This reads only shell-projection fields — never provider internals — and is already consumed server-side by the fork's Web Push seam (apps/server/src/t3x/webPush/attention.ts). It returns waiting_for_approval / completed / null, which is precisely the "is this child done, stuck on an approval, or still working" distinction the orchestrator needs.

                  Do not gate on session.status alone. The existing code documents settled/teardown races, and fork issue #38 records the hazard that synthetic turns deadlock status-gated logic.

                  Result collection

                  OrchestrationLatestTurn (packages/contracts/src/orchestration.ts:335) carries assistantMessageId: Schema.NullOr(MessageId) at :341, and ProjectionSnapshotQuery.getThreadDetailById (apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168) returns the full thread including messages. subagent_result is a projection read, not a provider call.

                  Registration seam (the only upstream edit)

                  The MCP server builds its toolkit layer in apps/server/src/mcp/McpHttpServer.ts:206-225:

                  constPreviewStandardToolkitRegistrationLive=McpServer.toolkit(PreviewStandardToolkit).pipe(Layer.provide(PreviewStandardToolkitHandlersLive),);exportconstPreviewToolkitRegistrationLive=Layer.mergeAll(...);exportconstlayer=PreviewToolkitRegistrationLive.pipe(Layer.provideMerge(McpTransportLive));

                  Add one merged layer sourced from apps/server/src/t3x/mcp/index.ts — the same aggregator discipline apps/server/src/t3x/index.ts:71/:100 already enforces for T3xLayerLive / T3xRoutesLive. Every future fork toolkit then costs zero additional upstream churn.

                  Deliberately avoid the capability gate in v1.McpCapability is a closed union with one member (apps/server/src/mcp/McpInvocationContext.ts:10 — export type McpCapability = "preview";) and it is issued from a hardcoded set (apps/server/src/mcp/McpSessionRegistry.ts:131 — capabilities: new Set(["preview"])). Extending both would add two more ledger rows for no v1 benefit. Gate the toolkit on a fork-local setting inside handlers.ts instead, and revisit if v2 needs per-thread scoping.

                  What degrades, per adapter, and how

                  Because the design uses no provider-native subagent feature, the capability does not degrade — the presentation and reliability do:

                  ConcernClaudeCodexOpenCodeCursorGrok
                  Can call the tools (MCP mounted)yesyesyesyesyes
                  Spawn tool call renders as "Subagent task"yes (ClaudeAdapter.ts:596)yesyesno — generic tool rowno — generic tool row
                  Native task.* telemetryyesnononono
                  Structured judge verdictsnative --json-schemanative --output-schemaprompt-instructedprompt-instructedprompt-instructed
                  Steering the orchestrator mid-runyesno (excluded from STEERABLE_PROVIDER_DRIVER_KINDS)yesyesyes

                  Mitigations: emit the fan-out trail via thread.activity.append so it is legible everywhere (not task.*); accept prose results in v1 and defer structured verdicts; document that Codex orchestrators cannot be steered mid-run.

                  Fan-out cap and why it is low

                  Session startup is serialized. ensureSessionForThread (ProviderCommandReactor.ts:684) runs inside buildSendTurnRequestForThread, which runs inside a single-item-at-a-timeDrainableWorker (ProviderCommandReactor.ts:1323). Spawning N children means N sequential CLI process spawns before any of them runs. The turn itself is forked (ProviderCommandReactor.ts:1117-1119 — .pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped)), so once started they do run concurrently. Cap v1 at 4 and make the cap a fork-local constant.

                  Separately, ProviderSessionReaper.ts:17 sets DEFAULT_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 — a parked child session is reaped after 30 minutes, invisibly to a polling orchestrator. subagent_status must report a reaped child as a distinct phase, not silently as "still working".

                  Restart durability

                  RunStore.ts persists { runId, parentThreadId, children: [{ childThreadId, label, phase }] } to stateDir, mirroring t3x-auto-resume.json. On boot, reconcile every non-terminal run against getThreadDetailById. Without this a server restart orphans every child thread with no trace.

                  Why this matters

                  Today "high-effort agent" in T3 Code means one Claude-only dropdown value that changes a flag on one provider's CLI, and whatever fan-out happens is invisible and unsteerable because it lives inside that CLI's process. This inverts that: each unit of parallel work becomes a first-class T3 thread, which means it inherits everything T3 already built — the activity timeline, approvals, checkpoints, per-turn diffs, interrupt, Web Push, auto-resume, mobile — with no per-feature work.

                  It is genuinely model-agnostic rather than nominally so: the orchestrator can be a Grok thread spawning Codex children, because the only interface it touches is OrchestrationEngineService.dispatch. That is a capability no single provider gives you, and it is the one thing a wrapper app is uniquely positioned to build.

                  It also removes a class of "why did nothing happen" confusion: a stalled child shows up as a thread with a phase and an approval card, not as a silent gap inside another process.

                  Smallest useful scope

                  v1 = async spawn + poll + collect, on shared cwd, no UI.

                  Ships:

                  • apps/server/src/t3x/orchestrate/ with subagent_spawn, subagent_status, subagent_result
                  • one aggregator seam line in apps/server/src/mcp/McpHttpServer.ts via apps/server/src/t3x/mcp/index.ts
                  • fan-out cap 4, fork-local constant
                  • RunStore JSON persistence + boot reconciliation
                  • thread.activity.append breadcrumbs on the parent for spawn / phase-change / result
                  • completion via projectThreadAwareness, results via getThreadDetailById + latestTurn.assistantMessageId
                  • tests mirroring apps/server/src/mcp/toolkits/preview/{tools,handlers}.test.ts

                  Explicitly out of scope for v1 (each is a follow-up issue):

                  • Git worktrees per child. This is the big one. Worktree bootstrap is not in the engine — it is implemented inline in the WebSocket transport (apps/server/src/ws.ts:750 dispatchBootstrapTurnStart, :891thread.create, :908gitWorkflow.createWorktree, :940runSetupProgram()). A server-side dispatcher calling engine.dispatch directly gets none of it. ws.ts measured churn is 41 commits in the 60 days before merge-base 64bf01619 — the highest of any candidate file. So v1 children share the parent's cwd, which makes it safe for read-heavy fan-out (parallel investigation, review, test authoring in disjoint directories) and unsafe for parallel edits to the same files. State that limit in the tool description so the model does not attempt it.
                  • Judge panels / best-of-N adjudication. Needs structured verdicts; see alternatives.
                  • UI grouping. With no parent link in OrchestrationThreadShell, 4 children land flat in the shell snapshot and each completion fires the needs-attention / Web Push coordinator.
                  • Extending ProviderAdapterCapabilities.
                  • Generic structured output in TextGenerationService.

                  Alternatives considered

                  1. Wait for upstream orchestrator-v2. PR pingdotgg#3638 already merged delegate_task / task_status / task_cancel / create_threads / t3_thread_* as orchestrator MCP tools — but into the stack branch t3code/codex-turn-mapping, not main. Its gate, pingdotgg#2829, is still open against main. Verified: merge commit dac514e6b is not an ancestor of upstream/main, origin/main, or fork HEAD. This is the strongest alternative and the honest reason to keep v1 tiny — the whole toolkit is three tools in fork-owned files and is cheap to delete when v2 lands. Naming should deliberately not collide with delegate_task/task_status so both can coexist during a transition.

                  2. Cherry-pick the v2 toolkit onto today's main.git show dac514e6b:apps/server/src/mcp/toolkits/orchestrator/tools.ts is real, reviewed code. But it targets the v2 orchestrator's data model, so porting it means porting or stubbing that model — a much larger change than three fork-local tools, and it creates the "parallel paths" hazard the fork has already been bitten by (a fork path that duplicates an upstream capability silently bypasses new upstream guards).

                  3. A declarative t3x reactor instead of model-callable tools. Model AutoResumeReactor directly: a server-side supervisor that runs a user-configured plan (stages, providers, prompts) without the model deciding anything. Better for deterministic verification pipelines and judge panels; worse for the "ultracode" experience the user asked for, where the model authors its own fan-out. They share ~80% of the machinery (RunStore, Runner, awareness polling) so (a) can be added on top of (b) later. Picking the MCP toolkit first is the choice that matches the request.

                  4. createSdkMcpServer from the Claude Agent SDK. Exported and typed but unused anywhere in the repo. Strictly worse here: Claude-only, requires a ClaudeAdapter.ts edit (churn 12), and defeats the entire point of the issue.

                  5. Extend TextGenerationService with a generic generateStructured(schema, prompt) for judge verdicts. The interface is closed at four git-flavoured operations (apps/server/src/textGeneration/TextGeneration.ts:77-82), so this means five implementations, and only Claude and Codex get a hard schema constraint — Cursor/Grok/OpenCode scrape prompt-instructed JSON with extractJsonObject. Cheaper: run the judge as its own child thread and parse its final assistant message with the same lenient extractor those three adapters already use. Loses per-call model/effort control.

                  6. Do nothing and document that ultracode is Claude-only. Legitimate. The counter is that ultracode's value is mostly the orchestration pattern, not the effort flag, and that pattern is provider-independent.

                  Risks or tradeoffs

                  Seam cost (measured against merge-base 64bf01619, 60-day window)

                  docs/t3x/SEAMS.md:5 records 34 upstream-owned files, +1616 / -187 lines, and :21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." This feature adds one row.

                  FileEditLinesChurnRisk
                  apps/server/src/mcp/McpHttpServer.tsone merged layer sourced from t3x/mcp/index.ts~36~18

                  Measured with git log --oneline --since="@$((MBTS-60*86400))" 64bf01619 -- <path>. For contrast, the paths this design deliberately avoids: apps/server/src/ws.ts = 41, apps/server/src/server.ts = 29, packages/contracts/src/orchestration.ts = 12, apps/server/src/provider/Layers/ClaudeAdapter.ts = 12, apps/web/src/components/ChatView.tsx = 63 (already the fork's worst row at risk 11466, SEAMS.md:41). Fork-owned apps/server/src/t3x/ has churn 0.

                  Rejected-but-tempting rows and their cost: McpSessionRegistry.ts (churn 7) + McpInvocationContext.ts (churn 3) for a capability gate — two rows for a v1 that has no per-thread scoping requirement. ProviderAdapter.ts has churn 0 and is cheap in isolation, but extending it forces edits to all five adapters (ClaudeAdapter.ts churn 12 among them).

                  The ledger must be updated in the same commit — header totals plus the new row — per the self-reference rule at SEAMS.md:22-28, and the t3x/mcp/index.ts aggregator should be documented there as the reason no further MCP rows will follow.

                  Parallel-paths hazard

                  This is the fork's known failure mode: a fork path that duplicates an upstream capability silently bypasses new upstream guards. Upstream is actively building the same thing (pingdotgg#3138, pingdotgg#4649, PRs pingdotgg#4663 / pingdotgg#5219 / pingdotgg#3638). Concretely: use distinct tool names from delegate_task/task_status, keep the entire implementation deletable in one rm -rf apps/server/src/t3x/orchestrate/, and re-check the SEAMS table every sync.

                  Machine and cost risks

                  • Fan-out thrashes a 16GB Mac. Each child is a full provider CLI process, and startup is serialized through the DrainableWorker. Cap 4, and expect the first child to start well before the fourth.
                  • No budget ceiling exists. Nothing in T3 bounds a run's token spend. A model that spawns 4 children, polls, and re-spawns can burn a lot unattended. v1 should hard-cap total spawns per parent thread.
                  • The 30-minute idle reaper (ProviderSessionReaper.ts:17) silently kills parked children.

                  UNVERIFIED — do not present these as settled

                  1. Do Cursor and Grok's ACP clients actually expose T3's MCP tools to the model? The mcpServers wiring is verified present (CursorAdapter.ts:544, GrokAdapter.ts:582), but nobody has confirmed the tool reaches the model's tool list on those providers. Experiment: start a thread on each of the five providers and ask the model to call mcp__t3-code__preview_status; a tool result on all five proves the channel end-to-end before any orchestration code is written. Do this first — it is the load-bearing assumption of the whole design.
                  2. Does a child thread created via engine.dispatch({type:"thread.create"}) without the ws.ts bootstrap path actually start a working session? All existing evidence is that AutoResumeReactor dispatches thread.turn.start to threads that already exist. Experiment: dispatch thread.create then thread.turn.start from a scratch Effect script and confirm turn.completed.
                  3. Whether the reaper's 30-minute teardown produces a distinguishable phase from projectThreadAwareness, or looks identical to "completed". Experiment: let a thread idle past 30 minutes and diff the projected phase.
                  4. What settings.ultracode: true actually does inside the Claude binary. This repo only sets the flag (ClaudeAdapter.ts:3521); the semantics are undocumented here. The claim "ultracode means parallel fan-out" is inferred from observed behaviour, not from this codebase. Experiment:strings the bundled Claude platform binary for ultracode and read the surrounding tool-registry code.
                  5. Whether 4 concurrent children is survivable on this machine. Nobody has measured it.

                  Examples or references

                  Upstream issues this overlaps (this is NOT a clean-slate feature)

                  File:line evidence (all verified in this checkout at main)

                  • Capabilities: apps/server/src/provider/Services/ProviderAdapter.ts:26-32; identical values at ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703
                  • Five drivers: apps/server/src/provider/builtInDrivers.ts:47
                  • MCP mounted by every adapter: ClaudeAdapter.ts:3523, CursorAdapter.ts:534, CodexAdapter.ts:1397, GrokAdapter.ts:572, OpenCodeAdapter.ts:1217
                  • MCP scope carries parent threadId: apps/server/src/mcp/McpInvocationContext.ts:10-19; issuance apps/server/src/mcp/McpSessionRegistry.ts:131
                  • Toolkit registration point: apps/server/src/mcp/McpHttpServer.ts:206-225; only toolkit today: apps/server/src/mcp/toolkits/preview/ (tools.ts, handlers.ts, tools.test.ts, handlers.test.ts)
                  • Fork aggregators: apps/server/src/t3x/index.ts:71 (T3xLayerLive), :100 (T3xRoutesLive); existing seams autoResume/, webPush/
                  • Autonomous dispatch precedent: apps/server/src/t3x/autoResume/Reactor.ts:75 (thread.activity.append), :111 (commandId)
                  • Bootstrap trapped in the transport: apps/server/src/ws.ts:750, :891, :908, :940, :961
                  • Serialized startup / forked turn: apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:684, :1117-1119, :1323
                  • Idle reaper: apps/server/src/provider/Layers/ProviderSessionReaper.ts:17
                  • Completion phase: packages/shared/src/agentAwareness.ts:53, :77
                  • Result addressing: packages/contracts/src/orchestration.ts:335, :341; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168
                  • Bootstrap schema (unused by engine): packages/contracts/src/orchestration.ts:677, :701, :720
                  • thread.activity.append command: packages/contracts/src/orchestration.ts:865
                  • Ultracode today: ClaudeProvider.ts:75, :406, :425; ClaudeAdapter.ts:3510, :3521
                  • Closed structured-output interface: apps/server/src/textGeneration/TextGeneration.ts:77-82
                  • Steering allowlist precedent: apps/web/src/outbox/composerSteering.logic.ts:13, :43, :51
                  • Seam ledger: docs/t3x/SEAMS.md:5, :21, :22-28, :41

                  Duplicate search performed before filing

                  Searched all 1,615 upstream pingdotgg/t3code issues (open + closed — count confirmed against gh api search/issuestotal_count) by dumping the full title corpus locally and grepping ~80 term variants, plus body-level gh search issues per concept (orchestration, delegation, subagent, parallel agents, swarm, judge, voting, best of n, consensus, agent team, teammate, ultracode, fan-out, pipeline, workflow), plus a gh search prs sweep. Also searched all 21 radroid/t3code fork issues. Upstream Discussions are disabled (hasDiscussionsEnabled: false), so issues and PRs are the complete surface.

                  Result: this is not a duplicate, but it is a strong overlap and the body says so explicitly.

                  Found and cross-referenced above: pingdotgg#3138 (Orchestration/Delegation — closest, owns the goal statement), pingdotgg#4649 (configurable pipelines — owns the staged-workflow half), pingdotgg#5218 / pingdotgg#4456 / pingdotgg#1740 / pingdotgg#538 / pingdotgg#2921 (subagent UI, nested threads, CLI orchestration — all the presentation half this issue defers), and in-flight PRs pingdotgg#4663, pingdotgg#5219.

                  Most decisive finding: PR pingdotgg#3638 is MERGED — but into the stack branch t3code/codex-turn-mapping, gated behind still-open pingdotgg#2829. Verified git merge-base --is-ancestor dac514e6b upstream/main → NOT an ancestor of upstream/main, origin/main, or fork HEAD, and no scheduled_tasks/orchestrator toolkit files are tracked in this checkout (ls apps/server/src/mcp/toolkitspreview only). So the capability exists upstream on a branch but is not reachable from main today, which is the justification for filing this on the fork rather than upstream.

                  Zero hits in either repo for judge panels, best-of-N, adjudication, voting, consensus, or swarm — that slice is genuinely unclaimed, and is called out as a deferred follow-up rather than folded into v1.

                  No fork issue duplicates this. The only adjacent fork issue is #38 (Loop Watch), which is about supervising a single long-running thread and explicitly rules cron out of scope; it is referenced for its session.status hazard note, not as an overlap.

                  Contribution

                  • I would be open to helping implement this.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    enhancementNew feature or request

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      [Feature]: Provider-agnostic orchestration ("ultracode for every model") — a fork-owned MCP toolkit that lets any adapter fan out into real T3 threads #43

                      Description

                      @radroid

                      Before submitting

                      • I searched existing issues and did not find a duplicate.
                      • I am describing a concrete problem or use case, not just a vague idea.

                      Area

                      apps/server

                      Problem or use case

                      What "ultracode" is, for a reader who has never used it

                      "Ultracode" is shorthand for a cluster of behaviours people expect from a high-effort coding agent, none of which are a single feature:

                      • Parallel fan-out. The agent decomposes a task and runs several workers at once (explore three call sites, write tests while another writes the implementation) instead of doing everything serially in one context.
                      • Structured output. Workers return machine-readable results (a verdict, a file list, a JSON summary) rather than prose the parent has to re-read and re-parse.
                      • Adversarial verification. A second agent is asked to break the first one's work — find the bug, find the missing test — rather than to agree with it.
                      • Judge panels / best-of-N. The same task is attempted N ways and a separate adjudicator picks a winner or merges the outputs.
                      • Loop-until-dry. The agent keeps iterating (fix → run tests → fix) until a stopping condition is met, rather than stopping after one pass.

                      What T3 Code actually has today

                      In this repo, ultracode is only a Claude effort level, not any of the above. It is a dropdown value that normalises to --effort xhigh plus a settings.ultracode: true flag handed to the Claude Agent SDK:

                      • apps/server/src/provider/Layers/ClaudeProvider.ts:75 (and :106, :142) — { value: "ultracode", label: "Ultracode" }, listed only for Claude models
                      • apps/server/src/provider/Layers/ClaudeProvider.ts:406if (effort === "ultracode") { return "xhigh"; }
                      • apps/server/src/provider/Layers/ClaudeAdapter.ts:3510 / :3521const ultracode = isClaudeUltracodeEffort(effort)...(ultracode ? { ultracode: true } : {})

                      So every one of the five ultracode behaviours, when it happens at all, happens inside the Claude CLI process, where T3 cannot inspect, interrupt, checkpoint, or steer it. That is the direct cause of "ultracode does not always work well in T3 Code": T3 renders provider-internal fan-out as flat, opaque rows.

                      • Subagent activity is a display heuristic, not a model: tool names containing agent/task/subagent get bucketed into the canonical item type collab_agent_tool_call, titled "Subagent task" (ClaudeAdapter.ts:596, :865). The same heuristic exists in OpenCodeAdapter.ts and CodexAdapter.ts and is absent from CursorAdapter.ts and GrokAdapter.ts.
                      • task.started / task.progress / task.completed exist in the canonical union (packages/contracts/src/providerRuntime.ts:177) and are ingested (apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts), but only ClaudeAdapter.ts ever emits them — verified: grep -rl 'task\.started' apps/server/src packages/contracts/src returns only ClaudeAdapter.ts, ProviderRuntimeIngestion.ts, and the contracts file. They are read-only telemetry about work T3 does not own.

                      Why this cannot be fixed inside the adapters

                      ProviderAdapterCapabilities has exactly one field, and all five adapters set it to the same value — the declarative capability surface differentiates nothing today:

                      // apps/server/src/provider/Services/ProviderAdapter.ts:28exportinterfaceProviderAdapterCapabilities{readonlysessionModelSwitch: ProviderSessionModelSwitchMode;}

                      ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703 — all sessionModelSwitch: "in-session".

                      There is no spawn/subagent primitive anywhere in ProviderAdapterShape, and there is no parent/child thread concept in T3's own domain: grep -rn parentThreadId apps/server/src packages/contracts/src returns nothing (the identifier exists only in the vendored Codex app-server generated schema). OrchestrationThread (packages/contracts/src/orchestration.ts:352) has no parent field.

                      Building fan-out inside adapters would mean five implementations against five unrelated protocols (Claude Agent SDK, Codex app-server JSON-RPC, ACP ×2, OpenCode HTTP), and two of them (Cursor, Grok) have no subagent concept at all. The repo already documents what happens when capability is under-declared: the web app hardcodes a per-driver allowlist because there is no flag to read —

                      // apps/web/src/outbox/composerSteering.logic.ts:13*Thecatchisthatthereisnosteeringcapabilityflaganywhereinthe*contracts,sosupport is implicitperadapterandhastobeassertedhere.// :43constSTEERABLE_PROVIDER_DRIVER_KINDS: ReadonlySet<string>=newSet([...]);

                      Proposed solution

                      Shape: one orchestrator thread + N child threads, driven entirely above the adapter layer

                      The orchestrator is an ordinary T3 thread on any provider. Its children are ordinary T3 threads. The only new machinery is a fork-owned MCP toolkit the model calls, plus a fork-owned store that tracks runs. Nothing touches ProviderAdapterShape, and no provider-native subagent feature is used or required.

                      Why this is provider-agnostic for free

                      Every one of the five adapters already mounts a T3-hosted MCP server into its provider session, with a per-thread bearer credential issued by T3:

                      • apps/server/src/provider/Layers/ClaudeAdapter.ts:3523
                      • apps/server/src/provider/Layers/CursorAdapter.ts:534
                      • apps/server/src/provider/Layers/CodexAdapter.ts:1397
                      • apps/server/src/provider/Layers/GrokAdapter.ts:572
                      • apps/server/src/provider/Layers/OpenCodeAdapter.ts:1217

                      all calling McpProviderSession.readMcpProviderSession(input.threadId). The invocation scope carries the calling thread's id, so a tool handler natively knows who the parent is:

                      // apps/server/src/mcp/McpInvocationContext.ts:12exportinterfaceMcpInvocationScope{readonlyenvironmentId: EnvironmentId;readonlythreadId: ThreadId;readonlyproviderSessionId: string;readonlyproviderInstanceId: ProviderInstanceId;readonlycapabilities: ReadonlySet<McpCapability>;readonlyissuedAt: number;}

                      Adding tools here means every provider gets them on the same day, with zero adapter edits.

                      New files (all fork-owned, zero upstream conflict)

                      apps/server/src/t3x/orchestrate/
                      tools.ts # Effect Tool.make definitions — copy apps/server/src/mcp/toolkits/preview/tools.ts
                      handlers.ts # copy apps/server/src/mcp/toolkits/preview/handlers.ts
                      RunStore.ts # JSON persistence in stateDir, modelled on t3x-auto-resume.json
                      Runner.ts # dispatches thread.create + thread.turn.start, watches for completion
                      index.ts # exports OrchestrateToolkitRegistrationLive + OrchestrateLayerLive
                      apps/server/src/t3x/mcp/index.ts # single aggregator for all future fork MCP toolkits
                      

                      apps/server/src/mcp/toolkits/preview/ is a complete, shipping template (tools.ts, handlers.ts, plus tests for both) — it is currently the only toolkit (ls apps/server/src/mcp/toolkitspreview).

                      The three tools (v1)

                      ToolBehaviour
                      subagent_spawnTakes { tasks: [{ label, prompt, providerInstanceId?, modelId?, runtimeMode? }] }, max 4. Creates one child thread per task, dispatches thread.turn.start with only the task prompt (no parent history), returns { runId, children: [{ childThreadId, label }] }immediately. Never blocks.
                      subagent_status{ runId } → per-child { label, childThreadId, phase } where phase comes from projectThreadAwareness. Cheap to poll.
                      subagent_result{ runId } or { childThreadId } → final assistant message text for each completed child, plus its phase.

                      Async-return + poll is deliberate: an MCP handler that blocks for minutes holds the parent's tool call open against adapter and CLI tool timeouts. Polling burns orchestrator turns but cannot deadlock.

                      Dispatch: reuse the proven fork precedent verbatim

                      AutoResumeReactor is already a server-side autonomous dispatcher that synthesises turns indistinguishable from a keystroke:

                      // apps/server/src/t3x/autoResume/Reactor.ts:111commandId: CommandId.make(`t3x-auto-resume:${commandUuid}`),

                      and it already writes its own audit trail with thread.activity.append (Reactor.ts:75; command declared at packages/contracts/src/orchestration.ts:865).

                      The orchestrator does the same: engine.dispatch({ type: "thread.create", ... }) then engine.dispatch({ type: "thread.turn.start", ... }) with commandId prefixed t3x-orchestrate:, and appends a breadcrumb to the parent thread for every spawn, phase change, and result read. That trail is provider-agnostic and shows up on Cursor/Grok/OpenCode identically — which is exactly why the design must not build on task.* (Claude-only, ClaudeAdapter.ts is the sole emitter).

                      Completion detection: already solved, do not reinvent

                      // packages/shared/src/agentAwareness.ts:53exportfunctionprojectThreadAwareness(...)// :77functionresolveThreadAwarenessPhase(thread): AgentAwarenessPhase|null

                      This reads only shell-projection fields — never provider internals — and is already consumed server-side by the fork's Web Push seam (apps/server/src/t3x/webPush/attention.ts). It returns waiting_for_approval / completed / null, which is precisely the "is this child done, stuck on an approval, or still working" distinction the orchestrator needs.

                      Do not gate on session.status alone. The existing code documents settled/teardown races, and fork issue #38 records the hazard that synthetic turns deadlock status-gated logic.

                      Result collection

                      OrchestrationLatestTurn (packages/contracts/src/orchestration.ts:335) carries assistantMessageId: Schema.NullOr(MessageId) at :341, and ProjectionSnapshotQuery.getThreadDetailById (apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168) returns the full thread including messages. subagent_result is a projection read, not a provider call.

                      Registration seam (the only upstream edit)

                      The MCP server builds its toolkit layer in apps/server/src/mcp/McpHttpServer.ts:206-225:

                      constPreviewStandardToolkitRegistrationLive=McpServer.toolkit(PreviewStandardToolkit).pipe(Layer.provide(PreviewStandardToolkitHandlersLive),);exportconstPreviewToolkitRegistrationLive=Layer.mergeAll(...);exportconstlayer=PreviewToolkitRegistrationLive.pipe(Layer.provideMerge(McpTransportLive));

                      Add one merged layer sourced from apps/server/src/t3x/mcp/index.ts — the same aggregator discipline apps/server/src/t3x/index.ts:71/:100 already enforces for T3xLayerLive / T3xRoutesLive. Every future fork toolkit then costs zero additional upstream churn.

                      Deliberately avoid the capability gate in v1.McpCapability is a closed union with one member (apps/server/src/mcp/McpInvocationContext.ts:10 — export type McpCapability = "preview";) and it is issued from a hardcoded set (apps/server/src/mcp/McpSessionRegistry.ts:131 — capabilities: new Set(["preview"])). Extending both would add two more ledger rows for no v1 benefit. Gate the toolkit on a fork-local setting inside handlers.ts instead, and revisit if v2 needs per-thread scoping.

                      What degrades, per adapter, and how

                      Because the design uses no provider-native subagent feature, the capability does not degrade — the presentation and reliability do:

                      ConcernClaudeCodexOpenCodeCursorGrok
                      Can call the tools (MCP mounted)yesyesyesyesyes
                      Spawn tool call renders as "Subagent task"yes (ClaudeAdapter.ts:596)yesyesno — generic tool rowno — generic tool row
                      Native task.* telemetryyesnononono
                      Structured judge verdictsnative --json-schemanative --output-schemaprompt-instructedprompt-instructedprompt-instructed
                      Steering the orchestrator mid-runyesno (excluded from STEERABLE_PROVIDER_DRIVER_KINDS)yesyesyes

                      Mitigations: emit the fan-out trail via thread.activity.append so it is legible everywhere (not task.*); accept prose results in v1 and defer structured verdicts; document that Codex orchestrators cannot be steered mid-run.

                      Fan-out cap and why it is low

                      Session startup is serialized. ensureSessionForThread (ProviderCommandReactor.ts:684) runs inside buildSendTurnRequestForThread, which runs inside a single-item-at-a-timeDrainableWorker (ProviderCommandReactor.ts:1323). Spawning N children means N sequential CLI process spawns before any of them runs. The turn itself is forked (ProviderCommandReactor.ts:1117-1119 — .pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped)), so once started they do run concurrently. Cap v1 at 4 and make the cap a fork-local constant.

                      Separately, ProviderSessionReaper.ts:17 sets DEFAULT_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 — a parked child session is reaped after 30 minutes, invisibly to a polling orchestrator. subagent_status must report a reaped child as a distinct phase, not silently as "still working".

                      Restart durability

                      RunStore.ts persists { runId, parentThreadId, children: [{ childThreadId, label, phase }] } to stateDir, mirroring t3x-auto-resume.json. On boot, reconcile every non-terminal run against getThreadDetailById. Without this a server restart orphans every child thread with no trace.

                      Why this matters

                      Today "high-effort agent" in T3 Code means one Claude-only dropdown value that changes a flag on one provider's CLI, and whatever fan-out happens is invisible and unsteerable because it lives inside that CLI's process. This inverts that: each unit of parallel work becomes a first-class T3 thread, which means it inherits everything T3 already built — the activity timeline, approvals, checkpoints, per-turn diffs, interrupt, Web Push, auto-resume, mobile — with no per-feature work.

                      It is genuinely model-agnostic rather than nominally so: the orchestrator can be a Grok thread spawning Codex children, because the only interface it touches is OrchestrationEngineService.dispatch. That is a capability no single provider gives you, and it is the one thing a wrapper app is uniquely positioned to build.

                      It also removes a class of "why did nothing happen" confusion: a stalled child shows up as a thread with a phase and an approval card, not as a silent gap inside another process.

                      Smallest useful scope

                      v1 = async spawn + poll + collect, on shared cwd, no UI.

                      Ships:

                      • apps/server/src/t3x/orchestrate/ with subagent_spawn, subagent_status, subagent_result
                      • one aggregator seam line in apps/server/src/mcp/McpHttpServer.ts via apps/server/src/t3x/mcp/index.ts
                      • fan-out cap 4, fork-local constant
                      • RunStore JSON persistence + boot reconciliation
                      • thread.activity.append breadcrumbs on the parent for spawn / phase-change / result
                      • completion via projectThreadAwareness, results via getThreadDetailById + latestTurn.assistantMessageId
                      • tests mirroring apps/server/src/mcp/toolkits/preview/{tools,handlers}.test.ts

                      Explicitly out of scope for v1 (each is a follow-up issue):

                      • Git worktrees per child. This is the big one. Worktree bootstrap is not in the engine — it is implemented inline in the WebSocket transport (apps/server/src/ws.ts:750 dispatchBootstrapTurnStart, :891thread.create, :908gitWorkflow.createWorktree, :940runSetupProgram()). A server-side dispatcher calling engine.dispatch directly gets none of it. ws.ts measured churn is 41 commits in the 60 days before merge-base 64bf01619 — the highest of any candidate file. So v1 children share the parent's cwd, which makes it safe for read-heavy fan-out (parallel investigation, review, test authoring in disjoint directories) and unsafe for parallel edits to the same files. State that limit in the tool description so the model does not attempt it.
                      • Judge panels / best-of-N adjudication. Needs structured verdicts; see alternatives.
                      • UI grouping. With no parent link in OrchestrationThreadShell, 4 children land flat in the shell snapshot and each completion fires the needs-attention / Web Push coordinator.
                      • Extending ProviderAdapterCapabilities.
                      • Generic structured output in TextGenerationService.

                      Alternatives considered

                      1. Wait for upstream orchestrator-v2. PR pingdotgg#3638 already merged delegate_task / task_status / task_cancel / create_threads / t3_thread_* as orchestrator MCP tools — but into the stack branch t3code/codex-turn-mapping, not main. Its gate, pingdotgg#2829, is still open against main. Verified: merge commit dac514e6b is not an ancestor of upstream/main, origin/main, or fork HEAD. This is the strongest alternative and the honest reason to keep v1 tiny — the whole toolkit is three tools in fork-owned files and is cheap to delete when v2 lands. Naming should deliberately not collide with delegate_task/task_status so both can coexist during a transition.

                      2. Cherry-pick the v2 toolkit onto today's main.git show dac514e6b:apps/server/src/mcp/toolkits/orchestrator/tools.ts is real, reviewed code. But it targets the v2 orchestrator's data model, so porting it means porting or stubbing that model — a much larger change than three fork-local tools, and it creates the "parallel paths" hazard the fork has already been bitten by (a fork path that duplicates an upstream capability silently bypasses new upstream guards).

                      3. A declarative t3x reactor instead of model-callable tools. Model AutoResumeReactor directly: a server-side supervisor that runs a user-configured plan (stages, providers, prompts) without the model deciding anything. Better for deterministic verification pipelines and judge panels; worse for the "ultracode" experience the user asked for, where the model authors its own fan-out. They share ~80% of the machinery (RunStore, Runner, awareness polling) so (a) can be added on top of (b) later. Picking the MCP toolkit first is the choice that matches the request.

                      4. createSdkMcpServer from the Claude Agent SDK. Exported and typed but unused anywhere in the repo. Strictly worse here: Claude-only, requires a ClaudeAdapter.ts edit (churn 12), and defeats the entire point of the issue.

                      5. Extend TextGenerationService with a generic generateStructured(schema, prompt) for judge verdicts. The interface is closed at four git-flavoured operations (apps/server/src/textGeneration/TextGeneration.ts:77-82), so this means five implementations, and only Claude and Codex get a hard schema constraint — Cursor/Grok/OpenCode scrape prompt-instructed JSON with extractJsonObject. Cheaper: run the judge as its own child thread and parse its final assistant message with the same lenient extractor those three adapters already use. Loses per-call model/effort control.

                      6. Do nothing and document that ultracode is Claude-only. Legitimate. The counter is that ultracode's value is mostly the orchestration pattern, not the effort flag, and that pattern is provider-independent.

                      Risks or tradeoffs

                      Seam cost (measured against merge-base 64bf01619, 60-day window)

                      docs/t3x/SEAMS.md:5 records 34 upstream-owned files, +1616 / -187 lines, and :21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." This feature adds one row.

                      FileEditLinesChurnRisk
                      apps/server/src/mcp/McpHttpServer.tsone merged layer sourced from t3x/mcp/index.ts~36~18

                      Measured with git log --oneline --since="@$((MBTS-60*86400))" 64bf01619 -- <path>. For contrast, the paths this design deliberately avoids: apps/server/src/ws.ts = 41, apps/server/src/server.ts = 29, packages/contracts/src/orchestration.ts = 12, apps/server/src/provider/Layers/ClaudeAdapter.ts = 12, apps/web/src/components/ChatView.tsx = 63 (already the fork's worst row at risk 11466, SEAMS.md:41). Fork-owned apps/server/src/t3x/ has churn 0.

                      Rejected-but-tempting rows and their cost: McpSessionRegistry.ts (churn 7) + McpInvocationContext.ts (churn 3) for a capability gate — two rows for a v1 that has no per-thread scoping requirement. ProviderAdapter.ts has churn 0 and is cheap in isolation, but extending it forces edits to all five adapters (ClaudeAdapter.ts churn 12 among them).

                      The ledger must be updated in the same commit — header totals plus the new row — per the self-reference rule at SEAMS.md:22-28, and the t3x/mcp/index.ts aggregator should be documented there as the reason no further MCP rows will follow.

                      Parallel-paths hazard

                      This is the fork's known failure mode: a fork path that duplicates an upstream capability silently bypasses new upstream guards. Upstream is actively building the same thing (pingdotgg#3138, pingdotgg#4649, PRs pingdotgg#4663 / pingdotgg#5219 / pingdotgg#3638). Concretely: use distinct tool names from delegate_task/task_status, keep the entire implementation deletable in one rm -rf apps/server/src/t3x/orchestrate/, and re-check the SEAMS table every sync.

                      Machine and cost risks

                      • Fan-out thrashes a 16GB Mac. Each child is a full provider CLI process, and startup is serialized through the DrainableWorker. Cap 4, and expect the first child to start well before the fourth.
                      • No budget ceiling exists. Nothing in T3 bounds a run's token spend. A model that spawns 4 children, polls, and re-spawns can burn a lot unattended. v1 should hard-cap total spawns per parent thread.
                      • The 30-minute idle reaper (ProviderSessionReaper.ts:17) silently kills parked children.

                      UNVERIFIED — do not present these as settled

                      1. Do Cursor and Grok's ACP clients actually expose T3's MCP tools to the model? The mcpServers wiring is verified present (CursorAdapter.ts:544, GrokAdapter.ts:582), but nobody has confirmed the tool reaches the model's tool list on those providers. Experiment: start a thread on each of the five providers and ask the model to call mcp__t3-code__preview_status; a tool result on all five proves the channel end-to-end before any orchestration code is written. Do this first — it is the load-bearing assumption of the whole design.
                      2. Does a child thread created via engine.dispatch({type:"thread.create"}) without the ws.ts bootstrap path actually start a working session? All existing evidence is that AutoResumeReactor dispatches thread.turn.start to threads that already exist. Experiment: dispatch thread.create then thread.turn.start from a scratch Effect script and confirm turn.completed.
                      3. Whether the reaper's 30-minute teardown produces a distinguishable phase from projectThreadAwareness, or looks identical to "completed". Experiment: let a thread idle past 30 minutes and diff the projected phase.
                      4. What settings.ultracode: true actually does inside the Claude binary. This repo only sets the flag (ClaudeAdapter.ts:3521); the semantics are undocumented here. The claim "ultracode means parallel fan-out" is inferred from observed behaviour, not from this codebase. Experiment:strings the bundled Claude platform binary for ultracode and read the surrounding tool-registry code.
                      5. Whether 4 concurrent children is survivable on this machine. Nobody has measured it.

                      Examples or references

                      Upstream issues this overlaps (this is NOT a clean-slate feature)

                      File:line evidence (all verified in this checkout at main)

                      • Capabilities: apps/server/src/provider/Services/ProviderAdapter.ts:26-32; identical values at ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703
                      • Five drivers: apps/server/src/provider/builtInDrivers.ts:47
                      • MCP mounted by every adapter: ClaudeAdapter.ts:3523, CursorAdapter.ts:534, CodexAdapter.ts:1397, GrokAdapter.ts:572, OpenCodeAdapter.ts:1217
                      • MCP scope carries parent threadId: apps/server/src/mcp/McpInvocationContext.ts:10-19; issuance apps/server/src/mcp/McpSessionRegistry.ts:131
                      • Toolkit registration point: apps/server/src/mcp/McpHttpServer.ts:206-225; only toolkit today: apps/server/src/mcp/toolkits/preview/ (tools.ts, handlers.ts, tools.test.ts, handlers.test.ts)
                      • Fork aggregators: apps/server/src/t3x/index.ts:71 (T3xLayerLive), :100 (T3xRoutesLive); existing seams autoResume/, webPush/
                      • Autonomous dispatch precedent: apps/server/src/t3x/autoResume/Reactor.ts:75 (thread.activity.append), :111 (commandId)
                      • Bootstrap trapped in the transport: apps/server/src/ws.ts:750, :891, :908, :940, :961
                      • Serialized startup / forked turn: apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:684, :1117-1119, :1323
                      • Idle reaper: apps/server/src/provider/Layers/ProviderSessionReaper.ts:17
                      • Completion phase: packages/shared/src/agentAwareness.ts:53, :77
                      • Result addressing: packages/contracts/src/orchestration.ts:335, :341; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168
                      • Bootstrap schema (unused by engine): packages/contracts/src/orchestration.ts:677, :701, :720
                      • thread.activity.append command: packages/contracts/src/orchestration.ts:865
                      • Ultracode today: ClaudeProvider.ts:75, :406, :425; ClaudeAdapter.ts:3510, :3521
                      • Closed structured-output interface: apps/server/src/textGeneration/TextGeneration.ts:77-82
                      • Steering allowlist precedent: apps/web/src/outbox/composerSteering.logic.ts:13, :43, :51
                      • Seam ledger: docs/t3x/SEAMS.md:5, :21, :22-28, :41

                      Duplicate search performed before filing

                      Searched all 1,615 upstream pingdotgg/t3code issues (open + closed — count confirmed against gh api search/issuestotal_count) by dumping the full title corpus locally and grepping ~80 term variants, plus body-level gh search issues per concept (orchestration, delegation, subagent, parallel agents, swarm, judge, voting, best of n, consensus, agent team, teammate, ultracode, fan-out, pipeline, workflow), plus a gh search prs sweep. Also searched all 21 radroid/t3code fork issues. Upstream Discussions are disabled (hasDiscussionsEnabled: false), so issues and PRs are the complete surface.

                      Result: this is not a duplicate, but it is a strong overlap and the body says so explicitly.

                      Found and cross-referenced above: pingdotgg#3138 (Orchestration/Delegation — closest, owns the goal statement), pingdotgg#4649 (configurable pipelines — owns the staged-workflow half), pingdotgg#5218 / pingdotgg#4456 / pingdotgg#1740 / pingdotgg#538 / pingdotgg#2921 (subagent UI, nested threads, CLI orchestration — all the presentation half this issue defers), and in-flight PRs pingdotgg#4663, pingdotgg#5219.

                      Most decisive finding: PR pingdotgg#3638 is MERGED — but into the stack branch t3code/codex-turn-mapping, gated behind still-open pingdotgg#2829. Verified git merge-base --is-ancestor dac514e6b upstream/main → NOT an ancestor of upstream/main, origin/main, or fork HEAD, and no scheduled_tasks/orchestrator toolkit files are tracked in this checkout (ls apps/server/src/mcp/toolkitspreview only). So the capability exists upstream on a branch but is not reachable from main today, which is the justification for filing this on the fork rather than upstream.

                      Zero hits in either repo for judge panels, best-of-N, adjudication, voting, consensus, or swarm — that slice is genuinely unclaimed, and is called out as a deferred follow-up rather than folded into v1.

                      No fork issue duplicates this. The only adjacent fork issue is #38 (Loop Watch), which is about supervising a single long-running thread and explicitly rules cron out of scope; it is referenced for its session.status hazard note, not as an overlap.

                      Contribution

                      • I would be open to helping implement this.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        enhancementNew feature or request

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          [Feature]: Provider-agnostic orchestration ("ultracode for every model") — a fork-owned MCP toolkit that lets any adapter fan out into real T3 threads #43

                          Description

                          @radroid

                          Before submitting

                          • I searched existing issues and did not find a duplicate.
                          • I am describing a concrete problem or use case, not just a vague idea.

                          Area

                          apps/server

                          Problem or use case

                          What "ultracode" is, for a reader who has never used it

                          "Ultracode" is shorthand for a cluster of behaviours people expect from a high-effort coding agent, none of which are a single feature:

                          • Parallel fan-out. The agent decomposes a task and runs several workers at once (explore three call sites, write tests while another writes the implementation) instead of doing everything serially in one context.
                          • Structured output. Workers return machine-readable results (a verdict, a file list, a JSON summary) rather than prose the parent has to re-read and re-parse.
                          • Adversarial verification. A second agent is asked to break the first one's work — find the bug, find the missing test — rather than to agree with it.
                          • Judge panels / best-of-N. The same task is attempted N ways and a separate adjudicator picks a winner or merges the outputs.
                          • Loop-until-dry. The agent keeps iterating (fix → run tests → fix) until a stopping condition is met, rather than stopping after one pass.

                          What T3 Code actually has today

                          In this repo, ultracode is only a Claude effort level, not any of the above. It is a dropdown value that normalises to --effort xhigh plus a settings.ultracode: true flag handed to the Claude Agent SDK:

                          • apps/server/src/provider/Layers/ClaudeProvider.ts:75 (and :106, :142) — { value: "ultracode", label: "Ultracode" }, listed only for Claude models
                          • apps/server/src/provider/Layers/ClaudeProvider.ts:406if (effort === "ultracode") { return "xhigh"; }
                          • apps/server/src/provider/Layers/ClaudeAdapter.ts:3510 / :3521const ultracode = isClaudeUltracodeEffort(effort)...(ultracode ? { ultracode: true } : {})

                          So every one of the five ultracode behaviours, when it happens at all, happens inside the Claude CLI process, where T3 cannot inspect, interrupt, checkpoint, or steer it. That is the direct cause of "ultracode does not always work well in T3 Code": T3 renders provider-internal fan-out as flat, opaque rows.

                          • Subagent activity is a display heuristic, not a model: tool names containing agent/task/subagent get bucketed into the canonical item type collab_agent_tool_call, titled "Subagent task" (ClaudeAdapter.ts:596, :865). The same heuristic exists in OpenCodeAdapter.ts and CodexAdapter.ts and is absent from CursorAdapter.ts and GrokAdapter.ts.
                          • task.started / task.progress / task.completed exist in the canonical union (packages/contracts/src/providerRuntime.ts:177) and are ingested (apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts), but only ClaudeAdapter.ts ever emits them — verified: grep -rl 'task\.started' apps/server/src packages/contracts/src returns only ClaudeAdapter.ts, ProviderRuntimeIngestion.ts, and the contracts file. They are read-only telemetry about work T3 does not own.

                          Why this cannot be fixed inside the adapters

                          ProviderAdapterCapabilities has exactly one field, and all five adapters set it to the same value — the declarative capability surface differentiates nothing today:

                          // apps/server/src/provider/Services/ProviderAdapter.ts:28exportinterfaceProviderAdapterCapabilities{readonlysessionModelSwitch: ProviderSessionModelSwitchMode;}

                          ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703 — all sessionModelSwitch: "in-session".

                          There is no spawn/subagent primitive anywhere in ProviderAdapterShape, and there is no parent/child thread concept in T3's own domain: grep -rn parentThreadId apps/server/src packages/contracts/src returns nothing (the identifier exists only in the vendored Codex app-server generated schema). OrchestrationThread (packages/contracts/src/orchestration.ts:352) has no parent field.

                          Building fan-out inside adapters would mean five implementations against five unrelated protocols (Claude Agent SDK, Codex app-server JSON-RPC, ACP ×2, OpenCode HTTP), and two of them (Cursor, Grok) have no subagent concept at all. The repo already documents what happens when capability is under-declared: the web app hardcodes a per-driver allowlist because there is no flag to read —

                          // apps/web/src/outbox/composerSteering.logic.ts:13*Thecatchisthatthereisnosteeringcapabilityflaganywhereinthe*contracts,sosupport is implicitperadapterandhastobeassertedhere.// :43constSTEERABLE_PROVIDER_DRIVER_KINDS: ReadonlySet<string>=newSet([...]);

                          Proposed solution

                          Shape: one orchestrator thread + N child threads, driven entirely above the adapter layer

                          The orchestrator is an ordinary T3 thread on any provider. Its children are ordinary T3 threads. The only new machinery is a fork-owned MCP toolkit the model calls, plus a fork-owned store that tracks runs. Nothing touches ProviderAdapterShape, and no provider-native subagent feature is used or required.

                          Why this is provider-agnostic for free

                          Every one of the five adapters already mounts a T3-hosted MCP server into its provider session, with a per-thread bearer credential issued by T3:

                          • apps/server/src/provider/Layers/ClaudeAdapter.ts:3523
                          • apps/server/src/provider/Layers/CursorAdapter.ts:534
                          • apps/server/src/provider/Layers/CodexAdapter.ts:1397
                          • apps/server/src/provider/Layers/GrokAdapter.ts:572
                          • apps/server/src/provider/Layers/OpenCodeAdapter.ts:1217

                          all calling McpProviderSession.readMcpProviderSession(input.threadId). The invocation scope carries the calling thread's id, so a tool handler natively knows who the parent is:

                          // apps/server/src/mcp/McpInvocationContext.ts:12exportinterfaceMcpInvocationScope{readonlyenvironmentId: EnvironmentId;readonlythreadId: ThreadId;readonlyproviderSessionId: string;readonlyproviderInstanceId: ProviderInstanceId;readonlycapabilities: ReadonlySet<McpCapability>;readonlyissuedAt: number;}

                          Adding tools here means every provider gets them on the same day, with zero adapter edits.

                          New files (all fork-owned, zero upstream conflict)

                          apps/server/src/t3x/orchestrate/
                          tools.ts # Effect Tool.make definitions — copy apps/server/src/mcp/toolkits/preview/tools.ts
                          handlers.ts # copy apps/server/src/mcp/toolkits/preview/handlers.ts
                          RunStore.ts # JSON persistence in stateDir, modelled on t3x-auto-resume.json
                          Runner.ts # dispatches thread.create + thread.turn.start, watches for completion
                          index.ts # exports OrchestrateToolkitRegistrationLive + OrchestrateLayerLive
                          apps/server/src/t3x/mcp/index.ts # single aggregator for all future fork MCP toolkits
                          

                          apps/server/src/mcp/toolkits/preview/ is a complete, shipping template (tools.ts, handlers.ts, plus tests for both) — it is currently the only toolkit (ls apps/server/src/mcp/toolkitspreview).

                          The three tools (v1)

                          ToolBehaviour
                          subagent_spawnTakes { tasks: [{ label, prompt, providerInstanceId?, modelId?, runtimeMode? }] }, max 4. Creates one child thread per task, dispatches thread.turn.start with only the task prompt (no parent history), returns { runId, children: [{ childThreadId, label }] }immediately. Never blocks.
                          subagent_status{ runId } → per-child { label, childThreadId, phase } where phase comes from projectThreadAwareness. Cheap to poll.
                          subagent_result{ runId } or { childThreadId } → final assistant message text for each completed child, plus its phase.

                          Async-return + poll is deliberate: an MCP handler that blocks for minutes holds the parent's tool call open against adapter and CLI tool timeouts. Polling burns orchestrator turns but cannot deadlock.

                          Dispatch: reuse the proven fork precedent verbatim

                          AutoResumeReactor is already a server-side autonomous dispatcher that synthesises turns indistinguishable from a keystroke:

                          // apps/server/src/t3x/autoResume/Reactor.ts:111commandId: CommandId.make(`t3x-auto-resume:${commandUuid}`),

                          and it already writes its own audit trail with thread.activity.append (Reactor.ts:75; command declared at packages/contracts/src/orchestration.ts:865).

                          The orchestrator does the same: engine.dispatch({ type: "thread.create", ... }) then engine.dispatch({ type: "thread.turn.start", ... }) with commandId prefixed t3x-orchestrate:, and appends a breadcrumb to the parent thread for every spawn, phase change, and result read. That trail is provider-agnostic and shows up on Cursor/Grok/OpenCode identically — which is exactly why the design must not build on task.* (Claude-only, ClaudeAdapter.ts is the sole emitter).

                          Completion detection: already solved, do not reinvent

                          // packages/shared/src/agentAwareness.ts:53exportfunctionprojectThreadAwareness(...)// :77functionresolveThreadAwarenessPhase(thread): AgentAwarenessPhase|null

                          This reads only shell-projection fields — never provider internals — and is already consumed server-side by the fork's Web Push seam (apps/server/src/t3x/webPush/attention.ts). It returns waiting_for_approval / completed / null, which is precisely the "is this child done, stuck on an approval, or still working" distinction the orchestrator needs.

                          Do not gate on session.status alone. The existing code documents settled/teardown races, and fork issue #38 records the hazard that synthetic turns deadlock status-gated logic.

                          Result collection

                          OrchestrationLatestTurn (packages/contracts/src/orchestration.ts:335) carries assistantMessageId: Schema.NullOr(MessageId) at :341, and ProjectionSnapshotQuery.getThreadDetailById (apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168) returns the full thread including messages. subagent_result is a projection read, not a provider call.

                          Registration seam (the only upstream edit)

                          The MCP server builds its toolkit layer in apps/server/src/mcp/McpHttpServer.ts:206-225:

                          constPreviewStandardToolkitRegistrationLive=McpServer.toolkit(PreviewStandardToolkit).pipe(Layer.provide(PreviewStandardToolkitHandlersLive),);exportconstPreviewToolkitRegistrationLive=Layer.mergeAll(...);exportconstlayer=PreviewToolkitRegistrationLive.pipe(Layer.provideMerge(McpTransportLive));

                          Add one merged layer sourced from apps/server/src/t3x/mcp/index.ts — the same aggregator discipline apps/server/src/t3x/index.ts:71/:100 already enforces for T3xLayerLive / T3xRoutesLive. Every future fork toolkit then costs zero additional upstream churn.

                          Deliberately avoid the capability gate in v1.McpCapability is a closed union with one member (apps/server/src/mcp/McpInvocationContext.ts:10 — export type McpCapability = "preview";) and it is issued from a hardcoded set (apps/server/src/mcp/McpSessionRegistry.ts:131 — capabilities: new Set(["preview"])). Extending both would add two more ledger rows for no v1 benefit. Gate the toolkit on a fork-local setting inside handlers.ts instead, and revisit if v2 needs per-thread scoping.

                          What degrades, per adapter, and how

                          Because the design uses no provider-native subagent feature, the capability does not degrade — the presentation and reliability do:

                          ConcernClaudeCodexOpenCodeCursorGrok
                          Can call the tools (MCP mounted)yesyesyesyesyes
                          Spawn tool call renders as "Subagent task"yes (ClaudeAdapter.ts:596)yesyesno — generic tool rowno — generic tool row
                          Native task.* telemetryyesnononono
                          Structured judge verdictsnative --json-schemanative --output-schemaprompt-instructedprompt-instructedprompt-instructed
                          Steering the orchestrator mid-runyesno (excluded from STEERABLE_PROVIDER_DRIVER_KINDS)yesyesyes

                          Mitigations: emit the fan-out trail via thread.activity.append so it is legible everywhere (not task.*); accept prose results in v1 and defer structured verdicts; document that Codex orchestrators cannot be steered mid-run.

                          Fan-out cap and why it is low

                          Session startup is serialized. ensureSessionForThread (ProviderCommandReactor.ts:684) runs inside buildSendTurnRequestForThread, which runs inside a single-item-at-a-timeDrainableWorker (ProviderCommandReactor.ts:1323). Spawning N children means N sequential CLI process spawns before any of them runs. The turn itself is forked (ProviderCommandReactor.ts:1117-1119 — .pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped)), so once started they do run concurrently. Cap v1 at 4 and make the cap a fork-local constant.

                          Separately, ProviderSessionReaper.ts:17 sets DEFAULT_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 — a parked child session is reaped after 30 minutes, invisibly to a polling orchestrator. subagent_status must report a reaped child as a distinct phase, not silently as "still working".

                          Restart durability

                          RunStore.ts persists { runId, parentThreadId, children: [{ childThreadId, label, phase }] } to stateDir, mirroring t3x-auto-resume.json. On boot, reconcile every non-terminal run against getThreadDetailById. Without this a server restart orphans every child thread with no trace.

                          Why this matters

                          Today "high-effort agent" in T3 Code means one Claude-only dropdown value that changes a flag on one provider's CLI, and whatever fan-out happens is invisible and unsteerable because it lives inside that CLI's process. This inverts that: each unit of parallel work becomes a first-class T3 thread, which means it inherits everything T3 already built — the activity timeline, approvals, checkpoints, per-turn diffs, interrupt, Web Push, auto-resume, mobile — with no per-feature work.

                          It is genuinely model-agnostic rather than nominally so: the orchestrator can be a Grok thread spawning Codex children, because the only interface it touches is OrchestrationEngineService.dispatch. That is a capability no single provider gives you, and it is the one thing a wrapper app is uniquely positioned to build.

                          It also removes a class of "why did nothing happen" confusion: a stalled child shows up as a thread with a phase and an approval card, not as a silent gap inside another process.

                          Smallest useful scope

                          v1 = async spawn + poll + collect, on shared cwd, no UI.

                          Ships:

                          • apps/server/src/t3x/orchestrate/ with subagent_spawn, subagent_status, subagent_result
                          • one aggregator seam line in apps/server/src/mcp/McpHttpServer.ts via apps/server/src/t3x/mcp/index.ts
                          • fan-out cap 4, fork-local constant
                          • RunStore JSON persistence + boot reconciliation
                          • thread.activity.append breadcrumbs on the parent for spawn / phase-change / result
                          • completion via projectThreadAwareness, results via getThreadDetailById + latestTurn.assistantMessageId
                          • tests mirroring apps/server/src/mcp/toolkits/preview/{tools,handlers}.test.ts

                          Explicitly out of scope for v1 (each is a follow-up issue):

                          • Git worktrees per child. This is the big one. Worktree bootstrap is not in the engine — it is implemented inline in the WebSocket transport (apps/server/src/ws.ts:750 dispatchBootstrapTurnStart, :891thread.create, :908gitWorkflow.createWorktree, :940runSetupProgram()). A server-side dispatcher calling engine.dispatch directly gets none of it. ws.ts measured churn is 41 commits in the 60 days before merge-base 64bf01619 — the highest of any candidate file. So v1 children share the parent's cwd, which makes it safe for read-heavy fan-out (parallel investigation, review, test authoring in disjoint directories) and unsafe for parallel edits to the same files. State that limit in the tool description so the model does not attempt it.
                          • Judge panels / best-of-N adjudication. Needs structured verdicts; see alternatives.
                          • UI grouping. With no parent link in OrchestrationThreadShell, 4 children land flat in the shell snapshot and each completion fires the needs-attention / Web Push coordinator.
                          • Extending ProviderAdapterCapabilities.
                          • Generic structured output in TextGenerationService.

                          Alternatives considered

                          1. Wait for upstream orchestrator-v2. PR pingdotgg#3638 already merged delegate_task / task_status / task_cancel / create_threads / t3_thread_* as orchestrator MCP tools — but into the stack branch t3code/codex-turn-mapping, not main. Its gate, pingdotgg#2829, is still open against main. Verified: merge commit dac514e6b is not an ancestor of upstream/main, origin/main, or fork HEAD. This is the strongest alternative and the honest reason to keep v1 tiny — the whole toolkit is three tools in fork-owned files and is cheap to delete when v2 lands. Naming should deliberately not collide with delegate_task/task_status so both can coexist during a transition.

                          2. Cherry-pick the v2 toolkit onto today's main.git show dac514e6b:apps/server/src/mcp/toolkits/orchestrator/tools.ts is real, reviewed code. But it targets the v2 orchestrator's data model, so porting it means porting or stubbing that model — a much larger change than three fork-local tools, and it creates the "parallel paths" hazard the fork has already been bitten by (a fork path that duplicates an upstream capability silently bypasses new upstream guards).

                          3. A declarative t3x reactor instead of model-callable tools. Model AutoResumeReactor directly: a server-side supervisor that runs a user-configured plan (stages, providers, prompts) without the model deciding anything. Better for deterministic verification pipelines and judge panels; worse for the "ultracode" experience the user asked for, where the model authors its own fan-out. They share ~80% of the machinery (RunStore, Runner, awareness polling) so (a) can be added on top of (b) later. Picking the MCP toolkit first is the choice that matches the request.

                          4. createSdkMcpServer from the Claude Agent SDK. Exported and typed but unused anywhere in the repo. Strictly worse here: Claude-only, requires a ClaudeAdapter.ts edit (churn 12), and defeats the entire point of the issue.

                          5. Extend TextGenerationService with a generic generateStructured(schema, prompt) for judge verdicts. The interface is closed at four git-flavoured operations (apps/server/src/textGeneration/TextGeneration.ts:77-82), so this means five implementations, and only Claude and Codex get a hard schema constraint — Cursor/Grok/OpenCode scrape prompt-instructed JSON with extractJsonObject. Cheaper: run the judge as its own child thread and parse its final assistant message with the same lenient extractor those three adapters already use. Loses per-call model/effort control.

                          6. Do nothing and document that ultracode is Claude-only. Legitimate. The counter is that ultracode's value is mostly the orchestration pattern, not the effort flag, and that pattern is provider-independent.

                          Risks or tradeoffs

                          Seam cost (measured against merge-base 64bf01619, 60-day window)

                          docs/t3x/SEAMS.md:5 records 34 upstream-owned files, +1616 / -187 lines, and :21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." This feature adds one row.

                          FileEditLinesChurnRisk
                          apps/server/src/mcp/McpHttpServer.tsone merged layer sourced from t3x/mcp/index.ts~36~18

                          Measured with git log --oneline --since="@$((MBTS-60*86400))" 64bf01619 -- <path>. For contrast, the paths this design deliberately avoids: apps/server/src/ws.ts = 41, apps/server/src/server.ts = 29, packages/contracts/src/orchestration.ts = 12, apps/server/src/provider/Layers/ClaudeAdapter.ts = 12, apps/web/src/components/ChatView.tsx = 63 (already the fork's worst row at risk 11466, SEAMS.md:41). Fork-owned apps/server/src/t3x/ has churn 0.

                          Rejected-but-tempting rows and their cost: McpSessionRegistry.ts (churn 7) + McpInvocationContext.ts (churn 3) for a capability gate — two rows for a v1 that has no per-thread scoping requirement. ProviderAdapter.ts has churn 0 and is cheap in isolation, but extending it forces edits to all five adapters (ClaudeAdapter.ts churn 12 among them).

                          The ledger must be updated in the same commit — header totals plus the new row — per the self-reference rule at SEAMS.md:22-28, and the t3x/mcp/index.ts aggregator should be documented there as the reason no further MCP rows will follow.

                          Parallel-paths hazard

                          This is the fork's known failure mode: a fork path that duplicates an upstream capability silently bypasses new upstream guards. Upstream is actively building the same thing (pingdotgg#3138, pingdotgg#4649, PRs pingdotgg#4663 / pingdotgg#5219 / pingdotgg#3638). Concretely: use distinct tool names from delegate_task/task_status, keep the entire implementation deletable in one rm -rf apps/server/src/t3x/orchestrate/, and re-check the SEAMS table every sync.

                          Machine and cost risks

                          • Fan-out thrashes a 16GB Mac. Each child is a full provider CLI process, and startup is serialized through the DrainableWorker. Cap 4, and expect the first child to start well before the fourth.
                          • No budget ceiling exists. Nothing in T3 bounds a run's token spend. A model that spawns 4 children, polls, and re-spawns can burn a lot unattended. v1 should hard-cap total spawns per parent thread.
                          • The 30-minute idle reaper (ProviderSessionReaper.ts:17) silently kills parked children.

                          UNVERIFIED — do not present these as settled

                          1. Do Cursor and Grok's ACP clients actually expose T3's MCP tools to the model? The mcpServers wiring is verified present (CursorAdapter.ts:544, GrokAdapter.ts:582), but nobody has confirmed the tool reaches the model's tool list on those providers. Experiment: start a thread on each of the five providers and ask the model to call mcp__t3-code__preview_status; a tool result on all five proves the channel end-to-end before any orchestration code is written. Do this first — it is the load-bearing assumption of the whole design.
                          2. Does a child thread created via engine.dispatch({type:"thread.create"}) without the ws.ts bootstrap path actually start a working session? All existing evidence is that AutoResumeReactor dispatches thread.turn.start to threads that already exist. Experiment: dispatch thread.create then thread.turn.start from a scratch Effect script and confirm turn.completed.
                          3. Whether the reaper's 30-minute teardown produces a distinguishable phase from projectThreadAwareness, or looks identical to "completed". Experiment: let a thread idle past 30 minutes and diff the projected phase.
                          4. What settings.ultracode: true actually does inside the Claude binary. This repo only sets the flag (ClaudeAdapter.ts:3521); the semantics are undocumented here. The claim "ultracode means parallel fan-out" is inferred from observed behaviour, not from this codebase. Experiment:strings the bundled Claude platform binary for ultracode and read the surrounding tool-registry code.
                          5. Whether 4 concurrent children is survivable on this machine. Nobody has measured it.

                          Examples or references

                          Upstream issues this overlaps (this is NOT a clean-slate feature)

                          File:line evidence (all verified in this checkout at main)

                          • Capabilities: apps/server/src/provider/Services/ProviderAdapter.ts:26-32; identical values at ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703
                          • Five drivers: apps/server/src/provider/builtInDrivers.ts:47
                          • MCP mounted by every adapter: ClaudeAdapter.ts:3523, CursorAdapter.ts:534, CodexAdapter.ts:1397, GrokAdapter.ts:572, OpenCodeAdapter.ts:1217
                          • MCP scope carries parent threadId: apps/server/src/mcp/McpInvocationContext.ts:10-19; issuance apps/server/src/mcp/McpSessionRegistry.ts:131
                          • Toolkit registration point: apps/server/src/mcp/McpHttpServer.ts:206-225; only toolkit today: apps/server/src/mcp/toolkits/preview/ (tools.ts, handlers.ts, tools.test.ts, handlers.test.ts)
                          • Fork aggregators: apps/server/src/t3x/index.ts:71 (T3xLayerLive), :100 (T3xRoutesLive); existing seams autoResume/, webPush/
                          • Autonomous dispatch precedent: apps/server/src/t3x/autoResume/Reactor.ts:75 (thread.activity.append), :111 (commandId)
                          • Bootstrap trapped in the transport: apps/server/src/ws.ts:750, :891, :908, :940, :961
                          • Serialized startup / forked turn: apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:684, :1117-1119, :1323
                          • Idle reaper: apps/server/src/provider/Layers/ProviderSessionReaper.ts:17
                          • Completion phase: packages/shared/src/agentAwareness.ts:53, :77
                          • Result addressing: packages/contracts/src/orchestration.ts:335, :341; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168
                          • Bootstrap schema (unused by engine): packages/contracts/src/orchestration.ts:677, :701, :720
                          • thread.activity.append command: packages/contracts/src/orchestration.ts:865
                          • Ultracode today: ClaudeProvider.ts:75, :406, :425; ClaudeAdapter.ts:3510, :3521
                          • Closed structured-output interface: apps/server/src/textGeneration/TextGeneration.ts:77-82
                          • Steering allowlist precedent: apps/web/src/outbox/composerSteering.logic.ts:13, :43, :51
                          • Seam ledger: docs/t3x/SEAMS.md:5, :21, :22-28, :41

                          Duplicate search performed before filing

                          Searched all 1,615 upstream pingdotgg/t3code issues (open + closed — count confirmed against gh api search/issuestotal_count) by dumping the full title corpus locally and grepping ~80 term variants, plus body-level gh search issues per concept (orchestration, delegation, subagent, parallel agents, swarm, judge, voting, best of n, consensus, agent team, teammate, ultracode, fan-out, pipeline, workflow), plus a gh search prs sweep. Also searched all 21 radroid/t3code fork issues. Upstream Discussions are disabled (hasDiscussionsEnabled: false), so issues and PRs are the complete surface.

                          Result: this is not a duplicate, but it is a strong overlap and the body says so explicitly.

                          Found and cross-referenced above: pingdotgg#3138 (Orchestration/Delegation — closest, owns the goal statement), pingdotgg#4649 (configurable pipelines — owns the staged-workflow half), pingdotgg#5218 / pingdotgg#4456 / pingdotgg#1740 / pingdotgg#538 / pingdotgg#2921 (subagent UI, nested threads, CLI orchestration — all the presentation half this issue defers), and in-flight PRs pingdotgg#4663, pingdotgg#5219.

                          Most decisive finding: PR pingdotgg#3638 is MERGED — but into the stack branch t3code/codex-turn-mapping, gated behind still-open pingdotgg#2829. Verified git merge-base --is-ancestor dac514e6b upstream/main → NOT an ancestor of upstream/main, origin/main, or fork HEAD, and no scheduled_tasks/orchestrator toolkit files are tracked in this checkout (ls apps/server/src/mcp/toolkitspreview only). So the capability exists upstream on a branch but is not reachable from main today, which is the justification for filing this on the fork rather than upstream.

                          Zero hits in either repo for judge panels, best-of-N, adjudication, voting, consensus, or swarm — that slice is genuinely unclaimed, and is called out as a deferred follow-up rather than folded into v1.

                          No fork issue duplicates this. The only adjacent fork issue is #38 (Loop Watch), which is about supervising a single long-running thread and explicitly rules cron out of scope; it is referenced for its session.status hazard note, not as an overlap.

                          Contribution

                          • I would be open to helping implement this.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            enhancementNew feature or request

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              [Feature]: Provider-agnostic orchestration ("ultracode for every model") — a fork-owned MCP toolkit that lets any adapter fan out into real T3 threads #43

                              Description

                              @radroid

                              Before submitting

                              • I searched existing issues and did not find a duplicate.
                              • I am describing a concrete problem or use case, not just a vague idea.

                              Area

                              apps/server

                              Problem or use case

                              What "ultracode" is, for a reader who has never used it

                              "Ultracode" is shorthand for a cluster of behaviours people expect from a high-effort coding agent, none of which are a single feature:

                              • Parallel fan-out. The agent decomposes a task and runs several workers at once (explore three call sites, write tests while another writes the implementation) instead of doing everything serially in one context.
                              • Structured output. Workers return machine-readable results (a verdict, a file list, a JSON summary) rather than prose the parent has to re-read and re-parse.
                              • Adversarial verification. A second agent is asked to break the first one's work — find the bug, find the missing test — rather than to agree with it.
                              • Judge panels / best-of-N. The same task is attempted N ways and a separate adjudicator picks a winner or merges the outputs.
                              • Loop-until-dry. The agent keeps iterating (fix → run tests → fix) until a stopping condition is met, rather than stopping after one pass.

                              What T3 Code actually has today

                              In this repo, ultracode is only a Claude effort level, not any of the above. It is a dropdown value that normalises to --effort xhigh plus a settings.ultracode: true flag handed to the Claude Agent SDK:

                              • apps/server/src/provider/Layers/ClaudeProvider.ts:75 (and :106, :142) — { value: "ultracode", label: "Ultracode" }, listed only for Claude models
                              • apps/server/src/provider/Layers/ClaudeProvider.ts:406if (effort === "ultracode") { return "xhigh"; }
                              • apps/server/src/provider/Layers/ClaudeAdapter.ts:3510 / :3521const ultracode = isClaudeUltracodeEffort(effort)...(ultracode ? { ultracode: true } : {})

                              So every one of the five ultracode behaviours, when it happens at all, happens inside the Claude CLI process, where T3 cannot inspect, interrupt, checkpoint, or steer it. That is the direct cause of "ultracode does not always work well in T3 Code": T3 renders provider-internal fan-out as flat, opaque rows.

                              • Subagent activity is a display heuristic, not a model: tool names containing agent/task/subagent get bucketed into the canonical item type collab_agent_tool_call, titled "Subagent task" (ClaudeAdapter.ts:596, :865). The same heuristic exists in OpenCodeAdapter.ts and CodexAdapter.ts and is absent from CursorAdapter.ts and GrokAdapter.ts.
                              • task.started / task.progress / task.completed exist in the canonical union (packages/contracts/src/providerRuntime.ts:177) and are ingested (apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts), but only ClaudeAdapter.ts ever emits them — verified: grep -rl 'task\.started' apps/server/src packages/contracts/src returns only ClaudeAdapter.ts, ProviderRuntimeIngestion.ts, and the contracts file. They are read-only telemetry about work T3 does not own.

                              Why this cannot be fixed inside the adapters

                              ProviderAdapterCapabilities has exactly one field, and all five adapters set it to the same value — the declarative capability surface differentiates nothing today:

                              // apps/server/src/provider/Services/ProviderAdapter.ts:28exportinterfaceProviderAdapterCapabilities{readonlysessionModelSwitch: ProviderSessionModelSwitchMode;}

                              ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703 — all sessionModelSwitch: "in-session".

                              There is no spawn/subagent primitive anywhere in ProviderAdapterShape, and there is no parent/child thread concept in T3's own domain: grep -rn parentThreadId apps/server/src packages/contracts/src returns nothing (the identifier exists only in the vendored Codex app-server generated schema). OrchestrationThread (packages/contracts/src/orchestration.ts:352) has no parent field.

                              Building fan-out inside adapters would mean five implementations against five unrelated protocols (Claude Agent SDK, Codex app-server JSON-RPC, ACP ×2, OpenCode HTTP), and two of them (Cursor, Grok) have no subagent concept at all. The repo already documents what happens when capability is under-declared: the web app hardcodes a per-driver allowlist because there is no flag to read —

                              // apps/web/src/outbox/composerSteering.logic.ts:13*Thecatchisthatthereisnosteeringcapabilityflaganywhereinthe*contracts,sosupport is implicitperadapterandhastobeassertedhere.// :43constSTEERABLE_PROVIDER_DRIVER_KINDS: ReadonlySet<string>=newSet([...]);

                              Proposed solution

                              Shape: one orchestrator thread + N child threads, driven entirely above the adapter layer

                              The orchestrator is an ordinary T3 thread on any provider. Its children are ordinary T3 threads. The only new machinery is a fork-owned MCP toolkit the model calls, plus a fork-owned store that tracks runs. Nothing touches ProviderAdapterShape, and no provider-native subagent feature is used or required.

                              Why this is provider-agnostic for free

                              Every one of the five adapters already mounts a T3-hosted MCP server into its provider session, with a per-thread bearer credential issued by T3:

                              • apps/server/src/provider/Layers/ClaudeAdapter.ts:3523
                              • apps/server/src/provider/Layers/CursorAdapter.ts:534
                              • apps/server/src/provider/Layers/CodexAdapter.ts:1397
                              • apps/server/src/provider/Layers/GrokAdapter.ts:572
                              • apps/server/src/provider/Layers/OpenCodeAdapter.ts:1217

                              all calling McpProviderSession.readMcpProviderSession(input.threadId). The invocation scope carries the calling thread's id, so a tool handler natively knows who the parent is:

                              // apps/server/src/mcp/McpInvocationContext.ts:12exportinterfaceMcpInvocationScope{readonlyenvironmentId: EnvironmentId;readonlythreadId: ThreadId;readonlyproviderSessionId: string;readonlyproviderInstanceId: ProviderInstanceId;readonlycapabilities: ReadonlySet<McpCapability>;readonlyissuedAt: number;}

                              Adding tools here means every provider gets them on the same day, with zero adapter edits.

                              New files (all fork-owned, zero upstream conflict)

                              apps/server/src/t3x/orchestrate/
                              tools.ts # Effect Tool.make definitions — copy apps/server/src/mcp/toolkits/preview/tools.ts
                              handlers.ts # copy apps/server/src/mcp/toolkits/preview/handlers.ts
                              RunStore.ts # JSON persistence in stateDir, modelled on t3x-auto-resume.json
                              Runner.ts # dispatches thread.create + thread.turn.start, watches for completion
                              index.ts # exports OrchestrateToolkitRegistrationLive + OrchestrateLayerLive
                              apps/server/src/t3x/mcp/index.ts # single aggregator for all future fork MCP toolkits
                              

                              apps/server/src/mcp/toolkits/preview/ is a complete, shipping template (tools.ts, handlers.ts, plus tests for both) — it is currently the only toolkit (ls apps/server/src/mcp/toolkitspreview).

                              The three tools (v1)

                              ToolBehaviour
                              subagent_spawnTakes { tasks: [{ label, prompt, providerInstanceId?, modelId?, runtimeMode? }] }, max 4. Creates one child thread per task, dispatches thread.turn.start with only the task prompt (no parent history), returns { runId, children: [{ childThreadId, label }] }immediately. Never blocks.
                              subagent_status{ runId } → per-child { label, childThreadId, phase } where phase comes from projectThreadAwareness. Cheap to poll.
                              subagent_result{ runId } or { childThreadId } → final assistant message text for each completed child, plus its phase.

                              Async-return + poll is deliberate: an MCP handler that blocks for minutes holds the parent's tool call open against adapter and CLI tool timeouts. Polling burns orchestrator turns but cannot deadlock.

                              Dispatch: reuse the proven fork precedent verbatim

                              AutoResumeReactor is already a server-side autonomous dispatcher that synthesises turns indistinguishable from a keystroke:

                              // apps/server/src/t3x/autoResume/Reactor.ts:111commandId: CommandId.make(`t3x-auto-resume:${commandUuid}`),

                              and it already writes its own audit trail with thread.activity.append (Reactor.ts:75; command declared at packages/contracts/src/orchestration.ts:865).

                              The orchestrator does the same: engine.dispatch({ type: "thread.create", ... }) then engine.dispatch({ type: "thread.turn.start", ... }) with commandId prefixed t3x-orchestrate:, and appends a breadcrumb to the parent thread for every spawn, phase change, and result read. That trail is provider-agnostic and shows up on Cursor/Grok/OpenCode identically — which is exactly why the design must not build on task.* (Claude-only, ClaudeAdapter.ts is the sole emitter).

                              Completion detection: already solved, do not reinvent

                              // packages/shared/src/agentAwareness.ts:53exportfunctionprojectThreadAwareness(...)// :77functionresolveThreadAwarenessPhase(thread): AgentAwarenessPhase|null

                              This reads only shell-projection fields — never provider internals — and is already consumed server-side by the fork's Web Push seam (apps/server/src/t3x/webPush/attention.ts). It returns waiting_for_approval / completed / null, which is precisely the "is this child done, stuck on an approval, or still working" distinction the orchestrator needs.

                              Do not gate on session.status alone. The existing code documents settled/teardown races, and fork issue #38 records the hazard that synthetic turns deadlock status-gated logic.

                              Result collection

                              OrchestrationLatestTurn (packages/contracts/src/orchestration.ts:335) carries assistantMessageId: Schema.NullOr(MessageId) at :341, and ProjectionSnapshotQuery.getThreadDetailById (apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168) returns the full thread including messages. subagent_result is a projection read, not a provider call.

                              Registration seam (the only upstream edit)

                              The MCP server builds its toolkit layer in apps/server/src/mcp/McpHttpServer.ts:206-225:

                              constPreviewStandardToolkitRegistrationLive=McpServer.toolkit(PreviewStandardToolkit).pipe(Layer.provide(PreviewStandardToolkitHandlersLive),);exportconstPreviewToolkitRegistrationLive=Layer.mergeAll(...);exportconstlayer=PreviewToolkitRegistrationLive.pipe(Layer.provideMerge(McpTransportLive));

                              Add one merged layer sourced from apps/server/src/t3x/mcp/index.ts — the same aggregator discipline apps/server/src/t3x/index.ts:71/:100 already enforces for T3xLayerLive / T3xRoutesLive. Every future fork toolkit then costs zero additional upstream churn.

                              Deliberately avoid the capability gate in v1.McpCapability is a closed union with one member (apps/server/src/mcp/McpInvocationContext.ts:10 — export type McpCapability = "preview";) and it is issued from a hardcoded set (apps/server/src/mcp/McpSessionRegistry.ts:131 — capabilities: new Set(["preview"])). Extending both would add two more ledger rows for no v1 benefit. Gate the toolkit on a fork-local setting inside handlers.ts instead, and revisit if v2 needs per-thread scoping.

                              What degrades, per adapter, and how

                              Because the design uses no provider-native subagent feature, the capability does not degrade — the presentation and reliability do:

                              ConcernClaudeCodexOpenCodeCursorGrok
                              Can call the tools (MCP mounted)yesyesyesyesyes
                              Spawn tool call renders as "Subagent task"yes (ClaudeAdapter.ts:596)yesyesno — generic tool rowno — generic tool row
                              Native task.* telemetryyesnononono
                              Structured judge verdictsnative --json-schemanative --output-schemaprompt-instructedprompt-instructedprompt-instructed
                              Steering the orchestrator mid-runyesno (excluded from STEERABLE_PROVIDER_DRIVER_KINDS)yesyesyes

                              Mitigations: emit the fan-out trail via thread.activity.append so it is legible everywhere (not task.*); accept prose results in v1 and defer structured verdicts; document that Codex orchestrators cannot be steered mid-run.

                              Fan-out cap and why it is low

                              Session startup is serialized. ensureSessionForThread (ProviderCommandReactor.ts:684) runs inside buildSendTurnRequestForThread, which runs inside a single-item-at-a-timeDrainableWorker (ProviderCommandReactor.ts:1323). Spawning N children means N sequential CLI process spawns before any of them runs. The turn itself is forked (ProviderCommandReactor.ts:1117-1119 — .pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped)), so once started they do run concurrently. Cap v1 at 4 and make the cap a fork-local constant.

                              Separately, ProviderSessionReaper.ts:17 sets DEFAULT_INACTIVITY_THRESHOLD_MS = 30 * 60 * 1000 — a parked child session is reaped after 30 minutes, invisibly to a polling orchestrator. subagent_status must report a reaped child as a distinct phase, not silently as "still working".

                              Restart durability

                              RunStore.ts persists { runId, parentThreadId, children: [{ childThreadId, label, phase }] } to stateDir, mirroring t3x-auto-resume.json. On boot, reconcile every non-terminal run against getThreadDetailById. Without this a server restart orphans every child thread with no trace.

                              Why this matters

                              Today "high-effort agent" in T3 Code means one Claude-only dropdown value that changes a flag on one provider's CLI, and whatever fan-out happens is invisible and unsteerable because it lives inside that CLI's process. This inverts that: each unit of parallel work becomes a first-class T3 thread, which means it inherits everything T3 already built — the activity timeline, approvals, checkpoints, per-turn diffs, interrupt, Web Push, auto-resume, mobile — with no per-feature work.

                              It is genuinely model-agnostic rather than nominally so: the orchestrator can be a Grok thread spawning Codex children, because the only interface it touches is OrchestrationEngineService.dispatch. That is a capability no single provider gives you, and it is the one thing a wrapper app is uniquely positioned to build.

                              It also removes a class of "why did nothing happen" confusion: a stalled child shows up as a thread with a phase and an approval card, not as a silent gap inside another process.

                              Smallest useful scope

                              v1 = async spawn + poll + collect, on shared cwd, no UI.

                              Ships:

                              • apps/server/src/t3x/orchestrate/ with subagent_spawn, subagent_status, subagent_result
                              • one aggregator seam line in apps/server/src/mcp/McpHttpServer.ts via apps/server/src/t3x/mcp/index.ts
                              • fan-out cap 4, fork-local constant
                              • RunStore JSON persistence + boot reconciliation
                              • thread.activity.append breadcrumbs on the parent for spawn / phase-change / result
                              • completion via projectThreadAwareness, results via getThreadDetailById + latestTurn.assistantMessageId
                              • tests mirroring apps/server/src/mcp/toolkits/preview/{tools,handlers}.test.ts

                              Explicitly out of scope for v1 (each is a follow-up issue):

                              • Git worktrees per child. This is the big one. Worktree bootstrap is not in the engine — it is implemented inline in the WebSocket transport (apps/server/src/ws.ts:750 dispatchBootstrapTurnStart, :891thread.create, :908gitWorkflow.createWorktree, :940runSetupProgram()). A server-side dispatcher calling engine.dispatch directly gets none of it. ws.ts measured churn is 41 commits in the 60 days before merge-base 64bf01619 — the highest of any candidate file. So v1 children share the parent's cwd, which makes it safe for read-heavy fan-out (parallel investigation, review, test authoring in disjoint directories) and unsafe for parallel edits to the same files. State that limit in the tool description so the model does not attempt it.
                              • Judge panels / best-of-N adjudication. Needs structured verdicts; see alternatives.
                              • UI grouping. With no parent link in OrchestrationThreadShell, 4 children land flat in the shell snapshot and each completion fires the needs-attention / Web Push coordinator.
                              • Extending ProviderAdapterCapabilities.
                              • Generic structured output in TextGenerationService.

                              Alternatives considered

                              1. Wait for upstream orchestrator-v2. PR pingdotgg#3638 already merged delegate_task / task_status / task_cancel / create_threads / t3_thread_* as orchestrator MCP tools — but into the stack branch t3code/codex-turn-mapping, not main. Its gate, pingdotgg#2829, is still open against main. Verified: merge commit dac514e6b is not an ancestor of upstream/main, origin/main, or fork HEAD. This is the strongest alternative and the honest reason to keep v1 tiny — the whole toolkit is three tools in fork-owned files and is cheap to delete when v2 lands. Naming should deliberately not collide with delegate_task/task_status so both can coexist during a transition.

                              2. Cherry-pick the v2 toolkit onto today's main.git show dac514e6b:apps/server/src/mcp/toolkits/orchestrator/tools.ts is real, reviewed code. But it targets the v2 orchestrator's data model, so porting it means porting or stubbing that model — a much larger change than three fork-local tools, and it creates the "parallel paths" hazard the fork has already been bitten by (a fork path that duplicates an upstream capability silently bypasses new upstream guards).

                              3. A declarative t3x reactor instead of model-callable tools. Model AutoResumeReactor directly: a server-side supervisor that runs a user-configured plan (stages, providers, prompts) without the model deciding anything. Better for deterministic verification pipelines and judge panels; worse for the "ultracode" experience the user asked for, where the model authors its own fan-out. They share ~80% of the machinery (RunStore, Runner, awareness polling) so (a) can be added on top of (b) later. Picking the MCP toolkit first is the choice that matches the request.

                              4. createSdkMcpServer from the Claude Agent SDK. Exported and typed but unused anywhere in the repo. Strictly worse here: Claude-only, requires a ClaudeAdapter.ts edit (churn 12), and defeats the entire point of the issue.

                              5. Extend TextGenerationService with a generic generateStructured(schema, prompt) for judge verdicts. The interface is closed at four git-flavoured operations (apps/server/src/textGeneration/TextGeneration.ts:77-82), so this means five implementations, and only Claude and Codex get a hard schema constraint — Cursor/Grok/OpenCode scrape prompt-instructed JSON with extractJsonObject. Cheaper: run the judge as its own child thread and parse its final assistant message with the same lenient extractor those three adapters already use. Loses per-call model/effort control.

                              6. Do nothing and document that ultracode is Claude-only. Legitimate. The counter is that ultracode's value is mostly the orchestration pattern, not the effort flag, and that pattern is provider-independent.

                              Risks or tradeoffs

                              Seam cost (measured against merge-base 64bf01619, 60-day window)

                              docs/t3x/SEAMS.md:5 records 34 upstream-owned files, +1616 / -187 lines, and :21 carries an explicit tripwire: "Before adding row 35, re-isolate something instead." This feature adds one row.

                              FileEditLinesChurnRisk
                              apps/server/src/mcp/McpHttpServer.tsone merged layer sourced from t3x/mcp/index.ts~36~18

                              Measured with git log --oneline --since="@$((MBTS-60*86400))" 64bf01619 -- <path>. For contrast, the paths this design deliberately avoids: apps/server/src/ws.ts = 41, apps/server/src/server.ts = 29, packages/contracts/src/orchestration.ts = 12, apps/server/src/provider/Layers/ClaudeAdapter.ts = 12, apps/web/src/components/ChatView.tsx = 63 (already the fork's worst row at risk 11466, SEAMS.md:41). Fork-owned apps/server/src/t3x/ has churn 0.

                              Rejected-but-tempting rows and their cost: McpSessionRegistry.ts (churn 7) + McpInvocationContext.ts (churn 3) for a capability gate — two rows for a v1 that has no per-thread scoping requirement. ProviderAdapter.ts has churn 0 and is cheap in isolation, but extending it forces edits to all five adapters (ClaudeAdapter.ts churn 12 among them).

                              The ledger must be updated in the same commit — header totals plus the new row — per the self-reference rule at SEAMS.md:22-28, and the t3x/mcp/index.ts aggregator should be documented there as the reason no further MCP rows will follow.

                              Parallel-paths hazard

                              This is the fork's known failure mode: a fork path that duplicates an upstream capability silently bypasses new upstream guards. Upstream is actively building the same thing (pingdotgg#3138, pingdotgg#4649, PRs pingdotgg#4663 / pingdotgg#5219 / pingdotgg#3638). Concretely: use distinct tool names from delegate_task/task_status, keep the entire implementation deletable in one rm -rf apps/server/src/t3x/orchestrate/, and re-check the SEAMS table every sync.

                              Machine and cost risks

                              • Fan-out thrashes a 16GB Mac. Each child is a full provider CLI process, and startup is serialized through the DrainableWorker. Cap 4, and expect the first child to start well before the fourth.
                              • No budget ceiling exists. Nothing in T3 bounds a run's token spend. A model that spawns 4 children, polls, and re-spawns can burn a lot unattended. v1 should hard-cap total spawns per parent thread.
                              • The 30-minute idle reaper (ProviderSessionReaper.ts:17) silently kills parked children.

                              UNVERIFIED — do not present these as settled

                              1. Do Cursor and Grok's ACP clients actually expose T3's MCP tools to the model? The mcpServers wiring is verified present (CursorAdapter.ts:544, GrokAdapter.ts:582), but nobody has confirmed the tool reaches the model's tool list on those providers. Experiment: start a thread on each of the five providers and ask the model to call mcp__t3-code__preview_status; a tool result on all five proves the channel end-to-end before any orchestration code is written. Do this first — it is the load-bearing assumption of the whole design.
                              2. Does a child thread created via engine.dispatch({type:"thread.create"}) without the ws.ts bootstrap path actually start a working session? All existing evidence is that AutoResumeReactor dispatches thread.turn.start to threads that already exist. Experiment: dispatch thread.create then thread.turn.start from a scratch Effect script and confirm turn.completed.
                              3. Whether the reaper's 30-minute teardown produces a distinguishable phase from projectThreadAwareness, or looks identical to "completed". Experiment: let a thread idle past 30 minutes and diff the projected phase.
                              4. What settings.ultracode: true actually does inside the Claude binary. This repo only sets the flag (ClaudeAdapter.ts:3521); the semantics are undocumented here. The claim "ultracode means parallel fan-out" is inferred from observed behaviour, not from this codebase. Experiment:strings the bundled Claude platform binary for ultracode and read the surrounding tool-registry code.
                              5. Whether 4 concurrent children is survivable on this machine. Nobody has measured it.

                              Examples or references

                              Upstream issues this overlaps (this is NOT a clean-slate feature)

                              File:line evidence (all verified in this checkout at main)

                              • Capabilities: apps/server/src/provider/Services/ProviderAdapter.ts:26-32; identical values at ClaudeAdapter.ts:3934, CodexAdapter.ts:1705, CursorAdapter.ts:1167, GrokAdapter.ts:1449, OpenCodeAdapter.ts:1703
                              • Five drivers: apps/server/src/provider/builtInDrivers.ts:47
                              • MCP mounted by every adapter: ClaudeAdapter.ts:3523, CursorAdapter.ts:534, CodexAdapter.ts:1397, GrokAdapter.ts:572, OpenCodeAdapter.ts:1217
                              • MCP scope carries parent threadId: apps/server/src/mcp/McpInvocationContext.ts:10-19; issuance apps/server/src/mcp/McpSessionRegistry.ts:131
                              • Toolkit registration point: apps/server/src/mcp/McpHttpServer.ts:206-225; only toolkit today: apps/server/src/mcp/toolkits/preview/ (tools.ts, handlers.ts, tools.test.ts, handlers.test.ts)
                              • Fork aggregators: apps/server/src/t3x/index.ts:71 (T3xLayerLive), :100 (T3xRoutesLive); existing seams autoResume/, webPush/
                              • Autonomous dispatch precedent: apps/server/src/t3x/autoResume/Reactor.ts:75 (thread.activity.append), :111 (commandId)
                              • Bootstrap trapped in the transport: apps/server/src/ws.ts:750, :891, :908, :940, :961
                              • Serialized startup / forked turn: apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:684, :1117-1119, :1323
                              • Idle reaper: apps/server/src/provider/Layers/ProviderSessionReaper.ts:17
                              • Completion phase: packages/shared/src/agentAwareness.ts:53, :77
                              • Result addressing: packages/contracts/src/orchestration.ts:335, :341; apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts:168
                              • Bootstrap schema (unused by engine): packages/contracts/src/orchestration.ts:677, :701, :720
                              • thread.activity.append command: packages/contracts/src/orchestration.ts:865
                              • Ultracode today: ClaudeProvider.ts:75, :406, :425; ClaudeAdapter.ts:3510, :3521
                              • Closed structured-output interface: apps/server/src/textGeneration/TextGeneration.ts:77-82
                              • Steering allowlist precedent: apps/web/src/outbox/composerSteering.logic.ts:13, :43, :51
                              • Seam ledger: docs/t3x/SEAMS.md:5, :21, :22-28, :41

                              Duplicate search performed before filing

                              Searched all 1,615 upstream pingdotgg/t3code issues (open + closed — count confirmed against gh api search/issuestotal_count) by dumping the full title corpus locally and grepping ~80 term variants, plus body-level gh search issues per concept (orchestration, delegation, subagent, parallel agents, swarm, judge, voting, best of n, consensus, agent team, teammate, ultracode, fan-out, pipeline, workflow), plus a gh search prs sweep. Also searched all 21 radroid/t3code fork issues. Upstream Discussions are disabled (hasDiscussionsEnabled: false), so issues and PRs are the complete surface.

                              Result: this is not a duplicate, but it is a strong overlap and the body says so explicitly.

                              Found and cross-referenced above: pingdotgg#3138 (Orchestration/Delegation — closest, owns the goal statement), pingdotgg#4649 (configurable pipelines — owns the staged-workflow half), pingdotgg#5218 / pingdotgg#4456 / pingdotgg#1740 / pingdotgg#538 / pingdotgg#2921 (subagent UI, nested threads, CLI orchestration — all the presentation half this issue defers), and in-flight PRs pingdotgg#4663, pingdotgg#5219.

                              Most decisive finding: PR pingdotgg#3638 is MERGED — but into the stack branch t3code/codex-turn-mapping, gated behind still-open pingdotgg#2829. Verified git merge-base --is-ancestor dac514e6b upstream/main → NOT an ancestor of upstream/main, origin/main, or fork HEAD, and no scheduled_tasks/orchestrator toolkit files are tracked in this checkout (ls apps/server/src/mcp/toolkitspreview only). So the capability exists upstream on a branch but is not reachable from main today, which is the justification for filing this on the fork rather than upstream.

                              Zero hits in either repo for judge panels, best-of-N, adjudication, voting, consensus, or swarm — that slice is genuinely unclaimed, and is called out as a deferred follow-up rather than folded into v1.

                              No fork issue duplicates this. The only adjacent fork issue is #38 (Loop Watch), which is about supervising a single long-running thread and explicitly rules cron out of scope; it is referenced for its session.status hazard note, not as an overlap.

                              Contribution

                              • I would be open to helping implement this.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                enhancementNew feature or request

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions