> ## Documentation Index
> Fetch the complete documentation index at: https://obversa.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenCode CLI Engine

> @obversa/engine-opencode-cli: one engine attempt through a fresh OpenCode CLI process, with your own OpenCode login, for any provider OpenCode runs.

`@obversa/engine-opencode-cli` runs one engine attempt through the
`opencode` command-line tool, in a fresh process. Use it to put a model
from any provider OpenCode runs into a seat, which gives a review panel a
third model family.

The plugin runs OpenCode [clean](#clean) by default: with your own home
folder and your OpenCode login, but with an empty config folder in place of
yours, so your OpenCode settings stay out. Set `clean: false` to run it the
way you run it, with your own config folder too. OpenCode loads the
repository's instruction files under its own rules. The plugin adds only what the step
needs, through OpenCode's own config: the tools and permission rules the
step declares, no autoupdate and no sharing. Every other tool is set to
`ask`, so OpenCode turns down each call to it and the tool doesn't run.
[Free models](#free-models) explains why.

## Install

<CodeGroup>
  ```bash npm theme={null}
  npm install @obversa/engine-opencode-cli
  ```

  ```bash pnpm theme={null}
  pnpm add @obversa/engine-opencode-cli
  ```
</CodeGroup>

Included in `@obversa/obversa`.

## Requirements

* OpenCode CLI 1.18.23, the version the plugin is tested with, at an
  absolute path you pass as `executable`. The plugin refuses any other
  version.
* OpenCode signed in to your model's provider, or login data you pass in
  `auth`.
* Any model OpenCode runs, free models such as `opencode/big-pickle`
  included. [Free models](#free-models) says what they cost.

## Free models

OpenCode's free models refuse any run whose config turns a tool off or
denies a permission. Their message is "OpenCode's free tier can only be
used from within OpenCode". So the plugin does neither. It sets each tool the
step doesn't declare to `ask`. A declared tool the step limits to a
pattern, such as `Bash(git status)`, is set to `ask` for everything else.

A free model gives up nothing for this. `opencode run` turns down every
`ask`, because nobody is there to answer it. The plugin never passes the
flags that would approve them, such as `--auto`. So a tool the step doesn't
declare never runs, and a step's limits hold the same way on a free model
as on a paid one.

What it costs: the model sees all of OpenCode's tools, not only the ones
the step declares. When it calls one the step doesn't declare, OpenCode
turns the call down and tells the model, and the model carries on with
the step.

## Identity

The plugin reports the provider and model family of the model each request
names, read from the `provider/model` string with
[`modelIdentity`](/docs/packages/api): `anthropic/claude-sonnet-4-5` is reported
as provider `anthropic` and family `claude`. The `identity` you pass to the
constructor is a claim about what the instance serves, and a request whose
model disagrees with it is refused before anything runs.

## Quickstart

`opencode(model, { executable })` is the seat helper a `workflow()` role
takes, and `resolveCommandExecutable` finds the absolute path when the file
runs:

```ts examples/teams/threshold-panel.ts (excerpt) {4-6} theme={null}
const realEngines: ThresholdPanelEngines = {
  claude,
  codex,
  opencode: (model) => opencode(model, {
    executable: resolveCommandExecutable('opencode'),
  }),
};
```

`new OpenCodeCliEngine(options)` is the same engine without the seat
identity:

```ts examples/safe-node-attempt.ts (excerpt) {2-4} theme={null}
  const opencode = validateAgentResult(await new OpenCodeCliEngine({
    executable: openCodeExecutable,
    version: '1.18.23',
    identity: { provider: 'opencode', modelFamily: null },
  }).run(
    request(directory, 'opencode/big-pickle', RESULT_SCHEMA),
    () => {},
    new AbortController().signal,
  ));
```

Structured results depend on the model following an instruction the plugin
adds: start the final answer with the line `OBVERSA_STRUCTURED_RESULT_V1`,
then one JSON value. The job's own parser reads that marked text part.

## Options

### `auth` and `environment`

The child gets your own environment, so OpenCode uses your own login.
`auth` is provider-keyed login data that OpenCode uses instead of the
logins you stored with `opencode auth login`. `opencode(model, options)`
takes the same `auth` option. `environment` sets more values on top of
your environment. Names that start with `OPENCODE_` are refused.

### `clean`

`true` points OpenCode at a new config folder for each OpenCode process,
holding only an empty `AGENTS.md`, and removes it afterwards. Your own settings, plugins, agents, MCP servers and
global instruction files stay out, `~/.claude/CLAUDE.md` included. The
repository's own config and instruction files still apply. OpenCode's data
folder, which holds your login, stays yours.
`opencode(model, options)` takes the same `clean` option.

Its row in the [engines table](/docs/packages/engines#the-engines): runs on your
setup, clean mode available, read-only held in both modes. Clean
mode moves the whole config folder (`XDG_CONFIG_HOME`), so commands the
step runs also start without their settings in it, such as the GitHub
CLI's login. A config folder you set in `OPENCODE_CONFIG_DIR` stays out
too. Your own skills in `~/.claude/skills` and `~/.agents/skills`
still load in clean mode, because OpenCode's switch that skips them also
skips the repository's own skills.

### The rest

| Field | Type | Default | Description |
| - | - | - | - |
| `executable` | `string` | required | Absolute path to the OpenCode CLI. |
| `version` | `string` | required | The version the CLI must report, `1.18.23`. |
| `identity` | `OpenCodeCliIdentity` | required | What this instance serves. |
| `tools` | `readonly string[]` | none | `OpenCodeSeatOptions` only: the tools the seat may use. |
| `auth` | `JsonObject` | none | Login data to use instead of yours. Also an `OpenCodeSeatOptions` field. |
| `effort` | `string` | none | Passed as `--variant <level>`, OpenCode's provider-specific reasoning effort. A request's `effort` wins. Also an `OpenCodeSeatOptions` field. See [Reasoning effort](/docs/packages/engines#reasoning-effort). |
| `clean` | `boolean` | [See the default](/docs/packages/engines#the-default) | On by default: your own setup stays out. `false` runs on your own setup. Also an `OpenCodeSeatOptions` field. [`clean`](#clean). |

## Errors

* **A version other than `1.18.23`** refuses the run.
* **A request whose model disagrees with `identity`** is refused before
  anything runs.
* **A symlink in the workspace that points outside it** refuses a step
  that can read or change files.
* **`EngineError`** kinds as the [API](/docs/packages/api#errors) lists them.

## API

* **`opencode(model, options)`**: a `TeamSeat` for a workflow role.
  [Quickstart](#quickstart).
* **`OpenCodeCliEngine`**: the engine class. **`OpenCodeCliEngineOptions`**,
  **`OpenCodeCliIdentity`**, **`OpenCodeSeat`**, **`OpenCodeSeatOptions`**,
  **`OpenCodeInvocation`**: its types.
* **`buildOpenCodeInvocation`**: the invocation builder, exported for tests.

## Next steps

* [A review panel with a threshold](/docs/patterns/review-panel): an OpenCode
  seat on a panel with Codex, with a real run.
* [Safe node attempts](/docs/recording/node-attempts): the offline example in
  full.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.