Configuration
Config files, precedence, providers, agents, permissions, MCP, snapshots, and the TUI settings file.
alforria reads JSON or JSONC. The main file is opencode.json (or
opencode.jsonc) for wire compatibility with the upstream project, and the
terminal UI reads a separate tui.json. Both are merged from several sources,
and later sources win.
Config sources
The main config is merged in this order, lowest priority first:
- The global config directory:
config.json, thenopencode.json, thenopencode.jsonc. - Project
opencode.jsonandopencode.jsoncfiles, found by walking up from the working directory to the filesystem root. Files closer to the working directory are merged later, so the closest file wins. opencode.jsonandopencode.jsoncinside.opencodedirectories, found by the same walk, plus~/.opencode.- The managed config directory (
/etc/opencodeby default). - Post-merge normalization:
modeentries fold intoagent, and the legacytoolsmap folds intopermission.
Objects merge deeply. Arrays replace the earlier value, except
instructions, whose entries are concatenated without duplicates.
The upstream variables OPENCODE_CONFIG, OPENCODE_CONFIG_DIR,
OPENCODE_CONFIG_CONTENT, OPENCODE_PERMISSION, and
OPENCODE_DISABLE_PROJECT_CONFIG are not honored by the main config loader
yet. OPENCODE_CONFIG_DIR is only searched for a tui.json, and
OPENCODE_DISABLE_PROJECT_CONFIG only skips project tui.json files and
project instruction files such as AGENTS.md.
The global files live under the config directory reported by
alforria debug paths. On every platform the defaults are XDG-style:
| Purpose | Default | Environment override |
|---|---|---|
| Config | ~/.config/opencode |
XDG_CONFIG_HOME |
| Data and sessions | ~/.local/share/opencode |
XDG_DATA_HOME |
| Cache | ~/.cache/opencode |
XDG_CACHE_HOME |
| State | ~/.local/state/opencode |
XDG_STATE_HOME |
The SQLite database is opencode.db in the data directory. Run
alforria debug paths to see the resolved paths on your machine.
Variables and JSONC
Config files may contain comments and trailing commas. Two substitutions are applied to config text before it is parsed:
{
"provider": {
"libertai": {
"options": {
"apiKey": "{env:LIBERTAI_API_KEY}"
}
},
"openai": {
"options": {
"apiKey": "{file:~/.secrets/openai-key}"
}
}
}
}
{env:VAR}reads an environment variable. An unset variable becomes an empty string.{file:path}reads a file, trims it, and inserts its contents with JSON string escaping, so put the token inside a JSON string. Relative paths resolve against the config file's directory, and~/expands to the home directory. A missing file is an error.
instructions takes file paths, globs, or http(s) URLs. Do not use a
{file:} token there: it would turn the file's text into a path.
{
"instructions": ["CONTRIBUTING.md", "docs/guidelines/*.md"]
}
Relative entries are matched from the working directory up to the project root, and each matching file is added to the system prompt.
The schema reference for editor completion is
https://opencode.ai/config.json, which is the shared upstream schema. See
the wire compatibility note.
Providers and models
Credentials are normally stored by alforria auth login. You can also declare
provider options directly:
{
"$schema": "https://opencode.ai/config.json",
"model": "libertai/<model-id>",
"small_model": "libertai/<small-model-id>",
"provider": {
"libertai": {
"options": {
"apiKey": "{env:LIBERTAI_API_KEY}",
"baseURL": "https://api.libertai.io/v1"
},
"models": {
"my-model": {
"name": "My Model",
"limit": { "context": 128000, "output": 8192 }
}
}
}
}
}
modelselects the default model inprovider/modelform.small_modelselects the model used for small auxiliary tasks.modelsadds or overrides model metadata for a provider.enabled_providersanddisabled_providersare arrays of provider ids.
Provider ids and model availability come from the catalog and from auth
entries. List them with alforria models or alforria models <provider>.
Refreshing the catalog from models.dev is explicit:
alforria models --refresh.
LibertAI's built-in endpoint is https://api.libertai.io/v1.
alforria auth login -p libertai starts its browser flow, which also accepts
a pasted code when the browser runs on another machine, and
alforria libertai usage shows plan usage and credits.
Agents
Two agents are built in:
buildexecutes tools according to the configured permissions.planis a planning mode that denies edits except to plan Markdown files. Your ownpermissionrules are merged on top of it, so a config-level"edit": "allow"re-enables edits in plan mode.
Custom agents are Markdown files with YAML frontmatter, discovered under
agent/**/*.md or agents/**/*.md in the global config directory or a
.opencode directory. A file in a subdirectory is named by its path, so
agent/review/rust.md becomes the agent review/rust. The body becomes the
agent prompt.
---
description: Reviews Rust changes without editing files
mode: subagent
model: libertai/<model-id>
permission:
edit: deny
bash:
"*": ask
"git diff*": allow
---
You review Rust code. Read the diff, identify correctness and safety issues,
and report them with file and line references. Do not edit files.
mode is primary, subagent, or all. Set default_agent in the main
config to choose the agent for new sessions. alforria agent list shows the
resolved agents. alforria agent create is not available yet, so write the
file by hand.
Agent permissions merge over the built-in defaults, so the agent's rules win.
The agent's tools map is also accepted and folds into permission for
compatibility.
Permissions
Permissions use allow, ask, and deny. The value can be a bare action or
an object of patterns:
{
"permission": {
"edit": "ask",
"bash": {
"*": "ask",
"git status": "allow",
"git diff*": "allow",
"rm *": "deny"
},
"webfetch": "allow",
"question": "allow",
"doom_loop": "ask"
}
}
- Patterns support
*(any characters, including/) and?(one character). A pattern ending in*also matches the bare prefix. - Rules are applied in sorted key order, not in the order you wrote them, and
the last matching rule wins. Specificity does not matter. With
{"git status": "allow", "git*": "deny"},git statusis denied, becausegit*sorts aftergit status. A"*"key sorts before keys that start with a letter, digit,.,/, or~, so those rules override a catch-all"*"in the same object. readrules cover file reads. Patterns such as*.envdefault toask.doom_loopgates repeated identical tool calls.external_directorygates access outside the project.questioncontrols whether the question tool can be used.
The built-in defaults allow ordinary tools, ask before reading .env files,
ask at the doom-loop gate, and deny the question tool. Both built-in primary
agents, build and plan, allow the question tool. Your rules are merged
last and can override each of these.
OPENCODE_PERMISSION is not honored yet; put permission rules in a config
file. --auto turns every non-denied ask into an allow for that process. Use
it only in an environment you are prepared to lose.
Tools
Individual tools can be disabled through permissions:
{
"permission": {
"bash": "deny",
"webfetch": "deny"
}
}
The legacy tools map is accepted and folded into permission. Prefer
permissions for new configuration.
The production registry includes bash, read, write, edit,
apply_patch, glob, grep, task, webfetch, websearch, todowrite,
question, and skill, plus validation fallbacks. MCP servers can add more
tools. Availability depends on the client, provider, flags, and model.
MCP servers
MCP servers can be configured in opencode.json or with the CLI:
{
"mcp": {
"filesystem": {
"type": "local",
"command": ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
"environment": { "LOG_LEVEL": "warn" },
"enabled": true
},
"remote": {
"type": "remote",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer {env:MCP_TOKEN}" },
"oauth": false
}
}
}
alforria mcp list
alforria mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /tmp
alforria mcp auth remote
Local servers take a command array; remote servers take a url and optional
headers. OAuth-enabled servers can be authenticated with alforria mcp auth.
alforria mcp debug <name> helps diagnose an OAuth connection.
Snapshots, sharing, and compaction
{
"snapshot": true,
"share": "disabled",
"compaction": { "auto": true }
}
snapshotenables Git-backed snapshots and revert. Snapshots are on unless this isfalse, and require a Git repository.sharecan disable sharing.OPENCODE_DISABLE_SHARE=1also disables it.- Compaction settings control automatic conversation compaction. See
alforria debug configfor the resolved values.
Terminal UI config
The TUI reads tui.json or tui.jsonc from these places, in order: the
global config directory, the file named by OPENCODE_TUI_CONFIG, project
directories from the filesystem root down to the working directory, then
.opencode directories and OPENCODE_CONFIG_DIR. Later files win, so project
files override the global one. A file that does not parse or validate is
skipped with a warning in the TUI log.
{
"$schema": "https://opencode.ai/tui.json",
"theme": "system",
"mouse": true,
"scroll_speed": 3,
"scroll_acceleration": { "enabled": false },
"diff_style": "auto",
"keybinds": {
"command_list": "ctrl+p"
},
"prompt": { "max_width": 75 }
}
Set a keybind to "none" to disable it. theme accepts a bundled or custom
theme name. Custom themes live in themes/*.json under the global config
directory or a project's .opencode directory.
Older theme, keybinds, and tui keys in opencode.json are migrated to a
sibling tui.json before the TUI starts, with a backup of the original file.
Environment variables
The most useful variables. OPENCODE_CONFIG, OPENCODE_CONFIG_CONTENT, and
OPENCODE_PERMISSION are not listed because they are not honored yet.
| Variable | Effect |
|---|---|
OPENCODE_TUI_CONFIG |
Load an additional tui.json file |
OPENCODE_CONFIG_DIR |
Extra directory searched for tui.json. The main config loader does not read it yet |
OPENCODE_DISABLE_PROJECT_CONFIG |
Skip project tui.json files and instruction files such as AGENTS.md. Project opencode.json files still load |
OPENCODE_SERVER_PASSWORD |
Server Basic auth password. Empty means no auth. When unset, only serve generates one |
OPENCODE_SERVER_USERNAME |
Server Basic auth username, default alforria |
OPENCODE_PRINT_LOGS |
Print logs to stderr, set by --print-logs. Only serve reads it |
OPENCODE_LOG_LEVEL |
Log level for --print-logs, set by --log-level |
OPENCODE_PURE |
Set by --pure. There is no plugin runtime yet, so it only changes the debug info output |
OPENCODE_DISABLE_MODELS_FETCH |
Do not fetch the model catalog |
OPENCODE_MODELS_URL / OPENCODE_MODELS_PATH |
Override the catalog source or cache path |
OPENCODE_DISABLE_SHARE |
Disable session sharing |
OPENCODE_DISABLE_LSP_DOWNLOAD |
Do not download LSP servers |
OPENCODE_DISABLE_EMBEDDED_WEB_UI |
Do not serve the embedded web client |
OPENCODE_WEBSEARCH_PROVIDER |
Select the web search backend |
OPENCODE_ENABLE_EXA |
Enable Exa web search |
Run alforria debug config to see the merged result, and
alforria debug paths to see where each file is read from.