Configuration¶
Config is a single JSON object read from two files, merged per key with project-local values winning:
- Global -
~/.config/replio/config.json(user-wide defaults, credentials). - Local -
.replio/config.jsonin the project path (project overrides).
Every process merges them in memory. Nothing is ever distributed to folders. Writes default to the local file and hold only the keys you actually selected - a save never re-writes the merged config. API keys are not part of the config: they live in the global provider registry (~/.config/replio/providers.json) and are managed through /connect (see Models).
# inspect in the REPL (origin: default/global/local)
/config
# set a value (project-local, Git-like default)
/config temperature 0.3
# set a structured value
/config tools.deny ["run_command", "web_search"]
# set a single line in the global config
/config --global temperature 0.2
# remove a project-local value, falling back to global/default
/config unset temperature
# remove a value from the global config
/config --global unset temperature
# reload from disk
/config reload
The replio config CLI does the same headlessly and is fully scriptable:
replio config get max_tokens --show-origin # one or more values + where they come from
replio config set max_tokens 0 # project-local
replio config set max_tokens 0 --global # global file
replio config unset max_tokens # remove from project-local
Deleting a project's .replio/config.json reverts that project to the global and built-in defaults. To keep settings across local deletions, globalize individual lines with --global (both the REPL /config and the CLI accept it), e.g. /config --global provider ollama, /config --global model <model>.
Schema¶
| Key | Default | Description |
|---|---|---|
provider |
"ollama" |
Provider name (ollama, openai, groq, anthropic, opencode, opencode-go, openai-compatible) |
model |
"llama3.2" |
Model name. A provider/model ref (e.g. opencode-go/deepseek-v4-flash) unfolds to that provider and model; an unfolded model must be approved (see Model refs and approval) |
base_url |
"https://api.ollama.com" |
Provider endpoint |
temperature |
0.7 |
Sampling temperature |
max_tokens |
8192 |
Output token cap sent to the provider. 0 = unset (provider default applies, e.g. Ollama caps at 2048). Default 8192 overrides low provider defaults |
stream_retries |
2 |
Extra attempts (after the first) when a provider stream ends before a completion event with no content |
stream_retry_delay |
0.5 |
Seconds to wait between stream retries |
auto_continue |
true |
On truncation (finish_reason=length) with a partial answer, re-request the turn with a "continue" instruction and stitch the parts into one message |
auto_continue_max |
2 |
Max continuation rounds per turn before the truncation is reported |
connect_check |
true |
Test the provider connection when config changes: /connect probes before saving (broken values are rejected unless confirmed), /provider warns on a failed probe. false skips all probes |
system_prompt |
"" |
Optional system prompt, injected for every front-end (REPL, run, serve) |
mode |
"build" |
Active agent mode (build, plan, or a custom mode from modes) |
tool_calling |
true |
Enable OpenAI-compatible function calling |
tool_status_visible |
true |
Show dimmed tool status in the REPL |
glyph_lines |
true |
Typed <glyph> <verb> <arg> status lines for mapped categories. When off, or for unmapped categories, the [tool: arg] oneliner is used |
glyph_params |
true |
Append the tool call parameters to glyph status lines and confirm prompts (e.g. ← Read engine.py [offset=299, limit=85]). Off for bare <glyph> <verb> <arg> |
show_errors |
true |
Show a dimmed ! Error: ... line (first line of the result) when a tool call fails. Off hides the line |
show_notes |
true |
Show a dimmed info line for soft tool results (e.g. (empty file), (no matches for "x")). Off hides the line |
tool_analysis |
false |
Model-generated one-line analysis of each tool result (log-only) |
delegate_echo |
true |
When the delegate tool runs, show the sub-agent's final answer and a sub footer (duration + completion tokens) in the REPL. Off hides the result. The sub footer is emitted alongside the sub-agent's own rendered output only when on |
session_tool_max_chars |
0 |
0 = unlimited. Caps persisted tool-result content |
tool_max_result_chars |
100000 |
Caps tool-result content returned to the model (... (truncated) appended). 0 = unlimited. With the default, the model sizes files via the file_read header and pages with offset/limit |
list_dir_max_entries |
200 |
Cap the number of entries list_dir returns (... (showing first N of M entries) appended). 0 = unlimited |
query_refine |
false |
Auto-refine short web-search queries via a lightweight model call |
query_refine_min_words |
3 |
Minimum query length before refinement applies |
query_refine_context |
4 |
Recent-message context to inject into refinement |
show_thinking |
false |
Stream thinking/reasoning tokens dimmed. When off, thinking is shown only as an animated spinner plus a + Thought N.Ns summary (display only - does not change what is sent to the model) |
show_thought_duration |
true |
When thinking is streamed (show_thinking on), print a dimmed (Thought N.Ns) line after each thinking block. Off hides it |
reasoning |
"auto" |
Request reasoning from the model and control its token budget: false/"off" = do not request, true/"on"/"auto" = request with provider default, "low"/"medium"/"high" = explicit budget hint. Mapping is provider-specific (OpenAI reasoning_effort, Claude thinking.budget_tokens, Qwen enable_thinking) |
markdown_streaming |
false |
Basic markdown-aware streaming |
word_streaming |
true |
Buffer REPL output to word boundaries so words render fully formed (no mid-word pauses). false streams character-by-character |
show_context_size |
true |
Dimmed context-size line after each response |
footer_tokens |
["context"] |
Which token counts the footer shows, in order, joined by /. context = <n> tokens (context/input size, chars/4 fallback), in/out/thinking = <n>t from provider usage (unavailable counts are skipped). Empty list hides the token section entirely |
clear_screen |
true |
Clear the screen before the REPL banner |
show_version |
true |
Show the version in the REPL banner |
compact_keep |
4 |
Messages to keep when compacting the provider context |
project_instructions |
"AGENTS.md" |
Per-worktree instructions file auto-loaded into the system prompt (e.g. AGENTS.md, CLAUDE.md). "" disables. Absent files are skipped. Content is capped at 20000 chars |
noise_tools |
["web_fetch", "open", "fetch_page"] |
Tool results replaced by a marker in persisted sessions |
web_search |
false |
Auto-search mode: search the web before answering |
search_results |
5 |
Number of search results to fetch |
tools.allow |
[] |
Name-level allowlist. Empty means no restriction |
tools.deny |
[] |
Name-level deny list (takes precedence over allow) |
tool_permission |
(see below) | Category permission actions |
mcp.servers |
[] |
MCP client server definitions (see mcp.md for the schema) |
mcp_server.allow_ask |
true |
When serving MCP, run ask-policy tools (deferred to the client) vs refuse them |
plugins |
(bundled) | Plugins to load. Empty = all discovered plugins load |
modes¶
Modes are named postures combining an instruction block with tool-policy overrides. The built-ins ship as defaults - build (no overrides) and plan (read-only: edit and bash categories denied):
{
"mode": "plan",
"modes": {
"build": { "system_prompt": "", "tool_permission": {} },
"plan": {
"system_prompt": "You are in plan mode (read-only)...",
"tool_permission": { "edit": "deny", "bash": "deny" }
}
}
}
Each mode may define system_prompt (instructions), tool_permission (category actions merged over the base tool_permission, mode wins per key), tools.deny (appended to the base deny list), and tools.allow (replaces the base allowlist when non-empty). An unknown mode value falls back to build. Switch live with /mode <name> or set --mode <name> on replio run / replio serve. The mode instruction and system_prompt are injected as a system message for every front-end. The active mode is recorded on each assistant message in the session log.
tool_permission¶
{
"ask": "allow",
"bash": "ask",
"bash_allow": ["pytest", "python -m unittest", "ruff", "git"],
"delegate": "allow",
"edit": "allow",
"list": "allow",
"mcp": "ask",
"read": "allow",
"web": "allow"
}
Actions are allow (no prompt), ask (y/N confirm), deny (tool hidden/refused). Read/write/list outside the project worktree escalate to ask automatically. The delegate category gates the delegate tool. On top of the category action, delegation resolves its permission from the target type - a configured type uses its own tool_permission overrides (category delegate defaulting to allow), while an agent type not in the registry defaults to deny (see types.md). The ask category gates the ask tool (default allow - the tool itself is the interaction; the answerer is the human or the lead agent, see tools.md).
bash_allow - command allowlist for run_command¶
tool_permission.bash_allow (list, default []) restricts run_command to commands whose first token matches an allowed prefix. Empty or unset means unrestricted (the bash category action applies to every command). When set:
- Each command is split into chained segments over
&&,||,;,|, and&, and every segment must start with one of the allowed prefixes (e.g.pytest -q && ruff check .needs bothpytestandruffallowed). - Shell-script forms are rejected outright: multi-line commands and heredocs (
<<) always returndeny. - A matching command falls through to the normal
bashaction (askby default,allow/denyper config). A non-matching command isdeny.
The check runs through the per-invocation policy resolver, so it composes with modes, name-level tools.deny/tools.allow, and the worktree escalation. bash_allow gives a coding agent a safe default set (tests, linters, git) without opening up arbitrary shell.
Model registry (not config)¶
Two global files live separately from config in ~/.config/replio/. Neither is part of the config merge - /config never lists or writes them, and they have no local scope.
Provider registry (providers.json)¶
~/.config/replio/providers.json (written 0600 when it holds keys) stores the active connections, keyed by provider name:
{
"ollama": {
"base_url": "https://custom.example/v1",
"api_key": "...",
"added_at": "...",
"last_used": "..."
}
}
api_keylives here, one per provider. It is the only place API keys live.base_urlis the effective base URL of the connection - the preset provider default (e.g./connect ollama) or a custom URL (/connect <url>); the engine falls back to it when the config leavesbase_urlempty.- Managed through
/connect(which writes the key and any custom base URL). Re-running/connectlets you re-enter a missing or stale key. - The engine resolves the API key for the active provider from this file (matching entry or
""), and falls back to a stored custombase_urlwhen the config has none. There is noapi_keyconfig key anymore, andreplio config set api_keywould store an unused ordinary value. Deleting a project config cannot lose the registry - it is global by design.
Model registry (models.json)¶
~/.config/replio/models.json is the history of approved models - entries {provider, model, added_at, last_used}, no API keys (keys and custom base URLs live in providers.json). It records every model you connect or switch to, so /model list shows what has been used per provider and > marks the active one. The active model still comes from config.model.
/connectrecords the model for the connection it just saved./model listshows the approved models grouped by provider with the active one marked>, and(key)when that provider has a stored key./model list --online [provider]probes a provider's advertised models live (default: current provider).