cyberine
Providers

OpenCode 2.0 beta (opencode2) — integrated as opencode2-deepseek, currently retired

  • Kind: headless/interactive coding agent, multi-provider — the successor to opencode-cli.md's v1
  • Status in Cyberine: integrated as the opt-in build worker opencode2-deepseek (CliAgentAdapter via makeOpenCode2Adapter, env CYBERINE_OPENCODE2_DEEPSEEK_MODEL/_VARIANT; src/core/workerProfiles.ts:684-698, src/orchestrator/buildAdapters.ts:1942-1959). Currently RETIRED since 2026-09-06 (DeepSeek account out of balance; src/core/workerProfiles.ts:1039-1042). Sections below the Live probe heading are kept as the doc-only pass that led to this wiring. The verdict was GO as a CliAgentAdapter sibling, which is what shipped.
  • Official docs: https://opencode.ai/v2/docs · /cli · /config · /permissions · /api · /build · /troubleshooting
  • Migration guide: https://opencode.ai/v2/docs/migrate-v1
  • Researched 2026-08-22 (cyber-06) from docs alone, then executed 2026-08-22 (cyber-07) against a real scratch install of 0.0.0-beta-17898. Sections above Live probe are the doc-derived pass and are kept verbatim as a record of what the documentation claims; where they conflict with the live probe, the probe wins. Four of the five "structural blockers" recorded from the docs turned out to be documentation gaps, not binary behaviour.

Why it was invisible to the changelog watch

v2 is unreachable from every surface CHANGELOG-WATCH.md's OpenCode row polls, which is the whole reason a user report ("opencode ra version 2 roi") beat the sweep to it:

