Guides

Using OpenCode

Run a model profile's soldiers on OpenCode instead of Claude Code, on OpenCode 1.x or 2.x, including its free Zen models.

A model profile can name a harness, the agent CLI its soldiers run on. With "harness": "opencode" the soldiers of that profile run on OpenCode, which can use many providers and models, including free ones. The commander stays in Claude Code unless you choose otherwise (see OpenCode as the commander), and a task keeps its harness through vx redispatch, vx prompt, vx decide, vx land and vx strike.

OpenCode has two generations that share the opencode command, and vexillum drives both:

  • 2.x (checked against 2.0.32). Its TUI has no --model flag, so vexillum creates the soldier's session on that model through OpenCode's background service and opens it.
  • 1.x, from 1.18.29. Its TUI takes --model.

vx dispatch runs opencode --version and records the major series on the task, and vx redispatch, vx prompt and vx decide use the same one. A task dispatched on 2.x is not relaunched on 1.x: install the series it was dispatched on, or strike it and dispatch again. Nothing about the version is cached between commands.

Only some free models keep your code private

A soldier sends whatever it reads to the model provider: your prompts, the files it opens, command output and anything in your environment that a command prints. OpenCode's own docs (opencode.ai/docs/zen, read on 2026-10-19) name four of Zen's free models as zero-retention: glm-5.3-flash-free, deepseek-v4.1-flash-free, minimax-m3.1-flash-free and longcat-2.5-preview-free. Several others are documented as collecting data to improve the model or as retaining it for 30 days, and a free model the page does not mention, such as space-bunny-free, is not documented as zero-retention, so treat it as logging everything. A free model also gives no guarantees as a commander. Terms change, so read them before you point a profile at a free model, and do not use one for private or proprietary code, or on a machine whose files or environment hold secrets. OpenCode, like Claude Code in vexillum, has full host access under your user.

Only OpenCode 2.x was verified live

The 2.x launch, the blocked-screen handling and the one-shot used by vx ship were run on a real OpenCode 2.0.32. The 1.x paths come from the 1.18.29 and 1.19.3 sources and are covered by tests against a fake opencode that reports 1.x, not by a real 1.x. docs/verification/opencode-soldier-checklist.md in the repository lists every step still to confirm.

Set it up

  1. Install OpenCode, then run opencode once and connect a provider. The free Zen models need no key. opencode models lists what you can use.

  2. Install herdr's integration for OpenCode, so herdr reads a soldier's state (working, idle, blocked) from OpenCode itself instead of from the screen:

    herdr integration install opencode
  3. Run the OpenCode TUI once after each OpenCode upgrade, before you dispatch. A new version can show a "what's new" dialog on its first start. Nobody is at a soldier's pane to dismiss it, so the soldier would sit on it.

  4. Run vx doctor. When a profile selects opencode, or an opencode binary is on your PATH, it reports the binary, its version (1.x from 1.18.29, or 2.x), whether an older 1.x install shadows a 2.x one earlier in PATH, and the herdr integration. They only warn: a missing piece never makes the environment not ready, and a setup without OpenCode sees none of these lines.

Add a profile

Add a profile with "harness": "opencode" to .vexillum/models.json (or the global ~/.vexillum/models.json). The same profile works on 1.x and 2.x:

{
  "profiles": {
    "opencode-scout": {
      "model": "opencode/glm-5.3-flash-free",
      "effort": "low",
      "when": "Scout whose question reduces to verifiable facts, run on a free zero-retention model.",
      "harness": "opencode"
    },
    "opencode-mission": {
      "model": "anthropic/claude-sonnet-5#high",
      "effort": "medium",
      "when": "Mission on OpenCode with Sonnet at its high variant.",
      "harness": "opencode"
    }
  }
}
vx dispatch "which flags does the build script accept?" --kind scout --profile opencode-scout

On an OpenCode profile the two fields mean:

FieldValueWhat happens
modelprovider/model, with an optional #variant, such as opencode/glm-5.3-flash-free or anthropic/claude-sonnet-5#highthe soldier starts on that model, and on that variant when there is one
effortany plain label, such as low or mediumrecorded on the task and shown in vx status, never passed to OpenCode

vexillum checks the shape when it reads the profile. On 2.x, vx dispatch also checks the provider, model and variant against what OpenCode lists for the camp (see Which model a soldier gets); on 1.x OpenCode reports a model it does not have when the soldier starts. Effort is not mapped to a variant because variant names differ per model: one model's high is another model's error. A variant is used only when the profile writes one after the #.

