The Client/Server Harness

one Effect-native daemon, many front ends

anomalyco/opencodeView the repository
LANGUAGE
TypeScript · Bun · Effect
SOURCE LOC
~495k (+173k tests)
PACKAGES
33
BUILT-IN TOOLS
17 (13 always on)
PROVIDERS
24 bundled + models.dev
OS SANDBOX
none — permission engine
All 22 blocks

Surfaces & entry

The turn

Tools

Context & memory

Model layer

Safety

Extensibility

State

Isometric plate: the 22 subsystems of opencodeBlocks are grouped into districts by hatching and tint; block height is a rough proxy for code mass. Lines trace the data flow of a single turn. The block index beside this diagram carries the same information as text.ABUVTGCHJDKSILMNQREFOP

Hover to read · drag to pan · ⌘/ctrl + scroll to zoom

How to read this plate

Each block is one subsystem of opencode. Its height is a rough proxy for code mass, calibrated the same way across every harness so the plates are comparable. Hatching and tint group blocks into districts — surfaces, the turn, tools, context, the model layer, safety, extensibility and state.

Lines trace the real data flow of a single turn plus the major supporting links. Pick any block for a plain description and the files it was read from, or trace the turn to walk the path a request actually takes.

What this is

opencode has no in-process fast path. Running opencode spawns a worker thread hosting the full HTTP server, and the terminal UI drives it over RPC and SSE like any other client. The same ~118-endpoint surface serves the TUI, the Electron desktop app, the browser client, VS Code, ACP editors and opencode attach across a LAN — with mDNS discovery, and per-request project resolution so one server handles many projects.

The backend is written in Effect end to end: services are layers, cancellation is fiber interruption, retries are schedules, and every method is a traced span. Crossing into the Vercel AI SDK's promise world requires an explicit bridge. It buys structured concurrency and free OpenTelemetry, at a steep cost in approachability.

There is no OS sandbox — the isolation work is done entirely by the permission engine, which is the most developed of the six. Rules are wildcard allow|ask|deny triples evaluated last-match-wins; answering "always" auto-resolves every other pending request the new rule now permits; a rejection rejects the rest. Shell commands are parsed with tree-sitter, not string-matched, so path arguments can be resolved and arity-aware always-patterns generated.

Two complete LLM stacks live side by side — the AI SDK path and a hand-written protocol layer for anthropic-messages, openai-chat, openai-responses, gemini and bedrock-converse — both emitting one event union. And the durable store is the streaming buffer: every delta is a SQLite row update, so reconnects and restarts replay from the same source of truth.

Implementation

Edits. A cascade of nine fuzzy replacers tried in order — simple, line-trimmed, block-anchor, whitespace-normalised, indentation-flexible, escape-normalised, trimmed-boundary, context-aware, multi-occurrence — each a generator of candidate matches, accepted only when unambiguous. BOM and line endings are preserved, a per-path semaphore serialises writes, and the computed unified diff is shown as permission metadata before the write lands.

Model-aware prompting. Nine per-model-family system prompts as .txt files (anthropic, gpt, codex, gemini, kimi, meta, beast, trinity, default) selected by substring match on the model id — and for GPT models the registry swaps edit/write out for apply_patch.

Compaction + pruning. Overflow triggers a hidden compaction agent with all tools denied, fed a serialised transcript with each tool output clipped to 2,000 characters and a preserved-recent budget clamped to 2k–15k tokens. Separately, opt-in pruning walks backwards clearing old tool-result bodies past a 40k-token protection window, never touching skill outputs.

Safety net. A shadow git repo commits the worktree at every step, so revert/unrevert can roll the tree and truncate history to any message. A doom-loop detector fires a permission ask when the last three parts are the identical tool with identical input.