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:

  1. The global config directory: config.json, then opencode.json, then opencode.jsonc.
  2. Project opencode.json and opencode.jsonc files, 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.
  3. opencode.json and opencode.jsonc inside .opencode directories, found by the same walk, plus ~/.opencode.
  4. The managed config directory (/etc/opencode by default).
  5. Post-merge normalization: mode entries fold into agent, and the legacy tools map folds into permission.

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 }
        }
      }
    }
  }
}
  • model selects the default model in provider/model form.
  • small_model selects the model used for small auxiliary tasks.
  • models adds or overrides model metadata for a provider.
  • enabled_providers and disabled_providers are 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:

  • build executes tools according to the configured permissions.
  • plan is a planning mode that denies edits except to plan Markdown files. Your own permission rules 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 status is denied, because git* sorts after git status. A "*" key sorts before keys that start with a letter, digit, ., /, or ~, so those rules override a catch-all "*" in the same object.
  • read rules cover file reads. Patterns such as *.env default to ask.
  • doom_loop gates repeated identical tool calls.
  • external_directory gates access outside the project.
  • question controls 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 }
}
  • snapshot enables Git-backed snapshots and revert. Snapshots are on unless this is false, and require a Git repository.
  • share can disable sharing. OPENCODE_DISABLE_SHARE=1 also disables it.
  • Compaction settings control automatic conversation compaction. See alforria debug config for 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.