A session represents an active connection between T3 Code and a provider (currently Codex). Sessions manage conversation threads, turn execution, and maintain stateful connections to the underlying agent infrastructure.
Session Lifecycle
Sessions progress through distinct states during their lifetime:
Connecting
The server spawns a codex app-server process and establishes JSON-RPC communication over stdio.
Ready
The session is idle and ready to accept new turns. No active agent execution is in progress.
Running
A turn is actively executing. The agent is processing user input, running tools, or generating responses.
Error
A recoverable error occurred. The session may be able to continue with intervention.
Closed
The session has been terminated and can no longer accept commands.
Session Schema
Sessions are represented by the ProviderSession type:
packages/contracts/src/provider.ts
export const ProviderSession = Schema . Struct ({
provider: ProviderKind , // "codex"
status: ProviderSessionStatus , // Current state
runtimeMode: RuntimeMode , // "full-access" | "approval-required"
cwd: Schema . optional ( TrimmedNonEmptyStringSchema ),
model: Schema . optional ( TrimmedNonEmptyStringSchema ),
threadId: ThreadId ,
resumeCursor: Schema . optional ( Schema . Unknown ),
activeTurnId: Schema . optional ( TurnId ),
createdAt: IsoDateTime ,
updatedAt: IsoDateTime ,
lastError: Schema . optional ( TrimmedNonEmptyStringSchema ),
});
The resumeCursor field stores provider-specific state needed to resume a thread after restart. For Codex, this contains the provider’s internal thread ID.
Starting a Session
Sessions are started via the ProviderService.startSession method:
interface ProviderSessionStartInput {
threadId : ThreadId ;
provider ?: "codex" ;
cwd ?: string ; // Working directory
model ?: string ; // Model identifier
resumeCursor ?: unknown ; // Resume existing thread
runtimeMode : RuntimeMode ; // Approval policy
providerOptions ?: { // Provider-specific config
codex ?: {
binaryPath ?: string ;
homePath ?: string ;
};
};
}
Starting a New Session
Resuming an Existing Session
const session = await providerService . startSession ( threadId , {
threadId ,
provider: "codex" ,
cwd: "/path/to/workspace" ,
model: "gpt-5.4" ,
runtimeMode: "full-access" ,
});
Session Resume Behavior
When a resumeCursor is provided, T3 Code attempts to resume the existing Codex thread:
Resume Attempt
Send thread/resume JSON-RPC request to Codex app-server with the provider thread ID
Success Path
If resume succeeds, the session reconnects to the existing thread state
Fallback Path
If resume fails with a recoverable error (thread not found, missing thread), automatically fall back to starting a fresh thread
Error Path
If resume fails with an unrecoverable error, the session transitions to error state
apps/server/src/codexAppServerManager.ts
if ( resumeThreadId ) {
try {
threadOpenMethod = "thread/resume" ;
threadOpenResponse = await this . sendRequest ( context , "thread/resume" , {
... sessionOverrides ,
threadId: resumeThreadId ,
});
} catch ( error ) {
if ( ! isRecoverableThreadResumeError ( error )) {
throw error ; // Unrecoverable
}
// Fallback to fresh start
threadOpenMethod = "thread/start" ;
threadOpenResponse = await this . sendRequest (
context ,
"thread/start" ,
threadStartParams
);
}
}
Recoverable Resume Errors : Errors containing snippets like “not found”, “missing thread”, “no such thread”, “unknown thread”, or “does not exist” trigger automatic fallback to a fresh thread start.
Session Events
Sessions emit lifecycle events through the provider event stream:
Emitted when the session begins connecting to the provider. {
kind : "session" ,
method : "session/connecting" ,
message : "Starting codex app-server"
}
session/threadOpenRequested
Emitted when attempting to open or resume a thread. {
kind : "session" ,
method : "session/threadOpenRequested" ,
message : "Attempting to resume thread abc123."
}
session/threadResumeFallback
Emitted when resume fails and falls back to fresh start. {
kind : "session" ,
method : "session/threadResumeFallback" ,
message : "Could not resume thread abc123; started a new thread instead."
}
Emitted when the session is ready to accept turns. {
kind : "session" ,
method : "session/ready" ,
message : "Connected to thread abc123"
}
Emitted when the session has been closed. {
kind : "session" ,
method : "session/closed" ,
message : "Session stopped"
}
Sending Turns
Once a session is ready, you can send turns to the agent:
interface ProviderSendTurnInput {
threadId : ThreadId ;
input ?: string ; // User message text
attachments ?: ChatAttachment []; // Images, etc.
model ?: string ; // Override model
interactionMode ?: "default" | "plan" ; // Interaction mode
}
const result = await providerService . sendTurn ({
threadId ,
input: "Add error handling to the API routes" ,
interactionMode: "default" ,
});
// result.turnId - Identifier for this turn
// result.resumeCursor - Updated resume state
When a turn starts, the session transitions from ready to running state and sets activeTurnId to the new turn’s ID.
Turn Lifecycle
Turns represent discrete agent execution cycles:
type TurnState =
| "running" // Turn is executing
| "interrupted" // User interrupted
| "completed" // Turn finished successfully
| "error" ; // Turn failed
Turn Events
Turns emit various events during execution:
turn/started Turn execution has begun
turn/completed Turn finished (success or error)
item/agentMessage/delta Streaming text from agent response
item/tool/started Tool execution started
Interrupting Turns
Active turns can be interrupted:
await providerService . interruptTurn ({
threadId ,
turnId , // Optional: defaults to active turn
});
When interrupted:
Session status changes to “ready”
activeTurnId is cleared
Partial work may be retained depending on provider behavior
Stopping Sessions
Sessions can be explicitly stopped:
await providerService . stopSession ({ threadId });
Stopping a session:
Cancels any active turns
Rejects pending approval requests
Kills the codex app-server process
Removes the session from the active session directory
Emits session/closed event
Stopped sessions cannot be restarted. You must create a new session, optionally with a resumeCursor to continue the conversation.
Session Directory
The ProviderSessionDirectory tracks active sessions:
interface ProviderSessionDirectory {
// Register a new session
register ( session : ProviderSession ) : Effect ;
// Update session state
update ( threadId : ThreadId , updates : Partial < ProviderSession >) : Effect ;
// Get session by thread ID
get ( threadId : ThreadId ) : Effect < ProviderSession | undefined >;
// Remove session
remove ( threadId : ThreadId ) : Effect ;
// List all active sessions
list () : Effect < ReadonlyArray < ProviderSession >>;
}
Provider-Specific Session Features
Codex Sessions
Codex sessions support additional capabilities:
Read the full conversation history: const snapshot = await codexAdapter . readThread ( threadId );
// snapshot.turns - Array of turns with items
Rewind conversation state by N turns: await codexAdapter . rollbackThread ( threadId , numTurns );
Codex sessions detect account type and plan: type CodexAccountType = "apiKey" | "chatgpt" | "unknown" ;
// Affects model availability (e.g., Spark model restrictions)
Best Practices
Always Store Resume Cursor Save resumeCursor after each successful operation to enable seamless restarts
Handle Resume Failures Be prepared for resume to fall back to fresh thread start
Monitor Session Status Watch for status transitions to handle errors and completion
Clean Up Sessions Explicitly stop sessions when done to free resources
Next Steps
Runtime Modes Configure approval policies and sandbox modes
Providers Learn about provider adapter architecture
Architecture Understand the overall system design