Skip to content

Latest commit

 

History

History
143 lines (121 loc) · 7.3 KB

File metadata and controls

143 lines (121 loc) · 7.3 KB

Architecture

T3 Code runs as a Node.js WebSocket server that wraps codex app-server (JSON-RPC over stdio) and serves a React web app.

┌─────────────────────────────────┐
│  Browser (React + Vite)         │
│  wsTransport (state machine)    │
│  Typed push decode at boundary  │
└──────────┬──────────────────────┘
           │ ws://localhost:3773
┌──────────▼──────────────────────┐
│  apps/server (Node.js)          │
│  WebSocket + HTTP static server │
│  ServerPushBus (ordered pushes) │
│  ServerReadiness (startup gate) │
│  OrchestrationEngine            │
│  ProviderService                │
│  CheckpointReactor              │
│  RuntimeReceiptBus              │
└──────────┬──────────────────────┘
           │ JSON-RPC over stdio
┌──────────▼──────────────────────┐
│  codex app-server               │
└─────────────────────────────────┘

Components

  • Browser app: The React app renders session state, owns the client-side WebSocket transport, and treats typed push events as the boundary between server runtime details and UI state.

  • Server: apps/server is the main coordinator. It serves the web app, accepts WebSocket requests, waits for startup readiness before welcoming clients, and sends all outbound pushes through a single ordered push path.

  • Provider runtime: codex app-server does the actual provider/session work. The server talks to it over JSON-RPC on stdio and translates those runtime events into the app's orchestration model.

  • Background workers: Long-running async flows such as runtime ingestion, command reaction, and checkpoint processing run as queue-backed workers. This keeps work ordered, reduces timing races, and gives tests a deterministic way to wait for the system to go idle.

  • Runtime signals: The server emits lightweight typed receipts when important async milestones finish, such as checkpoint capture, diff finalization, or a turn becoming fully quiescent. Tests and orchestration code wait on these signals instead of polling internal state.

Event Lifecycle

Startup and client connect

sequenceDiagram
    participant Browser
    participant Transport as WsTransport
    participant Server as wsServer
    participant Layers as serverLayers
    participant Ready as ServerReadiness
    participant Push as ServerPushBus

    Browser->>Transport: Load app and open WebSocket
    Transport->>Server: Connect
    Server->>Layers: Start runtime services
    Server->>Ready: Wait for startup barriers
    Ready-->>Server: Ready
    Server->>Push: Publish server.welcome
    Push-->>Transport: Ordered welcome push
    Transport-->>Browser: Hydrate initial state
Loading
  1. The browser boots WsTransport and registers typed listeners in wsNativeApi.
  2. The server accepts the connection in wsServer and brings up the runtime graph defined in serverLayers.
  3. ServerReadiness waits until the key startup barriers are complete.
  4. Once the server is ready, wsServer sends server.welcome from the contracts in ws.ts through ServerPushBus.
  5. The browser receives that ordered push through WsTransport, and wsNativeApi uses it to seed local client state.

User turn flow

sequenceDiagram
    participant Browser
    participant Transport as WsTransport
    participant Server as wsServer
    participant Provider as ProviderService
    participant Codex as codex app-server
    participant Ingest as ProviderRuntimeIngestion
    participant Engine as OrchestrationEngine
    participant Push as ServerPushBus

    Browser->>Transport: Send user action
    Transport->>Server: Typed WebSocket request
    Server->>Provider: Route request
    Provider->>Codex: JSON-RPC over stdio
    Codex-->>Ingest: Provider runtime events
    Ingest->>Engine: Normalize into orchestration events
    Engine-->>Server: Domain events
    Server->>Push: Publish orchestration.domainEvent
    Push-->>Browser: Typed push
Loading
  1. A user action in the browser becomes a typed request through WsTransport and the browser API layer in nativeApi.
  2. wsServer decodes that request using the shared WebSocket contracts in ws.ts and routes it to the right service.
  3. ProviderService starts or resumes a session and talks to codex app-server over JSON-RPC on stdio.
  4. Provider-native events are pulled back into the server by ProviderRuntimeIngestion, which converts them into orchestration events.
  5. OrchestrationEngine persists those events, updates the read model, and exposes them as domain events.
  6. wsServer pushes those updates to the browser through ServerPushBus on channels defined in orchestration.ts.

Async completion flow

sequenceDiagram
    participant Server as wsServer
    participant Worker as Queue-backed workers
    participant Cmd as ProviderCommandReactor
    participant Checkpoint as CheckpointReactor
    participant Receipt as RuntimeReceiptBus
    participant Push as ServerPushBus
    participant Browser

    Server->>Worker: Enqueue follow-up work
    Worker->>Cmd: Process provider commands
    Worker->>Checkpoint: Process checkpoint tasks
    Checkpoint->>Receipt: Publish completion receipt
    Cmd-->>Server: Produce orchestration changes
    Checkpoint-->>Server: Produce orchestration changes
    Server->>Push: Publish resulting state updates
    Push-->>Browser: User-visible push
Loading
  1. Some work continues after the initial request returns, especially in ProviderRuntimeIngestion, ProviderCommandReactor, and CheckpointReactor.
  2. These flows run as queue-backed workers using DrainableWorker, which helps keep side effects ordered and test synchronization deterministic.
  3. When a milestone completes, the server emits a typed receipt on RuntimeReceiptBus, such as checkpoint completion or turn quiescence.
  4. Tests and orchestration code wait on those receipts instead of polling git state, projections, or timers.
  5. Any user-visible state changes produced by that async work still go back through wsServer and ServerPushBus.