# Reasonix Guide README  ·  简体中文  ·  Spec > Day-to-day configuration and usage. For the engineering contract and internals > (data types, registries, package layout, roadmap), see the **[Spec](./SPEC.md)**. ## Contents - [Configuration](#configuration) - [Billing and display currency](./BILLING.md) - [CLI reference](./CLI.md) - [Environment variables](#environment-variables) - [Web frontend](#web-frontend) - [Configuration paths](./CONFIG_PATHS.md) - [Reasoning language](./REASONING_LANGUAGE.md) - [Task contracts and pause policy](./TASK_CONTRACT.md) - [Custom OpenAI-compatible providers](#custom-openai-compatible-providers) - [Hooks](#hooks) - [Desktop hooks](#desktop-hooks) - [Keyboard shortcuts](#keyboard-shortcuts) - [Permissions & sandbox](#permissions--sandbox) - [Capability diagnostics](#capability-diagnostics) - [Plugins (MCP)](#plugins-mcp) - [Slash commands](#slash-commands) - [Embedded documentation retrieval](#embedded-documentation-retrieval) - [@ references](#-references) - [Two-model collaboration](#two-model-collaboration) - [Cross-project task dispatch](#cross-project-task-dispatch) - [Letting the model change a setting](#letting-the-model-change-a-setting) - [Google Gemini, native (experimental)](#google-gemini-native-experimental) ## Configuration Resolution order: **flag > `./reasonix.toml` > the user config file > built-in defaults**. Starting with **Reasonix v1.8.1**, the user config lives at `~/.reasonix/config.toml` on macOS/Linux and `%AppData%\reasonix\config.toml` on Windows; see [Configuration paths](./CONFIG_PATHS.md) for migration and related data paths. Fields marked user/global only are not overridden by `./reasonix.toml`. Provider entries name secrets with `api_key_env`, while the secret values live in Reasonix's global `/.env`, shared by CLI and desktop. Project `.env`, home `.env`, inherited shell environment variables, legacy credentials, and the OS keyring are not provider-key runtime fallbacks; legacy credentials are only migration sources. Project `.env` still feeds workspace-scoped, non-provider `${VAR}` expansion for MCP/plugin settings without importing provider keys or Reasonix control variables. See [Configuration paths](./CONFIG_PATHS.md) for the full `config.toml` and `.env` structure. For the desktop and CLI usage of visible reasoning language, see [Reasoning language](./REASONING_LANGUAGE.md). ```toml default_model = "deepseek-flash" # executor; set [agent].planner_model to add a planner # language = "zh" # ui language; empty = auto-detect from $LANG / $REASONIX_LANG [ui] # shortcut_layout = "desktop" # classic|desktop; compatibility setting # cursor_shape = "bar" # block|underline|bar; CLI/TUI text cursor show_turn_usage = false # hide per-request token/cost receipts in the TUI; default true [agent] reasoning_language = "auto" # visible reasoning text: auto|zh|en # plan_mode_read_only_commands = ["gh issue view"] # legacy compatibility only; Plan bash now uses Permissions # planner_model = "deepseek-pro" # optional low-frequency planner # subagent_model = "deepseek-pro" # optional default for runAs=subagent skills # subagent_models = { review = "deepseek-pro", security_review = "deepseek-pro" } # max_subagent_depth = 8 # nested delegation depth; a named stop against runaway recursion, 0 removes it; set 1 for the single-layer boundary (user config only) # max_subagent_concurrency = 0 # session-wide sub-agent concurrency (task/fleet/skills); 0 = no limit (user config only) # max_parallel_writers = 0 # concurrent writers with non-overlapping write_paths; 0 = no limit, write claims keep writers apart (user config only) # max_side_sessions = 0 # side conversations open at once; 0 = no limit (user config only) # max_provider_retries = 10 # retries of a failed provider connection before the turn stops; 0 = keep trying until cancelled (user config only) # max_queued_inputs = 0 # prompts waiting in a session's queue; 0 = no limit, the queue's byte limits still apply (user config only) # max_subagent_steer_bytes = 16384 # bytes in one message of guidance to a running sub-agent, which keeps one message from filling its context; 0 = no limit (user config only) # max_subagent_steers_per_minute = 30 # messages of guidance per minute to one running sub-agent, a stop for a loop; 0 = no limit (user config only) # write_lease = "strict" # user file only; default optimistic: a writer that declares no path (bash, MCP) does not hold the whole workspace; strict makes it hold it # write_lease_max_wait_minutes = 0 # minutes a write waits behind another session's claim before it fails with workspace.claim_timeout; 0 (default) = wait as long as the turn lasts, 120 is a sensible limit (user config only) # subagent_claims = "strict" # user file only; default optimistic: a sub-agent that declares no write_paths holds only the path it is writing; strict makes it hold the whole workspace # plan_approval = "auto" # user file only; a submitted plan starts executing without the approval click, and the transcript says so; default ask # default_mode = "plan" # user file only; a new conversation starts in plan mode (a resumed one keeps its own; Settings has a switch); default normal # compact_ratio = 0.90 # sole auto trigger; presets 0.70 / 0.80 / 0.90 # max_output_tokens = 0 # recommended: automatic (DeepSeek default high → ~64K; not unlimited) # max_output_tokens = 32768 # ordinary coding / cost control # max_output_tokens = 65536 # heavy reasoning / long tool loops # max_output_tokens = 131072 # only after repeated finish_reason=length # max_output_tokens never changes compact_ratio; only the final send-time clip does [[providers]] name = "deepseek-flash" kind = "anthropic" base_url = "https://api.deepseek.com/anthropic" model = "deepseek-flash" api_key_env = "DEEPSEEK_API_KEY" web_search = true # also preset: deepseek-pro # idle_timeout_seconds = 300 # per-provider stream idle watchdog; how long a call may send nothing before it is read as dropped (default 300s; a silent pre-header wait is retried once, so up to ~2×) [tools] enabled = [] # omit/empty = all built-ins # disabled = ["web_fetch", "ask"] # removed from every request by name, framework and MCP tools included; a call to one is refused with the reason; a tool the host itself reads (todo_write, complete_step, await_user, conclude_blocked, update_goal, use_capability) stays on and a warning says which; a project file can only add names, and Settings lists those as turned off by the project; Settings has a switch per tool; applies to conversations opened afterwards bash_timeout_seconds = 120 # foreground safety cap; set 0 for no tool-local cap protect_changed_files = true # refuse a whole-file write over a change made since the agent last read or wrote the file browser_tools = true # built-in browser tools; the browser starts on first use # mcp_resources = true # user file only; a read_mcp_resource tool for resources a connected MCP server lists or builds from its declared templates (a {+x} template is wide: read the full address on the card before approving); asks first; default off # web_search_backends = ["searxng"] # user file only; ordered chain for web_search # searxng_url = "http://localhost:8888" # your own SearXNG instance mcp_startup_timeout_seconds = 30 # background initialize + tools/list safety cap mcp_call_timeout_seconds = 300 # default MCP call safety cap; per-plugin/tool overrides may raise it [tools.system_one] api_key_env = "TYPESAFE_API_KEY" # optional TypeSafe AI System One decision protocol model = "jev-latest" # base_url = "https://api.typesafe.ai" [tools.system_one.laya] local = false # true: run the official Python SDK locally (`pip install laya`) python = "python" # interpreter containing the laya package model = "auto" # auto|english|multilingual|typed-decisions # http_base_url = "http://127.0.0.1:8080" # self-hosted POST /v1/systemone gateway # http_api_key_env = "LAYA_API_KEY" # optional gateway Bearer token [environment] enabled = true # inject a stable startup summary of OS, shell, and common tools offline = false # set true when outbound network access is unavailable; prevents futile retries # [environment.tools] # go = "/opt/homebrew/bin/go" # optional explicit trusted path; workspace-local paths are not auto-executed [skills] # paths = ["~/my-skills", "../shared/skills"] # extra custom skill roots # excluded_paths = ["~/.agents/skills"] # hide convention roots without deleting folders # disabled_skills = ["review"] # hide skills until /skill enable # suppress_warnings = true # quiet non-fatal skill loading warnings; doctor still reports issues [permissions] mode = "ask" # writer fallback when no rule matches: ask|allow|deny deny = ["Bash(rm -rf*)", "Bash(git push*)"] # hard-blocked in every mode allow = ["Bash(go test:*)"] # never prompted [sandbox] # workspace_root = "" # file-writers confined here; empty = current dir # allow_write = ["/tmp"] # extra dirs write_file/edit_file/multi_edit/move_file may touch # forbid_read = ["${HOME}/.ssh"] # paths the agent must not read or list # allow_devices = ["/dev/nvidia*"] # host device nodes bound into the Linux bash sandbox (user config only) # egress_unlisted = "allow" # unattended long tasks (user config only): hosts outside allowed_domains pass without asking, said once each; denied_domains still refuse [peers] # enabled = false # your sessions on this machine can message each other; every message waits for that session's user to approve it; also a switch in Settings, applied at the next turn (user config only) # dispatch = false # the model may hand a task to a session in another of your projects, via dispatch_to_project (the session it opens sends its final answer back on its own), and ask you to open folders as projects with open_projects (one card lists every path; nothing is added or trusted until you approve it, and no mode answers it for you); needs enabled; turning it on is your consent for those sessions' tasks and replies, no card, and a project in your sidebar you have not decided about is trusted by being named; the opened session keeps the sender's model, preset, approval mode and planning, so a YOLO sender opens a YOLO session that answers the target project's ask rules too (deny rules still hold) (user config only) # max_dispatch_depth = 1 # how many sessions deep a dispatch may chain; 0 removes the ceiling (user config only) # max_dispatched_sessions = 8 # sessions one session may keep open from its dispatches; 0 removes the ceiling (user config only) # release_projects = [] # projects that take messages from your other sessions without the card; their own approvals and sandbox still apply; they start over a pause the host set itself (session switch, unreadable @ reference, reopen) but never over one you pressed (user config only) [mods] # enabled = false # run the hooks module of installed Claude Code mods; full trust, no sandbox (user config only) # hook_budget_ms = 0 # one hook's own running time in milliseconds; 0 keeps 10000 (user config only) [serve] # auth_mode = "token" # token (default)|password|none; none still requires the launch token for changes # token = "" # optional fixed token; empty token mode generates one at startup # password_hash = "" # bcrypt hash generated with reasonix serve --hash-password --password '...' # behind_proxy = false # true only behind a trusted reverse proxy [desktop] # max_workspaces = 0 # most projects the sidebar remembers; 0 = no limit (user config only) # session_ref_tokens = 8000 # most tokens one referenced past conversation (@session:) adds to a message; 500..200000 [[plugins]] name = "example" command = "reasonix-plugin-example" startup_timeout_seconds = 60 # optional initialize + tools/list cap call_timeout_seconds = 600 # optional per-server MCP call timeout tool_timeout_seconds = { "generate_video" = 1800 } # optional raw MCP tool names ``` For the full schema and every field's contract, see [`SPEC.md` §5](./SPEC.md#5-configuration-toml). The startup tool summary reuses a trusted snapshot in the current Reasonix data directory for up to 24 hours. Probes refreshed by another data directory do not replace that valid snapshot. On expiry, transient probe failures retain previous successful observations, keeping the system prefix stable across rebuilds and restarts; definitive changes, such as a removed tool, are adopted on refresh. Installed and project-configured MCP servers need no per-tool trust list. The dedicated two-model Planner may use every non-destructive MCP tool, even when the server omits `readOnlyHint`; strict read-only sub-agents still require `readOnlyHint: true` and no `destructiveHint`. `[agent].plan_mode_read_only_commands` is also retained for config round trips, but the main Plan workflow no longer has a separate bash allowlist or trust prompt. Bash classification and approval use the same Permissions rules in Plan and Standard mode; the Sandbox remains the filesystem, process, and network boundary. Dedicated planner and read-only subagent runners keep their own strict read-only tool registry and foreground-command classifier. ### Environment variables Most day-to-day settings belong in `config.toml` or the global Reasonix `.env` described above. The variables below are process-level advanced switches; set them before launching Reasonix. Project `.env` files are not a runtime source for Reasonix control variables. To hand extra variables to every command the `bash` tool runs, set the user-global `[tools.shell.env]` table in `config.toml` (for example `BASH_ENV`). It is applied over the inherited environment; a project `reasonix.toml` cannot set it, so a cloned repository cannot inject variables into your commands. A preset is not scoped to a command you asked for: `BASH_ENV` is sourced before *every* bash command, including the ones the host classifies as read-only and the ones it runs as verification. So keep it side-effect free and outside the workspace's writable roots: anything it writes can invalidate what a verification just proved. The table reaches the local bash tool only — a remote workspace and `reasonix review` build their own sandbox and do not carry it. ### CLI telemetry The CLI can send a once-per-day anonymous active-install ping and bounded, content-free event counters to `https://crash.reasonix.io`. Configure the user-global policy with: ```bash reasonix config telemetry # print the effective mode reasonix config telemetry auto # default: local interactive TTY only reasonix config telemetry on # also allow local headless `reasonix run` reasonix config telemetry off # disable and delete pending counter files ``` On the first eligible release-build interactive session, Reasonix explains the exact data boundary and asks once before any telemetry request. The prompt is `[Y/n]`: pressing Enter, `y`, or `yes` stores `auto`; `n` or `no` stores `off` and deletes pending counters. After the choice is saved, enabled reporting is silent and the prompt is not shown again. If the preference cannot be saved, nothing is uploaded. Reporting is always disabled in CI, development builds, and when `DO_NOT_TRACK` is set or `REASONIX_TELEMETRY=0`. Under `auto`, redirected/piped or otherwise non-interactive sessions do not report. When no choice has been saved yet, these ineligible sessions neither prompt nor report. Network failures after consent are silent and never change stdout, stderr, or the process exit code; unsent counters stay in a bounded local queue for a later invocation. The ping contains a dedicated random 128-bit CLI install ID, CLI version, OS, architecture, and the `cli` surface marker. This ID is separate from the desktop install ID and is not an account, hardware, repository, or session identifier. Counter batches use that same ID for daily active-install deduplication and contain only fixed buckets such as CLI mode/profile, permission/session mode, turn latency, finish reason, cache-hit range, generic Provider/tool error class, compaction, recovery counters, per-turn token-volume buckets, workspace-lease contention buckets, and normalized UI language. Closing the prompt without answering (end of input, Ctrl+D) stores nothing and uploads nothing; it asks again the next time. Reasonix never uploads prompts, answers, reasoning, tool names/arguments/output, paths, repositories/branches, session IDs, exact token or cost values, Provider/model names, base URLs, or environment variables. ### Trace export (OpenTelemetry) Reasonix can hand each turn to your own observability stack as OpenTelemetry GenAI traces. It is off, and nothing leaves the machine, until your user config switches it on and names a collector: ```toml [observability.otlp] enabled = true endpoint = "http://localhost:4318" # OTLP/HTTP; /v1/traces is appended header_name = "Authorization" # optional: the header carrying a credential header_env = "OTEL_EXPORTER_AUTH" # the variable holding its value, never the value service_name = "reasonix" # optional ``` - **What is sent.** One trace per turn: an `invoke_agent` span, a `chat` span per model attempt (provider, model, token counts, finish reason), and an `execute_tool` span per tool call (tool name, failure class), nested under the tool that delegated the work. - **What is never sent.** Prompts, answers, tool arguments and results, file paths and error text. Spans are built from typed event fields through one fixed attribute set, and a name that does not look like a name is sent as `unknown`. - **Who decides.** Only your user config. A repository's `reasonix.toml` cannot switch it on or move it; the ignored key is reported at load. - **Credentials.** `header_env` must be an `OTEL_*` or `REASONIX_OTEL_*` variable, so no setting can read an unrelated secret. A credential is refused over plain `http` to any host but loopback, the endpoint may not contain user info, and redirects are not followed. - **Failure.** Export never blocks a turn: a slow or dead collector drops spans from a bounded queue. - **Stability.** The upstream conventions are marked Development (read 2026-10-18); every payload carries `reasonix.semconv_experimental = true` and attribute names can change. Not run against a real backend: built against the OTLP/HTTP JSON encoding and tested with a local collector. Studio has the same switches under Settings, Network. A save reaches sessions opened afterwards. ### CLI crash reports An unhandled Go panic that reaches the CLI entrypoint is saved locally as a sanitized report under `/cli-crash-reports`. Reasonix keeps at most 10 files with owner-only permissions. The panic value is never serialized. Absolute source paths become `/.go:`, function arguments are removed, and the same secret, token, email, and long-identifier scrubbers run both when saving and immediately before sending. Crash reports are never uploaded automatically. Review and manage them with: ```bash reasonix report # preview newest; prompt before sending on a TTY reasonix report list # list local reports reasonix report show [ID] # preview without sending reasonix report send [ID] # explicit send; delete locally only after success reasonix report delete [ID] # delete without sending ``` Piped or redirected `reasonix report` calls only preview and never prompt or send. The CLI telemetry setting does not auto-send or auto-delete these separately reviewed reports. Runtime fatal throws, operating-system kills, and panics in unwrapped background goroutines cannot be recovered by Go and do not produce this local report. ## Web frontend For local use, `reasonix web` starts the browser UI and opens it in your default browser. Inside an interactive CLI session, `/web` snapshots the current session, restores the terminal, and opens an explicit `/sessions/#token=...` deep link. Even a never-used session keeps its reserved ID without forcing an empty transcript onto disk, so the first Web turn continues the same session identity. Studio and the Web UI offer **Side question** beside the composer for read-only questions without adding them to the main conversation. A submitted question appears immediately. Answer text streams as it arrives, with the current phase, tool name and elapsed time shown while it runs. Collapsing the panel keeps the request running; closing it cancels the request and discards that side conversation. If a request fails, partial text is marked interrupted and the draft remains for retry. ```bash cd your-project reasonix web ``` Use `reasonix web --no-open` when you want to start the foreground Web server and print its URL without opening a browser tab. The lower-level `reasonix serve` command starts the same engine without opening a browser by default. It remains the right entry point for remote development boxes, supervisors, tunnels, reverse proxies, and shareable authenticated sessions. `reasonix web` starts at `127.0.0.1:8787`, automatically tries 8788, 8789, and so on when a port is busy (up to 100 retries), and defaults to a newly generated token even when `[serve].auth_mode` is `none`. Each live process registers a single-writer heartbeat file under `/server/instances/`; clean shutdown removes its own file, while later instances lazily remove records whose owner process is confirmed dead. Multiple Web instances can therefore share one Reasonix home without overwriting registry state. The process stays attached to the terminal; stop it with Ctrl-C. `reasonix serve` also defaults to a freshly generated per-launch token unless `[serve].auth_mode` or `--auth` says otherwise. - `--auth none` (or `auth_mode = "none"`) is an explicit opt-out for a Serve fronted by its own authentication. Reads are open to whoever reaches a loopback or bound address; other Host names get 421 `serve.host_rejected`. - Every state-changing request, approvals included, still needs the launch token: `Authorization: Bearer `, or the cookie the printed `approvals:` link sets. Without it the answer is 403 `auth.launch_token_required`. - Serve writes the token to a 0600 file under `/remote/` and prints only its path; append `#token=` to the printed link. A managed launch with `--token-file` names that file instead. The phone QR code a terminal shows in token mode still carries the token. - Prefer `--token-file` over `--token`: argv is visible to other processes, sandboxed ones included. A plaintext `[serve].token` in the global config is readable from inside the sandbox; keep the secret in a file. - On macOS and Linux the OS sandbox denies the remote state directory and every token file. Windows has no bash sandbox, so there only the read tools refuse. - `[serve]` is read from the user config only; a project `reasonix.toml` cannot set it. Choose the mode explicitly when binding outside loopback: ```bash reasonix serve --auth token reasonix serve --addr 0.0.0.0:8787 --auth token reasonix serve --auth password --password 'temporary-password' ``` Token mode prints a share URL with `#token=...`; the Web page exchanges the fragment for an HttpOnly cookie before starting API or SSE requests, keeping the token out of request URLs, browser history, referrers, and access logs. Pass `--token` or set `[serve].token` to reuse a stable token. Password mode requires either `--password` at startup or a stored bcrypt hash: - Prefer `--password-file` over `--password`: argv is visible to every local user through `ps` and `/proc//cmdline`. - The file holds a single line and is readable only by its owner (`chmod 600`). ```bash reasonix serve --auth password --password-file ~/.reasonix-serve.password reasonix serve --hash-password --password-file ~/.reasonix-serve.password # /config.toml [serve] auth_mode = "password" # none|token|password password_hash = "$2a$12$..." behind_proxy = true # only behind a trusted reverse proxy ``` The web UI exposes chat, tool approvals, session history, rewind/fork/summarize, model and reasoning-effort controls, Goal, a live todo panel fed by the `todo_write` tool, extension status/card/form/notification surfaces, and provider balance when configured. Extension-hosted providers appear in the model picker. Run `/reload` while idle to fail-atomically reload extension sidecars and the runtime generation without restarting Serve. Use `--model`, `--max-steps`, or `--resume` for one-off launches; otherwise `serve` uses the user-global `default_model`. If the selected Provider has no saved API key, a loopback-bound Serve still starts and shows a Provider setup page instead of failing before the browser can connect. After authentication, enter the key there; Reasonix writes it to this host's global credential file with restricted permissions, rebuilds the active controller in the same process, and opens the normal UI. The credential-writing endpoint is disabled for non-loopback listeners. For a remote SSH window, "this host" means the remote host reached through the SSH tunnel; the key is not copied from the desktop machine. ## Editor integrations over ACP `reasonix acp` exposes Reasonix as an ACP v1 stdio agent for editors and other host clients. The dedicated **[ACP editor integration](./ACP.md)** guide covers startup, capability negotiation, session lifecycle, independent model/work/ collaboration/approval controls, client filesystem and terminal capabilities, MCP servers, permission requests, and the Reasonix mid-turn steering extension. ## Using Reasonix from DeepSeek Harness DeepSeek Harness (`dsh`) can delegate a task to Reasonix as a subagent over ACP, with configuration only. [Using Reasonix from DeepSeek Harness](./DSH.md) covers the install through DSH's plugin market or by hand, switching the plugin on, the permission policy and the limits. ## Remote SSH The remote module runs Reasonix on a remote host and reaches it over your own SSH connection — VS Code Remote-SSH style. It bootstraps a persistent headless `reasonix serve` on the remote host, forwards a local loopback port to it, and opens the existing serve web client through that tunnel. The agent, its tools, and its files all live on the remote host at full fidelity; nothing runs through a lossy file proxy. Linux, macOS and Windows remote hosts are supported. A Windows host needs PowerShell and OpenSSH, whichever login shell (cmd, PowerShell or Git Bash) its `DefaultShell` names. Hosts live in a user-global `[remote]` section of `config.toml`. Like `[secrets]`, a project `reasonix.toml` cannot inject or override remote hosts — a cloned repo can never steer where Reasonix opens SSH connections. Credentials follow the provider idiom: the host names an env var (`passphrase_env`, `password_env`) whose value lives in Reasonix's global `.env`; key material itself is never stored — `identity_file` is a path. Studio uses that host's explicit `workspace` as the default for host-level reads and opens that omit a workspace. Only when it is unset does the first nonempty `workspaces` entry supply the default; with neither set, the attach receives an empty workspace. An explicit open path takes precedence. Reordering other entries cannot override a declared `workspace`. These values come only from the user's host configuration, and disabled hosts are refused before any link reuse. The active SSH project's secondary line shows its actual connection directory, including isolated worktrees. Its project menu shows the default connection directory used by Refresh; these can differ. Settings' default action explicitly chooses a new default. A host-level read never borrows an unrelated pane's path. ```toml [remote] [[remote.hosts]] name = "gpu-box" host = "203.0.113.7" user = "dev" identity_file = "~/.ssh/id_ed25519" workspace = "~/projects/app" serve_install = "auto" # Remote CLI: auto | npm | upload | never provider = "local" # Model credentials: local (this machine, over the tunnel) | remote (that host's own) [[remote.hosts.forwards]] type = "local" # local (-L) | remote (-R) bind = "127.0.0.1:5432" target = "127.0.0.1:5432" ``` A WSL distribution on a Windows machine is a host with `kind = "wsl"` and a `distro` (see `reasonix remote wsl list`): - It has no address, so it takes only the folder and install fields. `host`, `port`, `user`, keys, passwords, `proxy_jump`, `use_ssh_config` and forwards are rejected on it. - Add one under Settings, Remote: the "WSL distribution" choice appears only on a machine that has WSL, and lists its distributions (WSL 1 ones are shown disabled). Studio then opens it like any other machine in the list. - Connecting needs WSL 2 and a distribution that has `ps` and `nohup`; a minimal image without them is refused with the missing name. - The kernel runs inside a shell session Studio keeps open, because WSL ends everything a session started when it ends. - When Studio closes its link, the kernel goes away with the distribution (WSL stops an idle one after about 15 seconds). - Conversations are kept on disk and resume on the next connect. - Nothing keeps a distribution running behind your back. - Model credentials come from the distribution's own configuration (`provider = "remote"`): under WSL's default networking the distribution cannot reach this machine's loopback, so this machine's provider is not shared. - A folder chosen as a Windows path (`C:\work\app` or `\\wsl.localhost\\home\me\app`) is translated by the distribution; a folder it cannot see is refused. - From the command line, `reasonix remote wsl add ` adds one, and `remote test` and `remote connect` then work as they do for an SSH host. - `connect` keeps the session open until you press Ctrl-C, and the kernel stops with it. - `status`, `forward`, `serve` and `fs` stay SSH-only and refuse a WSL machine with `remote: host is not reached over ssh`. For an agent's own shell commands, only `list`, `help` and `wsl list|test` run: - Every other `reasonix remote` subcommand refuses there (exit 77), because it changes the machine book or opens a link, which a person does. - The host marks the commands it runs for the agent to tell them apart. It guards against an agent that does not hide; it is not the sandbox. CLI: ```bash reasonix remote add gpu-box dev@203.0.113.7 --workspace '~/projects/app' reasonix remote import --all # import aliases; ssh -G resolves Include/Match rules when connecting reasonix remote test gpu-box # dial + auth + host-key confirmation reasonix remote connect gpu-box --open # bootstrap serve, tunnel, open the URL reasonix remote serve status gpu-box reasonix remote fs ls gpu-box:'~/projects/app' ``` Hosts with `use_ssh_config` enabled resolve the final effective configuration through the local OpenSSH `ssh -G`, including `Include`, wildcard `Host`, `Match` (including `Match exec`), repeated `IdentityFile`, `ProxyJump`, and `IdentitiesOnly`. Import stores the original alias instead of a stale snapshot. `connect` is a foreground supervisor (like `ssh -N` plus the serve bootstrap): it keeps the tunnel and configured forwards alive, auto-reconnects with exponential backoff if the link drops, and re-attaches forwards on reconnect. Ctrl-C disconnects the local side only — the remote serve keeps running, so the next `connect` reuses it. There is no background daemon in V1. Host keys are verified against your OpenSSH `~/.ssh/known_hosts` (read-only) plus a Reasonix-managed `~/.reasonix/remote/known_hosts`. A first-seen key prompts for trust-on-first-use and is recorded in the managed file; a key that contradicts a recorded one is a hard error that names the offending line and is never auto-accepted. Remote-side state lives under the remote host's `~/.reasonix/remote/`: `serve-.json` (pid, bound loopback address, workspace), `serve-.token` (0600; the auth token, passed to serve via `--token-file` so it never appears in `ps`), `serve-.broker` (0600; the provider broker token, see below), and `serve-.log`. ### Getting the kernel there The remote runs a `reasonix serve`. The binary is a static Go build — no runtime, no Node, no glibc — so putting it there is a file transfer, not an install. `serve_install = "auto"` tries, in order: 1. **The remote fetches its own release.** Reasonix reads `SHA256SUMS` here and hands that host the archive URL and the digest; the host downloads over its own connection and keeps the archive only if it matches. Nothing but the command crosses the SSH link, and a machine in a datacenter pulls the ~21MB archive far faster than a laptop can push it. 2. **Upload this machine's binary**, when the platforms match. 3. **Download here and push over SFTP**, for a host with no route to the release. 4. **npm**, last. `npm i -g reasonix` installs the same static binary but needs Node ≥18 over there to do it; it stays because a network that mirrors npm while throttling GitHub is a real one. Set `serve_install` to `npm`, `upload` or `never` to take exactly one route. `reasonix remote test ` reports which routes are open on a host before you connect, and names what closes each one that is not. ### Model credentials A remote session resolves its models on **your** machine by default. The connection carries a reverse (`-R`) forward back to a provider broker Reasonix runs on loopback here; the remote kernel calls that instead of a model endpoint, and the request is issued from this machine with this machine's key. So a remote host needs **no API key of its own and no egress to the model API** — a locked-down build machine works. The key is never written to the remote disk: only a per-connection broker token is, and only as a 0600 file. The remote also never sees the endpoint, headers, or proxy settings behind a provider; it sees the same catalog you do and nothing else. Set `provider = "remote"` on a host to leave it resolving providers from its own config instead, for a machine whose providers are deliberately not yours. `reasonix remote serve start` never uses a broker: that serve is meant to outlive the command that started it, and a broker published from a command that has exited is a port nobody holds. Two costs are worth knowing. Model traffic now leaves through your own uplink rather than the remote's, which is a downgrade when the remote sits on a much fatter pipe. And the models available to a remote pane are the ones configured here, so a provider only that host had configured no longer appears unless you switch it to `provider = "remote"`. In the desktop app, manage hosts under **Settings -> Remote SSH**, then use the status-bar chip or the host row's **Remote explorer** button to browse and edit files over SFTP, manage port forwards, and start/open the remote workspace. Opening a workspace creates a separate native Reasonix window, similar to a VS Code Remote SSH window. The primary window owns the SSH tunnel; the remote window is an isolated, lightweight shell and does not restore or acquire local conversation sessions. The remote window uses **this** machine's providers through the broker described above, so opening a workspace on a host that has never held an API key just works. On a host set to `provider = "remote"` the old path applies: the window shows the authenticated setup page first, saves the key only in the remote Reasonix credential file, and activates the Provider without restarting the remote Serve process. A transient SSH outage keeps the remote window open; the desktop reconnects in the background, re-attaches its loopback forward, and reloads the window against the recovered Serve. An authentication or host-key failure is terminal and closes the unusable remote window instead. ### Lifetime of the remote serve - The `reasonix serve` process on the host is persistent. Quitting the desktop or losing the link does not terminate it, and the next connect attaches to it. - A pane's session on that serve does not outlive the desktop. Each pane drives its own runtime there; closing the pane or quitting the desktop makes Studio call `POST /runtimes/{id}/close` on the remote serve before it takes the tunnel down. - That close cancels a turn in flight and releases the session lease, so another window can open the session. Only the pane's runtime is closed: the serve process keeps running and the next connect can attach to it. Runtimes opened by other clients are left alone too, whichever way the serve was started. - `provider = "remote"` decides only where model credentials come from. It does not detach a pane from the desktop. - A serve already running for the workspace is attached to, never replaced, whether `reasonix remote serve start`, another window or a hand-started `reasonix serve --port-file` launched it. A later connect never deletes its port or pid files and never stops it. - `serve start` uses no broker, so a host with `provider = "local"` cannot attach to it. That connect fails with an explanation and leaves the serve running. Set the host to `provider = "remote"` (it then needs its own credentials), or stop the serve with `reasonix remote serve stop `. ## Custom OpenAI-compatible providers In the desktop app, open **Settings -> Model -> Access -> Add model service -> Custom provider** for proxies, aggregators, or self-hosted services that speak the OpenAI-compatible chat API or Anthropic-compatible Messages API. For common providers, choose **Add model service -> Recommended preset** instead. New official DeepSeek entries use the Anthropic-compatible Messages endpoint by default and enable provider-side `web_search`; the same `DEEPSEEK_API_KEY` works for both protocols. On startup, Reasonix upgrades unmodified legacy `deepseek-flash` / `deepseek-pro` entries that still use the official endpoint and standard key/model settings. Customized official Chat Completions entries stay unchanged and show an **Upgrade protocol** action in Settings. Proxy endpoints, custom headers, model lists, and capability overrides are never migrated automatically. Existing separately named `deepseek-anthropic` entries remain compatible, but that redundant preset is no longer offered for new access. Reasonix can prefill editable custom-provider entries for Kimi CN, Kimi Global, Kimi Coding Plan, MiMo API, MiMo Anthropic, MiMo Token Plan CN/SGP/AMS and their Anthropic-compatible variants, MiniMax CN/Global API, MiniMax CN/Global Anthropic, GLM CN, Z.AI Global, GLM/Z.AI Coding Plan OpenAI-compatible and Anthropic-compatible endpoints, OpenCode Go, OpenCode Go Anthropic, OpenCode Go DeepSeek Anthropic, OpenCode Go DeepSeek Responses, OpenCode Zen Anthropic, Qwen/DashScope CN/Global, Qwen Coding Plan CN/Global OpenAI-compatible and Anthropic-compatible endpoints, StepFun OpenAI-compatible and Anthropic-compatible endpoints, NovitaAI, GMI Cloud, Vercel AI Gateway, HuggingFace Router, NVIDIA NIM, KiloCode, and Ollama Cloud. Plan names describe the access/payment route; they include CN/Global only when the provider exposes distinct regional endpoints. Kimi Coding Plan is therefore a dedicated plan endpoint, while Kimi direct API is split into CN and Global. The preset path usually needs only the provider API key: the key value is stored in Reasonix home `.env`, while `config.toml` stores the endpoint, model list, key environment-variable name, context window, vision model metadata, proxy bypass for China-only endpoints, MiniMax `reasoning_split`, GLM/MiniMax thinking heuristics, Anthropic-compatible Bearer auth where needed, Ollama Cloud max-effort support, and OpenCode Go per-model reasoning overrides. The dedicated OpenCode Go DeepSeek Anthropic and DeepSeek Responses presets expose the verified Flash routes and enable provider-side `web_search` by default; the Responses variant uses stateless context replay. The existing mixed OpenCode Go Anthropic preset remains scoped to Qwen and MiniMax so server tools are not sent to unverified models. DeepSeek Pro remains on the Chat Completions preset because live Anthropic and Responses requests currently fail in the OpenCode Go upstream conversion. The OpenCode Go preset includes its native `kimi-k3` subscription route with image input, `high`/`max` reasoning effort, and a 1,048,576-token context window. Existing untouched OpenCode Go preset installs are upgraded automatically; edited model catalogs are preserved. The Kimi CN and Kimi Global direct-API presets also include `kimi-k3` with image input, a 1,048,576-token context window, and the official `low`/`high`/`max` effort scale (default `max`). For the official K3 endpoints, Reasonix preserves complete assistant messages across turns, sends output limits as `max_completion_tokens`, and omits K3's fixed sampling parameters. Untouched legacy Kimi direct-API catalogs are upgraded automatically without changing the default model; custom catalogs and endpoints are preserved. After adding a preset, open its provider card if you need to change models, headers, endpoint, or compatibility settings. Fill **API address** with the provider endpoint that should receive the standard chat path. In this mode Reasonix previews and sends chat requests to: ```text /chat/completions ``` Enable **Full URL** when the service gives you a complete request URL, for example `https://gateway.example.com/v1/chat/completions`. Reasonix then sends chat requests directly to that URL and does not append `/chat/completions`. The preview under the field shows the exact request URL that will be used. Model discovery uses the API address to try likely model-list URLs such as `/models` and `/v1/models`. If the gateway requires a separate model-list endpoint, open **Compatibility settings** and set `models_url`, for example `https://gateway.example.com/v1/models`. If discovery is not available, fill the model list manually. **Full URL** still uses the OpenAI-compatible chat request body. It does not switch the request schema to the OpenAI Responses API. ### Compatibility settings The **Compatibility settings (usually leave unchanged)** section is for gateways whose authentication, model-list endpoint, or reasoning/thinking request shape differs from the normal OpenAI-compatible defaults. Leave these fields at their defaults unless the provider documentation or a proxy error tells you otherwise. For Anthropic-compatible services, such as some coding-plan endpoints, choose **Anthropic-compatible** as the connection protocol before saving. | Field | What it controls | When to change it | | --- | --- | --- | | `api_key_env` | The environment-variable name used for this provider's API key. Desktop-saved key values are stored in Reasonix home `.env` under this name; the TOML config stores only the name. | Change it when several providers need distinct keys, or leave it blank for a service that does not require an API key. | | `models_url` | The URL used only for model discovery. Chat requests still use the API address or Full URL above. | Set it when `/models` or `/v1/models` is not where the gateway exposes its model list. | | Extra request headers | Static HTTP headers, one `Header: value` per line. | Use for gateways such as OpenRouter that require `HTTP-Referer`, `X-Title`, or similar site headers. Keep bearer/API keys in the key field instead of duplicating them here. | | Extra request body | A JSON object merged into the top-level chat request body. | Use only for provider-specific flags such as `{"enable_thinking": true}`. Reasonix still owns core fields such as `model`, `messages`, `tools`, `stream`, and `thinking`, and null values are rejected. | | Authorization: Bearer | For Anthropic-compatible providers, sends the saved API key as `Authorization: Bearer ` instead of `x-api-key`. | Enable it only when the gateway documents Bearer auth, such as MiniMax Global or Vercel AI Gateway. | | Model capability mode | Which reasoning request protocol Reasonix should use for this provider. | Keep **Auto-detect** unless the gateway is misdetected or the model docs require a specific reasoning format. | | Thinking override | Provider-specific override for `thinking.type`. | Keep **Auto** unless the backend documents `enabled`, `disabled`, or `adaptive`. Unsupported values can make some OpenAI-compatible gateways reject the request. | | Balance URL | Optional endpoint for wallet/balance lookup. | Set it when the provider exposes a balance endpoint and you want the desktop status bar to show it. The key is sent only to this address and never follows a redirect. | | Balance format | How that endpoint answers: `deepseek`, `moonshot` or `openrouter` (`balance_format`). | A vendor's own domain (`api.deepseek.com`, `api.moonshot.cn`/`.ai`, `openrouter.ai`) is read in that vendor's format with nothing set. Any other address, a relay for example, must name the format, or it is shown as not configured and the endpoint is not asked. Existing entries on such addresses are written as `deepseek` once, which is how they were always read. Reasonix 1.x rewrites the shared config file without keys it does not know, so a format it dropped is put back at the next start from `provider-keys.json` beside the config, unless you removed it in the connection editor. | | Context window | The provider-wide token budget Reasonix uses for automatic context cleanup. `0` disables automatic compaction. | Set it to the provider's model context limit; use a per-model override below when selected models differ. | Each selected model also has an optional **Context window** input. Leave it blank to inherit the provider-wide value, or enter a positive token count to override that value for this model. This avoids premature compaction for long-context models and provider errors for shorter-context models sharing the same endpoint. Use the context-window limit from the model documentation, not the maximum output tokens. For example, 128K commonly means `128000`; if the provider documents `131072`, use that exact value. Values below 16384 show a non-blocking warning because they can trigger frequent compaction and reduce cache hit rates. Model capability mode options: | Option | Effect | | --- | --- | | Auto-detect (recommended) | Reasonix chooses the request shape from model capability metadata and endpoint detection. | | DeepSeek thinking | Uses DeepSeek-style thinking control, including `thinking.type` and DeepSeek-supported reasoning depth. | | OpenAI reasoning | Uses the standard OpenAI-compatible `reasoning_effort` levels. | | Plain chat | Sends no reasoning or thinking control fields. Use this for text-only proxies that reject reasoning parameters. | Thinking override options: | Option | Effect | | --- | --- | | Auto (provider default) | Does not write an explicit provider-level `thinking` override. Reasonix uses the provider/model default behavior. | | Enabled | Sends `thinking.type = "enabled"` for compatible providers. | | Disabled | Sends `thinking.type = "disabled"` for compatible providers. On DeepSeek-style providers this also avoids sending a reasoning depth hint. | | Adaptive (self-adjusting) | Sends or preserves `thinking.type = "adaptive"` only for providers that document adaptive thinking, such as MiniMax-M3-style endpoints. | Some OpenAI-compatible gateways require non-standard top-level request body fields. Add them with `extra_body` on the provider entry: ```toml [[providers]] name = "spark" kind = "openai" base_url = "https://maas-coding-api.cn-huabei-1.xf-yun.com/v2" models = ["xopglm52"] api_key_env = "SPARK_API_KEY" extra_body = { enable_thinking = true } ``` `extra_body` is merged into the chat JSON request body. Reasonix keeps core fields such as `model`, `messages`, `tools`, `stream`, and `thinking` under its own control. ## Hooks Reasonix has hooks: shell commands that run at fixed points of the agent loop and can observe, inject context, or (on two events) veto what is about to happen. They are separate from permissions: `[permissions]` rules decide whether a tool call is allowed or prompted, a hook runs your own code. | Event | Fires | Can block | | --- | --- | --- | | `PreToolUse` | before a tool call, after matching; `toolName` and `toolArgs` are in the payload | yes | | `PostToolUse` | after a tool call (success or failure) | no | | `PostToolUseFailure` | after a tool call returned an error | no | | `PermissionRequest` | before an approval prompt is shown | no | | `UserPromptSubmit` | before a user prompt starts a turn | yes | | `Stop` / `StopFailure` | when a turn ends / fails | no | | `SessionStart` / `SessionEnd` | when a session becomes active / is closed or rotated by `/new` | no | | `SubagentStart` / `SubagentStop` | around a foreground `task` call | no | | `Notification` | when the agent needs the user's attention | no | | `PreCompact` | before compaction; stdout becomes extra summary guidance | no | | `PostLLMCall` | after each model turn; non-empty stdout on exit 0 replaces the stored reasoning text | no | Hooks are configured in `/settings.json` (global) or `/.reasonix/settings.json` (project). Each event maps to a list of hooks: ```json { "hooks": { "PreToolUse": [ { "match": "bash", "command": "sh ~/.reasonix/hooks/no-git-push.sh", "description": "block git push", "timeout": 3000 } ], "SessionStart": [ { "command": "echo 'Team rule: run make lint before every commit.'" } ] } } ``` Fields: - `command` (required): run by the platform shell. - `match`: tool events only; an anchored regex over the tool name, so `file` does not match `read_file`. Empty or `*` means every tool. - `description`: the label shown in `/hooks` and in block notices. - `timeout`: milliseconds; default 5000 for `PreToolUse`, `PermissionRequest` and `UserPromptSubmit`, 30000 otherwise. - `cwd` and `env`: working directory and extra environment. A malformed file loads no hooks and does not stop Reasonix. **Contract.** The event payload arrives as one line of JSON on stdin (`event`, `sessionId`, `cwd`, and by event `toolName`, `toolArgs`, `prompt`, `toolResult`, `error`, ...). The exit code is the verdict: - `0` passes. On `SessionStart`, stdout (plain text, or JSON with `hookSpecificOutput.additionalContext`) is injected once into the next real user turn, as described under [Desktop hooks](#desktop-hooks). - `2` blocks, but only on `PreToolUse` and `UserPromptSubmit`. A blocked tool call is not run and the model receives `blocked: hook () stopped this call — · command: ... · source: ...`, so write the reason for the model to read. - Any other code, or exit 2 on a non-blocking event, only warns the user. - A timeout blocks on `PreToolUse` and `UserPromptSubmit` and warns elsewhere. A hook that cannot be spawned does not block, and neither does a script that crashes (exit 1, or 127 for a missing file): only an explicit exit 2 vetoes. **Example: refuse `git push` in bash.** The payload's `toolArgs` for `bash` is the tool's argument object, so the command is `toolArgs.command`. Save as `~/.reasonix/hooks/no-git-push.sh` (needs `jq`): ```sh #!/bin/sh cmd=$(jq -r '.toolArgs.command // empty') case "$cmd" in *"git push"*) echo "git push is disabled in this setup; ask the user to push." >&2 exit 2 ;; esac exit 0 ``` Without a hook, the simpler tool is a permission rule, which needs no script and holds in every mode: ```toml [permissions] deny = ["Bash(git push*)"] ``` Prefer the rule when a command pattern is enough; use a hook when the decision needs code (inspect arguments, consult a file, log, or react to a prompt). **Scope.** - Global hooks (`/settings.json`) and hooks from installed plugin packages apply, except in the read-only observe posture, which loads no hooks at all. - Project hooks (`/.reasonix/settings.json`) run only after the user approves them as they stand (`reasonix trust`); until then they are held back and a notice says so. Editing the file or a workspace script it names withdraws the approval. - `reasonix review` never runs project hooks, approved or not: the checkout is untrusted input, so only global and plugin hooks apply. - Only tool hooks (`PreToolUse`, `PostToolUse`, `PermissionRequest`), `PostLLMCall` and `PreCompact` fire in every agent a session runs, subagents included (see the `session_id` table below). **Limits.** - A hook cannot force the model's wording: a block reaches the model as a refused call, and the model decides what to say or try next. - There is no per-session scope: hooks come from the settings files and apply to every session that loads them. - Native hooks cannot answer an approval prompt; only a `PreToolUse` block refuses a call. ## Desktop hooks Desktop hooks run local commands at lifecycle events such as `SessionStart`, `UserPromptSubmit`, `PreToolUse`, and `PreCompact`. A successful `SessionStart` hook may write plain text to stdout, or return JSON with `hookSpecificOutput.additionalContext`; Reasonix injects that text once into the next real user turn as `...`. This is intended for plugin or workflow bootstrap context, including Superpowers-style startup instructions, without baking that workflow into Reasonix's system prompt. Plugin packages can provide this startup context through `hooks/session-start-codex` or a plugin-root `CLAUDE.md`. Claude-style `.claude/settings.json` command hooks are also mapped to matching Reasonix hook events. The injected hook context is dynamic current-turn context. It does not change the stable system prompt, memory prefix, or tool schema, though dynamic content can still reduce cache reuse for that turn. Tool hooks (`PreToolUse`, `PostToolUse`, `PermissionRequest`), `PostLLMCall` and `PreCompact` fire in every agent a session runs. Each fires under a `session_id` derived from the parent session's id at the moment the hook runs: | Agent | Hook `session_id` | | --- | --- | | Executor | `` | | Planner | `:planner` | | Guardian | `:guardian` | | `task`, `read_only_task`, `parallel_tasks`, `fleet`, `run_skill`, `read_only_skill` children | `:subagent:` | | `reasonix review` | a fresh id per run | `/new` or a branch switch moves every child to the new id along with the parent. `SubagentStart` and `SubagentStop`: - bracket a foreground `task` call only; `read_only_task`, `parallel_tasks`, `fleet`, skill children and background tasks fire neither; - carry the call's id as `callId`, so a consumer can pair them; - `SubagentStop` fires on every end: answer, failure, cancel or refusal; - neither can block: exit 2 only warns. ## Studio appearance schedule In **Settings → Appearance → Light and dark**, choose **Scheduled** to use dark mode during a daily local-time range and light mode outside it. The initial range is 20:00–07:00; edit either time to apply it immediately. The start is inclusive and the end is exclusive. Overnight ranges cross midnight. Equal times mean light all day; choose **Dark** for dark all day. **Follow the system**, **Light**, and **Dark** retain their existing behaviour. Choosing one stops the schedule without forgetting its times. The title-bar theme toggle also chooses a fixed mode. The range and mode stay on this device, using the same local preferences as other theme choices. Desktop restores these across launches; web preferences belong to the browser origin. If storage cannot be written, the choice lasts only for the current window. Scheduling uses the device's clock and timezone, not a server timezone. Skipped or repeated daylight-saving times follow the actual local clock. Returning to the window, restoring a page, or reloading recalculates the current scheme. The active theme pack and text contrast still apply. Scheduling changes the pack's light/dark variant, not the selected pack. The desktop window's native frame and background follow the same resolved scheme. Before the page loads, the shell uses the last saved resolved scheme as a startup tint. The page then recalculates from the current clock and updates the shell. CLI/TUI theme settings are unchanged. ## Keyboard shortcuts Shortcuts are documented by client because users usually look for the keys that work in the surface they are using. Desktop keeps its Plan toggle, while the CLI cycles Ask, Auto, and Plan with `Shift+Tab`. Desktop uses `Cmd+Y` on macOS or `Ctrl+Y` elsewhere for YOLO by default. If YOLO is rebound on Windows/Linux, `Ctrl+Y` becomes the standard composer redo fallback. Desktop paste stays on the platform paste key; in the CLI, terminal-native text paste and application-owned image paste use separate shortcuts. `[ui].shortcut_layout` is still accepted for old configs, but the shortcut behavior below is unified across layouts. For CLI/TUI text input, `[ui].cursor_shape` accepts `underline`, `block`, or `bar`. The default is `bar`: it remains easy to locate without covering double-width CJK characters in mixed-language input. Set it to `block` for a traditional terminal cursor or `underline` for a lower-profile cursor. This setting does not change desktop or web text fields. ### Desktop GUI Desktop shortcuts are managed from **Settings → Shortcuts**. Pick a configurable row, press a new key combination, and Reasonix saves it for the desktop app. Standard editing shortcuts such as Undo and Redo are shown as locked rows because the WebView's native text history uses those platform chords. Conflicting bindings are rejected so one shortcut never triggers two actions. Press `?` or use the help button in the topic bar to open the shortcuts sheet; it is generated from the same shortcut registry, so it reflects any custom bindings. Global shortcuts: | Key or control | What it does | Notes | | --- | --- | --- | | `Cmd+K` on macOS, `Ctrl+K` on Windows/Linux | Toggles the command palette | The palette focuses search when it opens; `Esc` closes it. | | `Cmd+,` on macOS, `Ctrl+,` on Windows/Linux | Opens Settings | Use **Shortcuts** in Settings to customize desktop bindings. | | `Cmd+W` on macOS, `Ctrl+W` on Windows/Linux | Closes the active top tab | The last tab is kept by the normal close-tab guard. | | `Cmd+B` / `Ctrl+B` | Shows or hides the left sidebar | Same action as clicking the sidebar toggle. | | `Cmd+Shift+B` / `Ctrl+Shift+B` | Expands or collapses the most recent shell output | Same action as clicking the collapsed shell-output hint. | | `Cmd+1`-`Cmd+9` on macOS, `Ctrl+1`-`Ctrl+9` elsewhere | Jumps to the matching visible chat in the sidebar | Hold `Cmd`/`Ctrl` briefly to reveal the numbered badges. Existing custom shortcuts that already use the same key take precedence. | | `Cmd++`, `Cmd+-`, `Cmd+0` on macOS; `Ctrl++`, `Ctrl+-`, `Ctrl+0` elsewhere | Increases, decreases, or resets the interface size | Steps through the same sizes as **Settings > Appearance > Size > Interface** (80% to 180%) and saves the same setting. `=` is accepted for the plus key on keyboards that report it that way. | | `?` | Opens the keyboard shortcuts sheet | The sheet shows the current effective desktop bindings. | Composer shortcuts: | Key or control | What it does | Notes | | --- | --- | --- | | `Enter` | Sends the current message | IME composition confirmation is left alone. | | `Shift+Enter` | Inserts a newline | The composer keeps focus. | | `Shift+Tab` | Toggles Plan on/off | Plan changes the workflow instruction; built-in writers keep the active Ask/Auto/YOLO and Sandbox boundary, while MCP writer/destructive targets stay hard-blocked for the whole planning phase. | | `Cmd+Z` on macOS, `Ctrl+Z` on Windows/Linux | Undoes the latest composer edit | Native typing stays in the WebView history; Reasonix-managed paste, cut, folded blocks, and structured tokens are restored as complete transactions. | | `Cmd+Shift+Z` on macOS, `Ctrl+Shift+Z` on Windows/Linux | Redoes the latest composer edit | On Windows/Linux, `Ctrl+Y` is also accepted after the YOLO shortcut has been rebound. | | `Cmd+Y` / `Ctrl+Y` (default) | Toggles YOLO on/off | Turning YOLO off restores the previous Ask/Auto base when known. The current binding is shown in **Settings → Shortcuts**. | | `Cmd+V` on macOS, `Ctrl+V` on Windows/Linux | Pastes clipboard content | Clipboard images are attached. Studio references ordinary files by their native path when available, including files selected with Add attachment, pasted, or dropped. Sources without a native path stream into the workspace. | | Plain `Up` / `Down` at the prompt boundary | Recalls older or newer submitted prompts | Modified arrows and native text navigation stay with the textarea. | | `Esc` while a turn is running | Cancels the running turn | If the turn has not produced a response yet, the draft is restored. | **Settings → Appearance → Conversation display → Composer hint** controls guidance in the empty task input. It is on by default. Turning it off hides both task and queue placeholders immediately and persists on this machine. The input's accessible name, entered text, queue content, IME handling and send shortcuts stay unchanged. Turning the hint back on restores the responsive guidance at the theme's normal contrast. Remembered projects have Move up / Move down in their project menu. Open it with the keyboard, navigate with arrows or Home/End, and use Escape to return to the trigger. Moving a project changes sidebar order without changing the launch project. Studio remembers which local and remote projects you collapse in the sidebar, including Collapse all / Expand all, when you reopen it in the same browser or desktop profile. New projects start expanded. These display choices stay on this device; they do not change sessions or synchronize to another client. **Recent** in the project heading finds saved conversations across projects, including completed work that has left **Running**. It uses each conversation's last saved activity, not its unread status, pin order or last opening time. The picker reads 50 conversations per page from the complete matching history. **Previous page**, **Next page** and its session/project search reach older conversations without the sidebar's 300-row display ceiling. Selecting a conversation opens it, or focuses its existing pane, switches the sidebar to **All**, clears its search and expands the owning project. Worktree conversations retain their own working directory. On phones, opening a session closes the rail as usual; reopening it shows the selected project and session. Archived conversations stay in **Archived**. Recent respects the saved hide-legacy preference; an explicit search can still find legacy history. Deleted files or unavailable folders report the normal opening error. The environment selector starts with this machine. Selecting a remote host reads its recent history through the existing connection flow; hidden or disabled hosts are not offered. Older kernels ask for an update rather than showing a partial sidebar window as complete recent history. Activity comes from persisted session metadata and survives restart. The picker reopens on the first page with no search; it does not create a separate visit history or change the saved project order. Ordinary Reasonix-managed worktree chats appear under their original project, without a separate project row for each checkout. Only isolated worktrees carry a neutral Isolated worktree label in the chat header and composer directory picker. Otherwise, the header has no directory badge and the picker shows just the branch name. Hover for an explanation and the actual directory path. Worktrees separate code directories; ports, databases, and other runtime resources may still be shared. Before sending, a selected worktree explains that creation starts when the message is sent. Worktree chats also carry a sidebar marker. Reopening history uses that chat's working directory, while starting a chat from the project uses the original directory. Ordering, search, paging, pins and archives span the project. When reading legacy history for the first time, the sidebar shows discovered conversations while summaries and turn counts are filled in the background. Conversations marked as reading their summary can already be opened. Search indicates incomplete coverage instead of reporting a definitive empty result. Failed summaries can be retried without blocking other conversations. Summary caches live in the current version's directory; legacy history is not rewritten. History search index rebuilds after an upgrade run in the background. Background reads yield when a conversation is opened or read, then continue after the foreground operation. Search results may remain incomplete until indexing finishes. Removing the project closes its idle panes and retains every directory and transcript. Existing transcripts stay in place and group on restart without migration. Worktrees added separately outside Reasonix-managed storage remain independent projects. With a paging-capable server, long conversations initially display the latest 200 messages. Scroll upward or choose Load earlier messages to read more. In-conversation find reads earlier pages as needed and indicates incomplete coverage; clearing the query stops further prefetching. Rewind, compaction and session changes reload recent content. Paging affects presentation only, not the full conversation used by the model. The list holds up to 32 projects; adding another requires removing one first. `serve-workspaces.json` now stores an object with `paths` and `launch`. Older binaries read only arrays and cannot preserve this format: do not share the same state home with an older binary after reordering. Menus and controls: | Key or control | What it does | Notes | | --- | --- | --- | | `Up` / `Down` in slash, `@`, or past-chat menus | Moves the highlighted item | Past-chat search uses the same navigation keys. | | `Enter` / `Tab` in those menus | Accepts the highlighted item | Directory-like entries can keep the menu open for the next level. | | `Esc` in those menus | Closes the current menu or returns from past-chat search | Regular typing continues after the menu closes. | | Ask / Auto / YOLO approval controls | Picks the tool approval posture directly | Clicking these controls is unchanged by keyboard shortcuts. | | Tool approval card | `Left` / `Right`, `Enter`, `1`-`4`, `Esc` | Move the highlighted action, confirm it, pick a numbered action, or deny. The default highlighted action is Allow once. | | Plan approval card | `Left` / `Right`, `Enter`, `1`-`3`, `Esc` | Move between Revise plan, Start execution, and Exit plan. The default highlighted action is Start execution. | | Plan control | Toggles Plan on/off | Same mode as `Shift+Tab`. | | Goal item in the collaboration menu | Starts, views, or clears Goal | Goal is not in any keyboard cycle. | #### Studio file attachments Ordinary local files, including large APKs, use path references whenever the desktop shell can identify the file. Selecting one external file grants read access to that file, not its siblings; selecting a folder grants its descendants, but a symlink inside it does not grant access outside that folder. Files without a native path use binary streaming into `.reasonix/attachments`. Streaming bounds memory use. Ordinary uploads have a default 1 GiB safety ceiling per request to protect disk space; native path references do not copy bytes. Incomplete files are removed if the transfer fails or is canceled. Removing an uploading attachment cancels its transfer. | Entry point | Limit | | --- | --- | | Native ordinary-file references | No attachment size cap; the file stays where it is. | | Ordinary-file binary upload | Default 1 GiB safety ceiling per request, adjustable in user config. Disk space and upstream limits still apply. | | Image upload | 10 MiB per image, checked before upload and by the kernel. | | Legacy JSON/Base64 attachment endpoint | 25 MiB general-file limit; retained for older clients. | | Encrypted cloud relay | 16 MiB per request body; oversized uploads stop at that budget. This is a relay limit, separate from native references and direct uploads. | In the user's `config.toml`, `[attachments] max_stream_bytes = 1073741824` sets the ordinary upload ceiling in bytes. - `0` removes that ceiling; negative values use the default. Project `reasonix.toml` cannot set this value. - An upload one byte over the limit is refused and its partial file removed. - The typed `attachment.too_large` response includes `limit_bytes` and `limit_source` (`default` or `user`). - A malformed user config refuses the upload until repaired. The image and relay limits remain independent. These limits govern attachment transport. A referenced file is read on demand; its full bytes do not become model input automatically. ### CLI / TUI The composer uses theme-coloured top and bottom borders and a slim bar cursor by default. Long drafts grow to the available maximum height; once they overflow, wheel events inside the composer scroll the draft without moving the insertion cursor, while wheel events in the transcript keep scrolling the conversation. Use `/theme auto|light|dark` to select the background mode, or `/theme