Config file
~/.octo/config.yml holds your default provider(s), model(s), and global settings, so a bare
octo works without re-typing flags. Every field is optional — a missing file, or a missing field,
falls back to the built-in default. Manage it with octo config rather than hand-editing where
possible.
Top-level keys
Section titled “Top-level keys”| Key | Type | Description |
|---|---|---|
endpoints |
list of endpoint | Configured providers, each bundling shared connection params and a list of models |
default |
string | Composite id <endpoint-id>::<model> used when nothing else selects one; empty falls back to the first endpoint’s first model |
lite |
string | Composite id <endpoint-id>::<model> for cheap internal calls (history compaction’s summarize step and session-title generation); empty means no lite model — those calls run on the session’s primary model |
vision_helper |
string | Composite id <endpoint-id>::<model> of a vision-capable model that describes images for text-only models. Empty (the default) leaves images refused as before. The model it names must have vision: true |
permission_mode |
string | interactive | strict | auto |
coauthor |
bool | Append Co-authored-by to git commits the agent writes (default true) |
access_key |
string | Shared secret for Web UI / API auth when exposed beyond loopback |
compact_auto_pct |
int | Auto-compaction threshold as % of the context window (default 75); the same threshold is checked both between turns and after each tool-call batch |
fallback_context_window |
int | Context window in tokens assumed for a model whose name matches no built-in entry (default 128000). Applies only to the fallback — a model the table knows keeps its own window unless its model entry sets context_window. Values below 1000 are rejected as the unit mistake (32 meaning 32k) |
reasoning_effort |
string | Global reasoning intensity: low | medium | high | xhigh | max; empty means off |
show_reasoning |
bool | Global default for surfacing the reasoning trace to the Web UI |
workspace_dir |
string | Default working dir for new web sessions; empty → ~/Octo, or set a literal path to override |
tools |
tools block | Per-tool behavior knobs |
browser |
browser block | Chrome connection settings |
goal |
{ enabled: bool } |
Gates /goal and the goal tools (default enabled) |
trash |
trash block | File recycle-bin behavior (overwrite backups, retention, size cap) |
uploads |
{ retention_days: int } |
Age-out for received attachment files — web uploads, IM-channel images, and IM document/file temp copies (default 30; negative disables) |
memory_backend |
object | Optional external semantic memory backend — see Memory backends |
language |
string | UI language preference (en | zh); empty means English |
notify |
bool | TUI only: send a desktop notification when a turn completes and is waiting for input. Ghostty / iTerm2 / Kitty forward it to the OS; other terminals fall back to the terminal bell (default true) |
terminal_title |
bool | TUI only: set the terminal tab/window title to the session name on startup (OSC 2, honoured by Ghostty, iTerm2, Kitty and most modern terminals; default true) |
update_check |
bool | Allow the automatic latest-release lookup against GitHub — the version badge and the desktop tray’s daily poll. This is octo’s only outbound request that isn’t a model call; set false and it makes none (default true). An explicit octo upgrade still reaches out |
When the agent edits this file, octo validates it right after the write and warns
in the turn if it no longer parses or has a semantic problem (a default
pointing at a missing endpoint, a duplicate id or half-filled entry) — a broken edit otherwise
degrades silently, since octo keeps using the last config that parsed cleanly.
Working directory
Section titled “Working directory”octo tracks a working directory per surface, not one global cwd:
-
TUI / CLI (
octo,octo -c) always run tools in the directory you launched them from —workspace_dirand the per-session override below don’t apply. Whatever directory the shell process inherited is whatread_file,terminal, and every other tool resolve relative paths against. -
Web sessions take theirs from the project they belong to. A loose task — one filed under no project — runs in
workspace_dir(empty →~/Octo, created on first use), and that is not something the session can override: a directory is a project’s property. Choosing one on the new-session landing page is what files the session under the project for that directory, creating it if there isn’t one.PATCH /api/sessions/{id}/working_diranswers 409 and says so.Which project a session belongs to is decided when it is created and fixed after that — there is no moving a session between projects (
PUT /api/sessions/{id}/groupanswers 409). To work in a project, start the session there: the “+” on the project’s row, or pick its directory on the landing page. Deleting a project is the only thing that takes its sessions out of it, and they become tasks.The reason it works this way: a task that could point itself at a repo ran its tools there while its memory stayed in the shared tier, since project memory is scoped by project membership (see memory). You got a session aimed at your project that remembered nothing about it. Attaching the directory to the project — the thing that already scopes memory — removes the split. Fixing membership at creation is the same argument applied to time: the directory, the memory tier, the project notes in the system prompt, and the hooks root all come from the project, and a move would leave a transcript whose first half ran somewhere else.
-
The server’s own launch directory (where
octo serveor the desktop app started) is separate from both of the above — it only seeds skill discovery and the project-level memory root, not any session’s tool cwd.
Endpoint
Section titled “Endpoint”Each item in endpoints: bundles the connection params shared by a set of models. The two-level
shape lets the same model name live under more than one endpoint — say claude-sonnet-5 via both
the official Anthropic endpoint and a relay — which the old flat list couldn’t express.
| Key | Type | Description |
|---|---|---|
id |
string | Unique identifier and composite-id prefix; matches ^[a-zA-Z0-9_-]+$, no ::. The wizard names it after the provider (anthropic, openai, custom); rename it freely |
name |
string | Optional display name shown in the UI; may differ from id |
provider |
string | anthropic | openai | custom |
base_url |
string | Endpoint URL; empty uses the vendor default. Required for the custom vendor |
api_key |
string | Plaintext fallback used only if the provider’s env var is empty (mode 0600). Optional for the custom vendor — local servers such as Ollama take none, and no auth header is sent |
protocol |
string | Wire format for the custom vendor only: anthropic | openai |
rpm |
int | Optional: cap on provider calls started against this endpoint per rolling 60-second window. 0/absent = unlimited |
max_concurrency |
int | Optional: cap on provider calls in flight against this endpoint at once. 0/absent = unlimited |
models |
list of model | The models offered through this endpoint |
rpm and max_concurrency exist for endpoints that publish a quota — free tiers typically allow
something like 8 requests a minute and one at a time, and answer HTTP 429 past that. Both gates
cover every caller sharing the endpoint (the conversation itself, sub-agents, workflow steps,
background title generation, the vision helper), which is what makes the cap hold when several run
at once; a request over the limit waits for room rather than failing. Set them from the provider’s
published numbers. Neither has a UI — hand-edit config.yml; editing the endpoint in the Web UI
leaves them untouched.
Two details worth knowing before you tighten them. Retries inside one call don’t spend a second
rpm permit, so a call that keeps failing can put up to four requests on the wire against one
permit — set rpm slightly under the published quota if the endpoint counts rejected requests.
And max_concurrency gates background work too: session titles come from a throwaway call with a
five-second budget fired when your message arrives, so at max_concurrency: 1 it queues behind
the turn itself and the session falls back to a snippet of your first message. Cap rpm alone to
keep model-written titles.
Each item in an endpoint’s models: list is independently selectable. --model <name> selects it —
and with it the endpoint’s connection params — while default / lite reference it by composite id
<endpoint-id>::<model>. Connection settings (base_url, api_key, protocol) and reasoning
(reasoning_effort, show_reasoning) live on the endpoint and top level respectively, not here.
| Key | Type | Description |
|---|---|---|
model |
string | The model id sent to the API (e.g. claude-sonnet-4-6) — also the name --model and the HTTP API use |
context_window |
int | Optional context window in tokens for this endpoint’s model deployment. Overrides the built-in model table; values from 1 through 999 are rejected as likely unit mistakes |
vision |
bool | Whether tools may hand this model images; a model-level capability, auto-detected for known models |
Context-window precedence is: the selected model entry’s context_window, then the built-in model
table, then fallback_context_window, then the built-in 128k default. Because the value belongs to
an endpoint model entry, two endpoints may expose the same model id with different limits; selecting
them by composite id keeps those limits separate.
endpoints: - id: anthropic provider: anthropic models: - model: claude-sonnet-5 context_window: 32000 vision: true - id: deepseek provider: custom protocol: anthropic base_url: https://api.deepseek.com/anthropic models: - model: deepseek-chatdefault: anthropic::claude-sonnet-5lite: anthropic::claude-sonnet-5vision_helper: "" # e.g. anthropic::claude-sonnet-5 — lets text-only models "see" imagesreasoning_effort: "" # low | medium | high | xhigh | max; empty = offpermission_mode: interactivecoauthor: trueTools block
Section titled “Tools block”tools: tool_search: enabled: auto # auto (default) | on | off threshold_pct: 10 disabled_skills: []disabled_skills hides listed skills from the model and the UI without deleting them from disk.
See Connect MCP servers for what tool_search does.
Browser block
Section titled “Browser block”browser: attach_running: true # reuse your logged-in Chrome instead of a throwaway profile connect_port: 9222 # attach via --remote-debugging-port instead user_data_dir: "" exec_path: "" download_dir: ""See Automate with browser control.
Trash block
Section titled “Trash block”trash: overwrite_backup: true # stage a copy before write_file/edit_file overwrites (git-clean files skipped) retention_days: 14 # age out entries at startup; negative disables max_size_mb: 10240 # evict oldest past this cap; negative disablesSee Sandbox the agent for how the recycle bin fits into the
safety model, and octo trash list|restore|rm|empty for recovery from the CLI.
Next: many of these settings have a matching CLI flag for a per-run override — see the CLI reference.