| 1 | # Configuration Paths |
| 2 | |
| 3 | Starting with **Reasonix v1.8.1**, Reasonix uses one user-facing home directory |
| 4 | for global configuration and user-owned state. CLI and desktop share this |
| 5 | location. |
| 6 | |
| 7 | ## Reasonix Home |
| 8 | |
| 9 | | Platform | Reasonix home | |
| 10 | | --- | --- | |
| 11 | | macOS | `~/.reasonix` | |
| 12 | | Linux | `~/.reasonix` | |
| 13 | | Windows | `%APPDATA%\reasonix` | |
| 14 | |
| 15 | Set `REASONIX_HOME` to override Reasonix home for tests, CI, or portable |
| 16 | installations. Normal users should not need it. |
| 17 | |
| 18 | When `REASONIX_HOME` is set, the runtime is fully self-contained: all |
| 19 | configuration, state, cache, and data live under that directory tree. Legacy |
| 20 | migration, OS-home convention directory scanning, and all other fallback paths |
| 21 | are skipped so no data leaks in from a system-wide production install. |
| 22 | |
| 23 | Advanced test and portable setups may set `REASONIX_STATE_HOME` to move runtime |
| 24 | state such as sessions, archives, and memory. It does not move global config or |
| 25 | provider credentials: those remain under `REASONIX_HOME`. If an older build wrote |
| 26 | provider keys to `REASONIX_STATE_HOME/.env`, Reasonix imports those keys |
| 27 | non-destructively when `<Reasonix home>/.env` is missing them. |
| 28 | |
| 29 | ## What Lives There |
| 30 | |
| 31 | | Data | Path | |
| 32 | | --- | --- | |
| 33 | | Global config | `<Reasonix home>/config.toml` | |
| 34 | | Global provider credentials | `<Reasonix home>/.env` | |
| 35 | | In-progress model credential commits | `<Reasonix home>/transactions/model-credentials/` | |
| 36 | | Completed model settings receipts | `<Reasonix home>/transactions/model-settings-receipts/` | |
| 37 | | Legacy credentials import source | `<Reasonix home>/credentials` | |
| 38 | | Global slash commands | `<Reasonix home>/commands/` | |
| 39 | | Global skills | `<Reasonix home>/skills/` | |
| 40 | | Global hooks | `<Reasonix home>/settings.json` | |
| 41 | | Remote-SSH managed known_hosts | `<Reasonix home>/remote/known_hosts` | |
| 42 | | Sessions | `<state root>/sessions/` | |
| 43 | | Archives | `<state root>/archive/` | |
| 44 | | Memory | `<state root>/memory/` and `<state root>/projects/` | |
| 45 | | Global Desktop topic metadata | `<state root>/desktop/topic-state-v1.sqlite` | |
| 46 | | Project Desktop topic metadata | `<state root>/projects/<workspace slug>/desktop/topic-state-v1.sqlite` | |
| 47 | | Disposable session catalog | `<cache root>/session-catalog/v6.sqlite` | |
| 48 | | Disposable history search catalog | `<cache root>/history-search/v1.sqlite` | |
| 49 | | Disposable usage catalog | `<cache root>/usage-catalog/v1.sqlite` | |
| 50 | | Disposable task catalog | `<cache root>/task-catalog/v1.sqlite` | |
| 51 | |
| 52 | `<state root>` defaults to `<Reasonix home>`. It only differs when |
| 53 | `REASONIX_STATE_HOME` is set. |
| 54 | |
| 55 | Desktop detects a project-directory name collision when it saves a newly added |
| 56 | project in `desktop-projects.json`. A new assignment is made only when another |
| 57 | recorded project still resolves to the same legacy directory. Existing projects |
| 58 | and projects imported from older workspace records keep their current |
| 59 | `<state root>/projects/<workspace slug>/` directory. Re-adding the original |
| 60 | project after its colliding peer was assigned elsewhere also keeps that legacy |
| 61 | directory. Only a newly added project that meets the collision rule uses |
| 62 | `<state root>/projects/@<SHA-256 of its absolute root>/`; its `.workspace-root` |
| 63 | file records the assignment. Session, topic, and project-memory paths follow |
| 64 | that assignment. Listing a project never creates or changes an assignment, and |
| 65 | existing files are never moved. If two projects were already recorded with the |
| 66 | same slug before this fix, their historical shared files remain in place: the |
| 67 | old directory does not identify which project owns each file. |
| 68 | Studio currently resolves only `<state root>/projects/<workspace slug>/` and does |
| 69 | not read `.workspace-root`; a newly assigned project's state is therefore not |
| 70 | shared with Studio until Studio supports these assignments. |
| 71 | |
| 72 | Desktop topic titles, title sources, creation times, and automatic-title state |
| 73 | are authoritative in these SQLite files. On first access, Desktop imports the |
| 74 | legacy `desktop-topic-*.json` files from a project's `.reasonix/` directory (or |
| 75 | the global Reasonix directory). A scope with legacy files continues mirroring |
| 76 | them for downgrade compatibility; a fresh scope does not create them. Legacy |
| 77 | files are retained, and project-local settings, skills, commands, attachments, |
| 78 | and `reasonix.toml` are unaffected. |
| 79 | |
| 80 | The session catalog is a rebuildable query projection, not user data. Session |
| 81 | JSONL, event logs, metadata sidecars, and `desktop-projects.json` remain |
| 82 | authoritative. See [Session Catalog and Desktop Startup](./SESSION_CATALOG.md). |
| 83 | The history projection is documented in |
| 84 | [History Search Catalog](./HISTORY_SEARCH_CATALOG.md). |
| 85 | The usage rollup projection is documented in [Usage Catalog](./USAGE_CATALOG.md). |
| 86 | Task snapshots and event logs likewise remain authoritative; the rebuildable |
| 87 | cross-project projection is documented in [Task Catalog](./TASK_CATALOG.md). |
| 88 | |
| 89 | The global user config is named `config.toml`. Project-local config files keep |
| 90 | the name `reasonix.toml`. If someone says "global reasonix.toml", they usually |
| 91 | mean `<Reasonix home>/config.toml`. |
| 92 | |
| 93 | ## Global `config.toml` |
| 94 | |
| 95 | `<Reasonix home>/config.toml` stores non-secret configuration shared by the CLI |
| 96 | and desktop app. It may contain the same provider, plugin, UI, desktop, tool, |
| 97 | skill, sandbox, bot, and agent settings that Reasonix renders into user config. |
| 98 | Provider entries store the name of the credential variable in `api_key_env`, not |
| 99 | the secret value. |
| 100 | |
| 101 | Saved provider and bot credential variables are removed from every |
| 102 | model-controlled child-process environment. On macOS and Linux, the global |
| 103 | credential `.env` is also hidden from Reasonix's file readers, sandboxed shell |
| 104 | commands, and MCP servers; this does not change the visibility of a project's |
| 105 | ordinary `.env`. Windows has no OS-level shell sandbox: shell commands and |
| 106 | local tools run as the same OS user and can deliberately read user-readable |
| 107 | files, including the credential store, so treat restricted permissions there |
| 108 | as a tool-layer write boundary rather than a credential vault. |
| 109 | |
| 110 | If a deny entry left behind by the retired Windows sandbox (v1.38.8 to |
| 111 | v1.38.10) blocks the credential store, Reasonix removes it automatically when |
| 112 | a marker from that sandbox run proves the entry came from Reasonix. Saving a |
| 113 | key works even without that proof: the save resets the file's ACL to the |
| 114 | current user without reading it, and if that is also denied it moves the |
| 115 | locked file aside as `.env.locked-<timestamp>` (a read deny does not block |
| 116 | the move) and writes a new store, so re-entering a key always succeeds. Plain reads never rewrite ACLs; they report |
| 117 | the original access error together with the repair outcome. |
| 118 | |
| 119 | Example: |
| 120 | |
| 121 | ```toml |
| 122 | config_version = 11 |
| 123 | default_model = "deepseek/deepseek-flash" |
| 124 | language = "zh" |
| 125 | credentials_store = "auto" # legacy compatibility; provider keys are in .env |
| 126 | |
| 127 | [ui] |
| 128 | theme = "auto" |
| 129 | cursor_shape = "bar" # CLI/TUI text cursor: underline|block|bar |
| 130 | show_turn_usage = false # hide per-request token/cost receipts in the TUI; default true |
| 131 | |
| 132 | [desktop] |
| 133 | provider_access = ["deepseek"] |
| 134 | |
| 135 | [[providers]] |
| 136 | name = "deepseek" |
| 137 | kind = "openai" |
| 138 | base_url = "https://api.deepseek.com" |
| 139 | models = ["deepseek-flash", "deepseek-v4-pro"] |
| 140 | default = "deepseek-flash" |
| 141 | api_key_env = "DEEPSEEK_API_KEY" |
| 142 | web_search = true |
| 143 | |
| 144 | [[plugins]] |
| 145 | name = "example" |
| 146 | command = "example-mcp-server" |
| 147 | ``` |
| 148 | |
| 149 | Do not put API key values in `config.toml`. This file is regular configuration: |
| 150 | it is safe to inspect, edit, migrate, and include in diagnostics after standard |
| 151 | redaction. Secrets belong in the global `.env` below. |
| 152 | |
| 153 | `[ui].cursor_shape` affects only the CLI/TUI composer. The default `bar` stays |
| 154 | visible without covering double-width CJK characters; use `block` or |
| 155 | `underline` if you prefer those cursor shapes. |
| 156 | |
| 157 | `[ui].show_turn_usage = false` hides the token and cost receipt appended to the |
| 158 | TUI transcript after each model request. Accounting and live status updates |
| 159 | remain active. The default is `true`. |
| 160 | |
| 161 | ### Custom provider `api_key_env` names |
| 162 | |
| 163 | When a provider credential is added, replaced, or explicitly cleared from |
| 164 | desktop settings, TUI `/setup`, or `reasonix setup`, Reasonix allocates a fresh |
| 165 | `REASONIX_CONNECTION_*_KEY` slot. It writes that slot first and atomically |
| 166 | publishes the selected provider's new `api_key_env` reference second. Other |
| 167 | providers keep their current references, even when they previously shared a |
| 168 | fixed variable. Existing fixed names remain readable and are not migrated at |
| 169 | startup. |
| 170 | |
| 171 | Legacy and manually authored provider entries may derive a default from the provider name. Names that normalize to |
| 172 | ASCII keep readable env names such as `LOCAL_GATEWAY_API_KEY`; names made |
| 173 | entirely of non-ASCII characters get a stable hash suffix such as |
| 174 | `CUSTOM_d39b9067_API_KEY` so two Chinese provider names do not share |
| 175 | `CUSTOM_API_KEY`. Names beginning with a digit get a `CUSTOM_` prefix so the |
| 176 | generated environment variable remains valid; for example, `9router` becomes |
| 177 | `CUSTOM_9ROUTER_API_KEY`. |
| 178 | |
| 179 | The CLI custom-provider wizard uses this rule for its draft name. For example |
| 180 | `https://token.sensenova.cn/v1` creates provider name |
| 181 | `custom-token-sensenova-cn`, whose draft key env is |
| 182 | `CUSTOM_TOKEN_SENSENOVA_CN_API_KEY`. Pressing Enter at the variable-name |
| 183 | prompt keeps that draft only until the key is saved; the saved connection then |
| 184 | uses a newly allocated private slot. |
| 185 | |
| 186 | A variable name you type at that prompt in `reasonix setup` is kept, so scripts |
| 187 | can refer to a stable name, as long as saving under it changes nothing another |
| 188 | connection reads: no other provider, bot or remote-host setting in the config |
| 189 | reads it, the global `.env` holds no value (or cleared marker) for it, and the |
| 190 | environment Reasonix runs in does not already set it. Otherwise the wizard says |
| 191 | what holds the name and asks again; Enter falls back to a |
| 192 | private slot. If the name is claimed between the prompt and saving, the save is |
| 193 | refused and nothing is written. |
| 194 | |
| 195 | Saving a new key later for a provider in the user config rewrites its |
| 196 | variable in place when that provider (or the set of providers the key is saved |
| 197 | for) is the only reader of it in the user config, both before and after the |
| 198 | edit, and the global `.env` already holds its value. A project that reads the |
| 199 | same name sees the new key, as it saw the old one. Providers declared in a |
| 200 | project `reasonix.toml` always get a private slot. The previous value is kept in the global `.env` under |
| 201 | a temporary variable until the config is published: a save that fails or is |
| 202 | interrupted puts it back, unless something else has written the variable since. |
| 203 | When another provider or setting also reads the variable, the new key goes to a |
| 204 | private slot as before and the shared variable is left unchanged. |
| 205 | |
| 206 | Existing configs are not rewritten on upgrade. If an old custom provider already |
| 207 | uses `CUSTOM_API_KEY`, it will keep working with that key. If several old custom |
| 208 | providers accidentally share `CUSTOM_API_KEY`, save each provider's API key |
| 209 | again to rotate that connection to a private slot. |
| 210 | |
| 211 | ### Custom provider endpoint URLs |
| 212 | |
| 213 | The desktop custom-provider form treats its **API address** as the exact request |
| 214 | URL and stores it in `request_url`; Reasonix does not append or rewrite its path. |
| 215 | Existing TOML entries are not reinterpreted: legacy `chat_url` keeps its former |
| 216 | OpenAI-only behavior, while Anthropic and Responses continue deriving their path |
| 217 | from `base_url` until the provider is explicitly saved in the current desktop UI. |
| 218 | Saving an OpenAI-compatible provider mirrors the exact address into legacy |
| 219 | `chat_url`, so previous releases continue using the same target. Previous |
| 220 | releases cannot honor arbitrary Anthropic or Responses request paths. |
| 221 | If model discovery needs a separate address, set `models_url`; otherwise Reasonix |
| 222 | probes candidates derived from `base_url`. |
| 223 | |
| 224 | If a gateway requires vendor-specific top-level request body fields, set |
| 225 | `extra_body`, for example `extra_body = { enable_thinking = true }`. These values |
| 226 | are merged into the OpenAI-compatible chat JSON request body without allowing |
| 227 | core fields such as `model`, `messages`, `tools`, or `stream` to be overridden. |
| 228 | |
| 229 | ## Global `.env` |
| 230 | |
| 231 | `<Reasonix home>/.env` is the single runtime source for provider API keys saved |
| 232 | by Reasonix. The setup wizard, desktop settings, CLI missing-key prompts, and |
| 233 | provider-key delete actions all read or write this file through the same |
| 234 | credential helpers. |
| 235 | |
| 236 | Structure: |
| 237 | |
| 238 | ```dotenv |
| 239 | DEEPSEEK_API_KEY=sk-... |
| 240 | GEMINI_API_KEY=... |
| 241 | ANTHROPIC_API_KEY=... |
| 242 | # reasonix-cleared OLD_API_KEY |
| 243 | ``` |
| 244 | |
| 245 | Rules: |
| 246 | |
| 247 | - one `KEY=value` assignment per line; |
| 248 | - blank lines and `#` comments are ignored; |
| 249 | - `export KEY=value` and quoted values are accepted when reading; |
| 250 | - multiline values are rejected by Reasonix writes; |
| 251 | - keys must use shell-style names such as `DEEPSEEK_API_KEY`; |
| 252 | - `# reasonix-cleared KEY` comments are non-secret tombstones written after a key |
| 253 | is deleted so legacy stores do not silently re-import it; |
| 254 | - Reasonix writes this file with restricted permissions where the OS supports |
| 255 | them. |
| 256 | |
| 257 | For provider requests, Reasonix resolves only this global `.env`. Project `.env` |
| 258 | files, home `.env` files, inherited shell environment variables, the old |
| 259 | `credentials` file, and the OS keyring do not act as runtime provider-key |
| 260 | fallbacks. Project `.env`, home `.env`, and inherited shell environment values |
| 261 | are not imported into the global credentials file. The old `credentials` file |
| 262 | and old keyring entries are read only as non-destructive migration sources when |
| 263 | the new global `.env` is missing a key. Project `.env` files are still read as |
| 264 | workspace-scoped, non-provider expansion sources for `${VAR}` references in |
| 265 | MCP/plugin env, headers, URLs, commands, and args; those values are not written |
| 266 | into the process environment, and Reasonix control variables such as |
| 267 | `REASONIX_HOME`, `REASONIX_STATE_HOME`, and `XDG_CONFIG_HOME` are ignored there. |
| 268 | |
| 269 | Caches remain in the OS cache directory, for example |
| 270 | `~/Library/Caches/reasonix` on macOS, `$XDG_CACHE_HOME/reasonix` or |
| 271 | `~/.cache/reasonix` on Linux, and `%LOCALAPPDATA%\reasonix\cache` on Windows. |
| 272 | Set `REASONIX_CACHE_HOME` to override the cache root. When `REASONIX_HOME` is |
| 273 | set, the cache is placed under `$REASONIX_HOME/cache` (unless |
| 274 | `REASONIX_CACHE_HOME` is also set, which takes precedence). |
| 275 | |
| 276 | ## Config Priority |
| 277 | |
| 278 | Runtime configuration is resolved in this order: |
| 279 | |
| 280 | ```text |
| 281 | command-line flags |
| 282 | > project ./reasonix.toml |
| 283 | > global <Reasonix home>/config.toml |
| 284 | > compatible legacy global config |
| 285 | > built-in defaults |
| 286 | ``` |
| 287 | |
| 288 | Writes always target the new global path: |
| 289 | |
| 290 | ```text |
| 291 | macOS/Linux: ~/.reasonix/config.toml |
| 292 | Windows: %APPDATA%\reasonix\config.toml |
| 293 | ``` |
| 294 | |
| 295 | ## Legacy Migration |
| 296 | |
| 297 | Starting with **v1.8.1**, Reasonix automatically checks legacy locations on |
| 298 | startup before the first config load. Migration is synchronous, one-time, and |
| 299 | non-destructive: old files are copied or converted to Reasonix home and left |
| 300 | untouched. |
| 301 | |
| 302 | Legacy config sources include: |
| 303 | |
| 304 | ```text |
| 305 | ~/Library/Application Support/reasonix/config.toml |
| 306 | ~/.config/reasonix/config.toml |
| 307 | ~/.reasonix/reasonix.toml |
| 308 | ~/.reasonix/config.json |
| 309 | ``` |
| 310 | |
| 311 | Legacy credentials, memory files, and sessions are also imported into Reasonix |
| 312 | home when the new destination does not already exist. Legacy provider keys are |
| 313 | copied into `<Reasonix home>/.env` only when that file does not already contain |
| 314 | the same key. If the new global config already exists, it wins and legacy config |
| 315 | files are only kept as compatibility fallbacks. |
| 316 | |
| 317 | Starting in **v1.9.1**, Reasonix also backfills MCP servers from known legacy |
| 318 | paths, legacy `config.json`, desktop-registered projects, and restored tab |
| 319 | projects into the global `<Reasonix home>/config.toml`. Existing global |
| 320 | `[[plugins]]` entries win by name, so project or legacy entries never overwrite a |
| 321 | server the user already configured globally. Source files are left untouched, and |
| 322 | the backfill writes a one-time marker so a user-deleted global MCP server is not |
| 323 | recreated repeatedly from an old project config. |
| 324 | |
| 325 | ## Manual Migration Rescue |
| 326 | |
| 327 | If Reasonix has already created the new home directory but some legacy data was |
| 328 | not present yet, or if the desktop app was opened before the old paths were |
| 329 | available, run the migration rescue command from either frontend: |
| 330 | |
| 331 | ```text |
| 332 | /migrate |
| 333 | ``` |
| 334 | |
| 335 | In the CLI TUI, type `/migrate` into the chat input. In the desktop app, type the |
| 336 | same command into the composer. The command prints progress notices while it: |
| 337 | |
| 338 | 1. checks legacy config and credentials, |
| 339 | 2. scans known legacy memory locations, |
| 340 | 3. scans known legacy session directories, |
| 341 | 4. imports memory files and sessions that were not previously imported, and |
| 342 | 5. prints a final summary. |
| 343 | |
| 344 | If old v0.x sessions live outside the known legacy locations — for example a |
| 345 | Windows v0.52 install/data directory chosen during setup — pass that directory |
| 346 | explicitly: |
| 347 | |
| 348 | ```text |
| 349 | /migrate --from "D:\OldReasonix" |
| 350 | ``` |
| 351 | |
| 352 | The explicit form imports sessions only. The path may be the old install |
| 353 | directory, a `.reasonix`/data directory, or the `sessions` directory itself; |
| 354 | Reasonix checks the common layouts below that root and uses a source-specific |
| 355 | marker, so a previous plain `/migrate` run does not hide the later import. |
| 356 | |
| 357 | The rescue command is intentionally non-destructive. It does not overwrite an |
| 358 | existing `<Reasonix home>/config.toml`; if the new config already exists, copy |
| 359 | any missing legacy settings across by hand. It copies legacy memory files only |
| 360 | when the destination file is absent. It also respects session import markers, so |
| 361 | sessions that were already imported and later deleted by the user will not be |
| 362 | restored on a later `/migrate` run. |
| 363 | |
| 364 | Version limits: |
| 365 | |
| 366 | - Automatic migration starts in **v1.8.1**. |
| 367 | - `/migrate` is available only in Go-based Reasonix builds that include the |
| 368 | command. If Reasonix reports `unknown command`, upgrade first and rerun it. |
| 369 | - The command is not available in the legacy `0.x` TypeScript line. |
| 370 | - Plain `/migrate` rescans the legacy locations listed above. Use |
| 371 | `/migrate --from <path>` only for a known v0.x session source; it is not a |
| 372 | backup restore tool or a downgrade importer. |
| 373 |