<!-- Markdown mirror. Canonical: https://empryo.com/docs/reference/configuration -->

# Configuration

> Every Empryo config field: default model, thinking, Genome graph, task router, agent features, compaction, key storage, instruction files, and env vars.

Config lives in two files. Project wins.

| Scope | Path |
|-------|------|
| Global | `~/.empryo/config.json` |
| Project | `.empryo/config.json` |

## Ask Empryo to change it

You do not have to open the file. Ask in the chat, "use adaptive thinking", "make the
diffs compact", "turn provider search off in this project", and the agent loads its
`settings` tool and writes the key for you.

It is not a file edit. The write goes through the same seam the Settings panel uses, so the
running app applies it and repaints: on the desktop the panel refetches and the window
restyles, in the terminal the UI redraws in place. Headless writes the file for the next run.

Most settings apply immediately: the engine re-reads config on every turn, the desktop rebuilds
its lanes and refetches the panel, and the terminal UI redraws in place. A few are read once at
startup. Instruction sources, live translations, live skills, the Neovim binary and config, LSP
auto-install, telemetry, and there the agent says the write landed and asks you to restart
(`/restart` in the terminal, quit and reopen the desktop app) rather than claiming it is already
on screen. The desktop's appearance block can only be written from a running desktop window.

| What the agent does | What you see |
|---|---|
| `settings` list / get | The key, its type, the effective value, and which layer it came from |
| `settings` set | The before → after value and the scope it wrote |
| `settings` unset | The override dropped, and whatever now applies in its place |

You can say where it goes, "globally" writes `~/.empryo/config.json`, "for this project"
writes `.empryo/config.json`. Unasked, it follows the same rule as the panel: the project layer
only when it already sets that key, otherwise global, and a write clears the shadowing copy in the other layer, so the
value you asked for is the value in force. Credentials are not part of this: signing in and
key storage stay on `/login`, `/keys` and `empryo --set-key`.

## Full example

```json
{
  "defaultModel": "anthropic/claude-sonnet-4-5",
  "thinking": { "mode": "adaptive" },
  "genome": true,
  "semanticSummaries": "ast",
  "diffStyle": "default",
  "chatStyle": "accent",
  "toolTimeout": 2,
  "compaction": {
    "strategy": "v2",
    "triggerThreshold": 0.7,
    "keepRecent": 4
  },
  "taskRouter": {
    "spark":      "anthropic/claude-haiku-4-5",
    "ember":      "anthropic/claude-sonnet-4-5",
    "compact":    "google/gemini-2.5-flash"
  },
  "agentFeatures": {
    "desloppify": true,
    "tierRouting": true,
    "dispatchCache": true,
    "targetFileValidation": true
  },
  "retry": { "maxAttempts": 5, "baseDelayMs": 3000 }
}
```

## Fields

### Model

| Field | Default | What it does |
|-------|---------|--------------|
| `defaultModel` | `"none"` | Active model ID. `"none"` (default) forces picker on launch. |
| `thinking.mode` | `"off"` | `"off"`, `"adaptive"`, `"enabled"` |
| `thinking.budgetTokens` | - | Tokens when `mode: "enabled"` (1024, 2048, 5000, 10000, 20000) |

### Display

| Field | Default | What it does |
|-------|---------|--------------|
| `diffStyle` | `"default"` | `"default"`, `"sidebyside"`, `"compact"` |
| `chatStyle` | `"accent"` | `"accent"`, `"bubble"` |
| `vimHints` | `true` | Show Vim keybinding hints |
| `nerdFont` | auto | Nerd Font icons, auto-detected from installed fonts; set `true`/`false` to force (off shows ASCII) |

### Intelligence

| Field | Default | What it does |
|-------|---------|--------------|
| `genome` | `true` | Build the codebase graph on launch |
| `semanticSummaries` | `"synthetic"` | `"synthetic"` (default), `"ast"`, `"llm"`, `"off"` |
| `toolTimeout` | `2` | Tool call timeout in minutes. `0` disables. |

### Compaction

See [compaction](/docs/context/compaction) for what these do.

```json
"compaction": {
  "strategy": "v2",
  "triggerThreshold": 0.7,
  "resetThreshold": 0.4,
  "keepRecent": 4,
  "maxToolResults": 30,
  "llmExtraction": true
}
```

### Retry

For 429s and transient errors. Delays double each attempt.