Surface the sweep checksWhat it shows
github.com/anomalyco/opencode/releasesonly v1.18.x tags — v2 has never been cut as a GitHub release
npm opencode-ai (latest)1.18.21 — v2 is not in this package at all
opencode.ai top navno link to /v2
opencode.ai/blog404
opencode upgrade (v1's own updater)reports From 1.17.10 → 1.18.21; takes a version target and a -m/--method (curl/npm/pnpm/bun/brew/choco/scoop), but has no channel that reaches v2

v2 ships from a different npm package — @opencode-ai/cli, on the beta dist-tag (0.0.0-beta-17898 as of 2026-08-22; that package's latest/next sit behind at 0.0.0-beta-17823). Its tui-v2 dist-tag (0.0.0-tui-v2-202606261840) is the same string an earlier pass had dismissed as a dead TUI experiment — it was pointing at v2.

Watch-method lesson: a sweep built on release tags plus a latest dist-tag is structurally blind to a major version that ships under a new package name on a pre-release tag. Any future major-version watch has to check the vendor's docs site paths too, not just its release feed.

Install (side by side, does not disturb v1)

"OpenCode 1 and OpenCode 2 can be installed side by side. V1 runs as opencode, while V2 installs and runs separately as opencode2."

npm install -g @opencode-ai/cli@beta (bun/pnpm/yarn/curl variants documented too). During the beta: "Homebrew, Arch Linux, Windows package managers, Docker, and standalone binaries are not supported during the beta."

Because the binary name differs, adopting v2 is additive — it cannot break the existing opencode v1 worker path. That is the one genuinely good piece of news for Cyberine.

The three intentional breaking changes

Verbatim from the migration guide:

  1. "Plugins use a new plugin API."
  2. "The server API and clients have new contracts."
  3. "Terminal client configuration moves from layered tui.json(c) files to one global cli.json file (auto migrated)." — that file is ~/.config/opencode/cli.json.

None of the three touches Cyberine directly: it uses no opencode plugin, no opencode server API, and no TUI config. "Supported V1 functionality outside those areas is intended to remain compatible with V1."

What the DOCS claim blocks a Cyberine v2 worker (superseded — see Live probe)

Superseded 2026-08-22 by the live probe below. Every bullet in this section is accurate as a description of what the v2 docs say, and four of the five are wrong as a description of what the binary does. Kept because the gap between the two is itself the finding: the beta's docs materially understate the CLI.

The blockers are NOT on that breaking-change list — they are in the headless surface, and they are structural.

  • run has no documented flags. The entire documented automation surface is: "Use opencode2 run to submit a prompt without opening the interactive interface. It is designed for scripts, CI jobs, and other workflows that need model output directly in the terminal." with the example opencode2 run "Explain this repository". Stripping the whole v2 CLI page yields exactly three flag tokens — --help, --server, --standalone — and the latter two are top-level server-selection flags, not run options. --model, --variant, --agent, --dir, --format, -s, --fork, --attach, -f are not documented for run. /v2/docs/cli/run 404s.
  • No documented --format json, and no documented stdout event contract. v1's JSONL (type of text/reasoning/tool_use/step_start/step_finish/error, with part.id + part.text, and step_finish carrying part.tokens.{input,output} + part.cost) has no counterpart anywhere in the v2 docs. parseOpenCodeStdout therefore has nothing to bind to.
  • The documented programmatic path is HTTP, not stdout. /v2/docs/api lists POST /api/generate ("Experimental one-shot generation routes"), POST /api/session/{sessionID}/generate, and GET /api/event ("Subscribe to events"); the TS client iterates events (for await (const event of client.event.subscribe())). opencode2 api <method> <path> exposes the same thing from the shell. So v2's intended integration is a client of a service — a different adapter shape from CliAgentAdapter's spawn-and-parse-stdout.
  • A per-user background daemon is now the default. "By default, OpenCode discovers or starts one shared background server for your user account. Every local OpenCode client connects to that server, which owns sessions, configuration, integrations, permissions, and tool execution." --standalone forces a private server per invocation; --server <url> targets one. Operational surface: opencode2 service status|restart|stop|start, service registration ~/.local/state/opencode/service.json, DB ~/.local/share/opencode/opencode.db (OPENCODE_DB overrides), logs ~/.local/share/opencode/log/opencode.log. A shared, persistent, cross-invocation singleton is a poor fit for worktree-isolated parallel workers — --standalone is the lever, at the cost of whatever the shared server warms.
  • No permission-bypass flag exists at all. Not on run, not global. Auto-approval is config-only (below). And: "Non-interactive clients must decide how to handle requests that require approval; explicit deny rules remain enforced." The docs do not say what happens when nothing can answer an ask in a non-interactive run — for Cyberine, whose spawns have stdin closed, that is the single most important unknown to probe.

Caveat that matters before anyone concludes v2 is unusable

Everything above is documentation absence, not verified binary absence. These are beta docs; opencode2 run --help on a real install may well expose flags the site does not list (the docs themselves point at opencode2 mini --help for that command's "session, model, agent, prompt, and replay options" rather than listing them). Do not write "v2 dropped --format json" into a decision — write "the v2 docs do not document it", then install the beta in a scratch container and read --help.

Config: same locations, new shapes

"V2 reads existing global and project configuration from the same locations as V1: ~/.config/opencode/opencode.json(c), <project>/opencode.json(c), and <project>/.opencode/opencode.json(c)... It normalizes supported V1 and native V2 fields in memory without rewriting the source file."

So the T493 per-worker config file location survives, and V1 syntax keeps working. Precedence (from /v2/docs/config): files merge root -> cwd, and the .opencode/ form overrides the direct form regardless of proximity.

Native-V2 renames worth knowing (migration guide):

V1V2
permission (grouped by tool)permissions (ordered array of {action, resource, effect})
"autoshare": true"share": "auto" (manual/auto/disabled)
agent / modeagents (prompt->system, disable->disabled, maxSteps->steps, temperature/top_p/options -> request.body)
snapshotsnapshots
attachmentmedia
commandcommands
referencereferences
providerproviders (model id->modelID, options->settings, cache_read/cache_write->cache.read/cache.write)
mcp.<name>mcp.servers.<name>, enabled -> inverse disabled, timeout -> {catalog, execution}, camelCase OAuth fields -> snake_case
model + variantone string: provider/model#variant

Permission actions renamed too: "bash is now shell, task is now subagent, and write and patch are now edit" (all three of edit/write/patch share the edit action).

Accepted-but-ignored in v2 (emits a warning): logLevel (use OPENCODE_LOG_LEVEL), server, top-level subagent_depth (use experimental.subagent_depth), compaction.tail_turns/compaction.prune, and several provider/model fields. Three of those are in Cyberine's 36-key allow-list — still legal to write, just inert under v2.

Permission semantics: "If no rule matches, the result is ask" and "The last matching rule wins. Put broad rules first and exceptions later." An allow-everything project rule is therefore one catchall with no later exception — constructed from the documented schema, not copied from a docs example, so verify it before trusting it:

{
  "$schema": "https://opencode.ai/config.json",
  "permissions": [{ "action": "*", "resource": "*", "effect": "allow" }]
}

Beta stability

"These docs are for the beta version of OpenCode, which will become OpenCode 2.0. The beta is still changing: things may break, and APIs, configuration, and plugin APIs may change."

No GA date is stated anywhere. @opencode-ai/client and @opencode-ai/sdk are both marked beta; the plain SDK is not out yet ("The regular SDK is coming soon" — only an Effect-native build exists, bun add @opencode-ai/sdk@dev).

Live probe (2026-08-22, 0.0.0-beta-17898)

Run by cyber-07. Method: npm install --prefix <scratch> @opencode-ai/cli@beta into a throwaway prefix inside .local/tmp/ (never -g), with XDG_DATA_HOME / XDG_STATE_HOME / XDG_CONFIG_HOME / XDG_CACHE_HOME and OPENCODE_DB all redirected into that prefix. Model deepseek/deepseek-chat via DEEPSEEK_API_KEY from .env. Verified after the run that the v1 binary was still 1.18.21 and ~/.local/share/opencode/opencode.db was unchanged in size.

Install note: this machine's npm blocks lifecycle scripts, so the package's postinstall did NOT run (npm warn allow-scripts). The binary works anyway. A -g install would hit the same gate — do not read a blocked postinstall as a broken install.

opencode2 run --help — the real flag table

The docs list no flags for run. The binary has thirteen:

FlagMeaning
--standaloneprivate server for this invocation instead of the shared background service
--server <url>connect to a specific server
--continue, -ccontinue the last session
--session, -s <id>continue a specific session
--forkfork the session before continuing
--model, -m <s>model as provider/model#variant — the unified string the migration table predicted
--agent <s>agent to use
--format <choice>default or json
--file, -f <s>attach a file to the message
--title <s>session title
--thinkingshow thinking blocks
--autoauto-approve permissions that are not explicitly denied
--log-level <choice>all/trace/debug/info/warn/error/fatal/none

So, against the doc-derived blocker list: run has flags; --format json exists; a permission-bypass flag exists and is called --auto — the same name that replaced --dangerously-skip-permissions in v1 1.18.21. Only the shared-daemon-by-default blocker survives, and --standalone is its documented lever.

The stdout event contract is v1's, unchanged

--format json emits one JSON object per line with {type, timestamp, sessionID, part} — the SAME envelope and the SAME type values Cyberine already knows (step_start, tool_use, step_finish, text; reasoning and error untested here). step_finish carries part.cost and part.tokens, now a superset of v1's:

{"input":14064,"output":86,"reasoning":0,"cache":{"read":256,"write":0}}

Cyberine's existing parseOpenCodeStdout was run against the captured v2 stream unmodified and returned the correct assistant text plus {inputTokens: 14064, outputTokens: 86, costUsd: 0.0019937568}, with no SCHEMA DRIFT warning. The v1 parser binds to v2 as-is.

CRITICAL: run hangs forever unless stdin is closed

The single most important operational finding, and it is invisible in the docs. In a non-TTY context opencode2 run produces zero bytes and never exits if it inherits an open stdin — even with the message passed as an argument and --auto set. Isolated with a controlled A/B, one variable, same command otherwise:

InvocationResult
opencode2 run --standalone --auto -m deepseek/deepseek-chat "say hi" < /dev/nullfinished in ~10s, 65 bytes
the identical command without < /dev/nulltimed out at 60s, 0 bytes

The first probe of this page's author burned 180s on the second form and was nearly written up as "v2 run is broken headless". It is not broken; it is waiting on stdin. Any v2 worker MUST close or redirect stdin. Cyberine's CliAgentAdapter already spawns with stdin closed, so this costs nothing to satisfy — but a hand-run reproduction will hang if you forget it.

An InterruptError: All fibers interrupted without error at ServerProcess.start appears in the log on SUCCESSFUL runs too. It is teardown noise, not a failure signal — do not diagnose against it.

--auto genuinely bypasses permissions headless

opencode2 run --standalone --auto --format json -m deepseek/deepseek-chat "Create a file named PROBE.md whose only line is: probe ok" produced a tool_use event with part.tool="write", part.state.status="completed", and the file on disk containing exactly probe ok. No prompt, no stall, stdin closed. The docs' "non-interactive clients must decide how to handle requests that require approval" does not bite when --auto is set.

Two operational hazards for worktree-isolated workers

  1. --standalone servers leak. Each --standalone invocation spawns serve --stdio --port 0 as a child that LISTENs on a localhost port. Killing the parent shell does not reap it — a server from an earlier probe was still listening minutes later. A v2 worker needs explicit process-group teardown, not just a kill of the spawned CLI.
  2. The server resolves a project root by walking UP, and then watches far more than that project. A server started from this repo subscribed watchers to ~/.claude/skills, ~/.agents/skills, ~/WORK/AI/shared-context-server/skills/ctq-brain, ~/WORK/AI/nf-tasks, and AGENTS.md at four directory levels — and reported event.location.directory as this repo even when invoked from a nested scratch repo under .local/tmp/. Nesting a worker's cwd inside another repo does not isolate it. Give a v2 worker a cwd outside any parent repo, or accept cross-project watchers.

Go / no-go

GO, as a CliAgentAdapter sibling — NOT the separate HTTP-client adapter the doc-only pass expected. The subprocess path is fully viable: run takes a model, an agent, --format json, and a working permission bypass, and emits the event stream Cyberine's parser already reads. The HTTP /api/generate path remains available but buys nothing a worker needs.

Not yet settled, and deliberately out of scope for this probe: reasoning and error event shapes under v2 (untested); whether #variant in --model behaves as the migration table claims; and whether the shared-daemon mode (no --standalone) is safe under parallel worktree workers. Adopting is a follow-up task, not this one — v2 is a moving beta with no GA date, and the v1 worker is unaffected either way because the binary name differs.

If Cyberine adopts v2 later

Steps 1, 2, 3 and 5 of the original sketch are ANSWERED by the live probe above; what remains is the build itself.

  1. Install and read --help for real. Done — flag table above.
  2. Decide subprocess vs HTTP. Done — subprocess. A v2 worker is a makeOpenCodeAdapter sibling in CliAgentAdapter.ts, reusing parseOpenCodeStdout unchanged.
  3. Pin model/permissions via config because there are no flags. Not needed — --model provider/model#variant and --auto are real flags. A per-worker config file is still the lever for anything without a flag.
  4. Force --standalone per invocation, and reap the serve --stdio child explicitly — it survives a parent kill (see hazard 1 above).
  5. Probe what a non-interactive ask does. Done — --auto bypasses cleanly; the real headless hazard is an inherited stdin, not permissions.
  6. Keep the v1 worker running throughout: different binary name, so both can coexist.

Remaining unknowns before a worker ships: reasoning/error event shapes under v2, #variant behaviour, and shared-daemon safety under parallel worktrees.

Sources