A fork of opencode that restores and adds a few things the upstream maintainers decided against. It is a drop-in replacement: same config, same data, same TUI, plus the divergences below.
Upstream removed TodoRead from the tool registry and later shipped versions where todowrite silently failed to register even with explicit permission configuration (#12680, #29118). The todo list is the backbone of long agent sessions here, so losing read access to it was not acceptable. From the thread that started this fork (05/2026):
this is more of a "I disagree with one decision
opencodemade" than a "I passionately want to develop my own, independent agent harness".
That is still the operating philosophy:
- drop-in, not a divergent product. The fork tracks upstream and pulls occasionally, when a missing feature or performance regression is solved there.
- diverge only on disagreement. Every delta below exists because the stock behavior got in the way of real sessions, not because a different design seemed nicer.
- PRs and issues are welcome when they relate to the fork's own divergences. Getting this into package managers is not a goal unless people actually use it.
todoread is a first-class read-only tool again, wired through the agent, registry, permissions, ACP, and the TUI sidebar; todowrite reliably registers. Subagents deny todoread by default so todo state stays scoped unless explicitly granted.
The biggest divergence. Upstream sends every connected MCP server's tool definitions to the model on every request; at ~40 servers / ~900 tools that made a bare "Hi" cost ~690k tokens, and any upstream server growing its catalogue silently raised the floor for every session.
Here, sessions start with zero MCP tools exposed:
- Servers still connect and their tools stay searchable, but nothing enters model context until shown.
- A built-in
mcptool lets the agentsearch/show/hide/list. Its description carries a compact catalogue of every reachable server, built from hand-writtenadvertisementfields in the server profiles (string, or{summary, facets}for grab-bag servers where facets are tool-name prefixes exposable on their own). Search is lexical (BM25-ish with idf, plural folding, fuzzy server names) with facet and tool hits rolling up to their server. - Toggles take effect on the next model step, so an agent can search, show, and use a server inside one turn. Exposure persists on the session row (survives compaction, restarts, and model switches); subagents inherit a snapshot at creation.
MCP_SERVERSswitchboard files (one plaintext file per directory, unioned from all ancestors of the cwd) decide which servers are catalogued; a!nameor!name:prefixline pins something exposed at session start, and the deepest file wins per token so a project can un-pin a parent's pin.- The ^p MCP dialog toggles per-session exposure with space; instance-wide connect/disconnect moved to ctrl+s.
GET/POST /mcp/exposureexposes the same thing over the API, and per-request telemetry logs exposed tool count and estimated token cost.
Measured effect: first turn in a directory with 32 servers connected dropped from ~690k tokens to ~46k.
experimental.mcpProfilesPath points at a directory of per-server JSON profile files (the same flat or mcpServers-wrapped shapes other MCP clients use, so the files stay portable). {env:VAR} and {file:...} references are expanded, letting profiles avoid hardcoded paths and secrets. The MCP_SERVERS switchboard, not the profile, decides what is enabled, so adding a profile never changes what starts up.
A plain opencode TUI hosts its server inside a Bun worker; a long-lived opencode serve (the pm2 app on :8397) is a separate process. Each has its own in-memory GlobalBus over one shared sqlite, so a turn injected through the serve (agent bus messages, bot-driven rounds, prompt_async) used to write rows an attached TUI could read but never heard about — the pane silently diverged from the transcript, permanently.
OPENCODE_EVENT_UPSTREAM=http://127.0.0.1:8397 (armed in ~/.bashrc) makes the TUI worker tap that serve's /global/event SSE stream and re-emit each event onto its own bus (src/bus/upstream.ts). Everything downstream (render, toasts, session list) works untouched; transport heartbeats are suppressed, payload ids deduped, reconnect backs off to 15s, and a dead upstream degrades to exactly the pre-tap behavior. Opt-in by env because pointing a worker's tap at its own server would echo events. (PRI-1199, 2026-10-31.)
replaceAll caused file corruption often enough (especially with Dart) that it is gone: stripped from the JSON schema sent to the model, and a deprecation error if passed anyway. Batch edits route through apply_patch instead.
<leader>v and /view open the current session transcript in $EDITOR, reusing the existing transcript formatter.
bun install./build.shto build the binary./install.sh(assumesdarwinand that you are fine with a newopencodescript in/usr/local/bin)brew uninstall opencodeornpm uninstall -g opencode-aito avoid shadowing
The wrapper launches the built darwin-arm64 binary with OPENCODE_DISABLE_CHANNEL_DB=true. On other platforms, adjust the dist path in wrapper.sh.
Everything not listed above behaves like stock opencode: docs, upstream repo.