Opair

The coding harness that eschews autonomy

Configuration

Opair can be configured with environment variables or a config.json file. This document is the guide for both.

Environment variables

If all you want is to use Opair with a single model provider and default settings, you can do it with a couple of environment variables, e.g.:

export OPAIR_PROVIDER=anthropic
export ANTHROPIC_API_KEY=...

Valid values for OPAIR_PROVIDER are any of the listed providers.

Valid API key environment variables are:

Config directory

By default, Opair looks for configuration in a per-user directory:

From there it tries to read config.json and it's also the default location for the loops/, mcp/, prompts/ and tools/ asset subdirectories. Session transcripts are saved there too under a sessions/ subdirectory.

All of these locations can be overriden at runtime with environment variables:

Setting these is especially useful if you've made changes to some assets and you don't want to clobber those changes when upgrading Opair.

config.json

config.json supports four top-level object properties: providers, roles, defaults and loops. If a config file doesn't match the expected schema, Opair aborts.

Providers

Each key under providers is the provider id. Valid provider ids are:

To enable a provider with the default settings, set it to an empty object:

{
  "providers": {
    "zai": {}
  }
}

Or you can set the following properties:

Default base URIs:

API keys are always read from the environment, either the default variable or the variable identified by apiKeyEnv.

Roles

Each key under roles is a role id. Valid role ids are:

Values under roles are model strings, either in the form {provider}/{model-id} or the literal strings large and small can be used to select the default large and small models (see defaults). Note the providers used here must be configured; if a provider is used that's not set under providers, Opair aborts.

Unset, most roles default to large. Only the title model uses small currently.

Defaults

defaults specifies which models to use a when a role doesn't set one explicitly:

Both values are model specs (see roles). With more than one provider configured, at least one of large or small is required. Model specs that reference a default which isn't set are rejected at load.

Loops

Each key under loops is a loop id. Valid loop ids are a subset of the valid role ids:

Values under loops are objects with the following properties:

Valid read permissions are:

Valid write permissions are everything under approveReads plus:

See the tools reference for more information about permissions.

Valid MCP integrations are:

See the MCP guide for more information about available MCP integrations.

Example

The example config below shows three different providers, specific models for certain roles, a loop-specific write approval and reasoning effort overrides:

{
  "providers": {
    "deepseek": {},
    "moonshot": {},
    "zai": {}
  },
  "defaults": {
    "large": "deepseek/deepseek-flash",
    "small": "zai/glm-5.3-flash"
  },
  "roles": {
    "plan": "moonshot/kimi-k3",
    "review": "zai/glm-5.3-flash"
  },
  "loops": {
    "driver": {
      "approveWrites": ["artifacts", "project"],
      "reasoningEffort": "low"
    },
    "navigator": {
      "reasoningEffort": "low"
    },
    "document": {
      "reasoningEffort": "high"
    },
    "plan": {
      "reasoningEffort": "low"
    }
  }
}

Example config files for each provider are in the root of the repo.

Model failover

When multiple providers are configured, HTTP 401, 402 and 403 responses will degrade an entire provider. A model-not-found error (HTTP 404) will degrade only that specific model. In both cases the router transparently fails over to the next available model or provider.

Transient errors (429, 5xx, network) are not failed over. They surface in the status area and you can retry.

Explicitly selecting a degraded model or provider via /model clears the degradation and retries. This can be useful after topping up API credits.

Running against llama.cpp

Start llama serve with:

Point baseUri at the server if it isn't on the default host and port. No LLAMACPP_API_KEY environment variable is needed unless your server needs authentication.

Reasoning effort is coarser than on hosted providers. "off" was verified against a live server to genuinely disable thinking. "low" and "high" both enable it, but whether llama-server distinguishes between the two was not verified.