Providers are the bridge between T3 Code’s orchestration layer and external agent systems. The provider architecture uses a clean adapter pattern to enable multi-provider support while keeping provider-specific logic isolated.
Provider Architecture
The provider layer follows a hierarchical service architecture:
┌─────────────────────────────────────────┐
│ ProviderService │
│ (Cross-provider facade) │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ ProviderAdapterRegistry │
│ (Resolves provider → adapter) │
└──────────────┬──────────────────────────┘
│
┌─────┴─────┐
│ │
┌────────▼─────┐ ┌───▼──────────┐
│ CodexAdapter │ │ Future │
│ │ │ Adapters │
└──────────────┘ └──────────────┘
T3 Code is currently Codex-first , with support for additional providers (like Claude Code) reserved in the contracts and architecture.
Provider Service
The ProviderService acts as the unified facade for all provider operations:
apps/server/src/provider/Services/ProviderService.ts
export interface ProviderServiceShape {
// Session lifecycle
readonly startSession : (
threadId : ThreadId ,
input : ProviderSessionStartInput ,
) => Effect . Effect < ProviderSession , ProviderServiceError >;
readonly stopSession : (
input : ProviderStopSessionInput ,
) => Effect . Effect < void , ProviderServiceError >;
// Turn execution
readonly sendTurn : (
input : ProviderSendTurnInput ,
) => Effect . Effect < ProviderTurnStartResult , ProviderServiceError >;
readonly interruptTurn : (
input : ProviderInterruptTurnInput ,
) => Effect . Effect < void , ProviderServiceError >;
// Interactive requests
readonly respondToRequest : (
input : ProviderRespondToRequestInput ,
) => Effect . Effect < void , ProviderServiceError >;
readonly respondToUserInput : (
input : ProviderRespondToUserInputInput ,
) => Effect . Effect < void , ProviderServiceError >;
// Conversation management
readonly rollbackConversation : ( input : {
readonly threadId : ThreadId ;
readonly numTurns : number ;
}) => Effect . Effect < void , ProviderServiceError >;
// Metadata
readonly listSessions : () => Effect . Effect < ReadonlyArray < ProviderSession >>;
readonly getCapabilities : (
provider : ProviderKind ,
) => Effect . Effect < ProviderAdapterCapabilities , ProviderServiceError >;
// Event stream
readonly streamEvents : Stream . Stream < ProviderRuntimeEvent >;
}
ProviderService resolves provider adapters through ProviderAdapterRegistry, routes session-scoped calls via ProviderSessionDirectory, and exposes one unified provider event stream to callers.
Provider Adapter Contract
Each provider implements the ProviderAdapterShape interface:
apps/server/src/provider/Services/ProviderAdapter.ts
export interface ProviderAdapterShape < TError > {
readonly provider : ProviderKind ;
readonly capabilities : ProviderAdapterCapabilities ;
// Session operations
readonly startSession : (
input : ProviderSessionStartInput ,
) => Effect . Effect < ProviderSession , TError >;
readonly stopSession : (
threadId : ThreadId
) => Effect . Effect < void , TError >;
readonly stopAll : () => Effect . Effect < void , TError >;
// Turn operations
readonly sendTurn : (
input : ProviderSendTurnInput ,
) => Effect . Effect < ProviderTurnStartResult , TError >;
readonly interruptTurn : (
threadId : ThreadId ,
turnId ?: TurnId ,
) => Effect . Effect < void , TError >;
// Approval handling
readonly respondToRequest : (
threadId : ThreadId ,
requestId : ApprovalRequestId ,
decision : ProviderApprovalDecision ,
) => Effect . Effect < void , TError >;
readonly respondToUserInput : (
threadId : ThreadId ,
requestId : ApprovalRequestId ,
answers : ProviderUserInputAnswers ,
) => Effect . Effect < void , TError >;
// Thread state
readonly readThread : (
threadId : ThreadId ,
) => Effect . Effect < ProviderThreadSnapshot , TError >;
readonly rollbackThread : (
threadId : ThreadId ,
numTurns : number ,
) => Effect . Effect < ProviderThreadSnapshot , TError >;
// Session directory
readonly listSessions : () => Effect . Effect < ReadonlyArray < ProviderSession >>;
readonly hasSession : ( threadId : ThreadId ) => Effect . Effect < boolean >;
// Event stream
readonly streamEvents : Stream . Stream < ProviderRuntimeEvent >;
}
Provider Capabilities
Adapters declare their capabilities through static metadata:
export type ProviderSessionModelSwitchMode =
| "in-session" // Can change model without restarting
| "restart-session" // Must restart session to change model
| "unsupported" ; // Model switching not supported
export interface ProviderAdapterCapabilities {
readonly sessionModelSwitch : ProviderSessionModelSwitchMode ;
}
Capabilities enable T3 Code to adapt its behavior based on provider limitations. For example, the UI can prompt for session restart when changing models if the provider requires it.
Codex Adapter
The CodexAdapter implements provider support for Codex:
Process Management
Spawns and manages codex app-server child processes via CodexAppServerManager
JSON-RPC Communication
Handles JSON-RPC protocol over stdio for session and turn operations
Event Transformation
Transforms Codex notifications into standardized ProviderRuntimeEvent format
Session Directory Integration
Registers sessions with ProviderSessionDirectory for tracking and routing
Codex-Specific Features
Detects Codex account type and plan to determine model availability: interface CodexAccountSnapshot {
readonly type : "apiKey" | "chatgpt" | "unknown" ;
readonly planType : CodexPlanType | null ;
readonly sparkEnabled : boolean ;
}
// Spark model restrictions based on plan
const CODEX_SPARK_DISABLED_PLAN_TYPES = new Set < CodexPlanType >([
"free" , "go" , "plus"
]);
Normalizes model slugs and applies account-based restrictions: export function resolveCodexModelForAccount (
model : string | undefined ,
account : CodexAccountSnapshot ,
) : string | undefined {
if ( model !== CODEX_SPARK_MODEL || account . sparkEnabled ) {
return model ;
}
return CODEX_DEFAULT_MODEL ; // Fallback for restricted accounts
}
Supports Codex-specific collaboration modes (default and plan): function buildCodexCollaborationMode ( input : {
readonly interactionMode ?: "default" | "plan" ;
readonly model ?: string ;
readonly effort ?: string ;
}) : {
mode : "default" | "plan" ;
settings : {
model : string ;
reasoning_effort : string ;
developer_instructions : string ;
};
};
Implements intelligent thread resume with automatic fallback: const RECOVERABLE_THREAD_RESUME_ERROR_SNIPPETS = [
"not found" ,
"missing thread" ,
"no such thread" ,
"unknown thread" ,
"does not exist" ,
];
Provider Events
Providers emit events through a unified event stream:
packages/contracts/src/provider.ts
export const ProviderEvent = Schema . Struct ({
id: EventId ,
kind: ProviderEventKind , // "session" | "notification" | "request" | "error"
provider: ProviderKind , // "codex"
threadId: ThreadId ,
createdAt: IsoDateTime ,
method: TrimmedNonEmptyStringSchema ,
message: Schema . optional ( TrimmedNonEmptyStringSchema ),
turnId: Schema . optional ( TurnId ),
itemId: Schema . optional ( ProviderItemId ),
requestId: Schema . optional ( ApprovalRequestId ),
requestKind: Schema . optional ( ProviderRequestKind ),
textDelta: Schema . optional ( Schema . String ),
payload: Schema . optional ( Schema . Unknown ),
});
Event Kinds
session Lifecycle events: connecting, ready, closed
notification Provider activity: tool calls, message deltas, turn completion
request Approval requests: command, file-read, file-change
error Error events: session errors, protocol errors
Provider Session Directory
The ProviderSessionDirectory tracks active sessions across all providers:
interface ProviderSessionDirectory {
// Register new session
register (
session : ProviderSession
) : Effect . Effect < void , ProviderSessionDirectoryError >;
// Update session state
update (
threadId : ThreadId ,
updates : Partial < ProviderSession >,
) : Effect . Effect < void , ProviderSessionDirectoryError >;
// Get session by thread ID
get (
threadId : ThreadId
) : Effect . Effect < ProviderSession | undefined >;
// Remove session
remove ( threadId : ThreadId ) : Effect . Effect < void >;
// List all sessions
list () : Effect . Effect < ReadonlyArray < ProviderSession >>;
// Event stream for session changes
streamEvents : Stream . Stream < ProviderSessionDirectoryEvent >;
}
The session directory maintains an in-memory registry of active sessions, enabling quick lookups and routing without database queries.
Provider Adapter Registry
The ProviderAdapterRegistry resolves provider kinds to concrete adapters:
interface ProviderAdapterRegistry {
// Register an adapter
register (
adapter : ProviderAdapterShape < unknown >
) : Effect . Effect < void >;
// Get adapter by provider kind
get (
provider : ProviderKind
) : Effect . Effect < ProviderAdapterShape < unknown >, ProviderServiceError >;
// List all registered providers
listProviders () : Effect . Effect < ReadonlyArray < ProviderKind >>;
}
Adding New Providers
To add a new provider adapter:
Update Contracts
Add the provider to ProviderKind literal in packages/contracts/src/orchestration.ts: export const ProviderKind = Schema . Literal ( "codex" , "claude-code" );
Implement Adapter
Create a new adapter implementing ProviderAdapterShape<TError>: export class ClaudeCodeAdapter implements ProviderAdapterShape < ClaudeCodeError > {
readonly provider = "claude-code" ;
readonly capabilities = { sessionModelSwitch: "in-session" };
// Implement all required methods...
}
Register Adapter
Register the adapter with ProviderAdapterRegistry during server startup
Update UI
Add provider selection and configuration UI in the web app
Provider Errors
Providers use typed errors for robust error handling:
apps/server/src/provider/Errors.ts
export type ProviderServiceError =
| ProviderValidationError
| ProviderSessionError
| CodexError
| CheckpointError ;
export class ProviderSessionError extends Data . TaggedError ( "ProviderSessionError" )<{
readonly reason : string ;
}> {}
export class CodexError extends Data . TaggedError ( "CodexError" )<{
readonly reason : string ;
readonly cause ?: unknown ;
}> {}
Using Effect’s typed errors enables exhaustive error handling and better debugging at the call site.
Event Projection
Provider events are projected into orchestration domain events:
ProviderRuntimeEvent
↓
[ Server - side projection ]
↓
OrchestrationEvent
↓
[ WebSocket push ]
↓
Client receives via "orchestration.domainEvent" channel
This projection:
Decouples provider protocols from client expectations
Enables consistent event replay and debugging
Supports multiple concurrent provider sessions
Facilitates event sourcing and audit trails
Best Practices
Isolate Provider Logic Keep provider-specific code in adapters, not in ProviderService
Use Typed Errors Define specific error types for each provider to enable precise error handling
Emit Rich Events Include sufficient context in events for debugging and replay
Handle Graceful Degradation Implement fallback behavior for unsupported capabilities
Provider Health Monitoring
The ProviderHealth service monitors adapter availability:
interface ProviderHealth {
// Check if provider is healthy
check (
provider : ProviderKind
) : Effect . Effect < ProviderHealthStatus >;
// Get health status for all providers
checkAll () : Effect . Effect < ReadonlyArray < ProviderHealthStatus >>;
}
interface ProviderHealthStatus {
provider : ProviderKind ;
healthy : boolean ;
message ?: string ;
lastChecked : IsoDateTime ;
}
Next Steps
Sessions Learn about session lifecycle and management
Runtime Modes Understand approval policies and security controls
Architecture Explore the overall system design
API Reference Browse the complete API documentation