Part I · Boundaries and vocabularyThe ownership boundary
Chapter 1source checked

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.

docs/internals/overview.md:5–28 ↗verbatim · markdown · fd6effc6
T3 Code is a server runtime that owns agent sessions, workspaces, and version control, plus clients
(web, desktop, mobile) that talk to it over one authenticated Effect RPC WebSocket. The server is the
execution boundary: every provider process, terminal, git operation, and filesystem read happens
there, never in the client.
 
```
┌────────────────────────────────────────────────┐
│ Clients: apps/web, apps/desktop, apps/mobile   │
│ shared runtime: packages/client-runtime        │
│  connection supervisor, RPC session, Atom state│
└──────────────────┬─────────────────────────────┘
                   │ Effect RPC over WebSocket (/ws)
                   │ contract: packages/contracts
┌──────────────────▼─────────────────────────────┐
│ apps/server                                    │
│  orchestration engine (event-sourced)          │
│  provider driver registry (5 built-in drivers) │
│  checkpointing, VCS, terminals, filesystem     │
└──────────────────┬─────────────────────────────┘
                   │ per-driver transport
┌──────────────────▼─────────────────────────────┐
│ Agent CLIs: Codex, Claude, Cursor, Grok,       │
│ OpenCode                                       │
└────────────────────────────────────────────────┘
Read this as: This boundary explains both remote access and multi-provider support. A remote client changes the transport to the same server; a new harness changes the adapter below it.

Six ownership layers

Ownership of durable, runtime, provider-native, client-local, and workspace state
OwnerOwnsDoes not own
T3 domaindurable project/thread history, lifecycle intent, messages, activities, proposed plans, checkpoint metadata, query projectionslive process objects, provider-native memory, every transient UI signal
ProviderService + session directoryinstance routing and persisted binding, cwd, model/runtime payload, continuation cursor, statusnative cursor meaning or provider reasoning
Provider adapterlive process/session object, native protocol, cursor interpretation, approval/input mapping, canonical event translationcross-provider product policy or durable projections
Provider runtimemodel/tool loop, native conversation/context, compaction, provider account authenticationT3’s multi-environment UI, checkpoint model, or product lifecycle
Client shellnavigation, presentation, caches, device settings; mobile drafts and durable outbox; Electron OS integrationsauthoritative environment thread history or remote workspace execution
Selected environmentworkspace files, provider processes, Git, PTYs, server SQLite/files/settings/secretsother environments supervised by the same client

A normalized, deliberately bounded seam

apps/server/src/provider/Services/ProviderAdapter.ts:47–71 ↗verbatim · typescript · a75847d1
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>;
 
  /**
   * Interrupt an active turn.
   */
  readonly interruptTurn: (threadId: ThreadId, turnId?: TurnId) => Effect.Effect<void, TError>;
Read this as: The common lifecycle is start, send, and interrupt. The adapter returns Effect values so failures and dependencies remain typed, while provider-specific protocols stay outside orchestration.
apps/server/src/provider/Services/ProviderAdapter.ts:126–134 ↗verbatim · typescript · 4a95ebce
  /**
   * 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>;
Read this as: Every implementation emits the same ProviderRuntimeEvent stream. This is the upward-facing seam that keeps the projector and clients provider-agnostic.

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

apps/server/src/server.ts:353–416 ↗verbatim · typescript · 11cc8845
const AuthLayerLive = EnvironmentAuth.layer.pipe(
  Layer.provideMerge(PersistenceLayerLive),
  Layer.provide(ServerSecretStore.layer),
);
 
const CloudManagedEndpointRuntimeLive = Layer.mergeAll(
  RelayClientLive,
  CloudManagedEndpointRuntime.layer.pipe(
    Layer.provide(ServerSecretStore.layer),
    Layer.provide(RelayClientLive),
  ),
);
 
const ProviderRuntimeLayerLive = ProviderSessionReaperLive.pipe(
  Layer.provideMerge(ProviderLayerLive),
  Layer.provideMerge(OrchestrationLayerLive),
);
 
const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe(
  // Core Services
  Layer.provideMerge(ServerSettingsLayerLive),
  Layer.provideMerge(CheckpointingLayerLive),
  Layer.provideMerge(SourceControlProviderRegistryLayerLive),
  Layer.provideMerge(GitLayerLive),
  Layer.provideMerge(VcsLayerLive),
  Layer.provideMerge(ProviderRuntimeLayerLive),
  Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)),
  Layer.provideMerge(PersistenceLayerLive),
  Layer.provideMerge(Keybindings.layer),
  Layer.provideMerge(ProviderRegistryLive),
  // The instance registry is the new routing keystone — text generation,
  // adapter lookup, and runtime ingestion all resolve `ProviderInstanceId`
  // through this layer. Built-in drivers come from `BUILT_IN_DRIVERS`;
  // `providerInstances` hydration merges `settings.providers.<kind>`
  // with explicit `providerInstances` entries on boot.
  Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
  // Shared native/canonical NDJSON writers used by both the per-instance
  // drivers (native stream, written from inside each `<X>Adapter`) and
  // `ProviderService` (canonical stream, written after event normalization).
  // Provided once at the runtime level so every consumer sees the same
  // logger instances.
  Layer.provideMerge(ProviderEventLoggers.layer),
  // `OpenCodeDriver.create()` yields `OpenCodeRuntime`; previously the old
  // `ProviderRegistryLive` pulled `OpenCodeRuntimeLive` in for itself, but
  // the rewritten registry reads snapshots off the instance registry and
  // no longer transitively provides it. Exposing it at the runtime level
  // keeps a single Live for all opencode consumers.
  Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive),
  Layer.provideMerge(WorkspaceLayerLive),
  Layer.provideMerge(ProjectFaviconResolverLayerLive),
  Layer.provideMerge(RepositoryIdentityResolver.layer),
  Layer.provideMerge(ServerEnvironment.layer),
  Layer.provideMerge(AuthLayerLive),
  Layer.provideMerge(ServerSecretStore.layer),
  Layer.provideMerge(
    Layer.mergeAll(
      CloudCliTokenManager.layer.pipe(
        Layer.provide(ServerSecretStore.layer),
        Layer.provide(ExternalLauncher.layer),
      ),
      CloudManagedEndpointRuntimeLive,
    ),
  ),
);
Read this as: This is actual layer composition rather than an import list: authorization, managed remote endpoints, provider runtime, event ingestion, command reactions, and the server dependency graph are wired into one owned runtime.
Decision

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.

Decision

Keep provider complexity at the edge

The orchestration core speaks canonical commands and events; Codex/Claude/ACP/OpenCode quirks remain inside adapters.

Trade-off

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.

Trade-off

Capabilities can diverge

A common product model still needs explicit answers for provider-only features, resume semantics, plan events, usage, and approvals.

Source trail

T3
Source-locked editionRead against fa219001d · 23 Aug 2026
Book search

Find a concept, module, or source path

Type two or more characters.