OpenCode — Deep Dive Knowledge Base
Reverse-engineered analysis of the OpenCode codebase: architecture, orchestration, tools, database, and design philosophy.
Shared from "Study" on Inkdown
Reverse-engineered analysis of the OpenCode codebase: architecture, orchestration, tools, database, and design philosophy.
OpenCode is an AI-powered coding assistant for local development. It runs entirely on your machine — no cloud dependency for code access — giving the LLM full codebase context.
Primary use cases:
Target audience: Software developers (full-stack, backend, frontend, DevOps, open-source contributors) who want AI assistance wired into their daily workflow with local control and safety.
Pattern: Service-Oriented with Effect (functional programming)
Every feature is a Service with three parts:
| Part | Purpose |
|---|---|
| Interface | Defines what the service can do |
| Implementation | How it does it |
| Layer | How to inject its dependencies |
Core services:
| Service | Responsibility |
|---|---|
| Session | Manages AI conversations |
| Agent | AI personalities (build, plan, explore, etc.) |
| Config | Loads settings from files / env / remote |
| Auth | API keys and OAuth handling |
| Provider | Connects to LLM providers (OpenAI, Anthropic, etc.) |
| Tool | File ops, shell, web, orchestration |
| Server | HTTP API for headless operation |
| Permission | Evaluates and enforces tool access rules |
| Question | Structured human-in-loop prompts |
| Background | Manages async/background job execution |
Data flow:
Key technologies:
| Layer | Technology |
|---|---|
| Language | TypeScript |
| Functional core | Effect |
| Validation | Effect Schema |
| UI | SolidJS |
| ORM | Drizzle ORM |
| Database | SQLite |
| Runtime & build | Bun |
What they built themselves:
task toolWhat external libraries handle (infrastructure, not orchestration):
| Library | Role |
|---|---|
| Effect | FP foundation (like React for UI — structural, not orchestration) |
| Vercel AI SDK | Optional provider transport adapter (one of two runtime options) |
| Drizzle | Database persistence |
packages/llm/src/route/)One protocol works across many providers — protocol-agnostic routing.
packages/llm/src/tool-runtime.ts)packages/opencode/src/session/llm.ts)Each agent has:
| Agent | Mode | Purpose | Key tools |
|---|---|---|---|
build | primary | Default — executes tools | all permitted tools |
plan | primary | Planning mode | no edit tools |
general | primary | Multi-step parallel tasks | broad tool set |
explore | subagent | Fast codebase exploration | grep, glob, read only |
scout | subagent | External docs & dependency research (experimental) | webfetch, websearch |
compaction | subagent | Context compaction | read |
title | subagent | Generate session titles | none |
summary | subagent | Summarise sessions | read |
User input → session processor → agent selected (based on config/request) → LLM called with agent's prompt + available tools
Sub-agents are not picked from a queue. The parent agent explicitly spawns them by calling the task tool:
This creates a new independent session:
src/agent/subagent-permissions.ts merges permissions in this order:
Sub-agents cannot have more permissions than their parent. This prevents permission escalation.
| Mode | Behaviour |
|---|---|
| Synchronous | Parent waits for subagent to complete |
Background (background: true) | Subagent runs async, parent continues immediately (experimental) |
| Resume | Pass task_id to continue same subagent session |
No hard limit in code. Constrained by:
ask)Tools are merged from three sources in the registry:
File operations:
read — read file contents with offset/limitwrite — write/create filesedit — edit files with diff applicationapply_patch — apply git-style patchesSearch:
grep — search file contents (uses ripgrep)glob — find files by patternrepo_overview — get repository structureShell:
shell / bash — execute shell commandsrepo_clone — clone git repositoriesWeb:
websearch — search web (Exa or Parallel via MCP)webfetch — fetch URLs, convert to markdown/text/htmlAgent orchestration:
task — spawn sub-agent sessionstask_status — check sub-agent statusplan — enter/exit plan modeInteraction:
question — ask user structured questionstodo — read task liststodowrite — write todo itemsLanguage Server:
lsp — LSP operations (go-to-definition, diagnostics, etc.)Utilities:
external_directory — access directories outside projectskill — execute user-defined skillsinvalid — error handling for invalid tool callsTwo MCP-based providers:
| Provider | MCP URL | API Key |
|---|---|---|
| Exa | https://mcp.exa.ai/mcp | EXA_API_KEY |
| Parallel | https://search.parallel.ai/mcp | PARALLEL_API_KEY |
Selection: env var OPENCODE_WEBSEARCH_PROVIDER, flags, or hash-based random (session ID checksum % 2).
Default params: numResults: 8, livecrawl: fallback, timeout: 25s.
Technique: HTTP requests via Effect's HttpClient — no headless browser.
turndownhtmlparser2markdown, text, htmlNot included. No Puppeteer, Playwright, or any headless browser in core. Must be added via MCP server, custom tool, or skill plugin.
OpenCode uses Effect fibers — structured concurrency on top of Node.js's single-threaded event loop.
| Feature | async/await | Effect Fibers |
|---|---|---|
| Scoped cleanup | ❌ manual | ✅ automatic |
| Interruption | ❌ limited | ✅ Fiber.interrupt() |
| Controlled concurrency | ❌ manual Promise.all | ✅ { concurrency: N } |
| Background jobs | ❌ none | ✅ background.start() |
| Structured hierarchy | ❌ none | ✅ scoped fibers |
Does NOT consume more CPU cores. All fibers run on the single Node.js event loop. Operations are I/O bound (HTTP requests to LLM APIs, file reads) — not CPU bound.
CPU cores would only matter with worker threads or child processes — neither of which OpenCode uses.
| Operation | Concurrency |
|---|---|
| Tool calls | unbounded |
| File reads | 8 |
| Grep operations | 16 |
| Network fetches | 4 |
| Sequential operations | 1 |
src/background/job.ts)Jobs stored in memory Map — not Redis, not a queue system.
OpenCode has no resource limits for sub-agents. No CPU quota, memory quota, network quota, or max concurrent sub-agents. Constraints come from outside:
| Source | Constraint |
|---|---|
| LLM provider | API rate limits |
| User | Approval prompts |
| Permission system | Tool access rules |
| System | Memory/network exhaustion |
OpenCode is a local developer tool, not a multi-tenant production system. It trusts the developer, provides permission controls, but enforces no resource quotas.
Creating a new chat (/new) does not cancel existing running sessions. Each session has its own independent runner. Multiple sessions can be "busy" simultaneously. Explicit cancel is required: POST /session/{sessionID}/abort.
Three outcomes:
once — allow this single callalways — allow forever (saved to SQLite)reject — deny + rejects all pending requests for the sessionPermission rules per agent:
Blocking mechanism: Effect-based deferred promises — tool execution halts until user responds. No race conditions.
LLM calls question tool → UI displays dialog → user responds → answers returned to LLM → LLM continues.
Solid:
always approvals persisted in SQLite (survive restarts)Not perfect:
Location: packages/opencode/src/session/
Created, Updated, Deleted, Diff, Errorbusy — LLM processingidle — waiting for inputsession/llm.ts)Per-request decision:
One SQLite file per project. No Redis, no external database, no distributed system.
Location: ~/.config/opencode/projects/{projectID}/db.sqlite
Key columns: id, project_id, parent_id (sub-agent hierarchy), title, agent, model, cost, tokens_*, permission (JSON), revert (JSON), timestamps.
Indexes: project_id, workspace_id, parent_id
Key columns: id, session_id, time_created, data (JSON — full MessageV2.Info)
Indexes: Composite (session_id, time_created, id) — optimised for timeline queries.
Key columns: id, message_id, session_id, data (JSON — MessageV2.Part)
Types of parts: text, tool_call, tool_result, reasoning, error.
Indexes: (message_id, id), session_id
PK: (session_id, position) — composite for ordering.
Columns: content, status, priority, position, timestamps.
Columns: id, worktree, vcs, name, icon_*, sandboxes (JSON), commands (JSON), timestamps.
PK: project_id. Stores full Ruleset as JSON. One row per project.
Columns: id, type, name, branch, directory, extra (JSON), project_id, time_used.
Stores OAuth tokens, refresh tokens, expiry, active account/org state.
Stores share URL, share ID, secret per session.
event_sequence: tracks aggregate sequence numbers.
event: stores typed events with aggregate_id, seq, type, data (JSON).
| Technique | Detail |
|---|---|
| JSON mode columns | text({ mode: "json" }) — automatic parsing, type-safe |
| Cascade deletes | Orphan cleanup on session/message delete |
| Integer timestamps | Unix ms — compact, fast comparisons |
| Text (ULID) PKs | No auto-increment — distributed-friendly |
| Composite indexes | On query-critical column combos |
| Foreign key constraints | Referential integrity enforced |
| Auto timestamps | $default and $onUpdate callbacks |
| Minimal schema | No redundant columns; complex data → JSON |
| Per-project DB | No multi-tenant overhead |
✅ Single-user local development
✅ Small-to-medium datasets (thousands of messages)
✅ Read-heavy (display chat history)
✅ Session-based queries
❌ Not for multi-user concurrent access
❌ Not for millions of records
❌ Not for complex analytical queries
Location: packages/opencode/src/auth/
Methods supported:
Storage: JSON files with 0o600 permissions (user-only read/write).
Provider-specific auth management — each LLM provider has its own auth handler.
Location: packages/opencode/src/server/
Features:
The server enables headless operation — the TUI, web, and desktop UIs all consume this API. The SDK (packages/sdk) is generated from OpenAPI spec.
testEffect helper)sleep for readiness — use Effect synchronisation insteadtest/AGENTS.md, test/EFFECT_TEST_MIGRATION.mdStrengths:
Weaknesses / things to be aware of:
| Feature | Status |
|---|---|
| Headless browser | ❌ Not included |
| Redis / external cache | ❌ Not used |
| Distributed locks | ❌ Not needed (single-process) |
| Task queue (BullMQ, etc.) | ❌ In-memory only |
| Resource quotas for sub-agents | ❌ Not implemented |
| Max sub-agent limit | ❌ No hard limit |
| Permission revocation UI | ❌ Must edit config manually |
| Approval timeout | ❌ Can hang forever |
| CPU/OS thread parallelism | ❌ Single-threaded event loop |
| Orchestration framework | ❌ Custom-built from scratch |
The core orchestration in OpenCode is domain-agnostic. The coding-specific parts are only:
Everything else — agent hierarchy, session management, permission evaluation, sub-agent spawning, tool registry, human-in-loop, Effect concurrency — works for any domain.
| Domain | Custom tools | Custom agents |
|---|---|---|
| Research assistant | websearch, paper_fetch, citation_extract, summary_generate | researcher, writer, analyst |
| Financial analyst | stock_query, portfolio_analyze, trade_execute, report_generate | analyst, trader, risk_assessor |
| Healthcare | patient_query, diagnosis_assist, treatment_plan | clinician, researcher, reviewer |
| Marketing | campaign_create, analytics_fetch, content_generate | strategist, copywriter, analyst |
@your-org/agent-harness package; OpenCode becomes one implementation