This document set describes the target architecture for the next orchestration model. It is not a patch plan for the current implementation and it intentionally ignores migration/backward compatibility. Those concerns should be handled after the target model is coherent.
V2 is an orchestrator rewrite, not a rewrite of the whole app domain platform. Existing non-orchestration domains, persistence/migration infrastructure, websocket/RPC infrastructure, and projection streaming semantics should be retained unless V2 exposes a concrete orchestration-specific gap.
V2 is designed around the real provider behavior observed in the Codex app-server probes, but it is not Codex-specific. Codex is treated as the richest protocol we currently have; weaker providers are adapted into the same model with app-owned ids and explicit capability flags.
| 1 | Native provider protocol |
| 2 | -> Rotating raw provider diagnostics |
| 3 | -> Provider adapter / normalizer |
| 4 | -> V2 orchestration event store using existing persistence patterns |
| 5 | -> Runtime execution graph |
| 6 | -> Conversation projection |
| 7 | -> UI / API views |
Raw provider diagnostics store what the provider actually sent or received for debugging and replay capture. The durable app state is the normalized orchestration state: events/entities, correlation refs, projections, and command receipts. The runtime graph stores what provider behavior means. The conversation projection stores what users see.
V2 should integrate with the existing orchestration command/event/projection infrastructure patterns rather than creating an unrelated event system. The V2-specific work is the graph model, provider lifecycle semantics, adapter contracts, normalizers, policies, and projections.
V2 owns its event schema. The V1 command and event unions are gone; V1 rows survive only as input to the legacy importer (apps/server/src/orchestration-v2/legacy/), and project events keep their own application event shape.
| 1 | AppThread |
| 2 | Run 1 |
| 3 | root ExecutionNode |
| 4 | tool ExecutionNode |
| 5 | approval ExecutionNode |
| 6 | subagent ExecutionNode |
| 7 | ProviderThread |
| 8 | child root ExecutionNode |
| 9 | Run 2 |
| 10 | root ExecutionNode |
An app thread is the user-visible conversation. A run is the counted user-visible turn. Execution nodes are the tree of work inside the run. Provider threads are provider-native conversation handles that can be attached to app threads or nested execution nodes.
Provider switches do not create new app threads. They create or reactivate provider threads and attach context handoff summaries to the next run.
Forks do create new app threads, but they should not force provider selection at fork time. Forking records thread lineage and a pending source point. The first run on the fork resolves that source point through native provider fork when possible or portable context transfer when needed.
The Codex app-server probes showed several protocol realities that the V2 model must preserve:
thread/status/changed can become idle before or around completion, but turn/completed is the authoritative turn terminal event.turn/interrupt completes as a request first; the interrupted terminal state arrives later through turn/completed.thread/rollback returns an authoritative provider thread snapshot after rollback.turn/completed events can occur before the parent/root turn completes.These observations are why V2 separates app runs, provider turns, and execution nodes.
V2 should reuse existing app infrastructure where that infrastructure is not the source of the orchestration bug class:
V2 may add V2-native orchestration events, projection tables, command policies, and provider execution services. It should not add a durable raw-provider-event database or a separate generic event bus unless the existing infrastructure cannot satisfy a documented V2 requirement.
V2 command dispatch returns the last committed stored-event sequence for accepted commands. Duplicate command ids must return the same receipt-backed result without re-running provider side effects. This is the API-level boundary the frontend uses for reconnect/recovery cursors.
The current app pipeline uses domain events plus reactors to trigger provider side effects:
| 1 | command |
| 2 | -> domain event(s) |
| 3 | -> projection |
| 4 | -> reactor observes event |
| 5 | -> provider side effect |
| 6 | -> provider output ingestion |
| 7 | -> domain event(s) |
This is valid, but it can become hard to trace because the provider side effect is implicit in a live subscription. If V2 needs stronger restart/recovery/debuggability, introduce a durable effect outbox for orchestration side effects:
| 1 | command |
| 2 | -> transaction: |
| 3 | append domain event(s) |
| 4 | append provider effect request(s) |
| 5 | -> projection |
| 6 | -> stream to UI |
| 7 | |
| 8 | effect worker |
| 9 | -> claim effect request |
| 10 | -> call provider |
| 11 | -> ingest provider output |
| 12 | -> append domain event(s) |
| 13 | -> mark effect request completed/failed |
Example effect request:
| 1 | type ProviderEffectRequest = { |
| 2 | id: ProviderEffectRequestId; |
| 3 | kind: |
| 4 | | "provider.turn.start" |
| 5 | | "provider.turn.interrupt" |
| 6 | | "provider.runtime-request.respond" |
| 7 | | "provider.thread.rollback" |
| 8 | | "provider.thread.fork"; |
| 9 | status: "pending" | "running" | "completed" | "failed" | "cancelled"; |
| 10 | threadId: ThreadId; |
| 11 | runId: RunId | null; |
| 12 | nodeId: NodeId | null; |
| 13 | provider: ProviderKind; |
| 14 | payload: unknown; |
| 15 | causationEventId: EventId; |
| 16 | commandId: CommandId | null; |
| 17 | attempts: number; |
| 18 | lastError: string | null; |
| 19 | }; |
This should be treated as an infrastructure improvement, not a prerequisite for the first V2 slice. The first V2 implementation can use the existing reactor pattern, but it should keep the provider side-effect boundary clear enough that moving to an outbox later does not require redesigning the graph or provider adapters.