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:
anthropic:ANTHROPIC_API_KEYdeepseek:DEEPSEEK_API_KEYllamacpp:LLAMACPP_API_KEY(only if your server needs auth)mistral:MISTRAL_API_KEYmoonshot:MOONSHOT_API_KEYopenai:OPENAI_API_KEYxiaomi:XIAOMI_API_KEYzai:ZAI_API_KEY
Config directory
By default, Opair looks for configuration in a per-user directory:
- MacOS:
~/Library/Application Support/opair/ - Linux:
$XDG_CONFIG_HOME/opair/
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:
OPAIR_CONFIG_FILE: path toconfig.jsonOPAIR_LOOPS_DIR: path to loop definitionsOPAIR_MCP_DIR: path to MCP definitionsOPAIR_PROMPTS_DIR: path to promptsOPAIR_TOOLS_DIR: path to tool definitions
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:
anthropicdeepseekllamacppmistralmoonshotopenaixiaomizaienablers(internal platform for employees of Algolia)
To enable a provider with the default settings, set it to an empty object:
{
"providers": {
"zai": {}
}
}
Or you can set the following properties:
apiKeyEnv: Environment variable that holds the API key, defaults to{PROVIDER}_API_KEY.baseUri: Base URI for the provider API.
Default base URIs:
anthropic:https://api.anthropic.comdeepseek:https://api.deepseek.com/v1llamacpp:http://127.0.0.1:8080/v1mistral:https://api.mistral.ai/v1moonshot:https://api.moonshot.ai/v1openai:https://api.openai.com/v1xiaomi:https://api.xiaomimimo.com/v1zai:https://api.z.ai/api/paas/v4
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:
driver: The role for driver mode, where the agent has the keyboard, metaphorically.navigator: The role for navigator mode, where the user has the keyboard.plan: The role for the planning agent.review: The role for the review agent.document: The role for the documentation agent.explain: The role for the explanation agent.compactUncached: The role for a non-standard compaction agent, which doesn't respect the prompt cache (meant to be used when the prompt cache is known empty, e.g. after switching models).title: The role for the agent that names sessions when they're persisted to disk.
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:
-
large: Default model for longer prompts. -
small: Default model for shorter prompts.
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:
driver: The loop for driver mode.navigator: The loop for navigator mode.plan: The loop for the planning agent.review: The loop for the review agent.document: The loop for the documentation agent.explain: The loop for the explanation agent.
Values under loops are objects
with the following properties:
-
approveReads: Array of read permissions that do not require user approval. -
approveWrites: Array of write permissions that do not require user approval. -
approve: Array of MCP integrations that do not require user approval. -
reasoningEffort: Reasoning effort for the loop, can be"off","low"or"high".
Valid read permissions are:
-
project: Files inside the project that are not git-ignored, not dot files and not children of dot directories. -
project-dot: Files inside the project that are dot files or children of dot directories. -
project-ignore: Files inside the project that are git-ignored. -
git: Information from git. -
github: Information from github. -
any: Any other reads.
Valid write permissions are
everything under approveReads plus:
-
artifacts: Build output. -
agent: Files that were previously created by the current agent (theplananddocumentroles have this permission set by default, so they can edit their own output without prompting for approval).
See the tools reference for more information about permissions.
Valid MCP integrations are:
-
mcp:atlassian -
mcp:cloudflare-bindings -
mcp:cloudflare-observability -
mcp:github -
mcp:gitlab -
mcp:linear -
mcp:sentry -
mcp:slack
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:
-
--jinja: Required for tool calling to work. -
--reasoning-format deepseek: Separatesreasoning_contentfromcontent. -
--no-context-shift: Recommended. Treats context overflow as an error instead of silently dropping earlier messages.
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.