```json
"retry": { "maxAttempts": 5, "baseDelayMs": 3000 }
```

### Web access

| Field | Default | What it does |
|-------|---------|--------------|
| `webSearch` | `true` | Allow the `web_search` tool at all. Off means no web search on any path. |
| `nativeSearch` | `true` | Let the provider run the search on its own servers, on the lanes where that is known to work. Requires `webSearch`. |

With `nativeSearch` on, a supported model searches in one step and returns its own citations
instead of Empryo fetching pages on your machine. Your query then goes only to your model
provider, not also to DuckDuckGo, Brave or Jina. Two trade-offs worth knowing:

- Because the provider executes the search, there is no per-query approval prompt to intercept.
  Turn `nativeSearch` off to keep every search client-side and promptable.
- The provider's search tool adds a fixed ~2.2K input tokens to each request whether or not the
  model searches. It sits in the cached prefix, so the effective cost is much lower than that.

It is on only for the lanes that have been tested against a real key: Claude, direct and through
the proxy, GPT through the proxy, and the OpenRouter models that support it. Other lanes are
built but untested, so they stay off and use the search below instead. A provider-side search
tool that the model rejects fails the whole request, not just the search, which is why an
untested lane is not switched on for you. `/web-search` names the backend in play.

Everything else falls back to the search backends you have keyed, with free DuckDuckGo as the
floor: gateways, local models, untested lanes, and any model whose provider has no search of its
own. Switch both in Settings › Provider, or with `/provider websearch` and
`/provider nativesearch`.

| Variable | Purpose |
|----------|---------|
| `EMPRYO_NATIVE_SEARCH_UNVERIFIED` | Set to `1` to use provider-side search on the untested lanes too: Gemini, Grok and Groq direct. If it works on yours, please say so. |

### Live updates between releases

| Field | Default | What it does |
|-------|---------|--------------|
| `liveTranslations` | `true` | Fetch newer translation catalogs from the public repo. |
| `liveSkills` | `true` | Refresh the [skill Empryo ships](/docs/tools/skills) from empryo.com. Only a higher version, only over a copy you never edited, only when the published sha256 matches the bytes fetched. |

### Provider tools

Four tools come from the provider rather than from Empryo's belt. Whether one exists on the wire
is decided by the lane and the model, so the flag is permission, not a guarantee, see
[provider tools](/docs/context/provider-tools) for what each one resolves to where.

| Field | Default | What it does |
|-------|---------|--------------|
| `computerUseApps` |, | Which apps the agent may DRIVE: a `default` plus per-platform allow/deny by macOS bundle id, Windows process name, Linux window class. Checked against the frontmost app at action time. |
| `computerUse` | `false` | Screenshots, clicks and keystrokes against a real display, plus the AX-first `computer_*` tools on every lane. Anthropic's and OpenAI's own tools where the lane carries them. See [computer use](/docs/tools/computer-use) for the policy switches, profiles and cues. |
| `codeExecution` | `true` | The provider's own sandbox: `code_execution` on Claude 4.5+, `code_interpreter` on OpenAI. Gateways carry neither. |
| `anthropicTextEditor` | `false` | Claude's `str_replace_based_edit_tool`, beside Empryo's own editing tools. Anthropic wire only. |
| `nativeSearch` | `true` | Provider-side web search, described above. |

```bash
empryo --capabilities anthropic/claude-opus-4-6   # what actually resolves, and why not
```

They live under **General** in `/provider-settings` (not the Claude tab, computer use reaches
every lane), in the desktop's Provider Options drawer, or from `/provider computeruse`,
`/provider codeexec`, `/provider texteditor`. Each row names the backend it resolves to on the
model you have selected, or why it resolves to none.

### Surfaces & appearance

