Architecture comparison · grounded in source

How the request reaches the LLM — and who owns the loop

Three coding-agent shells, three very different answers to the same question. T3 Code wraps the official Claude Agent SDK and Codex app-server and deliberately owns no loop. opencode owns the loop, the model call, and the tool execution itself. This report traces a single prompt through each, then shows how the subscription model shapes the experience you actually feel.

T3 Code · TypeScript · Effect opencode · TS core + Go TUI · Bun/Hono Codex app-server · JSON-RPC over stdio Claude Agent SDK · streaming async iterable
01

Three architectures at a glance

The defining decision each project makes: where does the model call live, and where does the agent loop live? Everything else — transport, persistence, auth — flows from that.

T3 Code — the thin orchestrator

T3 delegates the model call and the loop to external SDKs. Its server is an event-sourced orchestration shell that translates provider events into UI updates.

  • Browser ↔ Node server over WebSocket (request/response + push)
  • Event-sourced log: OrchestrationEngine appends events, four independent subscribers react
  • Claude via @anthropic-ai/claude-agent-sdk query() — SDK spawns the CLI
  • Codex via spawn("codex",["app-server"]) + hand-rolled JSON-RPC over stdin/stdout
  • Canonical ProviderRuntimeEvent union is provider-agnostic — adapters project into it
  • Bring-your-own-auth: relies on your local claude/codex CLI login. No T3 account.

opencode — the self-contained agent

