Control surface, not agent brain
For each environment, T3 Code authoritatively coordinates repository and provider execution without replacing a provider's model loop, native context, or authentication.
What this chapter resolves
- Draw the ownership boundary between client, T3 server, provider adapter, and provider CLI.
- Explain why multi-harness support is an adapter problem rather than a shared agent implementation.
- Identify the server services that make T3 Code more than a terminal wrapper.
The product’s cleanest architectural sentence is: each T3 environment is a server-authoritative control plane around replaceable coding-agent runtimes.
Clients are thin with respect to trusted workspace execution—not small programs.
They express intent and render projected state, while Electron and mobile still own
substantial native behavior. For the selected environment, the server owns
repository-facing orchestration. A provider runtime owns its model/tool loop. The
adapter and ProviderService divide live protocol behavior from persisted session
binding and recovery state.
provider credentials, or machine state for the environment's. The desktop app bundles a server,
but its renderer follows the same boundary.
## Ownership boundaries
Provider processes, terminals, Git, and project files belong to the server. Shared connection and
domain state belongs in `packages/client-runtime`; clients supply platform services and UI.
Keeping that logic shared prevents reconnect and multi-environment behavior from diverging between
web and mobile. See [connection runtime](./connection-runtime.md) and
[remote environments](./remote.md).
The [RPC contract](../../packages/contracts/src/rpc.ts) is the boundary between independently
versioned clients and servers. Subscriptions send the state a client needs, so a client viewing one
thread does not pay for every thread's history. Authentication of a socket does not authorize every
method on it. See [environment auth](./environment-auth.md).
### Pull request linking compatibility
Web, desktop, mobile, and environments upgrade independently. Negotiate linking through the
environment descriptor, never through a client version or an assumed coordinated release:
| Environment capability | Client behavior |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `threadPullRequests: true` | Use persisted `pullRequests[]`, multi-link commands, stack UI, and reverse thread lookup. |Six ownership layers
| Owner | Owns | Does not own |
|---|---|---|
| T3 domain | durable project/thread history, lifecycle intent, messages, activities, proposed plans, checkpoint metadata, query projections | live process objects, provider-native memory, every transient UI signal |
| ProviderService + session directory | instance routing and persisted binding, cwd, model/runtime payload, continuation cursor, status | native cursor meaning or provider reasoning |
| Provider adapter | live process/session object, native protocol, cursor interpretation, approval/input mapping, canonical event translation | cross-provider product policy or durable projections |
| Provider runtime | model/tool loop, native conversation/context, compaction, provider account authentication | T3’s multi-environment UI, checkpoint model, or product lifecycle |
| Client shell | navigation, presentation, caches, device settings; mobile drafts and durable outbox; Electron OS integrations | authoritative environment thread history or remote workspace execution |
| Selected environment | workspace files, provider processes, Git, PTYs, server SQLite/files/settings/secrets | other environments supervised by the same client |
A normalized, deliberately bounded seam
export interface ProviderAdapterShape<TError> {
/**
* Provider kind implemented by this adapter.
*/
readonly provider: ProviderDriverKind;
readonly capabilities: ProviderAdapterCapabilities;
/**
* Start a provider-backed session.
*/
readonly startSession: (
input: ProviderSessionStartInput,
) => Effect.Effect<ProviderSession, TError>;
/**
* Send a turn to an active provider session.
*/
readonly sendTurn: (
input: ProviderSendTurnInput,
) => Effect.Effect<ProviderTurnStartResult, TError>;
/** Omitted when this adapter does not support manual context compaction. */
readonly compaction?: ProviderCompaction<TError>;
/**
* Interrupt an active turn.
*/
readonly interruptTurn: (threadId: ThreadId, turnId?: TurnId) => Effect.Effect<void, TError>; /**
* Stop all sessions owned by this adapter.
*/
readonly stopAll: () => Effect.Effect<void, TError>;
/**
* Canonical runtime event stream emitted by this adapter.
*/
readonly streamEvents: Stream.Stream<ProviderRuntimeEvent>;The contract is not a promise of feature equality. Its lifecycle, interaction, history, rollback, feedback, and event-stream surface is deliberately bounded, but not tiny. At this revision, the separate common capability object only describes session model switching; other semantic differences are expressed by adapter behavior, errors, and provider presentation. Uniform orchestration is achieved by normalizing at the boundary, not by pretending the providers are identical.
Why the server is the real product kernel
Layer.provideMerge(VcsDriverRegistryLayerLive),
);
const VcsLayerLive = Layer.empty.pipe(
Layer.provideMerge(VcsProjectConfig.layer),
Layer.provideMerge(VcsDriverRegistryLayerLive),
Layer.provideMerge(VcsProvisioningService.layer.pipe(Layer.provide(VcsDriverRegistryLayerLive))),
Layer.provideMerge(GitWorkflowLayerLive),
Layer.provideMerge(ReviewLayerLive),
Layer.provideMerge(SourceControlRepositoryServiceLayerLive),
Layer.provideMerge(
VcsStatusBroadcaster.layer.pipe(
Layer.provide(GitWorkflowLayerLive),
Layer.provide(
VcsStatusBroadcaster.autoPullPolicyLayer.pipe(Layer.provide(ServerSettingsLayerLive)),
),
),
),
);
const CheckpointingLayerLive = Layer.empty.pipe(
Layer.provideMerge(CheckpointDiffQuery.layer),
Layer.provideMerge(CheckpointStore.layer.pipe(Layer.provide(VcsDriverRegistryLayerLive))),
);
const PortScannerLayerLive = PortScanner.layer.pipe(Layer.provide(ProcessRunner.layer));
const TerminalLayerLive = TerminalManager.layer.pipe(
Layer.provide(PtyAdapterLive),
Layer.provide(PortScannerLayerLive),
Layer.provide(NativeTelemetryLayerLive),
);
const PreviewLayerLive = Layer.empty.pipe(
Layer.provideMerge(PreviewManager.layer),
Layer.provideMerge(PortScannerLayerLive),
);
const DeviceLayerLive = DeviceService.layer.pipe(
Layer.provide(ServerSettingsLayerLive),
Layer.provide(ProcessRunner.layer),
Layer.provide(NetService.layer),
);
const WorkspaceEntriesLayerLive = WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer));
const WorkspaceFileSystemLayerLive = WorkspaceFileSystem.layer.pipe(
Layer.provide(WorkspacePaths.layer),
Layer.provide(WorkspaceEntriesLayerLive),
);
const WorkspaceLayerLive = Layer.mergeAll(
WorkspacePaths.layer,
WorkspaceEntriesLayerLive,
WorkspaceFileSystemLayerLive,
);
const ProjectFaviconResolverLayerLive = ProjectFaviconResolver.layer.pipe(
Layer.provide(WorkspacePaths.layer),
Layer.provide(T3ProjectFileLoader.layer),
);
const ServerEnvironmentLayerLive = ServerEnvironment.layer.pipe(
Layer.provide(ServerSecretStore.layer),Keep workspace authority server-side
A phone can supervise the same environment as desktop because remote repository, provider, Git, and PTY operations remain behind that environment's server.
Keep provider complexity at the edge
The orchestration core speaks canonical commands and events; Codex/Claude/ACP/OpenCode quirks remain inside adapters.
The server becomes a large trust boundary
Remote authentication and per-method scopes matter because one socket fronts shells, files, Git, and autonomous coding agents.
Capabilities can diverge
A common product model still needs explicit answers for provider-only features, resume semantics, plan events, usage, and approvals.