| Field | Default | What it does |
|-------|---------|--------------|
| `theme` | `{}` | Theme name and transparency. `/theme` writes it. See [themes](/docs/tools/themes). |
| `locale` | system | UI language as a BCP-47 tag (`ja`, `zh-Hans`, `ar`). `/language` writes it; `--locale` overrides one headless run. |
| `localizedCommands` | `false` | Accept slash commands typed in the interface language (`/لغة` for `/language`) as aliases. |
| `presentation` | `"minimal"` | Transcript presentation. |
| `chatLayout` | `"speakers"` | TUI framing: `speakers`, `rail`, `bubbles`. The desktop always paints speakers. |
| `showReasoning` | `true` | Show reasoning/thinking content in chat. |
| `collapseDiffs` | `false` | Auto-compact diffs when streaming ends (`Ctrl+O` expands). |
| `gitDecorations` | `true` | Git status colouring and status letters on file-tree rows. |
| `motion` | `true` | Arrival fades, landing cues, focus glides. Off is an accessibility requirement, not a taste dial. |
| `editorSplit` | `60` | Editor/chat split, as the editor's width percentage. |
| `images` |, | Image attachment handling on every surface. |
| `tabNaming` | `true` | Name a tab from its first prompt (the router's `namer` lane). `rename_tab` lets the agent rename its own. |
| `defaultForgeMode` | `"default"` | Forge mode new sessions start in. |
| `coAuthorCommits` | `false` | Add a co-author trailer to AI-assisted commits. |

### Editor, LSP & the browser

| Field | Default | What it does |
|-------|---------|--------------|
| `nvimPath` / `nvimConfig` | auto | Neovim binary and which config the embedded editor loads. See [the editor](/docs/tools/editor). |
| `editorIntegration` |, | How the embedded editor and the chat share a buffer. |
| `codeIntelligence` |, | Depth of the [intelligence](/docs/concepts/intelligence) stack (LSP → AST → tree-sitter). |
| `disabledLspServers` | `[]` | Language servers to skip, by Mason package name. Project overrides global. |
| `lspAutoInstall` | `true` | Install recommended servers for detected languages in the background, once per package per project. |
| `browser` |, | The desktop [browser](/docs/tools/browser) panel. |
| `terminalBrowser` | `true` | The same `browser` tool on TUI + headless through a headless system webview. `false` removes the tool. |
| `terminalBrowserAllowExternal` | `false` | Let the terminal browser open public URLs. Off = localhost and private ranges only. |
| `fff` | `true` | Resident file-search index behind @-mentions, glob and the grep fast path. Falls back to ripgrep/fd when the native lib is missing. |

### Genome & context budgets

| Field | Default | What it does |
|-------|---------|--------------|
| `genomeTokenBudget` | auto | Token budget for the rendered Genome. Auto scales with conversation length. |
| `genomeChurnWeight` | `60` | How hard the map leans on git churn (how often a file changes) against PageRank (how much the code leans on it). `0` restores the pure-PageRank map. |
| `cochangeHints` | `true` | Append "usually changes with this: …" to `git status`, files git history pairs with your change that are missing from it. Local, no model call. |
| `semanticSummaryLimit` | `300` | Max symbols given LLM summaries (PageRank-ranked). Controls the cost of `semanticSummaries: "llm"`. |
| `semanticAutoRegen` | `false` | Regenerate LLM summaries when files change. `ast`/`synthetic` always auto-regen. |
| `contextManagement` |, | Context assembly knobs; see [compaction](/docs/context/compaction). |
| `contextWindowOverrides` | `{}` | Per-model real context window, in tokens. Beats provider metadata when an upstream lies. |
| `maxRequestChars` | `{}` | Per-model request-body ceiling in characters, for gateways that reject on body size. Auto-compaction budgets against it. |
| `performance` |, | Reasoning effort and render/indexing caps. `effort` sets the level, `effortByFamily` sets it per model family, and a per-provider key overrides both. See [thinking, effort & retries](/docs/context/provider-options#reasoning-effort). |

### Agents, limits & watchdog

| Field | Default | What it does |
|-------|---------|--------------|
| `disabledTools` | `[]` | Tools switched off, persisted across sessions. Always-on tools are stripped from the list. See [tools](/docs/reference/tools). |
| `advisor` |, | The [advisor](/docs/tools/side-agents#advisor): model and defaults for `/advise` and the `advisor` tool. |
| `goalLoop` |, | [Goal-loop](/docs/agents/goal-loop) autonomy: iterations, runtime probe, escalation. |
| `routerRules` | `[]` | Rules that route a turn to a different model. See the [task router](/docs/recipes/task-router). |
| `modelFallback` | `{}` | Per-model fallback chains, tried in order on transient failures. See [model fallback](/docs/recipes/model-fallback). |
| `subagentMaxSteps` | off | Hard step cap for dispatched subagents and reviewers. Off = no cap; occupancy guardrails still bound every run. |
| `subagentMaxMs` | off | Wall-clock budget for one side loop. A loop past it ends cleanly at the next step boundary. |
| `watchdog` | `false` | Abort-and-retry on stream stalls. Slow lanes and silent reasoning are normal, so this is opt-in. |
| `watchdogTimeouts` |, | The stall thresholds the watchdog uses. `/timeouts` writes both. |
| `mcpServers` | `[]` | [MCP servers](/docs/tools/mcp) to spawn (stdio) or connect to (HTTP+SSE). |

### Accounts & gateways

| Field | Default | What it does |
|-------|---------|--------------|
| `copilotHost` | github.com | Enterprise GitHub host for [Copilot](/docs/providers/copilot). Global scope only; `COPILOT_GITHUB_DOMAIN` overrides. |
| `llmgatewayAuth` | `"auto"` | Which LLM Gateway credential to use when both exist: `key`, `login`, or auto. |
| `llmgatewayLoginOrg` | `"default"` | Which org the browser-login key was minted in. `devpass` bills the subscription. |
| `llmgatewayBudget` |, | Spending cap and reset day the usage ring measures against. |
| `voice` |, | [Voice input](/docs/surfaces/voice-input): STT model, insert mode, endpoint, recorder command. |
| `telemetry` | on | Anonymous usage counts. `false`, `DO_NOT_TRACK=1` or `EMPRYO_TELEMETRY=0` opts out. |

State Empryo writes for itself, `onboardingComplete`, `configVersion`, `noticeVersion`,
`addonsPromptShown`, and the various `*IntroSeen` flags, is not configuration. Editing it
only re-shows or hides a one-time notice.

## Task router

Assign different models to different jobs. See the [task router recipe](/docs/recipes/task-router) for tuning tips.

```json
"taskRouter": {
  "spark":      "anthropic/claude-haiku-4-5",
  "ember":      "anthropic/claude-sonnet-4-5",
  "webSearch":  "anthropic/claude-haiku-4-5",
  "desloppify": "anthropic/claude-haiku-4-5",
  "verify":     "anthropic/claude-haiku-4-5",
  "compact":    "google/gemini-2.5-flash",
  "semantic":   "anthropic/claude-haiku-4-5",
  "default":    null
}
```

| Slot | Runs when |
|------|-----------|
| `spark` | Read-only research agents |
| `ember` | Code-writing agents |
| `webSearch` | Web search agent |
| `desloppify` | Cleanup pass after code edits |
| `verify` | Adversarial review after code edits |
| `compact` | Context compaction |
| `semantic` | Genome one-line summaries |
| `default` | Fallback for background tasks |

Resolution: `taskRouter[slot]` → `taskRouter.default` → active model.

## Agent features

```json
"agentFeatures": {
  "marionette": false,
  "desloppify": true,
  "tierRouting": true,
  "dispatchCache": true,
  "targetFileValidation": true
}
```

| Feature | What it does |
|---------|--------------|
| `marionette` | Compile each sent prompt into a repo map (focus files, graph edges, memories) before the agent starts, model via `taskRouter.marionette` |
| `desloppify` | Run cleanup agent after code agents |
| `tierRouting` | Auto-route trivial tasks to cheap model |
| `dispatchCache` | Share file reads across dispatches |
| `targetFileValidation` | Require file paths on dispatch tasks |
| `pythonKernel` | Put a **persistent Python interpreter** on the belt (see below), off by default |

Toggle with `/agent-features` (or `/marionette` for the prompt compiler directly).

### Python kernel (RLM mode)

```json
"agentFeatures": { "pythonKernel": true }
```

Adds one tool, `python_kernel`: a Python interpreter that **keeps its state
between calls**. A dataframe loaded in one call is still loaded in the next, so
the agent can interrogate it across a whole turn instead of re-running a script
per question, `shell` cannot do that, because every `python -c` is a fresh
process. IPython is used when it is importable (rich reprs, `_`), plain CPython
otherwise. One kernel per workspace, cwd is the workspace root, `reset: true`
clears every variable, and a call that overruns its timeout kills and restarts
the interpreter rather than hanging the turn.

Off by default and deliberately: it runs arbitrary code with your privileges,
which is the same power `shell` has. Requires `python3` on PATH, without one
the tool is not added at all. `EMPRYO_PYTHON_KERNEL=1` turns it on for a single
run.

### Marionette lane

How the prompt compiler surveys the repository before it writes the map.

```json
"marionetteMode": "fast"
```

| Lane | How it surveys | Typical |
|------|----------------|---------|
| `none` | repository index only, no model call | ~0.2s, 0 tokens |
| `fast` *(default)* | index survey, then one tool-less call judges and writes | ~20s |
| `assist` | index survey, then a short tooled pass confirms | ~55s |
| `deep` | no pre-retrieval, the model drives the whole survey | ~75s |

Measured on 11 real requests against a large TypeScript monorepo (haiku-4.5): `fast`
found the right files as often as `none` while roughly doubling precision, and beat
`deep` by 30 points of recall at a third of the latency. `deep` keeps the highest
ceiling on requests whose product wording matches nothing in the code, at the cost of
high run-to-run variance.

Switch with `/marionette none|fast|assist|deep`, in Settings → Router → Behaviors, or
per run with `--marionette-mode <lane>`. `marionetteBudget` (steps / time) only applies
to `assist` and `deep`. The retrieval lanes have no loop to bound.

## Providers

Pick keys and add custom providers on the dedicated pages:

- [All providers](/docs/providers/overview) - env vars, `--set-key` commands, keyUrls.
- [Custom providers](/docs/providers/custom) - any OpenAI-compatible API.
- [Copilot](/docs/providers/copilot) - use your GitHub Copilot subscription.

### Extra request-body parameters

Some capabilities are switched on by a field in the request body rather than by a
tool. DashScope reads `enable_search`, a router may want a hint of its own, and a
gateway you host can read anything it likes. `extraBody` merges JSON you write
into every chat request Empryo sends on that lane.

```json
{
  "extraBody": {
    "proxy": { "enable_search": true },
    "proxy/gpt-5.6-luna": { "verbosity": "low" }
  }
}
```

Keys are a provider id or a full model id. Both apply, and the model entry is
merged after the provider one, so it wins where they overlap. Custom providers
have their own [`extraBody` field](/docs/providers/custom#extra-request-body-parameters),
which the map above can still override per model.

The merge happens after everything Empryo built, so these keys replace what they
collide with, and a key your provider rejects fails the request instead of being
dropped quietly. Embedding requests never carry them.

> **Note:**
> `extraBody` is trust-gated. A project `.empryo/config.json` can rewrite any field
> of a request, `messages` included, so Empryo withholds it from a repository you
> have not trusted and tells you it did. Run `/trust` to allow it, or keep the
> params in your global config where a checkout cannot reach them.

## Auth & key storage

Keys go to your OS keychain when available, with a file fallback otherwise.

| Platform | Backend |
|----------|---------|
| macOS | Login Keychain (`security`), service `empryo` |
| Linux | libsecret via `secret-tool` (GNOME Keyring, KWallet) |
| Linux without `secret-tool` | `~/.empryo/secrets.json`, mode `0600` |
| Windows | DPAPI (`crypt32.dll`) - `%LOCALAPPDATA%\Empryo\secrets.dat`, user-scoped |
| Windows without DPAPI | Plain JSON under same dir, mode-restricted |
| Windows (WSL) | Same as Linux above |

`empryo --set-key <provider> <key>` writes to whichever backend is active. Inspect with `empryo --list-providers`.

### Several keys on one provider

Run `--set-key` again with a different key and the first is kept beside it
instead of replaced. When one account runs out of quota mid-turn, Empryo
switches to the next stored key and carries on. When every key is spent, you
get the provider's own refusal.

A short rate limit waits on the same key instead, and a key that comes from an
environment variable is never rotated. This works for built-in and
[custom](/docs/providers/custom#several-accounts-on-one-provider) providers
alike. `/keys` lists what is stored.

### Lookup order

```json
{ "keyPriority": "env" }
```

| Value | Order |
|-------|-------|
| `"env"` (default) | env var → keychain → file |
| `"app"` | keychain → file → env var |

Use `"app"` when shell-exported keys keep overriding stored ones.

### Pass a key per-launch

Env vars work for the TUI too - useful for one-off sessions or CI:

```bash
LLM_GATEWAY_API_KEY=sk-... empryo
ANTHROPIC_API_KEY=sk-ant-... empryo
```

Nothing is persisted. There is no `--key` flag - the TUI only reads stored keys and env vars.

### Remove a key

`/keys` inside the TUI lists every provider with delete shortcuts. Or delete from the keychain directly (`security delete-generic-password -a empryo -s anthropic-api-key` on macOS).

## Instruction files

Auto-load project rules from AI-tool markdown files:

```json
{ "instructionFiles": ["empryo", "claude", "cursorrules"] }
```

| Key | File | Default |
|-----|------|---------|
| `empryo` | `EMPRYO.md` | on |
| `claude` | `CLAUDE.md` | off |
| `cursorrules` | `.cursorrules` | off |
| `github-copilot` | `.github/copilot-instructions.md` | off |
| `cline` | `.clinerules` | off |
| `windsurf` | `.windsurfrules` | off |
| `aider` | `.aider.conf.yml` | off |
| `codex` | `AGENTS.md` | off |
| `amp` | `AMPLIFY.md` | off |
| `claude-local` | `CLAUDE.local.md` | off |
| `gemini` | `GEMINI.md` | off |
| `qwen` | `QWEN.md` | off |
| `agent-md` | `AGENT.md` | off |
| `warp` | `WARP.md` | off |
| `junie` | `.junie/guidelines.md` | off |
| `zed` | `.rules` | off |
| `continue` | `.continuerules` | off |
| `roo` | `.roorules` | off |
| `augment` | `.augment-guidelines` | off |
| `trae` | `.trae/rules/project_rules.md` | off |
| `goose` | `.goosehints` | off |

Each source is read from the project root and from your home directory (project text goes later in the prompt, so it wins on conflict). Toggle in the TUI with `/instructions`, on the desktop under Settings → Instruction files.

### Routing instruction files by model, provider or agent

Every enabled file loads for every lane unless `instructionRouting` says who it is for. A source with no entry is common to all, a source with an entry loads only where one of its patterns matches the model, or the custom agent, about to run:

```json
{
  "instructionFiles": ["empryo", "claude", "codex"],
  "instructionRouting": {
    "claude": ["claude"],
    "codex": ["openai", "agent:reviewer"]
  }
}
```

With that, a Claude tab carries `EMPRYO.md` + `CLAUDE.md`, a GPT tab `EMPRYO.md` + `AGENTS.md`, and switching a tab's model switches the files on the next turn, no restart.

| Pattern | Matches |
|---|---|
| `claude`, `openai`, `google`, `xai`, `deepseek` | the model's **family**, through any gateway (`openrouter/openai/gpt-5`, `llmgateway/gpt-5`, `proxy/gpt-*`, `subscriptions/…` all read as `openai`); `gpt`, `anthropic`, `gemini`, `grok` are accepted aliases |
| `provider:proxy` | the provider half of the id exactly |
| `family:google` | the family, spelled out |
| `anthropic/*`, `proxy/gpt-*` | the full id, `*` wildcards |
| `*sonnet*`, `gpt-5*` | the bare model name, `*` wildcards |
| `agent:<name>` | the custom agent the lane runs as (`--agent`, `/agent`, the composer's agent key) |

The three surfaces edit the same table: `/instructions` shows a **for: …** row under each enabled file (Enter cycles Everyone → Claude → OpenAI → Gemini, `/instructions route <source> <all|claude|openai|google>` scripts it), the desktop shows the same choice beside each switch, and the `settings` tool writes `instructionRouting` directly for any pattern the presets do not cover. A file that is on but routed elsewhere is marked *not for this model* so it never looks loaded while doing nothing. Project config wins over global, so a repo can route its own files without touching yours.

## Privacy

Block files from the agent:

```
/privacy add .env
/privacy add secrets/**
```

Built-ins already cover `.env`, `.pem`, `credentials`, `id_rsa`, `.npmrc`, `.netrc`, `shadow`, `passwd`.

## Environment variables

| Variable | Default | Purpose |
|----------|---------|---------|
| `EMPRYO_NO_GENOME` | unset | Skip the Genome scan at startup. |
| `EMPRYO_MAX_OUTPUT_TOKENS` | `64000` | Per-step output cap for all agents (forge + subagents + web-search). Raise for reasoning models that hit `finish_reason=length`. |
| `EMPRYO_PRESETS` | unset | Comma-separated preset specs to load on boot. Set automatically by `--plugin <spec>` flags. See [presets](/docs/reference/presets). |
| `EMPRYO_NO_PROMPT` | unset | Skip first-run addon wizard (set to `1`). Useful in CI. |
| `EMPRYO_DEBUG_API` | unset | Dump per-step API requests to `~/.empryo/api-export/`. Also un-silences AI SDK warnings, which otherwise stay off stderr (they are diagnostics about the request shape, and on the desktop stderr becomes Errors-panel rows). |
| `EMPRYO_DEV_UI` | unset | Show dev-only slash commands (`/ui-demo`, etc). |
| `EMPRYO_ENABLE_GHOSTTY` | unset | Force-enable the embedded floating terminal on Windows (off by default - see [Windows notes](/docs/installation#windows-notes)). |
| `EMPRYO_HEARTH_SOCKET` | `<configDir>/hearth.sock` | Override the Hearth IPC socket path. |
| `EMPRYO_HEARTH_APPROVAL_TIMEOUT_MS` | `300000` | Approval timeout for remote tool calls. |
| `EMPRYO_HEARTH_DENY_READ_REMOTE` | unset | Block read-only tools from remote surfaces (set to `1`). |
| `EMPRYO_PROXY_VERSION` | latest | Pin a CLIProxyAPI version when installing the proxy addon. |
| `EMPRYO_AUTO_INSTALL_ADDONS` | unset | Comma-separated addons to install automatically (`proxy,neovim`). |
| `EMPRYO_MEMORY_LIMIT_MB` | `1536` | Ceiling on Empryo's **own** heap. Past it, memory-hungry analysis (type-aware diagnostics) declines rather than pushing the collector into a spiral. |
| `EMPRYO_TREE_MEMORY_LIMIT_MB` | `4096` | Ceiling on Empryo **plus every process it spawned**, language servers, mostly. A separate question from the heap ceiling: a big tsserver is not a reason to stop type-checking, but it is a reason to worry about the machine. |
| `EMPRYO_LSP_IDLE_MS` | `180000` | How long a language server may sit unused before it is stopped. Restarting one costs 37, 700ms depending on the server; holding four idle costs ~750MB. Lower it on a small machine, raise it if you bounce between languages. |
| `EMPRYO_LSP_MAX_CLIENTS` | `6` | How many language servers may run at once; past this the coldest is stopped. Six keeps the worst case (~550MB each) inside the 4GB tree ceiling. |

> **Note:**
> The four values above are defaults measured on one machine. A malformed, zero or
> negative value falls back to the default. An empty variable never reads as
> "no limit".

## Cells & Morphs

Global config only (`~/.empryo/config.json`), so a repo cannot loosen it:

| Key | Values | Meaning |
|---|---|---|
| `cells.reach` | `"morph"` (default) · `"all"` | Which sessions carry the `cells` tool, morph rules and board context. `morph`: only a morph-mode tab. `all`: every session ("work on PROJ-142 from my board"). Panel switch: **Morphs reach every session**. |
| `cells.views` | `"any"` (default) · `"signed"` · `"off"` | May a morph's `view` block, its own code, in-process, run. `signed`: only morphs signed by Empryo or `trustedKeys`. `off`: dropped, with the reason on the card. |
| `cells.autoApprove` | `false` (default) · `true` | Skip the agent-layer approval gate for morphs you wrote yourself. |
| `cells.requireSigned` | `"official"` · `"trusted"` · `"any"` | Only signed morphs activate. |
| `cells.trustedKeys` | base64 Ed25519 public keys | The organisation's signing keys for `"trusted"`. |
| `cells.directories` | URLs to `index.json` | Internal morph directories under Start from… → Directory. |

A morph hidden from the agent (**Agent access** on its card in Cells & Morphs) stays hidden whatever `reach` says.

Two of a morph's reaches are gated by **repo trust**, not by a config key: a feed that reads the checkout (`files://specs/*.md`), a live socket (`wss://` / loopback `ws://`), and a `view` all stay dark until you trust the repo (`/trust`, or the gate the app shows on first open). A morph whose `surface.place` is `"canvas"` takes the centre of the window in place of the transcript while it is open, the title-bar button and `/canvas` swap back, and the chat keeps streaming behind it. Full reference: [Cells & Morphs](/docs/tools/morphs).

## Hooks

Covered on the [hooks page](/docs/tools/hooks). Short version: Empryo reads `hooks.{Event}` from all 5 config sources (Claude Code's three + Empryo's two), fires all matches.

## Storage

`/storage` shows per-component disk usage (Genome, sessions, plans, memory, input history, binaries, fonts) with one-click cleanup.

## Scope priority

Session → Project → Global. Changes via commands land in whatever scope the command targets. Use `/model-scope` to move the active model between project and global.
