Skip to content

Latest commit

 

History

History
92 lines (72 loc) · 4.88 KB

File metadata and controls

92 lines (72 loc) · 4.88 KB

Provider architecture

For maintainers. Using T3 Code? See docs/user.

A provider is the agent runtime that does the actual work. T3 Code supports several, and the orchestration layer does not know which one is behind a thread.

Built-in drivers

builtInDrivers.ts exports BUILT_IN_DRIVERS with five entries:

Driver kind Driver source
codex Drivers/CodexDriver.ts
claudeAgent Drivers/ClaudeDriver.ts
cursor Drivers/CursorDriver.ts
grok Drivers/GrokDriver.ts
opencode Drivers/OpenCodeDriver.ts

Each driver declares its driverKind, a configSchema, and a create function that builds an adapter in a child scope. Adapter implementations live beside them in apps/server/src/provider/Layers/ (CodexAdapter.ts, ClaudeAdapter.ts, and so on) and conform to ProviderAdapter.ts. Read the driver plus its adapter to see how a specific agent's transport, config, and event shapes are mapped.

Registry and routing

Two registries separate configuration from live processes:

  • ProviderInstanceRegistry keys configured instances by ProviderInstanceId. Creating one looks up the driver by driverKind, decodes entry.config with that driver's schema, opens a child scope, and calls driver.create.
  • ProviderAdapterRegistry resolves an instance ID to its live adapter via getByInstance.

ProviderService sits on top. It combines the adapter registry with the provider session directory to route session and turn operations for a thread, so callers name a thread, not an agent.

Adding a driver means writing the driver plus adapter and adding it to BUILT_IN_DRIVERS. No orchestration, contract, or client change is required for the common case.

How provider work is requested

Clients never call a provider directly. They dispatch orchestration commands over the RPC method orchestration.dispatchCommand, defined with the rest of the orchestration surface in orchestration.ts. The client-dispatchable provider-facing commands are thread.turn.start, thread.turn.interrupt, thread.approval.respond, thread.user-input.respond, thread.checkpoint.revert, and thread.session.stop, plus the mode setters thread.runtime-mode.set and thread.interaction-mode.set.

The engine persists an event for the command, and a server-side reactor performs the provider call. Provider output comes back as internal commands such as thread.message.assistant.delta and thread.session.set, which clients observe through orchestration.subscribeThread. See overview.md for the command/event loop.

Server-side workers

Provider work flows through three queue-backed workers. All three are built with makeDrainableWorker from DrainableWorker.ts and expose drain for deterministic test synchronization.

  1. ProviderRuntimeIngestion consumes provider runtime streams and emits orchestration commands.
  2. ProviderCommandReactor reacts to orchestration intent events and dispatches provider calls.
  3. CheckpointReactor captures workspace checkpoints on turn start and completion, and performs reverts.

Buffered assistant delivery

A thread in buffered assistant delivery mode accumulates assistant text instead of streaming each delta. The buffer is not held until turn completion. In ProviderRuntimeIngestion, MAX_BUFFERED_ASSISTANT_CHARS is 24,000: the append that would exceed it invalidates the buffer and spills the whole accumulated text as one delta. The buffer also flushes at interaction boundaries, when a request opens (approval) or user input is requested, via flushBufferedAssistantMessagesForTurn.