A variant the model does not have is an error. On 2.x vx dispatch refuses it and names the variants the model has. A one-shot (vx ship's reviewer and fixer, which use opencode run) fails with Variant unavailable for <model>: <variant>. The footer of the soldier's pane shows the variant, such as · high, when it applies; check it the first time you use a variant.

Which model a soldier gets

On 2.x the TUI cannot be told a model on its command line, and the configuration that decides the model is read by OpenCode's shared background service, not by the TUI. A file written into the camp, or an environment variable on the pane, would either clash with a project's own OpenCode configuration or be ignored. So vx dispatch gives each soldier a session of its own:

  1. The service. vexillum makes sure OpenCode's background service is running. If it is not, vexillum starts it itself with opencode service start, from an environment with OPENCODE_CONFIG, OPENCODE_CONFIG_DIR and OPENCODE_CONFIG_CONTENT removed. The service reads those variables once, when it starts, and keeps them for every OpenCode window that connects to it afterwards. opencode api would start a stopped service with whatever environment its caller has, and so would the soldier's own TUI, so vexillum starts the service first and never leaves it to them. A service that is already running is used as it is.
  2. The check. vexillum asks the service which models it can run for the camp and checks the profile's provider, model and variant against that list. When one is not there, the dispatch fails before any pane exists and names what does exist, for example OpenCode's provider "anthropic" has no model "claude-sonnet-9"; its models: .... The task is left failed, and vx strike removes it.
  3. The session. vexillum creates a session located in the camp, with the profile's model and variant, titled vexillum <task-id>. It sets no agent and no permission rules.
  4. The start. The soldier starts as opencode --auto --session <id>, with OPENCODE_DISABLE_AUTOUPDATE=1 in its pane.

A session's model ranks above the project's own model and above its default agent's own model, so a project that sets its own default agent or model does not override the profile. Only the soldier's own agent runs on the session's model: OpenCode's built-in subagents keep the models they are configured with, and the soldier can still switch model in its TUI.

Nothing is written into the camp or the project, no environment variable carries configuration, and no line is added to info/exclude. A project's own opencode.json or opencode.jsonc, its agents (even one named vx-soldier), its default agent and its permission denies apply exactly as they do when you run OpenCode there. A repository that tracks OpenCode configuration dispatches like any other, and a mission can create opencode.json or .opencode/ without git add -f. A task with no model starts a plain OpenCode on the project's defaults, with no session.

The session id is recorded on the task (harness_session in vx status --json). vx redispatch opens the same session again when the service still has it and it is located in the camp the task gets back, so the conversation continues. If the service lost it (it was deleted, or the service's data was reset) or the camp is another one, vexillum creates a new session. vx strike does not delete the session: it stays in OpenCode's session list, titled vexillum <task-id>, until you delete it there.

On 1.x nothing is created or written: the model goes with --model, and a variant with the OPENCODE_CONFIG_CONTENT environment variable of the soldier's pane. Both generations set OPENCODE_DISABLE_AUTOUPDATE=1, so a running soldier is not updated under you.

Older versions of vexillum wrote .opencode/agents/vx-soldier.md and opencode.json into the camp of a 2.x soldier and added /.opencode/ and /opencode.json to the repository's info/exclude. A reused camp that still holds those files has them removed before the next launch, but only when they are byte-identical to what vexillum wrote, so an edit of yours is never deleted. The lines in info/exclude are left alone, because a soldier started by an older version may still depend on them. They do nothing now, apart from hiding an untracked opencode.json in your main checkout from git status; delete them from .git/info/exclude when no older soldier is running.

Permissions

A soldier starts with --auto, which approves every permission that is not explicitly denied. vexillum sets no permission rules of its own, in the session or anywhere else. In 2.x the last matching rule wins, so a wildcard rule of vexillum's would override the denies in your own OpenCode config. Without a rule of its own, your denies keep applying inside a soldier. As with every harness, landing is the control: nothing reaches your history until you approve vx land.

What differs from a Claude Code soldier

TopicClaude Code soldierOpenCode soldier
Permissions--dangerously-skip-permissions--auto; your own deny rules still apply. See above.
Model--model and --effort--model on 1.x; a session created for the soldier on 2.x. Effort is recorded, not passed.
First prompttyped into the panedelivered through herdr agent prompt once the pane is ready. vx dispatch waits for an agent_not_ready start error to clear before it treats the start as failed.
RulesCLAUDE.md, which imports AGENTS.mdOpenCode reads AGENTS.md and, only when there is none, CLAUDE.md. It does not expand @AGENTS.md or any other @file reference inside them. The vexillum block lives in AGENTS.md (or in CLAUDE.md when the project has no AGENTS.md), and OpenCode reads whichever holds it.
Pluginsn/aV1 plugins do not run in V2. A project that relies on one for its workflow behaves differently under a 2.x soldier.
Skillsthe project's .claude/skillsnone. Soldiers do not use skills in this release.
QuestionsClaude's question selectorOpenCode's own question form and permission prompt. See below.
vx shipthe tribunal's reviewer and fixer run claude -pthey run opencode run --auto --format json on the mission's model and variant, with no input on stdin

Blocked soldiers

With --auto a permission prompt is rare, and the usual way an OpenCode soldier blocks is its question form. vx decide answers what it can recognize, on 2.x only, and reads the pane again right before it types so it never types into a screen that has moved on:

Screenvx decide <task-id> <answer>
Permission promptallow once presses Enter. reject presses Esc, and the soldier sees "The user declined this tool call". always allow is refused: it may save a rule for good, and a one-line answer cannot carry that decision.
Question forman option's number (2) or its exact text selects it with arrow-down presses and Enter. Any other text is typed into the form's "Type your own answer" field, one key press per character, so it is limited to printable ASCII. A bare number that is not an option is refused instead of typed.
A screen vexillum does not know, or any 1.x screenrefused, with nothing typed. Attach to the pane with herdr and answer there.
No blocked screen (the soldier ended its turn with a needs-decision: line)the answer is delivered as plain text, like every other harness

herdr itself rejects herdr agent prompt with agent_blocked while a pane is blocked, which is why a typed answer goes in through key presses. See Blocked and interrupted.

Not in this release

  • Mapping effort to a variant.
  • Structured answers for a question form of several questions.
  • Skills for OpenCode soldiers.

For a checklist to verify an OpenCode setup, 1.x in particular, on a real machine, see docs/verification/opencode-soldier-checklist.md in the repository.

On this page