Co-authored-by: maria-rcks <maria@kuuro.net> Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com> Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> Co-authored-by: Vitalii Yehorov <vitalyiegorov@gmail.com> Co-authored-by: Jake Leventhal <jakeleventhal@me.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Alex Southwell <saphid@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Nicholas Wasmiller <derped@mineperial.com> Co-authored-by: PB <poilmb@gmail.com> Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com> Co-authored-by: Yash Singh <saiansh2525@gmail.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: Gabriel De Andrade <30420087+gabrielelpidio@users.noreply.github.com> Co-authored-by: Dara Adedeji <76637177+SunkenInTime@users.noreply.github.com> Co-authored-by: scratchyone <scratchywon@gmail.com> Co-authored-by: Dominic Roy <dominic@sdko.org> Co-authored-by: chukfinley <chuk@chuk.dev> Co-authored-by: Primož Ajdišek <bigpod@bigpod.si> Co-authored-by: benthecarman <benthecarman@live.com> Co-authored-by: NaveDanan <nave0712@gmail.com> Co-authored-by: aaditagrawal <103925638+aaditagrawal@users.noreply.github.com> Co-authored-by: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Co-authored-by: Nick Anisimov <n.anisimov.23@gmail.com> Co-authored-by: MacKinley Smith <smithmackinley@gmail.com> Co-authored-by: Yordis Prieto <yordis.prieto@gmail.com> Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: AKolenda <akole779@mtroyal.ca> Co-authored-by: Guillermo Casanova <75276669+Gigioxx@users.noreply.github.com> Co-authored-by: Otavio Salvador <otavio@ossystems.com.br> Co-authored-by: Shirish Pothi <183252392+shirishpothi@users.noreply.github.com> Co-authored-by: Ishaan Kothari <ishaanko.mail@gmail.com> Co-authored-by: Bob Fowler <bob@rjf.ca> Co-authored-by: Anton Bezdenezhnykh <gamer392@yandex.ru> Co-authored-by: ValeraZSD <48602572+ValeraZSD@users.noreply.github.com> Co-authored-by: Ephraim <ephraim39hr14m@gmail.com> Co-authored-by: Ryan Ilano <ryanilano@users.noreply.github.com> Co-authored-by: Alex <me@pixp.cc> Co-authored-by: maco <gosarmarcel7@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Tristan Knight <admin@snappeh.com> Co-authored-by: PR Batch Tester <agent@local.test> Co-authored-by: oliver <97427849+flamboh@users.noreply.github.com> Co-authored-by: kamkm <99585688+Kamkmgamer@users.noreply.github.com> Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
8.9 KiB
Orchestration V2
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.
Documents
- Core Graph And Data Model
- Entity IDs And Correlation
- Feature Lifecycles
- Thread Lineage And Context Transfer
- Provider Switching And Context Handoff
- Orchestrator MCP Server
- Provider Capability System
- Testing Strategy
Primary Goals
- Preserve provider-native lifecycle fidelity without leaking provider ids into app identity.
- Model root turns, subagents, tools, approvals, plans, and checkpoints as one execution graph.
- Make root-run completion the only event that completes a user-visible turn.
- Support forking from normal threads and completed subagent/provider threads.
- Support changing providers between runs as a first-class context handoff.
- Model forks, provider handoffs, merge-back, and subagents through shared thread lineage and context transfer primitives.
- Support providers with weak or missing ids through deterministic app-owned id allocation.
- Make feature behavior capability-driven, not provider-name-driven.
Key Invariants
- App ids are primary. Provider ids are refs.
- Provider events are never rewritten to look like another provider event.
- Child execution completion never closes the parent run.
- Checkpoints attach to checkpointable execution scopes. Root-run checkpoints advance app run history; child/subagent checkpoints are nested and do not advance the parent run count.
- Rollback is expressed in app run count and reconciled with provider conversation state.
- Every command targets app ids; adapters translate to provider refs at the edge.
- Missing provider capability is represented explicitly and handled by policy.
- Provider switches create explicit context handoff artifacts; they are not hidden prompt hacks.
- Forks record app-level lineage first. Provider-native forks and portable context handoffs are lazy resolution strategies chosen when a run starts.
- Tests should prefer replay-backed integration coverage over mocked unit tests. The only normal substitute in orchestration tests is the provider runtime transport.
Conceptual Layers
Native provider protocol
-> Rotating raw provider diagnostics
-> Provider adapter / normalizer
-> V2 orchestration event store using existing persistence patterns
-> Runtime execution graph
-> Conversation projection
-> 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.
Minimal Mental Model
AppThread
Run 1
root ExecutionNode
tool ExecutionNode
approval ExecutionNode
subagent ExecutionNode
ProviderThread
child root ExecutionNode
Run 2
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.
Probe-Derived Requirements
The Codex app-server probes showed several protocol realities that the V2 model must preserve:
thread/status/changedcan become idle before or around completion, butturn/completedis the authoritative turn terminal event.turn/interruptcompletes as a request first; the interrupted terminal state arrives later throughturn/completed.- Approval requests are provider-initiated JSON-RPC requests scoped to provider thread, turn, and item.
thread/rollbackreturns an authoritative provider thread snapshot after rollback.- Subagent child
turn/completedevents can occur before the parent/root turn completes. - Child provider turns are real provider turns and must not be remapped onto the parent provider turn id.
These observations are why V2 separates app runs, provider turns, and execution nodes.
Existing Platform Boundaries
V2 should reuse existing app infrastructure where that infrastructure is not the source of the orchestration bug class:
- Command dispatch should keep the existing serialized/idempotent command handling and command receipt pattern.
- Projection streams should keep the existing snapshot-plus-cursor semantics used by the current API.
- Persistence should keep the existing SQLite/migration/repository infrastructure and projection-stream semantics.
- Raw provider frames should continue to be diagnostic log data, with bounded retention through rotating logs.
- Replay tests should replace only the provider transport/process boundary.
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.
Tracked Follow-Up: Durable Effect Outbox
The current app pipeline uses domain events plus reactors to trigger provider side effects:
command
-> domain event(s)
-> projection
-> reactor observes event
-> provider side effect
-> provider output ingestion
-> 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:
command
-> transaction:
append domain event(s)
append provider effect request(s)
-> projection
-> stream to UI
effect worker
-> claim effect request
-> call provider
-> ingest provider output
-> append domain event(s)
-> mark effect request completed/failed
Example effect request:
type ProviderEffectRequest = {
id: ProviderEffectRequestId;
kind:
| "provider.turn.start"
| "provider.turn.interrupt"
| "provider.runtime-request.respond"
| "provider.thread.rollback"
| "provider.thread.fork";
status: "pending" | "running" | "completed" | "failed" | "cancelled";
threadId: ThreadId;
runId: RunId | null;
nodeId: NodeId | null;
provider: ProviderKind;
payload: unknown;
causationEventId: EventId;
commandId: CommandId | null;
attempts: number;
lastError: string | null;
};
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.