opencode owns the entire agent. It calls the model directly through the Vercel AI SDK, runs an explicit while(true) loop, and executes every tool itself.

  • Go TUI ↔ TS (Bun/Hono) server over HTTP + Server-Sent Events
  • Owns the model call: streamText() from the ai package, 75+ providers via @ai-sdk/*
  • Explicit loop in session/prompt.ts runLoop() — call → tools → results → call again
  • Tools are local TS modules (read, edit, shell, task…) executed in Bun
  • SQLite + Drizzle ORM persistence; git snapshots for /undo
  • Free OSS (MIT); BYOK, OAuth-reuse of existing subs, or optional Zen pay-as-you-go credits
The one-line contrast

T3 asks "what did the agent just do?" and relays it. opencode asks "what should the agent do next?" and does it. Both are legitimate; they sit at opposite ends of the build-vs-buy spectrum for the agent runtime.

02

Trace one prompt — where each request goes

Pick a provider and step through the lifecycle. Each lit node is where bytes actually move; arrows that turn clay show the direction of the current step. The dashed box appears only when the architecture has a loop owned by that layer.

step 0/6 · idle
03

Where the agent loop actually lives

This is the most misunderstood part. "Keeping the loop going" means very different things in each system. The loop is the agent's autonomy — reasoning, calling tools, reasoning again until it decides it's done.

T3 Code (Claude & Codex)

Loop lives in the SDK / CLI

T3 sends one SDKUserMessage per turn onto a queue. Claude/Codex then streams autonomously until turn.completed. No code in T3 re-dispatches a turn.

Proof: turn.completed handler in ProviderRuntimeIngestion.ts:1143 only finalizes state — verified, it never emits thread.turn.start.
opencode

Loop lives in opencode itself

opencode's own runLoop() in session/prompt.ts is a literal while(true): call the model, check if it ended on tool calls, execute them, write results to SQLite, loop. The exit condition is opencode's, not the model's.

Consequence: opencode can cap steps (agent.steps), inject MAX_STEPS_PROMPT, compact context mid-loop — controls T3 cannot exert without forking the SDK.
Implication

What each choice costs

T3's choice — thin shell — means a new provider is "just" a new adapter emitting the same events. But T3 cannot see inside Claude's reasoning loop, cap its steps, or inject mid-turn guidance.

opencode's choice — own the loop — means full control (steps, compaction, subtasks, any model) but opencode must reimplement tool dispatch, context management, and provider quirks that the Claude/Codex runtimes give T3 for free.

Neither is universally right. T3 optimizes for breadth of providers with minimal code; opencode optimizes for depth of control over a single agent.
packages/opencode/src/session/prompt.tstypescript
// opencode's explicit loop — the thing T3 deliberately does NOT have
const runLoop = Effect.fn(function*(sessionID) {
  let step = 0;
  while (true) {
    yield* status.set(sessionID, { type: "busy" });
    const msgs = yield* MessageV2.filterCompactedEffect(sessionID);
    const { lastAssistant, hasToolCalls } = latest(msgs);

    // EXIT: assistant finished AND not waiting on tool results
    if (lastAssistant?.finish && lastAssistant.finish !== "tool-calls" && !hasToolCalls)
      break;

    step++;
    const result = yield* handle.process({ messages, tools, model, ... });
    // ↑ streamText() → tool-call events → execute tools locally → write results
  }
});
04

Side-by-side comparison

Every cell below is grounded in source. "T3 (Claude)" and "T3 (Codex)" share the orchestration layer but differ at the adapter; they're shown as one column each because the contrast is instructive.

scroll to compare all three providers
Dimension T3 · Claude T3 · Codex opencode
Process ownershipWho spawns the model runtime SDK does. T3 calls query(); the SDK spawns claude.ClaudeAdapter.ts:2729 T3 does. spawn("codex",["app-server"]) directly.codexAppServerManager.ts:552 opencode does. Calls streamText() in-process; no CLI subprocess.
Transport to modelWire format Streaming SDKMessage async iterable (Anthropic stream-json-ish, via SDK)Stream.fromAsyncIterable Newline-delimited JSON-RPC over stdin/stdout via readlinewriteMessage:1325 AI SDK fullStream over HTTPS (Anthropic/OpenAI/75+ providers)
How a prompt is sentThe write call Queue.offer(promptQueue, SDKUserMessage) — one per turnClaudeAdapter.ts:2938 stdin.write(JSON + "\n") with method turn/startcodex…:814 streamText({ model, messages, tools }) — one call per loop iteration
Who owns the agent loopThe "reason → tool → reason" cycle SDK / Claude CLI Codex app-server opencode (explicit while(true))
Client ↔ server transport WebSocket (req/resp + orchestration.domainEvent push)shared by both T3 providers HTTP + Server-Sent Events (Go TUI client)
Tool executionWhere tools run Inside Claude CLI (SDK-managed); T3 only gates via canUseTool callbackClaudeAdapter.ts:2542 Inside Codex app-server; T3 answers approval requests via JSON-RPC responsescodex…:902 Inside opencode's own Bun process (TS tool modules: read/edit/shell/task…)
Streaming to UI content.delta → buffered (24k-char coalescing) → WS pushProviderRuntimeIngestion.ts:1035 AI SDK deltas → event bus → SSE to TUI (token-by-token)
Persistence model Event-sourced log + SQLite projections (OrchestrationEngine) State-based SQLite + Drizzle (sessions/messages/parts tables); git snapshots for undo
Auth / model access Bring-your-own — your local claude / codex CLI login. No T3 account. no T3 subscription BYOK keys · OAuth-reuse (ChatGPT/Copilot/Claude subs) · optional Zen credits
Default model claude-sonnet-4-6 gpt-5.4 Configurable per agent; model recorded per session
Provider abstraction ProviderKind = "codex" | "claudeAgent" · shared ProviderRuntimeEvent unionorchestration.ts:30 Provider.Service resolving to AI-SDK LanguageModelV3 · 75+ providers via Models.dev
Read this table top-to-bottom for one insight

From "process ownership" down to "tool execution," every row that involves running the model or a tool is owned externally in T3 and internally in opencode. The rows where T3 and opencode look similar (transport to client, persistence) are exactly the rows that are not the agent — they're the shell around it. That's the cleanest way to see what each project has chosen to build vs. buy.

05

How the subscription creates the experience

The headline finding from the source: T3 has no subscription system at all. So "how does the subscription create the T3 experience?" has a subtle answer — the subscription that matters is the one you hold with Anthropic or OpenAI, not with T3. Here are the three real models and what each unlocks.

T3 Code (both providers)

Bring-your-own subscription

Free · your existing plan

T3 ships zero auth, account, billing, or entitlement code (verified — the persistence layer has no user/subscription tables; the only "auth" is an optional shared WS secret).

It inherits whatever login your local claude and codex CLIs already carry. That means your Claude Pro/Max or ChatGPT Plus/Pro subscription is the T3 subscription.

T3 reads the ChatGPT plan via Codex's account/read only to decide if the Spark model is allowed — it never charges you.
opencode · paths 1 & 2

BYOK + OAuth reuse

Free OSS · BYO keys

opencode (the software) is MIT-licensed and free. You paste API keys into /connect (stored locally in auth.json) — tokens billed directly by the provider.

Or you log in with GitHub/OpenAI/Anthropic to reuse subscriptions you already pay for (ChatGPT Plus/Pro, Copilot, Claude Pro/Max).

No opencode account. The agent runs identically whether you use keys or reused subs.
opencode · path 3

Zen — curated pay-as-you-go

Credits · not a tier

The only place opencode itself intermediates billing. Zen offers a handpicked, benchmarked set of models on a credit balance. Critically: "no tiered plans that unlock features."

Auto-reloads $20 below $5; card fees passed at cost (4.4% + $0.30). Workspaces free in beta.

Zen is a convenience layer (one bill, tested models) — it does not gate the experience.
The real answer to "how the subscription creates the experience"

For T3, the experience you feel — which models are available, your rate limits, your context window, whether Opus is an option — is entirely determined by the tier of the underlying Claude/ChatGPT subscription your CLI is logged into. T3 is a viewer on top of that entitlement. Swap a Pro login for a free login and the T3 experience degrades instantly, with no T3 code involved.

opencode is the opposite: because it owns the model call, it can offer any provider on equal footing, mix BYOK with Zen, and let you route different sessions to different backends. The experience is shaped by opencode's provider matrix, not by a single subscription's tier.

What the experience layer actually delivers

Same prompt, same model — the "experience" is the orchestration around the call. Here's what each layer adds on top of raw model access.

T3 LAYER 1

Universal event projection

Both Claude and Codex stream into the same ProviderRuntimeEvent shapes. The browser doesn't know or care which provider ran — same components, same diff viewer, same approval UI.

ProviderRuntimeIngestion.ts
T3 LAYER 2

Buffered streaming + checkpoints

24k-char delta coalescing keeps the WS quiet while feeling live. Per-turn git checkpoints (thread.checkpoint.revert) give undo that the raw CLIs don't expose.

OrchestrationEngine + checkpoints
opencode LAYER 1

Any model, one agent

Because opencode owns the loop, you can run Sonnet for planning and Haiku for subtasks in the same session, or swap to GPT mid-thread. Provider choice is a per-call decision.

provider/provider.ts · Models.dev
opencode LAYER 2

Step caps + context compaction

opencode can inject MAX_STEPS_PROMPT on the final allowed step and compact context when the window overflows — controls that exist because the loop is its own.

session/prompt.ts runLoop()
06

What to remember

T3 Code, in four lines

  • Send: one thread.turn.start command → one message onto the SDK's prompt queue.
  • Reach the LLM: the SDK / app-server makes the actual model call. T3 never sees an API key or HTTP request.
  • Loop: there isn't one in T3. Claude/Codex stream until turn.completed; T3 projects those events to the browser.
  • Subscription: none — your Claude/ChatGPT plan is the entitlement. T3 is the viewer, not the biller.

opencode, in four lines

  • Send: an HTTP request boots runLoop(), which calls streamText() itself.
  • Reach the LLM: opencode opens the HTTPS connection to Anthropic/OpenAI/etc. via the AI SDK.
  • Loop: explicit while(true) in opencode — call model, run tools, check exit, repeat.
  • Subscription: free OSS; BYOK or reuse existing subs; optional Zen credits. The agent is subscription-agnostic.
The deepest contrast

T3's elegance is in not owning the hard part — by delegating the loop and model call to official SDKs, it gets a polished multi-provider UX for the cost of two adapters, at the price of forfeiting in-loop control. opencode's elegance is in owning the hard part — it pays the cost of reimplementing tool dispatch and provider coverage to gain total control over agent behavior and model choice. Same destination, opposite bets on where the value sits.