English | 简体中文 | 繁體中文 | 日本語 | Русский
devo onboard is the recommended setup path. For manual configuration, Devo
merges settings in this order:
- Built-in defaults
DEVO_HOME/config.toml- user-level config, defaulting to~/.devo/config.tomlon macOS/Linux andC:\Users\yourname\.devo\config.tomlon Windows<workspace>/.devo/config.toml- project-level config- CLI flags
Credentials live separately in DEVO_HOME/auth.json; config.toml should refer
to credential ids instead of storing API keys directly.
Minimal shape (built-in model + provider binding):
[defaults]
model_binding = "deepseek-v4-flash-api-deepseek-com"
[providers."api.deepseek.com"]
enabled = true
name = "api.deepseek.com"
base_url = "https://api.deepseek.com"
credential = "api_deepseek_com_api_key"
wire_apis = ["openai_chat_completions"]
[model_bindings.deepseek-v4-flash-api-deepseek-com]
enabled = true
model_slug = "deepseek-v4-flash"
provider = "api.deepseek.com"
request_model = "deepseek-v4-flash"
display_name = "DeepSeek V4 Flash"
invocation_method = "openai_chat_completions"
default_reasoning_effort = "high"The important separation is:
model_slugselects Devo's local model metadata by slug.- The binding's
providerselects a[providers.<id>]connection record. request_modelis the provider-facing model id sent on the wire.invocation_methodselects the operational provider protocol. See Invocation methods.
Model metadata also has a provider field. It describes the wire API the model
expects, while the binding's invocation_method chooses the connection used at
runtime; keep those values aligned. API keys remain in auth.json and are
connected through the provider's credential reference.
Existing configuration using model_name remains readable. Devo writes the
field as request_model the next time that binding is saved.
Devo does not store API keys in config.toml. When you bring your own key:
- Store the secret in user-scoped
DEVO_HOME/auth.json. - Point
[providers.<id>].credentialat that credential id fromconfig.toml.
devo onboard and the Desktop/TUI provider flows write both files for you.
The following pairs a custom DeepSeek model (Anthropic Messages), a provider
endpoint, and a credential stored only in auth.json.
~/.devo/config.toml (or C:\Users\yourname\.devo\config.toml on Windows):
[defaults]
model_binding = "deepseek-example"
[model.my-deepseek]
display_name = "DeepSeek V4 Flash"
description = "Custom Anthropic Messages coding model for DeepSeek."
channel = "Custom"
# Wire API this model expects. Must match the binding's invocation_method.
provider = "anthropic_messages"
context_window = 200000
effective_context_window_percent = 95
max_tokens = 8192
temperature = 0.2
reasoning_capability = { togglewithlevels = ["high", "max"] }
reasoning_implementation = "request_parameter"
base_instructions = "(optional) You are Devo, a coding agent."
input_modalities = ["text"]
# For multimodalities
# input_modalities = ["text", "image"] ...
[providers.deepseek]
enabled = true
name = "DeepSeek Anthropic Compatible"
base_url = "https://api.deepseek.com/anthropic"
# Credential id only — the secret lives in auth.json.
credential = "deepseek_compatible_api_key"
wire_apis = ["anthropic_messages"]
[model_bindings.deepseek-example]
enabled = true
model_slug = "my-deepseek"
provider = "deepseek"
request_model = "deepseek-v4-flash"
display_name = "DeepSeek V4 Flash"
invocation_method = "anthropic_messages"Matching ~/.devo/auth.json (or C:\Users\yourname\.devo\auth.json):
{
"version": 1,
"credentials": {
"deepseek_compatible_api_key": {
"kind": "api_key",
"value": "sk-deepseek-your-api-key"
}
}
}Rules:
- Only
api_keycredentials are supported today. - The credential id must match
[providers.<id>].credentialexactly. - Keep
auth.jsonunderDEVO_HOME. Do not commit it to a project repo. - Workspace
<workspace>/.devo/config.tomlmay reference credential ids, but secret values stay in user-scopedauth.json. - Updating only the key means editing
auth.json; leaveconfig.tomlunchanged when the credential id stays the same.
invocation_method (on a model binding) and wire_apis (on a provider) select
which HTTP API Devo uses for that connection. Model metadata provider should
use the same value so catalog capabilities match the runtime connection.
| Value | Protocol | Typical endpoints |
|---|---|---|
openai_chat_completions |
OpenAI Chat Completions | Most OpenAI-compatible gateways (DeepSeek, Qwen, Kimi, OpenRouter, many local proxies) |
openai_responses |
OpenAI Responses | Providers that expose the Responses API |
anthropic_messages |
Anthropic Messages | Anthropic-compatible Messages endpoints |
Configure model metadata in user or workspace config.toml under
[model.<slug>]. A section for a built-in slug is a partial override: omitted
fields retain their built-in values. A new slug creates a custom model with safe
defaults, which should then be connected through both [providers.<id>] and
[model_bindings.<id>] as in the
end-to-end example.
For example, this changes only the built-in context window:
[model.qwen3-coder-next]
context_window = 262144
effective_context_window_percent = 90The exact effective context formula is
context_window * effective_context_window_percent / 100; the result is the
context available to the model and the automatic-compaction boundary.
Configurable metadata includes display_name, the picker-facing model name;
description, explanatory text shown to users; and channel, the grouping
label used to organize models. context_window and
effective_context_window_percent determine effective context, while
max_tokens is the default response-output limit. Sampling defaults are
temperature for randomness, top_p for nucleus probability mass, and top_k
for the candidate-token cap. The provider wire API is one of
openai_chat_completions, openai_responses, or anthropic_messages.
Reasoning metadata is typed: reasoning_capability can be unsupported,
toggle, { levels = [...] }, or { togglewithlevels = [...] };
reasoning_implementation can be disabled, request_parameter, or a typed
model_variant table. A model variant maps a logical reasoning selection to a
different provider-facing model id, optional effective effort, and optional
extra request body instead of changing a parameter on the same model;
default_reasoning_effort selects the default typed effort. input_modalities
accepts text and image; truncation_policy chooses a byte or token limit for
oversized tool-result content before it is included in a model request; and
supports_image_detail_original enables original image detail.
Omitting base_instructions retains built-in instructions for a built-in model
or uses Devo's default instructions for a custom model. An explicit empty string
(base_instructions = "") means no base instructions.
Legacy model = "slug" remains readable. Because [model.<slug>] now owns the
top-level model table namespace, new configuration must select the active
connection with [defaults].model_binding instead of the legacy scalar key.
Top-level keys in DEVO_HOME/config.toml also store a few UI preferences:
theme = "aurora"
collapse_reasoning = true
compaction_token_limit = 250000themeselects the TUI color theme (also set via Settings › Appearance).collapse_reasoningcontrols reasoning display (also set via/show-reasoning):true(default): while streaming, show only the latest 3 lines; when finished, keep short reasoning in full and collapse longer reasoning to a one-lineThought · …summary (full text remains available in Ctrl+T).false: show full reasoning while streaming and after it finishes.
compaction_token_limitis the global absolute auto-compaction threshold in tokens (also set via Settings › Compaction threshold). When set, every session clamps this value to the active model'scontext_window. When unset, sessions use the model effective context window.
Old ~/.devo/models.json and <workspace>/.devo/models.json files are ignored.
Manually copy the fields you still want into [model.<slug>] sections in the
user or workspace config.toml, then add or retain the matching provider and
model binding. Keep API keys in auth.json; refer to them from
[providers.<id>].credential.
Devo connects to Model Context Protocol
servers configured in user or workspace config.toml under [mcp]. Each server
is one entry in the servers array, and its transport table selects how Devo
connects. Supported transports are stdio, streamable_http, and the deprecated
sse.
You can configure MCP either by editing config.toml or with the CLI
(devo mcp …). Prefer the CLI for day-to-day add / enable / disable / remove;
edit TOML when you need transport details, env vars, or headers.
Devo ships an optional semantic search MCP binary next to devo. The config
entry is injected when missing and stays disabled until you enable it:
[[mcp.servers]]
id = "code_search"
display_name = "Code Search"
enabled = false
startup_policy = "lazy"
[mcp.servers.transport]
kind = "stdio"
command = ["devo-code-search-mcp"]devo mcp enable code_search
# or, in an interactive session: /mcps → Code Search → EnableWhen enabled, the model-facing tool name is mcp__code_search__code_search.
The devo-code-search-mcp binary is installed next to devo.
Manage user-level MCP servers (~/.devo/config.toml) with devo mcp:
# List configured servers (effective / user config)
devo mcp list
# Add a stdio server (command + args after --)
devo mcp add time -- docker run -i --rm mcp/time
devo mcp add filesystem --env HOME=/tmp -- npx -y @modelcontextprotocol/server-filesystem .
# Add Streamable HTTP (`--transport http` writes kind = "streamable_http")
devo mcp add --transport http hello-mcp http://localhost:8080/mcp
devo mcp add --transport http github --bearer-token "$TOKEN" https://api.githubcopilot.com/mcp/
# Add legacy SSE
devo mcp add --transport sse legacy-mcp https://example.com/mcp/sse
# Enable / disable / remove by server id
devo mcp enable time
devo mcp disable time
devo mcp remove timeCLI devo mcp enable|disable writes user config.toml for offline use. An
already-running interactive session applies enable/disable live through the TUI
/mcps path (mcp/set_enabled RPC).
Verify configuration in the TUI with /mcps (interactive server list → detail →
tools). Clients can also call mcp/list, mcp/tools, and mcp/set_enabled.
Stdio example:
[mcp]
auto_start = true
[[mcp.servers]]
id = "filesystem"
display_name = "Filesystem"
enabled = true
startup_policy = "lazy" # eager | lazy | manual
trust_policy = "user" # user | workspace | untrusted
allowed_capabilities = ["tools", "resources", "prompts"]
roots_policy = "workspace" # none | workspace | custom
[mcp.servers.transport]
kind = "stdio"
command = ["npx", "-y", "@modelcontextprotocol/server-filesystem", "."]
# cwd = "/path/to/workdir"
# env = { MY_VAR = "value" }
# env_vars = ["HOME", "PATH"]Streamable HTTP with a bearer token:
[[mcp.servers]]
id = "github"
display_name = "GitHub"
startup_policy = "lazy"
[mcp.servers.transport]
kind = "streamable_http"
url = "https://api.githubcopilot.com/mcp/"
auth = { kind = "bearer_token", token = "replace-me" }
http_headers = { "X-Custom" = "static-value" }
env_http_headers = { "Authorization" = "GITHUB_TOKEN" }Legacy SSE transport:
[mcp.servers.transport]
kind = "sse"
url = "https://example.com/mcp/sse"Field notes:
auto_startdefaults totrue. Enable/disable of MCP servers in a running session is applied live viamcp/set_enabled(TUI/mcps).startup_policycontrols when an enabled server starts:eagerduring bootstrap,lazyon first use, ormanualonly by explicit request.- For stdio,
envprovides literal values andenv_varslists names inherited from the local environment;{ name = "X", source = "remote" }is not supported for stdio. - For HTTP transports,
http_headersprovides literal headers andenv_http_headersmaps a header name to the environment variable that supplies its value. - Empty
allowed_capabilitiesmeans no restriction. The runtime currently focuses ontools; resource reads are not wired yet. output_limitssetsmax_tool_output_bytes(default 1 MiB) andmax_resource_bytes(default 10 MiB).- Top-level
mcp_oauth_credentials_storeisauto(default),file, orkeyringand selects where OAuth credentials are stored. - Prefer environment-injected headers or values over hard-coding tokens into
config.toml.auth_refexists on each server record but is not wired to the runtime yet.
Merge behavior: [mcp] is merged field-wise like other tables, but servers is
an array. A project-level [[mcp.servers]] list therefore replaces the
user-level list instead of merging by id.