WithNav
You set the course. Nav lights the way.
WithNav is a pair-programming system. You keep the keyboard and stay the author of the code. Several models of different speeds watch your work, complete it, judge it and discuss it with you. You get the speed of AI assistance without losing your mental model of your own codebase.
In this guide you are the Captain. Nav is the assistant you talk to. WithNav is the whole system around them.
The four layers
Each layer has one job and its own model. You choose the models; see Layers.
| Layer | What it does | Where you see it |
|---|---|---|
| Reflex | Completes what you type, fast. | Ghost text in the editor (suggestions). |
| Instinct | Makes small, typed decisions: when to speak, whether a tool call is safe. It never writes free text. | Mostly invisible. It decides when Nav speaks and which tool calls need your approval. |
| Metacognition | Compares your code with your intent and reviews risky tool calls. | Short remarks at the end of a line. You rate each one: useful, noise or dismissed. |
| Cognition | The conversation. It discusses concepts and problems, and does delegated work with the full set of tools. | The Nav chat in VS Code, the terminal or the browser. |
Without a delegation, Nav assists and you write the code. When you ask Nav to do a piece of work itself, that is a delegation.
Nav and the crew
Cognition runs agents. Nav is the main one, working as build (implement, verify, report) or
plan (research and write plans in Markdown). Behind Nav is a crew you can call on:
| Member | Agent id | Role |
|---|---|---|
| Vigie, the Lookout | vigie | Read-only scouting of code, documentation and the web, with sources. |
| Lux, the Expert Advisor | lux | Read-only analysis and recommendations. |
| Mousse, the Lightweight Deckhand | mousse | Small local edits, after your approval. |
| Cartographe, the Cartographer | cartographe | On demand. Draws maps as Mermaid charts in .withnav/charts/. |
| Sondeur, the Deep Diver | sondeur | On demand. Root-cause work; runs tests and profilers after your approval. |
What you install
nav, one binary. It is the agent core and its clients:nav tui: a terminal chat client.nav web: a chat client in your browser, with the InstinctFlow Studio.nav run: one task from a script or a shell.nav serve: the session server alone.nav lsp: the language server behind the editor features.nav doctorandnav auth: diagnostics and API keys.
- The VS Code extension. It starts
nav lsp, shows remarks and suggestions, and hosts the Nav chat. It can bundlenav.
Where to go next
- Install WithNav.
- Follow the Quick start to a first working session.
- Read Permissions before you let Nav run commands.
WithNav is pre-1.0 and moves fast. Every release is listed in the Changelog.
Install
WithNav has two parts: the nav binary and the VS Code extension. Most people want the
extension, which can bring its own nav.
Requirements
- Linux, x64 or arm64.
- VS Code 1.91 or later, if you use the extension.
- Native Windows is not supported. Use WSL or a VS Code Remote window and install the Linux package there.
- The extension is not on the VS Code Marketplace yet. Install it from a file.
VS Code extension
Download a package from the GitHub Releases page and install it:
code --install-extension withnav-linux-x64-<version>.vsix
Pick the package that matches your machine:
| File | Use it when | nav |
|---|---|---|
withnav-linux-x64-<version>.vsix | Linux on x64 | Bundled (static binary). Nothing else to install. |
withnav-linux-arm64-<version>.vsix | Linux on arm64 | Bundled (static binary). Nothing else to install. |
withnav-<version>.vsix (universal) | macOS, Nix, development | Not included. Put nav on your PATH, or set withnav.serverPath. |
A path you set in withnav.serverPath always wins over the bundled binary. Set it to the
absolute path of nav. Do not add lsp to it; the extension adds that itself.
To check which binary the extension runs, open the WithNav output channel. Its first line is
Using nav binary (...): setting, bundled or path.
nav without the extension
Each release also publishes bare nav binaries as tarballs, and a SHA256SUMS file for all the
release files. Unpack a tarball and put nav on your PATH, for example in ~/.local/bin/.
Nix
The flake at the repository root builds both parts:
nix build .#withnav .#vscode-withnav
In a system flake, add this repository as an input and apply overlays.default. It provides:
pkgs.withnav: thenavcommand.pkgs.vscode-withnav: the extension, for Home Manager’sprograms.vscode.profiles.<name>.extensions.
From source
You need Rust 1.89 or later, and Node.js 18 or later for the extension.
cargo build --release --bins
install -m755 target/release/nav ~/.local/bin/
(cd vscode-withnav && npm ci && npm run package)
code --install-extension vscode-withnav/withnav-*.vsix
npm run package makes a universal package. It needs nav on your PATH or
withnav.serverPath. To build the release packages with a bundled binary, see
the extension page.
Check it works
nav --version
Then follow the Quick start.
Quick start
This page takes you from a fresh install to a first working session. It uses one OpenAI-compatible gateway and one model. You can add more later.
1. Describe your gateway and model
WithNav reads its catalogue from ~/.config/withnav/models.json. It lists the gateways you
can call and the models behind them. Create the file:
{
"providers": {
"openai": {
"base_url": "https://api.openai.com/v1",
"api_key_env": "OPENAI_API_KEY"
}
},
"models": {
"openai/gpt-4.1": {
"provider": "openai",
"model": "gpt-4.1",
"api": "chat",
"context_window": 1000000,
"supports_tools": true
}
}
}
Change the gateway, model and context_window to match your account. Every model name is
<gateway>/<upstream id>; see Gateways and models.
2. Give the default agent a model
~/.config/withnav/config.json decides which model does which job:
{
"default_agent": "build",
"agents": {
"build": { "model": "openai/gpt-4.1" }
}
}
This is the minimum. The build agent can now work. The three lower layers stay off until you
assign them a model; see Layers.
3. Store your API key
Either export the variable named in api_key_env:
export OPENAI_API_KEY=...
or store the key with WithNav:
nav auth set openai
nav auth set reads one line from standard input and does not hide it as you type. To keep it off
the screen, pipe it in, for example printf '%s\n' "$OPENAI_API_KEY" | nav auth set openai. The
key goes to ~/.config/withnav/auth.json with mode 0600. A stored key wins over the environment
variable.
4. Check the setup
nav doctor --offline
--offline makes no network call. With the files above you should see warnings that the Reflex,
Instinct and Metacognition layers are not assigned. That is expected. There must be no ✕ failed
line. See Troubleshooting for how to read the output.
5. Start
In a project folder, pick one:
- VS Code: open the folder and click the WithNav icon in the activity bar to open the Nav chat. See VS Code extension.
- Terminal: run
nav tui. - Browser: run
nav web. - One task:
nav run -p "explain what this repository does".
write, edit, bash and fetch ask before they act, so a first session is safe to try. See
Permissions.
Next steps
- Add models for the lower layers: Layers.
- Start from a full example instead: Gateways and models.
- Already use OpenCode? Import its models.
Using Nav
You can talk to Nav from four places. They all use the same agents, the same configuration and the same permission rules.
| Where | Start it | Best for |
|---|---|---|
| VS Code | Install the extension | Daily work. It adds remarks and ghost-text suggestions to the editor, and hosts the Nav chat. |
| Terminal | nav tui | A full chat client in your terminal. |
| Browser | nav web | A chat in your browser, with the Settings screen and the InstinctFlow Studio. |
| One task | nav run -p "<task>" | Scripts and quick questions. |
The command line page lists every command and flag.
Pick an agent
A conversation runs as one primary agent. You can switch between turns.
buildis the default. It implements, verifies and reports.planresearches and writes Markdown plans. It does not change code.
In the terminal client, press tab on an empty message to switch between them. Everywhere,
/agent <id> does the same, and /new [agent] starts a fresh conversation on a given agent. Nav
can also hand work to the crew (vigie, lux, mousse and so on) as subagents. See
Agents.
Slash commands
Type / in any chat. A palette lists the commands. These work in every client:
| Command | What it does |
|---|---|
/help | List the commands. |
/model [<gateway/id>] | Show the current model, or switch to another one. |
/variant [<name|off|inherit>] | Show or set the model variant. |
/agent [<id>] | Show or change the primary agent. |
/new [agent] | Start a fresh conversation. The old one is kept. |
/stop | Stop the session. |
/context | Show how the context window is used. |
/compact [focus] | Summarise the older part of a long conversation. |
/bypass [on|off] | Skip ordinary permission prompts. See Permissions. |
Type //text to send the literal text /text as a message.
How /model, /variant, /agent and /new behave is explained in
Editor context and session overrides.
Context and compaction
WithNav estimates the size of the next request and shows how full the model’s window is. When a
conversation grows too long, /compact summarises the older part. Compaction also runs
automatically before a request that would not fit. By default the Metacognition model writes the
summary (compaction.writer; see Configuration).
The terminal client: nav tui
nav tui starts a private server and opens a chat. To use a server that is already running, pass
--url; see the command line.
- Tabs. Up to nine conversations,
alt+1toalt+9.ctrl+nopens a new one,alt+wcloses the tab and keeps the conversation,alt+bhides the tab bar. - Header. Workspace, git branch, agent and model.
- Right column. Context breakdown, skills and git changes.
alt+rshows or hides it. - Command palette.
ctrl+por/. It also holds/config(edit settings),/sessions,/skills,/themeand/quit. - Models.
f2opens the model picker, with variants.ctrl+fmarks a favourite. - Sessions.
ctrl+olists past conversations to resume. - Subagents.
alt+sopens a subagent’s read-only transcript.alt+ccontinues one that ran out of steps. - Bypass.
alt+pturns bypass on or off. - Themes. Eight: Ayu, Nord, Tide and Moss, each dark and light. Tide Dark is the default.
Choose one with
/theme; it is saved astui.theme. - Help.
f1shows every shortcut.ctrl+qquits,escorctrl+cstops a running turn.
Permission questions appear inline: y allows once, n denies (you can add a message), and a
appears when a reusable approval is possible.
The browser client: nav web
nav web starts WithNav on a private address on your computer, prints a link and opens it. The
link carries a secret that changes on every run and only works on this machine. Press ctrl+c to
stop.
The page lets you chat in any workspace you pass with --workspace (repeat the flag for several;
the current directory is the default) and in folders you used before. Each shows its git branch
and whether it has changes. The gear in the top bar opens Settings, and the command palette opens
the InstinctFlow Studio.
Settings screens
The model of each layer and agent, permissions, subagent limits, watchdogs and the theme can be changed without editing JSON:
- In the browser: the gear in the top bar.
- In the terminal:
/config. Changes are written toconfig.jsonwithctrl+s. - In VS Code: the WithNav: Open Settings command, or the gear in the Nav chat.
All three write the same config.json. Gateways are read-only there, and API keys are never
shown. Layer models apply after you restart WithNav. The rest applies to new sessions.
WithNav — VS Code extension
WithNav pair-programming companion client. Launches the nav lsp language server over stdio,
renders its end-of-line remarks, offers inline suggestions from the reflex layer, forwards
diagnostics and user feedback, and hosts the Cognition chat view.
What it shows
-
Agent-first conversations: choose a primary agent (the configured default is preselected), then optionally override its model.
/new [agent]starts fresh on that agent’s assignment, without inheriting an earlier conversation’s override./agentuses the selected agent’s own assignment or saved session override./modeland/variantoverride only that agent in that conversation; the server persists and restores the choice. -
Canonical model catalog: searchable choices use exact
gateway/upstream_idnames, grouped under collapsible gateway/provider headings. Search includes collapsed groups; only the first 50 matching choices render. Hover the chat header for lower layers’ configured models, variants and disabled reasons, and cognition’s effective model/source. “Usable” means valid configuration, not an online-status check. Assignments are read-only; config editing is out of scope. Remarks use the configured metacognition layer; the old model-selection setting has been removed. -
Remarks: short end-of-line comments from the metacognition, with a verdict (useful, noise, dismissed).
-
Suggestions: ghost text from the reflex layer, as native inline completion. Turn them off with
withnav.reflex.enabled. -
Cognition chat: a webview in the WithNav activity-bar container (view id
withnav.chat, named “Nav”). It talks to the session server thatnav lspruns over HTTP and SSE. It streams the conversation, lists past conversations of the workspace to resume, and has a model picker for new and open conversations (switches during a running turn apply after it finishes) and a gauge of the next request’s estimated context against the model’s window; hover or focus it for the breakdown, input budget and last request’s count. Type/for a keyboard-accessible command palette:/model,/variant,/agent,/compact,/context,/skills,/stop,/newand/help./skillsfills a selected skill into your next request; command outcomes appear as notices. Commands never attach editor context;//textsends the literal/text. Unknown or refused commands keep your draft. Nav wakes appear in it as messages from Nav. It sends the open file and selection as editor context. -
Subagents in chat: see delegated goals and live states beneath the conversation. Open a subagent’s read-only transcript in a separate editor tab without leaving the parent chat. Stop or continue a parked subagent with confirmation; permission requests are still reviewed in the parent conversation.
-
Agent controls in chat: permission cards show the full reason and command (or a file path and bounded content), with Allow once and Deny plus an optional deny message. Subagents’ requests stay in the parent conversation. Sending while approval is pending asks for confirmation. Out-of-steps and watchdog pauses offer Continue; watchdog pauses also offer Stop. Subagent output is labelled untrusted, selected skills appear beside your messages, and tool rows have expandable approval notes when the server provides review evidence.
Requirements
- Linux x64 and arm64: install
withnav-linux-x64-<version>.vsixorwithnav-linux-arm64-<version>.vsixfrom the GitHub Release. It bundles a staticnav; nothing else to install. Awithnav.serverPathyou set wins over the bundled binary. - Universal package (
withnav-<version>.vsix, no binary; macOS, Nix, development): put anavbinary onPATH, or setwithnav.serverPathto its absolute path. The extension appendslsp; do not put the subcommand in the setting. Oldwithnav-lspandnavai-lsppaths usenavin the same directory, with a one-time notice to update the setting. - Windows:
navdoes not run natively; use WSL or a Remote window and install the Linux package there. - The “WithNav” output channel starts with the line
Using nav binary (...): setting, bundled or path. - Node.js 18+ to build.
Build and package
cd vscode-withnav
npm ci
npm run compile
npm run package
code --install-extension withnav-*.vsix
npm run package bundles the runtime dependency (vscode-languageclient) with esbuild into
out/extension.js, so the .vsix ships one self-contained file (no node_modules inside the
package; .vscodeignore excludes them). vsce package triggers npm run compile via the
vscode:prepublish hook.
scripts/package-vsix.sh <linux-x64|linux-arm64|universal> (from the repository root) writes the
release packages into dist/. The platform variants build a static nav with cargo-zigbuild and
zig (or reuse NAV_BINARY) and ship it as bin/nav.
How it works
- Activation:
onStartupFinished; the client uses document selector[{ scheme: "file" }]. - Server notifications handled:
withnav/remark,withnav/clear,withnav/status. - Client notifications sent:
withnav/feedback,withnav/diagnostics(debounced 500 ms per uri,filescheme only, excluding Nav’s own diagnostics),withnav/intent. - Settings use
withnav.*; explicitnavai.*values are honored when the new key is unset, with a one-time migration notice. Configuration lives underwithnav.*settings, sent asinitializationOptionsand viaworkspace/didChangeConfiguration. Settingwithnav.enabledto false stops the server; toggling it back on restarts it (a failed start is retried on the next settings change). - If the server binary can’t start, the status bar shows
$(error) WithNavwith the reason in the tooltip — the extension stays alive.
Commands and keybindings
| Command | Key | Action |
|---|---|---|
withnav.markUseful | Ctrl+Alt+Y | Rate the current remark “useful”, then clear it |
withnav.markNoise | Ctrl+Alt+N | Rate the current remark “noise”, then clear it |
withnav.dismiss | Ctrl+Alt+D | Send “dismissed”, then clear it |
withnav.setIntent | click status bar | Tell Nav your current goal |
withnav.focusChat | command palette (“WithNav: Focus Cognition Chat”) | Open and focus the Cognition chat view |
Keybindings are active only when a remark is visible (withnav.remarkVisible context key).
Development
npm run watch— rebuild the bundle on change.npm run typecheck—tsc --noEmittype check only.npm test— deterministic event projection, replay and extension-host control tests.- Output channel “WithNav” shows the LSP traffic log.
Settings
| Setting | Default | Description |
|---|---|---|
withnav.enabled | true | Enable Nav |
withnav.serverPath | nav | Binary (PATH or absolute), launched with lsp; when unset, the bundled bin/nav is used if present |
withnav.idleMs | 3000 | Idle delay before Nav may speak |
withnav.minChangedLines | 3 | Minimum changed lines to consider speaking |
withnav.cooldownMs | 20000 | Cooldown between remarks on one file |
withnav.reflex.enabled | true | Offer inline suggestions from the reflex layer |
Command line
nav is one binary with several subcommands. nav --help lists them, and nav <command> --help
shows the flags of one.
| Command | What it does |
|---|---|
nav tui | Terminal chat client. |
nav web | Browser chat client. |
nav run | Run one task and exit. |
nav serve | The session server alone (HTTP and SSE). |
nav auth | Store API keys. |
nav doctor | Read-only diagnostics. |
nav lsp | The language server the editor extension launches. |
nav flows catalogue | Print the flow schema of this build as JSON. |
Add --version to any of them to print the version.
nav tui
nav tui [--workspace <dir>] [--model <gateway/id> | --session <id>] [--models <file> | --url <server>]
| Flag | Meaning |
|---|---|
--workspace <dir> | Folder the conversation works in. Default: the current directory. |
--model <gateway/id> | Model for a new conversation. Cannot be combined with --session. |
--session <id> | Resume a past conversation. Cannot be combined with --model. |
--models <file> | Use this models.json for the embedded server. Cannot be combined with --url. |
--url <server> | Connect to a running server instead of starting one. If it needs a bearer token, set WITHNAV_SERVER_TOKEN. Cannot be combined with --models. |
The terminal client writes its log to ~/.local/share/withnav/tui.log, never to the screen.
nav web
nav web [--workspace <path>]... [--models <file>] [--port <n>] [--no-open]
| Flag | Meaning |
|---|---|
--workspace <path> | A workspace to offer in the browser. Repeat for several. Default: the current directory. |
--models <file> | Use this models.json for the embedded server. |
--port <n> | Loopback port to listen on. Default 0, which picks a free one. |
--no-open | Print the address without opening the browser. |
nav run
nav run -p "<task>" [flags]
Runs one task in the terminal and exits. If you pass no -p, or -p -, the prompt is read from
standard input. nav run never approves a tool call by itself, unless you pass --yes.
| Flag | Meaning |
|---|---|
-m, --model <gateway/id> | Model to use. |
-p, --prompt <text> | The task. A prompt that is a /command runs that command. |
--workspace <dir> | Folder to work in. Default: the current directory. |
--models <file> | Use this models.json. |
--agent <agent> | Primary agent: build (default) or plan. |
--variant <name|off> | A variant the model offers, or off for none. Default: the model’s default variant. |
--show-thinking | Show the model’s reasoning. |
--yes | Unsafe. Turn on bypass for the run: ordinary permission prompts run once. Protected ones still stop the run with exit code 3. |
Exit codes:
| Code | Meaning |
|---|---|
0 | Done. |
1 | Error. |
2 | The agent asked a question. |
3 | A tool call needs approval (permission_required). |
130 | Interrupted. |
nav serve
nav serve [--bind <addr>] [--allow-non-loopback] [--models <file>]
Runs the session server alone, with no client: an HTTP API with Server-Sent Events.
| Flag | Meaning |
|---|---|
--bind <addr> | Address to listen on. Default 127.0.0.1:7310. |
--allow-non-loopback | Allow an address that is not loopback. |
--models <file> | Use this models.json. |
The server has no authentication and no TLS. Keep it on loopback. If you open it to a
network with --allow-non-loopback, protecting it is your job: use a private network or a
reverse proxy.
nav auth
nav auth set <provider>
nav auth list
nav auth remove <provider>
Keys are stored in ~/.config/withnav/auth.json with mode 0600. set reads one line from
standard input and does not hide it as you type, so pipe the key in if anyone can see your
screen. list prints the providers that have a key. A stored key wins over the environment
variable named by api_key_env.
nav doctor
nav doctor [--offline] [--json] [--workspace <dir>] [--layer <reflex|instinct|metacognition>]
Checks your configuration. It is covered in Troubleshooting.
nav lsp
nav lsp [--models <file>]
nav lsp replay [--log <file>] [--json]
The language server for editors. The VS Code extension launches it for you over stdio, so you
rarely run it yourself. nav lsp replay replays the built-in editor flows offline against a
decision log (default ~/.local/share/withnav/withnav.jsonl) and prints a summary, or a JSON
report with --json.
Environment variables
| Variable | Meaning |
|---|---|
WITHNAV_SERVER_TOKEN | Bearer token for nav tui --url. |
WITHNAV_MAX_STEPS | Cap on the steps of one turn. Unset: no cap for the main agent (a watchdog supervises it) and 50 for subagents. A value that is not a non-negative integer logs a warning and caps the turn at 500 steps (50 for subagents). |
| API key variables | The names you give in api_key_env in models.json, for example OPENAI_API_KEY. |
The old NAVAI_* names still work as fallbacks, with a warning. See
Older installs.
Configuration
WithNav is configured with plain JSON files and Markdown files. Two JSON files do most of the work:
models.jsonis the catalogue. It says which gateways exist and which models they serve. It is a list of what is available, not a list of choices.config.jsonmakes the assignments. It says which model each layer and each agent uses, plus a few settings.
Keeping them apart lets you add a model to the catalogue without changing what WithNav uses.
| Page | What it covers |
|---|---|
| Gateways and models | models.json, API keys, a full example. |
| Layers | Choosing the models for Reflex, Instinct and Metacognition. |
| Agents | Choosing models for build, plan and the crew; custom agents. |
| Editor context and session overrides | What /model, /variant, /agent and /new do. |
| Rules and skills | AGENTS.md, rules.md and SKILL.md files. |
| Import a catalogue from OpenCode | Build models.json from an OpenCode config. |
Files and paths
On Linux, the configuration directory is ~/.config/withnav/ and the data directory is
~/.local/share/withnav/.
| Where | What |
|---|---|
~/.config/withnav/models.json | The catalogue: gateways, qualified model names, API kinds, capabilities, limits and variants. A models.json in the workspace wins over it, and --models <file> wins over both. |
~/.config/withnav/auth.json | API keys, mode 0600. Write them with nav auth set <provider>. |
~/.config/withnav/config.json | layers, agents, default_agent, and other settings such as tui.theme. |
~/.config/withnav/agents/*.md, <project>/.withnav/agents/*.md | Agent definitions. ~/.config/opencode/agents/ is also read. |
~/.config/withnav/flows/, <project>/.withnav/flows/ | Editor flows. See InstinctFlow. An older pipeline.toml is converted once, and the original is kept. |
<project>/.withnav/intent.md | The intent record: what you are trying to do. The metacognition layer compares your code with it. |
<project>/.withnav/charts/ | Mermaid charts written by the Cartographe agent. |
~/.agents/AGENTS.md, ~/.agents/rules.md, ~/.agents/skills/ | Your rules and skills. ~/.claude/ is read too. See Rules and skills. |
~/.local/share/withnav/withnav.db | Conversation history. |
~/.local/share/withnav/withnav.jsonl | Decision log. |
~/.local/share/withnav/grants.json | Folder approvals you saved for fetch. See Permissions. |
~/.local/share/withnav/tui.log | Log of the terminal client. |
~/.local/share/withnav/model-catalog/ | Cache of the model lists reported by your gateways. |
nav doctor prints the configuration directory it actually uses.
Settings reference
These are the keys of config.json. Anything not listed is kept untouched, so several
versions and clients can share the file. The Settings screens write the same file; see
Using Nav.
| Key | Default | Meaning |
|---|---|---|
layers.reflex | none (layer off) | Model for Reflex. Must use the fim API. |
layers.instinct | none (layer off) | Model for Instinct. Must use the systemone API. |
layers.metacognition | none (layer off) | Model for Metacognition. Must use the chat API. |
default_agent | build | The primary agent a new session starts with. See Agents. |
agents.<id>.model, agents.<id>.variant | none | Model and variant of an agent. See Agents. |
compaction.writer | metacognition | Who writes compaction summaries: metacognition, or session for the session’s own model. If Metacognition is unavailable, the session model is used. |
compaction.chunk_tokens | 64000 | Size of one rolling summary chunk, capped at half of the writer’s input budget. A positive integer. |
permissions.automatic_review | true | false asks you for every bash, write and edit call. See Permissions. |
permissions.review_timeout_secs | 40 | Deadline of one Metacognition permission review, in seconds. An integer from 5 to 300. It applies to new sessions and is inherited by their children. |
permissions.scratch_dirs | private runtime directory, then a shared temp one | Absolute paths where the agent may write temporary files without asking. [] turns it off. See Permissions. |
subagents.max_concurrent | 4 | How many subagents one session may run at once. An integer from 1 to 64. |
subagents.watchdog_interval_secs | 300 | How often Instinct checks that a background subagent makes progress. 0 turns it off. |
agent.watchdog_interval_secs | 300 | How often Instinct checks that the main conversation makes progress. 0 turns it off. See Long turns. |
tui.theme | tide-dark | Terminal colour theme: ayu-dark, ayu-light, nord-dark, nord-light, tide-dark, tide-light, moss-dark or moss-light. |
Layer models apply after you restart WithNav. The other keys apply to new sessions.
An invalid value is an error, not a silent default. nav doctor names the key.
Example setup
The repository holds a complete pair of examples:
models.example.json
and config.example.json.
Adapt the endpoints and assignments, then copy them to ~/.config/withnav/. They contain no real
keys. The notes on Gateways and models explain the choices in them.
Gateways and models
models.json is a catalogue. It declares gateways (endpoints you can call) and the
models each one serves.
Model names
Every model has a qualified name, <gateway>/<upstream id>. In omniroute/combo/gpt-6-astra:
omnirouteis the gateway, declared inmodels.json.combo/gpt-6-astrais the upstream id. It is sent to the gateway verbatim.
The qualified name keeps two models with the same upstream id on different gateways apart. Names
may not contain whitespace. The old alias entries default, fast and cognition no longer
exist and are not accepted as compatibility names.
A gateway
"providers": {
"omniroute": {
"base_url": "http://localhost:20128/v1",
"api_key_env": "OMNIROUTE_API_KEY",
"catalog_format": "omniroute",
"headers": {},
"connect_timeout_ms": 5000,
"request_timeout_ms": 600000
}
}
| Key | Meaning |
|---|---|
base_url | The OpenAI-compatible endpoint. |
api_key_env | The name of the environment variable that holds the key. Never put the key itself in models.json. |
headers | Extra HTTP headers. |
connect_timeout_ms | Default 5000. |
request_timeout_ms | Default 600000. |
catalog_format | Optional. "omniroute" means the gateway’s model list reports reasoning-effort tiers, and a model that omits variants gets one variant per tier. |
A model
"models": {
"omniroute/combo/gpt-6-astra": {
"provider": "omniroute",
"model": "combo/gpt-6-astra",
"api": "chat",
"supports_tools": true,
"supports_parallel_tool_calls": true,
"variants": {
"low": { "reasoning_effort": "low" },
"medium": { "reasoning_effort": "medium" },
"high": { "reasoning_effort": "high" }
},
"reasoning_history": "preserve"
}
}
The key must equal <provider>/<model>. WithNav refuses a catalogue where they disagree.
| Key | Meaning |
|---|---|
provider | The gateway name. |
model | The upstream id. |
api | chat (default), fim or systemone. Each layer needs a specific one. |
context_window | Tokens the model accepts. A usable chat model needs one, from here or from the gateway’s own model list. |
max_input_tokens, max_output_tokens | Optional limits. |
max_message_tokens | Optional. Bound for one serialised compaction message. Must be positive. |
supports_tools | Whether the model can call tools. Default false. |
supports_parallel_tool_calls | Optional. |
reasoning_history | preserve (default) or omit. |
default_variant | The variant a new conversation starts with. If you leave it out, there is no variant. |
variants | Named request overrides; see below. |
thinking is deprecated and ignored. Write default_variant instead.
Variants
A variant is a named set of extra fields merged into the request body, for example
reasoning_effort. The example catalogue declares low, medium and high as the request-body
field reasoning_effort. It never adds a -high suffix to the model name.
A variant cannot set the fields the request owns: model, messages, tools, tool_choice,
parallel_tool_calls, functions, function_call, stream, stream_options, max_tokens,
max_completion_tokens, n and temperature.
If variants is left out, a model on a gateway with catalog_format: "omniroute" gets its
variants from the gateway. "variants": {} means none.
API keys
For each gateway, WithNav looks for a key in this order:
- The key stored in
~/.config/withnav/auth.json. Write it withnav auth set <provider>. - The environment variable named by
api_key_env.
A stored key wins over the environment. See nav auth.
Example setup
Start from
models.example.json and
config.example.json.
Adapt endpoints and assignments before copying them to ~/.config/withnav/. No real keys are
included. Provide them with nav auth set omniroute and nav auth set inception, or with the
example’s OMNIROUTE_API_KEY and INCEPTION_API_KEY environment variables.
Things to know about the example:
- Chat models leave their context window out on purpose, so WithNav reads it from the gateway. A usable chat model needs a window it can resolve.
- Reflex (FIM) and Instinct (System One) models carry fixed sample windows and output caps. Check those values against your own deployment.
- Combo models declare
low,mediumandhighas request-bodyreasoning_effort.
Layers
Reflex, Instinct and Metacognition each use one model at a time. You assign it in
config.json under layers. Cognition is different: it runs agents, each with its
own model.
| Layer | Assignment | Required api | Job |
|---|---|---|---|
| Reflex | one model, editor only | fim | Inline completion as you type (Mercury in the example). |
| Instinct | one model | systemone | Typed decisions and supervision: when to speak, permission review, progress checks. |
| Metacognition | one model | chat | Remarks beside your code, and review of risky calls. Prefers the low variant when the model offers one, otherwise the model’s default. |
| Cognition | agents, each with its own role and model | chat | The agent loop you talk to. |
Assign a model
A layer takes a model name, or an object with a variant:
{
"layers": {
"reflex": "inception/mercury-edit-2",
"instinct": "omniroute/typesafe/jev-1.13",
"metacognition": {
"model": "omniroute/combo/deepseek-v4-flash",
"variant": "low"
}
}
}
- The model must exist in the catalogue and use the
apithe layer requires. variantis optional. Leave it out for the model’s default. Usenullor"off"for no variant. Any other value must name a variant the model offers.
What happens when a layer is not usable
A missing, unknown or API-incompatible assignment turns that layer off, with a clear diagnostic. It does not switch to another model silently.
| Layer off | Effect |
|---|---|
| Reflex | No inline suggestions. |
| Instinct | Permission review asks you when it cannot decide. Supervision of long turns falls back to a visible, renewable 500-step cap. |
| Metacognition | No remarks. Compaction summaries use the session’s model. |
nav doctor reports each layer and why it is off. See
Troubleshooting.
Where you see the choices
The terminal’s right panel and the VS Code chat header tooltip show the model of each lower layer
and of the current agent, marked when overridden. In the editor clients, assignments are
read-only; change them in config.json or a Settings screen.
Agents
Cognition runs agents. An agent is a role: a prompt, a set of tools, an access level and, optionally, a model and a variant. The agent you talk to is the primary agent. Others are subagents that a primary agent hands a task to.
| Agent | Kind | What it does |
|---|---|---|
build | primary | The default. Implements, verifies and reports. |
plan | primary | Researches and writes Markdown plans. Gated calls are checked by Instinct; see Permissions. |
vigie | subagent | Read-only scouting of code, documentation and the web, with sources. |
lux | subagent | Read-only analysis and expert advice. |
mousse | subagent | Small local edits, after your approval. |
cartographe | subagent, on demand | Writes Mermaid charts to .withnav/charts/. |
sondeur | subagent, on demand | Root-cause work. Tests and profilers run after your approval. |
You switch the primary agent between turns (/agent <id>, or tab in the terminal client). The
agent sets what the model is told to do. It does not set what it may run; permissions
do that.
Tools
Agents work through these tools: read, write, edit, bash, grep, glob, fetch, ask
and task (hand work to a subagent), plus skill when skills are found.
write, edit, bash and fetch go through the permission gate.
Assign models to agents
In config.json, under agents:
{
"default_agent": "build",
"agents": {
"build": { "model": "omniroute/cc/claude-opus-5-5" },
"vigie": { "model": "omniroute/openai/gpt-6-luna", "variant": "medium" }
}
}
The rules:
default_agentis the agent a new session starts with. It isbuildwhen absent, and it must be a primary agent.- The default agent must have a usable chat model. Without one, WithNav does not start.
- Other agents are optional. An unassigned primary agent (such as
plan) uses the default agent’s model. An unassigned subagent uses its parent’s effective model and variant, including any local override. - An assignment whose model is unknown or unusable falls back to the default agent’s model and logs a warning that names the agent and the model.
- An unknown or non-primary
default_agent, or a default agent without a usable chat model, stops server and language-server startup. The error names the field, the model and the valid agents. - An assignment in
config.jsonwins over amodel:in an agent definition’s header. - Adding an assignment does not create an agent definition.
variantfollows the same rules as for layers.
To use the crew, call on vigie (scouting), lux (advice) or mousse (small edits). The others
are available on demand.
The old agent ids explore, librarian, review, oracle and explorer still resolve, to
vigie, vigie, lux, lux and mousse, with a deprecation warning. They are planned for
removal in 0.8.
Custom agents
Define an agent in a Markdown file in one of these folders. Later ones replace earlier ones with the same id:
~/.config/opencode/agents/*.md(only read; shared with OpenCode)~/.config/withnav/agents/*.md<project>/.withnav/agents/*.md
A file is a YAML header between --- lines, then a Markdown body that becomes the role prompt:
---
description: Reviews diffs for missing tests
mode: subagent
model: omniroute/openai/gpt-6-astra
tools:
write: false
edit: false
access: read-only
---
You review a diff and list the changes that have no test.
| Header key | Meaning |
|---|---|
description | What the agent is for. |
mode | primary, subagent or all. all when absent. |
model | Optional model. An assignment in config.json wins. |
tools | A map of tool name to true or false. |
access | read-only or full. full when absent. |
build and plan are reserved. A file with one of those names never replaces the built-in and is
reported. A malformed header rejects that file only. Other keys are kept but have no effect.
Subagents
An agent can start a subagent with the task tool. A subagent is a real child session with its
own transcript. In the terminal client and in VS Code you can open a subagent’s read-only
transcript while the parent chat stays open. A subagent’s permission requests appear in the
parent conversation, and you answer them there. Its result comes back to the parent once, as
text labelled untrusted.
A session runs up to subagents.max_concurrent subagents at once (default 4). If a subagent runs
out of steps (50 by default), you can continue it (alt+c in the terminal, a button in VS Code).
Editor context and session overrides
Editor sessions
A session started by an editor client is an editor session. It adds three things to its agent:
the pair-programmer prompt, the intent tool and the feed of what the navigator sees.
- It starts on the same default agent as any other session. Editor context does not choose a model.
- Subagents do not inherit the editor attachment.
Change the model in a conversation
Clients choose an agent first, then optionally a local model override for it. These commands work in every client:
| Command | Effect |
|---|---|
/model | Report the effective model, where the choice comes from, and the available models. |
/model <gateway/id> | Switch the current agent’s model in this session. If the agent is busy, the switch happens after the turn. The new model’s default variant is used. The history must fit the new model’s budget; WithNav compacts it when it can. |
/variant | Report the effective variant, its source and the choices. |
/variant <name> | Set a named variant. |
/variant off | Disable variants explicitly. |
/variant inherit | Remove the override and return to the assignment or model default. |
/agent <id> | Switch to that agent, using its assignment or its saved override. |
/new [agent] | Start a fresh conversation on that agent’s assignment. It does not carry over the previous session’s overrides. |
Overrides are saved per (session, agent). Switch away and back, or reopen the session, and
they come back. They never change the global config.json.
A subagent with its own model uses that assignment. An unassigned subagent inherits its parent’s effective model and variant, including local overrides.
Pickers
Model pickers group models by gateway, then by provider within it (omniroute, then cc,
combo, and so on). Groups collapse, but search includes hidden groups. In VS Code only the first
50 matches are shown. “Usable” means a valid configuration, not an online check.
Rules and skills
Rules
WithNav reads instruction files when a session starts and sends them to the model. Later text is closer to your work and wins. The order is:
- Your global file:
~/.agents/AGENTS.md, or~/.claude/CLAUDE.mdif the first is missing. - The optional global supplement
~/.agents/rules.md. - For each directory from the repository root down to the workspace:
AGENTS.md, orCLAUDE.mdif there is noAGENTS.md, then the optionalrules.md.
The repository root is the nearest parent folder with a .git directory or file. Without git, it
is the workspace itself, so nothing above it is read. Each file is read once, and the size of each
file and of the total is capped.
Skills
A skill is a folder with a SKILL.md file. WithNav finds skills when a session starts, lists them
to the model and adds the skill tool to load one by id. In the chat, /skills lets you pick a
skill to load for your next message.
It looks in these places, nearest first:
- For each directory from the workspace up to the repository root:
.agents/skills,.claude/skills,.opencode/skills. - Your
~/.agents/skills, then~/.claude/skills.
If two skills share an id, the nearer scope wins. At the same distance, .agents beats .claude,
which beats .opencode, and your own .agents beats your .claude. If two skills of equal rank
have the same id, neither is offered.
A skill’s scripts go through the permission gate like any other command. You can allow a user skill’s scripts once per machine.
Import a catalogue from OpenCode
If you already use OpenCode, scripts/import-opencode-models builds models.json and auth.json
from its config. Run it from a checkout of the repository.
scripts/import-opencode-models [--dry-run] [--config-dir DIR] [--opencode FILE] [--with-windows]
scripts/import-opencode-models --self-test
| Flag | Meaning |
|---|---|
--dry-run | Print a summary and write nothing. Try this first. |
--config-dir DIR | Where to write the WithNav files. |
--opencode FILE | The OpenCode config to read. Default ~/.config/opencode/opencode.jsonc. |
--with-windows | Copy context windows from OpenCode instead of letting WithNav read them from the gateway. |
--self-test | Run the script’s own tests and exit. |
What it does
- It imports OpenAI-compatible providers only. Any other provider is skipped with a warning.
- It writes qualified model names (
<gateway>/<upstream id>) and sets"catalog_format": "omniroute"on theomnirouteprovider. The same upstream id from two gateways stays distinct. - It never writes layer or agent choices to
config.json. You make those yourself; see Layers and Agents. - Combo models get
low,mediumandhighvariants in the request body. Variants you declared by hand are kept when you run it again. - It leaves context windows out, so WithNav reads them from the provider.
--with-windowscopies them instead.
API keys
- A literal
apiKeygoes toauth.json(mode 0600). - An
{env:NAME}key becomes"api_key_env": "NAME"inmodels.json. Any key stored inauth.jsonfor that provider is removed, because WithNav prefers a stored key over the environment and it would hide{env:NAME}. Other entries are kept, and the script says what it removed. - Other references (such as
{file:...}) are refused. So is abaseURLthat carries credentials.
Safety
Nothing is written until the whole config checks out. Each file is replaced atomically, but the two are not replaced together. If the second replacement fails, the first file is already updated, and the script tells you which one was and which was not.
Permissions
Nav can read files, change them and run commands. WithNav puts a gate in front of the calls that can do harm, so you stay in control.
The gate guards against drift. It is not a sandbox.
bashis not confined to your workspace, and detection of sensitive files inside a shell command is best effort. Treat approving a command as running it yourself.
What is gated
| Tool | Gate |
|---|---|
write, edit, bash | Every call is reviewed before it runs. |
fetch | Asks first. |
read outside the project, or of credentials | Reviewed like bash. |
grep, glob outside the project | Refused. Use read or bash for that. |
read, grep, glob inside the project | Run without asking. |
How a call is decided
- Fixed checks look at the call: redirections, commands that change things, the real destination of a path. They only ever make a call stricter.
- Instinct answers a few typed questions about the call: its effect, its scope, whether it
can be undone, whether it publishes anything, how sensitive it is and whether it fits your
task.
- A confident, read-only call runs on Instinct’s answer alone.
- A low-risk change inside the project that you can recover from (for example with git) also runs. Builds, tests and commits are typical.
- Metacognition reviews the rest, with no tools, and either approves or does not. It sees your recent messages, recent work, and the script a command runs.
- You are asked about anything that is not approved: a rejection, an unsure answer, a malformed answer, a timeout, a call too large to review, or no reviewer. The prompt shows the reason and the command, or the file path and the start of the content.
Review has a deadline. Set permissions.review_timeout_secs in config.json to an integer from 5
to 300 (default 40). It applies to new sessions and is inherited by their children. If the review
times out, a read that Instinct already judged safe keeps its safe fallback. A change asks you.
Some things always ask you, however confident the reviewers are:
- Changes to WithNav’s own configuration, credentials and keys.
git pushthat deletes or forces, in any spelling.- Shell syntax that hides what a command does, such as
cdbefore the real command, a relativePATHentry or redirections to expanded paths. - Destruction, shared publication, and changes outside the project.
To turn the automatic review off and be asked about every bash, write and edit call, set
permissions.automatic_review to false. An invalid value fails closed.
Answering a prompt
| Answer | Meaning |
|---|---|
| Allow once | Run this call. |
| Deny | Do not run it. You can add a message that the model sees. |
| Allow for this folder / site | Only offered for fetch. The approval is saved per workspace in ~/.local/share/withnav/grants.json and applies to later sessions there. Delete the workspace’s entry in that file to revoke it. |
| Allow WithNav config for this session | Offered for edits to WithNav’s own config. Reviews still run, and credentials still ask. |
| Allow this skill’s scripts on this machine | Offered for a user skill’s script. It saves the skill’s location and each script’s SHA-256 in skill-grants.json in the config directory. Reviews still run, and changes ask again. |
In the terminal these are the keys y, a and n. In VS Code they are buttons on a card that
shows the full reason and command.
For bash, write and edit there is no reusable “allow folder”: every call is reviewed. Your
Allow once on an edit or write also counts for the same file until your next message; it never
covers credentials or protected configuration.
Bypass
Bypass lets one session skip ordinary permission prompts.
- Turn it on with
/bypass on(and/bypass off),alt+pin the terminal, the WithNav: Toggle Bypass Permissions command in VS Code, the Bypass button in the browser, ornav run --yes. - With it on,
write,edit,bashandfetchcalls that would have asked run once instead, after the usual reviews. - Requests for WithNav’s own configuration, credentials and keys still ask you.
nav run --yesstops with exit code 3 on one. - It is off for every new or resumed session and is never saved. A session’s subagents follow it.
- A clear warning shows while it is on:
⚠ bypassin the terminal, a badge in VS Code and the browser. - It is not a sandbox:
bashkeeps its access to your machine.
The toolchain trust rule
A program you installed yourself, such as cargo from rustup, is trusted like a system one. So
cargo test does not go to review every time.
A program qualifies when all of these hold:
- You or root own it.
- Nobody else can write it, or any directory above it.
- It is outside the workspace.
The trade-off: any program you own that meets these rules is trusted by ownership. An agent that can write one there, with the shell or a build script, can have it run as a known tool. To limit that:
- Edits through the
writeandedittools to~/.cargo/binand~/.rustup/toolchains(or$CARGO_HOMEand$RUSTUP_HOME) always ask you. - Replacing the resolved executable, or the rustup proxy, invalidates an approval for a command. A toolchain update behind an unchanged proxy does not.
plan and the guard
For the plan agent, Instinct also checks that each gated call fits a planning role. A plain
.md write is exempt; everything else is cleared by that check or asked to you. The exemption
holds only for a regular Markdown file, reached without a symbolic link, in a folder that already
exists.
Scratch folders
The agent may write temporary files, without asking, in two folders:
- A private one:
$XDG_RUNTIME_DIR/withnav, or~/.cache/withnav/scratchif that is not usable. - A shared one:
<temp dir>/withnav, usually/tmp/withnav.
Each must be a real directory owned by you with mode 0700. A folder that is replaced, made public
or reached through a symlink loses the exemption. Change the list with
permissions.scratch_dirs (absolute paths; [] turns it off).
Long turns and the watchdog
A conversation has no step limit by default. Instead, every 300 seconds
(agent.watchdog_interval_secs; 0 turns it off), Instinct checks that the turn is making
progress.
- Active: the turn continues.
- Stuck: the turn stops after its current tools finish, keeping the work done.
- In doubt: Metacognition decides. If it hesitates too, the turn pauses and asks you.
alt+cin the terminal (or the Continue button) continues it. A new message replaces the paused turn.
If the watchdog is off, or Instinct is unavailable, a visible 500-step cap applies instead, and
only you can renew it. WITHNAV_MAX_STEPS sets an explicit cap. Subagents keep their own 50-step
budget.
InstinctFlow: a Captain’s guide
1. What a flow is
A flow is an acyclic set of nodes listening to one trigger. An event
arrives with a payload; the flow’s when expression decides whether to start.
Each node declares its inputs. A decision asks the instinct a typed question;
an expression computes without a model; an action proposes something for
WithNav to do. A node’s active port carries the run along its wires. A constant
belongs to the definition; a variable exists only for this run. A fallback
replaces the run’s proposed actions on failure. The Studio edits the definition,
Save writes it, and reload changes what runs. A workspace flow requires
explicit trust; a global flow comes from your own configuration.
event + payload → trigger / when → decision → expression → action → then → action
yes/no true/false
failure anywhere → fallback (not the earlier proposed actions)
2. Quick start: match the message test
This example logs an exact message match. It needs no decision and therefore makes no instinct call. It does not answer the message or change a permission.
- Open the Studio from your workspace in the WebUI. Its route is
#/studio?ws=<absolute-workspace>&flow=<name>&loc=workspace. - Choose New flow, name it
message-test, choose This workspace, and set Event it listens to tomessage.received. Create writes an initial flow containing alogaction; it does not reload it. - Select the trigger card. In its inputs, select
event.text: the chip declares the aliastext. Declare the input first, then use its alias inwhen. Setwhentotext == "test". Do not writeevent.text == "test"there. - Select the
logaction and set itstextparameter toMatched test. The trigger’s event port must be wired to this action. - If the trust banner appears, inspect the workspace flows, then choose Trust this workspace’s flows. Trust allows declared context reads, not tool permissions. Trust itself does not reload.
- Save, then Reload. Check the reload result;
saved · not reloadedmeans the file is newer than the running definition. - Click the sample payload in the bottom bar and replace it with:
{"command_id":"sample","author":"captain","text":"test"} - Choose Run sample. The path proposes
log; application says not applied. This is correct: samples never apply actions. ChangetexttoTestortest\nand run again: the path says skipped: when is false. - Choose Clear run to remove the run highlighting and recorded values. The complete definition, also usable in the TOML view:
[flow]
version = 1
name = "message-test"
on = "message.received"
enabled = true
blocking = false
start = ["log"]
when = 'text == "test"'
[flow.inputs]
text = "event.text"
[[node]]
id = "log"
type = "action"
action = "log"
[node.params]
text = "Matched test"
== compares the whole string exactly, including case and whitespace; it is not
a contains test. Expression strings use double quotes. The surrounding single
quotes above are TOML’s way of keeping those double quotes intact. There is no
in [...]: write text == "test" || text == "hello". Flow inputs are local to
when; every node that reads text must declare its own text = "event.text".
3. Studio tour and gestures
| Where | What to do there |
|---|---|
| Left: flow list | Select by trigger, name and location. See blocking/async and enabled status; create a flow; edit the selected flow’s constants. An enabled toggle saves the flag, but still needs reload. |
| Top: flow header | Check location, path, unsaved/not-reloaded status, validation errors and health. Set blocking and its budget; Save, Discard or Reload. |
| Centre: Canvas | Add a decision, expression or action from the toolbar; arrange cards and connect ports. |
| Right: inspector | Select the trigger or a node to edit its fields, declared inputs, instructions, parameters and ports. Inspect rendered state and run detail. |
| Centre: TOML | Edit the same definition as text. Errors show locations; invalid text leaves the last valid canvas visible and locked. |
| Bottom: run bar | Edit sample JSON, Run sample, choose a record, View run, Replay, Clear run. Inspect path, proposed actions, failure, fallback and application status. |
| Health | Header/list counters for the last 24 hours of live runs: runs, failures, fallbacks applied, cost. Samples and replay do not count. |
| Gesture | Result |
| — | — |
| Click a card | Select its node and open its inspector. |
| Drag a card | Move it; positions are saved as layout, not behaviour. |
| Drag an output port to another card’s input | Add a wire. One port can feed several nodes. Cycles and invalid connections are refused. |
| Click a wire, then Delete | Remove the selected wire. Backspace also works with canvas focus. |
| Click a card, then Delete | Remove that node and its wires. The synthetic trigger cannot be removed. Check inputs referring to the removed node. |
| Drag canvas background / scroll | Pan / zoom the React Flow canvas. |
| Escape in New flow | Close the creation dialog. |
| Clear run | Leave run mode: remove highlighted path, displayed run values and replay detail; keep the definition and stored records. |
| Run mode is an overlay, not another definition and not a live subscription. Dimmed | |
| nodes mean not reached in the displayed run, not disabled. Editing does not turn a | |
| sample into a live run. Clear it before interpreting the canvas as your draft. | |
| Built-ins are read-only: no card movement, wires, deletion or field editing. | |
| To customise one, put a same-name definition (and any sibling template it uses) | |
| in your workspace or global flows directory. Do not try to Save the built-in. | |
| Text saves preserve comments; canvas saves rewrite canonical TOML and may lose | |
| comments. Invalid TOML can be explicitly saved for later repair, but cannot run; | |
| an invalid canvas cannot be saved. Samples can test valid unsaved edits without | |
| silently saving or reloading them. |
4. Nodes
Decision: give the instinct evidence
Each decision has one instructions string and its own [node.inputs]. The
instructions describe the question; the inputs are the evidence sent as state.
A decision needs inputs. A decision with no inputs and no state template is
refused at validation (“a decision needs at least one input: the instinct has
nothing to judge”): the instinct would receive an empty state, which the provider
rejects with HTTP 400. Adding text to instructions does not supply state. New
decisions created from the toolbar start with the trigger’s payload fields. Select at least a meaningful
payload or host input and inspect State · inputs sent with the question.
| Type | Configuration | Answer and active port |
|---|---|---|
noul | instructions, threshold (finite 0–1 number or "constant.NAME") | Yes probability; yes when answer ≥ threshold, otherwise no. |
choice | instructions, [node.criteria]: 2–16 named options, each with nonempty instructions | Option string; the matching criteria key is the port. |
score | instructions, levels: 2–10 distinct labels, weakest first; at_least: exact label | Fractional expected index; at_least when ≥ that label’s zero-based index, otherwise below. Not rounded to a level. |
[[node]]
id = "requested"
type = "noul"
instructions = "Does this message explicitly request a code change?"
threshold = 0.75
[node.inputs]
text = "event.text"
[node.next]
yes = ["audit"]
no = []
Destinations must exist; an empty port exits that chain. Labels are display text,
not names to reference. Ready decisions with identical rendered state and client
identity can share one call; cost belongs to the batch, not separately to each node.
Default state sorts aliases and renders alias = <JSON value> lines. Optional
state_template = "state.md" uses a relative sibling file with {{alias}}
placeholders, declared inputs only. No absolute paths, .., symlinks or includes.
Template literal braces escape as {{{{ / }}}}. Studio carries these files
through saves; it does not edit templates. Images are currently refused.
Expression: deterministic condition and variables
An expression has condition and ports true / false. [node.assign] declares
variables, evaluated in lexicographic name order before the condition; later
assignments can read earlier ones. Variables are immutable after the node completes
and start afresh on each run. They are not session settings or persistent memory.
[[node]]
id = "gate"
type = "expression"
condition = "is_set(p) && p >= THRESHOLD"
[node.inputs]
p = "answer.requested"
agent = "session.agent"
[node.assign]
THRESHOLD = 'if(agent == "plan", 0.9, 0.6)'
[node.next]
true = ["audit"]
false = []
Action: catalogue parameters, not arbitrary code
An action has action, [node.params] (even if empty) and optional then wires,
whose destinations must be actions. Parameters must match the catalogue’s types.
Use a literal, or { ref = "alias" } for a typed declared input. Text parameters
also accept {alias} interpolation; {{ / }} escape literal braces. Absent
values render as —. Placeholders do not evaluate expressions; instructions do
not interpolate. An action can only do what the catalogue offers (section 7).
[[node]]
id = "audit"
type = "action"
action = "log"
[node.inputs]
p = "answer.requested"
[node.params]
text = "Requested confidence: {p}"
[node.next]
then = ["another_action"]
5. Inputs and references
Declare alias = "reference" in [flow.inputs], [node.inputs] or
[fallback.inputs] as appropriate. Expressions and parameters use aliases,
not dotted references. A declaration in one place does not declare it elsewhere.
| Reference | Value |
|---|---|
event.<field> | One exact payload field offered by this trigger (section 7); no arbitrary nested traversal. |
Host name, e.g. session.agent | Context collected by WithNav, only when declared and needed. |
constant.MIN_YES | Scalar from [constants]: string, finite number, integer or boolean. |
answer.requested | Upstream decision answer: noul number, choice string, score number. |
answer.kind.confidence | Upstream choice/score confidence. |
answer.kind.probabilities.option | Choice probability for an option; score probability keys are zero-based index strings. |
result.gate | Upstream expression’s boolean condition result. |
variable.gate.THRESHOLD | Variable qualified by the expression node that wrote it. |
| Host inputs in the WithNav catalogue (all transported as text; scalar types remain | |
| available to expressions): | |
| Host input | Semantic type |
| — | — |
session.id | string |
session.agent | string |
session.model | string |
session.workspace | string |
conversation.last_driver | string |
conversation.digest | string |
conversation.last_turns | array |
intent.record | string |
workspace.root | string |
workspace.branch | string |
workspace.dirty_files | array |
editor.open_files | array |
editor.cursor | object |
editor.selection | string |
| Offered does not mean present: unavailable session/editor/git context is absent. | |
| Objects and arrays render as canonical JSON text; this does not enable indexing or | |
| JSON traversal inside expressions. There are no inputs for credentials, arbitrary | |
| files or provider destinations. | |
| Output references must name strict upstream ancestors, not the node itself, | |
| siblings or downstream nodes. The inspector disables non-upstream answers. An | |
| upstream node not visited on this path produces absent, not zero, false or empty | |
| text. A node starts on any active incoming wire (OR, not a join), then waits | |
| only for the upstream outputs it declares to finish or become unreachable. Each | |
| node runs at most once; independent chains can run concurrently. | |
Use is_set(p) && p > 0.5, or if(is_set(p), p, 0.0), to handle absence safely. | |
Other operations on absent values fail the run. Decisions show <absent> in state; | |
cut inputs show [cut]. Fallback cannot read graph outputs after a failure. |
6. Expression language reference
The same bounded language is used by when, condition and assignment values.
when and condition must produce a boolean.
| Syntax | Meaning |
|---|---|
true, false, numbers, "text" | Boolean, finite numeric and string literals. |
+ - * / %, unary - | Numeric arithmetic only. |
== != < <= > >= | Comparisons, including string equality/order; compatible types required. |
| `! && | |
( … ) | Grouping; use parentheses to make intent clear. |
is_set(alias) | Whether a declared input is present; argument must be an identifier. |
if(boolean, value, value) | Lazy selection; both branch types must agree. |
No in [...], ternary ?:, string concatenation, exponentiation, regex, tuples, | |
| indexing, dotted identifiers, arbitrary functions, semicolons or assignment | |
operators. Assignment belongs only in [node.assign]. Type mismatch, division | |
| by zero or a nonfinite result fails, rather than being coerced. | |
| Per expression: at most 4096 UTF-8 bytes, 128 lexical tokens, 32 operators, | |
| 16 nested parentheses, AST depth 32 and 128 AST nodes. A node may declare at most | |
| 16 inputs; an expression may declare at most 16 assignments. |
7. WithNav triggers and actions
Payload fields below are required unless marked ?. s = string, i = integer,
o = object, a = array. Use event.<field> when declaring an input.
Unknown payload fields are rejected.
Action groups: common = wake, inject_note, log; editor = common plus
remark; tool = common plus no_objection, deny, ask_captain.
| Trigger | Payload fields | Can block | Allowed actions |
|---|---|---|---|
tool.pre_call | tool:s, args:o, call_id:s | yes | tool |
message.received | command_id:s, author:s, text:s, selected_skill?:o | yes | common |
turn.end | turn_id:s, text:s, status:s | no | common |
save | uri:s, language_id:s, version:i, diff:s; editor extras below | no | editor |
idle | uri:s, language_id:s, version:i, diff:s, idle_ms:i; editor extras | no | editor |
diagnostics | uri:s, language_id:s, version:i, diagnostics:a, diff:s; editor extras | no | editor |
conversation | turn_id:s, text:s, uri?:s; editor extras | no | editor |
Every editor trigger also offers these optional payload fields, used by the built-ins and migrated definitions (not host-input names):
| Type | Exact optional fields |
|---|---|
| string | template_file, template_language, template_intent, template_diff, template_context, template_diagnostics, template_live_state, template_conversation |
| integer | changed_lines, since_last_remark_ms, since_last_wake_ms |
| boolean | called_for_help, has_errors, has_intent, has_conversation |
Message authors are Captain, subagent or system; selected_skill is
{name: string, origin: "captain"}. Turn status is completed, interrupted or
failed. Diagnostics entries carry message, numeric LSP severity and range.
Only root-session events emit flows in v1; editor events use attached root context.
message.received runs after admission checks, before transcript consumption;
turn.end is asynchronous after the turn settles. Tool preflight rejection is
final and is not offered to flows.
All listed parameters are required strings; no_objection has an empty map.
| Action | Parameters | Kind | What WithNav does |
|---|---|---|---|
no_objection | {} | verdict, priority 0 | Continue normal tool permission review, unchanged; not permission to run. |
ask_captain | note | verdict, priority 1 | Hold the call for a Captain-only permission request, skipping model reviews. |
deny | note | verdict, priority 2 | Do not run the tool; return the note as the denied tool result. |
remark | prompt | effect | Ask metacognition for an editor remark using inline rendered text, not a prompt-file lookup. |
wake | note | effect | Bring the note to cognition’s attention; wake delivery rules/cooldown still apply. |
inject_note | text | effect | Deliver a flow-labelled note at a safe boundary. |
log | text | effect | Annotate the trace only. |
Verdicts are only for blocking tool.pre_call flows, including fallbacks.
At most one verdict may be reachable per run. wake / inject_note have typed
non-Captain provenance (source: flow, flow name, run id), remain untrusted data,
and never update the Captain request, choose a skill, answer an ask or generate
recursive message.received events. No action runs arbitrary code, grants tool
permission, switches agent/model or starts a session.
8. Blocking, budget and fallback
Blocking holds the event while the flow runs. tool.pre_call can veto or ask;
blocking message.received only waits for routing effects, not permission.
Other triggers must be asynchronous. Async effects apply when the run finishes;
they cannot change an already-admitted event.
Set blocking = true and required budget_ms = 1..2000; omit budget_ms
when non-blocking. The budget is one deadline covering input collection, waiting
for call capacity and all chains, not a new allowance per decision. Async runs
have a 5-second host deadline; each System One call has at most 2 seconds.
Failure discards all proposed graph actions, even if another chain finished. The matching fallback replaces them:
| Key | Used for |
|---|---|
on_failure | General failure; expression, input, cancelled and limit failures. |
on_timeout | Exhausted deadline. |
on_provider_error | Provider unreachable, rejected the request (HTTP 4xx) or failed (HTTP 5xx). |
on_invalid_answer | Missing/wrong/out-of-range answer or malformed probability set. |
A specific key overrides on_failure. Each value is { action, params, then? };
inline then chains have depth at most 4 and still obey trigger/action validation.
Fallback inputs can read event, host and constants only. failure_kind and
failure_message are predefined aliases; do not redeclare them.
[fallback]
on_failure = { action = "log", params = { text = "Failed: {failure_kind}: {failure_message}" } }
Without a matching fallback, a blocking tool flow defaults to ask_captain with
Flow failed: {failure_message}. Blocking message flows have no verdict default:
the message continues after failure/deadline, with host outage handling. Async
flows have no default fallback; failure with none leaves not applied.
Cancellation of a no-longer-current event applies no live effect or fallback.
Several enabled flows on one event run with independent budgets. Blocking verdicts
combine as deny > ask Captain > no objection. Tied notes are retained in flow-name
order; effects accumulate in flow-name / topological order, preserving then.
A flow’s no objection never skips normal permission review. Bypass never answers
a flow’s ask_captain, including follow-ups, and never defeats a flow’s denial.
An approved flow-origin call is still rechecked for preflight, binding and hard floor.
Limits include 64 nodes/flow, at most 16 decisions reachable in a run, 64 loaded
flows/workspace, 32 KiB per collected input, 64 KiB total inputs/run and rendered
state, and 256 KiB definition/provider response. Cut text is marked, not hidden;
total-input/state overflow fails with limit. Effects already applied by the host
cannot be rolled back; failed or uncertain application is never retried automatically.
The action-text cap remains 16 KiB: an action interpolating a 32 KiB input also
fails with limit. On the asynchronous built-in save flow, context and diff both
exceeding 32 KiB are cut individually, then exhaust the 64 KiB run input budget
alongside the other selected inputs. The failure is recorded; there is no fallback
and nothing is applied.
9. Files, trust, reload and TOML
| Location | Path / precedence |
|---|---|
| Workspace flow | <workspace>/.withnav/flows/<name>.toml; trusted workspace wins by name. |
| Global flow | ~/.config/withnav/flows/<name>.toml (or configured config directory); wins over built-in. |
| Built-in | Shipped in WithNav: navigator-default-save, navigator-default-idle, navigator-default-conversation; read-only. |
| Trust | <config_dir>/flows-trust.json, private Captain-granted canonical-workspace trust, not tracked in the workspace. |
| Run records | <data_dir>/flows/runs/<sha256-canonical-workspace>/runs.jsonl, private captures. |
Untrusted workspace flows stay listed and editable, but cannot run, sample, replay or shadow globals. Inspect files before granting trust. Missing/malformed or insecure trust storage fails closed. Revoking trust stops new workspace runs immediately; runs already in progress finish. Trust grants no model destination or tool permission. Definitions/templates must not use symlinks.
Save is not reload, including enabled toggles and edits outside the Studio.
No definition-file watching: a git pull alone does not change what runs. Reload
via the Studio, /flows reload, or restart. All eligible definitions, including
disabled and shadowed files, are validated. Any invalid file prevents replacing
the whole running set; the previous set stays. Startup failure uses validated
built-ins only. In-flight runs keep their old immutable definition/templates.
A successful reload changes this process immediately and signals other WithNav processes to reload on their next event. It is not simultaneous application across processes: inspect the propagation message and diagnostics in each process. An unsaved draft is not part of reload.
Format essentials: one UTF-8 file per flow, version 1; name must match filename
stem and [a-z][a-z0-9-]{0,63}. Node ids, aliases, constants, variables and choice
ports use [A-Za-z_][A-Za-z0-9_]{0,63}, case-sensitive. trigger is reserved as
the synthetic card id. enabled defaults true, blocking false, absent when
true; input/next maps default empty. start is required and nonempty; nodes are
required. Unknown fields, unreachable nodes, cycles, wrong ports, non-upstream
references and conflicting reachable verdicts are errors. [layout] is optional.
Complete guard-mutations definition
This is the engine fixture, including its fallback choices and layout. It asks about the most consequential effect, then whether changes were requested or how far a destructive change could reach. Its timeout deliberately takes no objection (normal review still applies). Choose that trade-off consciously for your own flow.
[flow]
version = 1
name = "guard-mutations"
on = "tool.pre_call"
enabled = true
blocking = true
budget_ms = 800
start = ["effect"]
when = 'tool == "bash" || tool == "write" || tool == "edit" || tool == "fetch"'
[flow.inputs]
tool = "event.tool"
[constants]
MIN_YES = 0.75
[fallback]
on_failure = { action = "ask_captain", params = { note = "Flow failed: {failure_message}" } }
on_timeout = { action = "no_objection", params = {} }
on_provider_error = { action = "ask_captain", params = { note = "Instinct unavailable" } }
on_invalid_answer = { action = "ask_captain", params = { note = "Invalid instinct answer" } }
[[node]]
id = "effect"
type = "choice"
instructions = "What will this call do? Choose its most consequential effect."
[node.inputs]
tool = "event.tool"
args = "event.args"
agent = "session.agent"
[node.criteria]
read_only = "Only reads files or prints information."
local_change = "Changes files inside the workspace."
network = "Reaches a host outside the machine."
destructive = "Deletes, force-pushes or rewrites history."
[node.next]
read_only = ["continue"]
local_change = ["asked_for"]
network = ["trusted_host"]
destructive = ["blast_radius"]
[[node]]
id = "asked_for"
type = "noul"
instructions = "Did the Captain or the intent record ask for this exact change?"
threshold = "constant.MIN_YES"
[node.inputs]
last_driver = "conversation.last_driver"
intent = "intent.record"
args = "event.args"
[node.next]
yes = ["continue"]
no = ["plan_overreach"]
[[node]]
id = "trusted_host"
type = "noul"
instructions = "Is this a read-only request to an identifiable public documentation host?"
threshold = 0.9
[node.inputs]
args = "event.args"
[node.next]
yes = ["continue"]
no = ["ask"]
[[node]]
id = "blast_radius"
type = "score"
instructions = "How far could this reach if it goes wrong? Levels are weakest first."
levels = ["one file", "one folder", "whole repo", "outside the repo"]
at_least = "whole repo"
[node.inputs]
args = "event.args"
root = "workspace.root"
dirty = "workspace.dirty_files"
[node.next]
at_least = ["deny"]
below = ["ask"]
[[node]]
id = "plan_overreach"
type = "expression"
condition = 'asked_for < THRESHOLD'
[node.inputs]
asked_for = "answer.asked_for"
agent = "session.agent"
[node.assign]
# The plan agent should not change code: demand a clearer request from it.
THRESHOLD = 'if(agent == "plan", 0.9, 0.6)'
[node.next]
true = ["deny"]
false = ["ask"]
[[node]]
id = "continue"
type = "action"
action = "no_objection"
[node.params]
[[node]]
id = "ask"
type = "action"
action = "ask_captain"
[node.inputs]
effect = "answer.effect"
asked_for = "answer.asked_for"
[node.params]
note = "Review {effect}; requested confidence: {asked_for}."
[[node]]
id = "deny"
type = "action"
action = "deny"
[node.params]
note = "Change not requested, or destructive reach too broad. Propose it to the Captain."
[node.next]
then = ["audit"]
[[node]]
id = "audit"
type = "action"
action = "log"
[node.params]
text = "guard-mutations proposed denial"
[layout]
trigger = { x = 16, y = 230 }
effect = { x = 226, y = 200 }
asked_for = { x = 476, y = 30 }
trusted_host = { x = 476, y = 250 }
blast_radius = { x = 476, y = 450 }
plan_overreach = { x = 726, y = 120 }
continue = { x = 966, y = 30 }
ask = { x = 966, y = 260 }
deny = { x = 966, y = 470 }
audit = { x = 1180, y = 470 }
10. Testing and records
- Run sample uses the current valid draft or saved definition and editable catalogue payload. On the real API it makes real instinct calls when decisions are reached, but never applies actions. Host context can be absent: Studio samples currently send no session id. A mock API banner means in-memory files and invented answers instead; nothing is saved to disk.
- View run reads the original record, including live application results; it neither calls the model nor runs the definition.
- Replay uses captured inputs and answers, never live context or model calls,
and never applies actions. It is comparable only if selected inputs, cut flags,
rendered state, question and model/variant match. Changed expressions/actions
can take a new path using unchanged decisions. A newly reached uncaptured
decision or changed question yields
missing_capture/question_changed, not an invented answer. Noncomparable replay has no new outcome. - Run records separate status (
success,skipped,failure), mode (live,sample,replay), proposed actions, fallback and application (not_applied,applied,failed,uncertain). Inspect all of them: success means evaluation succeeded, not that an action ran. Batch usage/cost may be unknown; shared batch cost must not be counted once per node. - Health covers 24 hours of production runs only. Repeated applied fallbacks indicate a budget, provider, input or definition problem worth fixing.
Captures contain payloads, code, rendered state and notes: treat them as private. Directories are 0700, records 0600; retention is at most 7 days, 1000 records and 32 MiB per workspace. Redacted/missing evidence makes replay noncomparable.
Command line
Export WithNav’s current host catalogue first; the standalone CLI does not infer it:
nav flows catalogue > catalogue.json
instinct-flow validate .withnav/flows --catalogue catalogue.json
instinct-flow run .withnav/flows/message-test.toml --payload event.json \
--catalogue catalogue.json --endpoint https://your-provider.example/systemone \
--model your-systemone-model
instinct-flow replay .withnav/flows/message-test.toml --runs runs.jsonl \
--catalogue catalogue.json
nav lsp replay --flows --log withnav.jsonl
For run, event.json contains just the payload object shown in quick start.
Supply the API key through INSTINCT_API_KEY, not a command-line literal.
The endpoint is the full /systemone URL. The CLI reads neither WithNav’s model
configuration nor auth.json, and its host inputs are absent. It prints a JSON
run record with proposed actions and no application; it is not a live host.
validate checks .toml files in the directory and sibling templates without
model calls. replay prints a replay result for each JSONL record.
nav lsp replay --flows replays the embedded editor flows against legacy
withnav.jsonl captures, preserving question/state provenance, without model calls.
It is not a path option for arbitrary workspace flows. --json produces a
machine-readable report; --flows cannot combine with --pipeline or --reask.
11. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Validation error “a decision needs at least one input” | The decision sends no evidence; instructions alone are not state. | Select meaningful inputs on that decision (State section), inspect the rendered state, Save and Reload. |
provider rejected the request (HTTP 4xx) | The instinct endpoint refused the request (model id, malformed state). Not an outage: no “instinct unavailable” notice. | Check the instinct layer’s model in Settings, then the rendered state of the failing decision. |
provider unreachable / provider error (HTTP 5xx) | Instinct endpoint down or failing. The Captain is told once per outage. | Check the gateway; blocking flows apply their fallback meanwhile. |
skipped: when is false | Payload does not satisfy exact comparison. | Inspect text, case and whitespace; edit sample JSON or correct the condition. |
Unknown name in when | Alias not declared in [flow.inputs], or dotted reference used as expression name. | Select the input chip first; bind text = "event.text", then use text. Node inputs do not declare flow inputs. |
in [...] / single-quoted expression string fails | Unsupported expression syntax. | Use double-quoted string literals and ` |
| Workspace flow cannot sample/replay or does not run | Workspace is untrusted. | Inspect definitions; grant trust explicitly, then reload. Global/built-in flows remain available. |
| Saved but live behaviour is unchanged | Save did not reload, or another process has not seen the signal yet. | Reload, inspect errors/propagation; other processes reload on their next event. |
| Reload fails after editing a different flow | Replacement is all-or-nothing, including eligible disabled/shadowed definitions. | Fix every reported error; reload again. The previous set remains running. |
Sample/replay says not applied | Intentional test behaviour. | Inspect proposed actions/path; use View run on a live record to inspect real application. |
Async live failure says not applied, fallback none | Async runs have no default fallback. | Fix the recorded failure or declare an allowed effect fallback. |
| large file: limit failure | Context and diff reach the 64 KiB total input budget, rendered state overflows, or action text exceeds 16 KiB. | Inspect the recorded limit message and cut inputs; reduce selected evidence/action text. Built-in save is asynchronous with no fallback: nothing is applied. |
Wake suppressed: wake cooldown (120 s) | Wakes share the session cooldown; repeated wake is held back. | Wait 120 seconds; inspect application detail. Do not assume a proposed wake was delivered. |
instinct unavailable notice | First provider error/timeout since startup or a successful call. Further outage notices are suppressed. | Check instinct assignment, System One endpoint/key and deadline; one successful call resets the notice. |
| Missing upstream input / expression failure | Node was not visited on this path, or host context unavailable. | Use is_set(alias) with lazy && or if; read only strict upstream outputs. |
| Cards dimmed or inspector shows old values | A recorded/sample run overlay is still displayed. | Choose Clear run; it does not delete records or edits. |
| Cannot delete wire/card | Canvas not focused, built-in read-only, or invalid TOML locks editing. | Select it on an editable canvas and press Delete; fix TOML or create a same-name override for a built-in. |
| Save conflict / stale revision | Another edit changed the file on disk. | Preserve your draft separately, then Reopen the file; reopening loses unsaved edits. |
| Replay is not comparable | Request changed or required capture is absent/redacted. | Inspect per-node replay status; obtain a new sample/live capture rather than guessing an answer. |
Next: create message-test, declare text, and run the exact sample before adding
a decision.
Troubleshooting
Start with nav doctor. It reads your configuration, says what is wrong and suggests a fix.
Check your setup with nav doctor
nav doctor --workspace /path/to/project
nav doctor --offline --json
nav doctor --layer instinct
| Flag | Meaning |
|---|---|
--offline | Make zero network calls. Read only local config and cache, and mark the live checks as skipped. A variant offer that cannot be verified is a warning, not a false success. |
--json | Print one JSON report on standard output: schema_version, checks and summary. |
--workspace <dir> | Check this project. Default: the current directory. |
--layer <reflex|instinct|metacognition> | Limit the layer and live checks to one layer. Shared config, agent, compaction and workspace checks still run. |
Without
--offline, doctor sends small synthetic requests to your configured providers and may incur charges. It makes two Reflex FIM calls (plain and with bounded context), three Instinct calls (an editor decision, a role-conformance check and one permission review), and one tool-free Metacognition chat call. Instinct keeps its 2-second deadline. FIM is bounded at 10 seconds and chat at 20 seconds. No project source, session or tool is executed or sent. Provider catalogue discovery may also run to verify offers.
Doctor resolves configuration exactly as WithNav does: the same platform config directory,
workspace catalogue, agent precedence, default fallback and layer assignments. It also reports the
compaction writer, the trusted system bash, the nav lsp version and, when it can detect it,
the VS Code server path (nav by default). Missing optional features and failed detection are
warnings.
Doctor changes nothing. It does not migrate, fix, trust, reload or write anything, including metadata caches. Workspace flows are not even read until you trust them.
Read the output
✓ ok assignment.layer.instinct: gateway/typesafe/jev variant=off
✓ ok live.instinct.editor: valid answer (145 ms)
⚠ warning flows.workspace_trust: untrusted: workspace flow validation skipped; no workspace flow read
✕ failed live.metacognition.chat: HTTP 401 rejected; Check provider authentication, model id and API support.
Each line is status id: value, then a hint when there is one. Statuses are ok, warning and
failed.
In --json, each check has an id, a status, the measured value, an optional sanitised error
category, a hint and the elapsed milliseconds. Neither mode prints response bodies,
credential-bearing URLs, keys or environment values. Attach nav doctor --offline --json to a bug
report when configuration may be involved.
Exit code: 0 means no failed check (warnings are allowed). 1 means at least one failure.
2 means invalid flags or layer names.
Common problems
Error: cannot load startup models.json
WithNav found no catalogue, or it is invalid. It looks for models.json in this order: the path
you gave with --models, the workspace, then ~/.config/withnav/. Create one; see the
Quick start. A catalogue key that does not equal <provider>/<model>, a gateway
that is not declared, or a duplicate key is rejected.
default_agent '<name>' is unknown or has no usable model
A bad default_agent, or a default agent with no usable chat model, stops server and
language-server startup. The error names the field, the model and the valid agents. Check:
default_agentnames a primary agent (buildorplan, or a custom primary one).agents.<id>.modelis a name that exists inmodels.json, in the form<gateway>/<id>.- The gateway has a key:
nav auth list, or itsapi_key_envvariable is exported. For example,model 'openai/gpt-4.1' authentication unavailable for provider 'openai'. - The model has a context window, from
models.jsonor from the gateway.
See Agents.
A layer does nothing
A missing, unknown or API-incompatible assignment turns that layer off, with a diagnostic. Look
for assignment.layer.<name> in nav doctor. The model must use the right api: fim for Reflex,
systemone for Instinct and chat for Metacognition. See Layers.
No other model is chosen for you. A layer that is off never falls back silently:
- Without Reflex, there are no suggestions.
- Without Instinct, permission review asks you.
- Without Metacognition, there are no remarks.
An unknown model for a non-default agent falls back to the default agent’s model, with a warning that names the agent and the requested model.
✕ failed auth.provider.<name>
The gateway has no usable key. Run nav auth set <provider>, or export the variable named in
api_key_env. A stored key wins over the environment variable.
Permission prompts for a command you trust
Read Permissions. A program you own, in a folder only you can write, outside the workspace, is trusted. Anything else is reviewed. Do not use bypass to avoid prompts you do not understand.
nav run exits with code 3
A tool call needs approval and nobody can give it. nav run never approves on its own. Rerun
with --yes only if you accept that ordinary calls run unreviewed; protected requests still stop it.
VS Code shows $(error) WithNav in the status bar
The extension could not start the server. Hover the status item for the reason. Open the
WithNav output channel: its first line, Using nav binary (...), shows which binary it tried.
Then check withnav.serverPath and see Install. The extension stays alive and
retries on the next settings change.
Old conversations cannot be resumed
A conversation whose model identity cannot be proved stays readable as a read-only archive, with the reason resume is disabled. It is never silently attached to a different model. Session ids are kept.
Older installs
WithNav was called Navai before 0.7. The old names still work in 0.7 and print a warning. Removal is planned for 0.8; see ADR 0014. Switch to the new names:
| Old | New |
|---|---|
navai command | nav |
.navai/, .navigator/ in a project | .withnav/. When .withnav/ is missing, the old folders are read, with a hint. |
NAVAI_* environment variables | The same name with WITHNAV_. The old one is read when the new one is unset. |
navai.* VS Code settings | withnav.*. An old value is honoured when the new key is unset, with a notice. |
withnav-lsp or navai-lsp in withnav.serverPath | nav. The extension uses nav in the same directory and asks you to update the setting. |
Agent ids explore, librarian, review, oracle, explorer | vigie, vigie, lux, lux, mousse |
~/.config/navai | ~/.config/withnav. nav doctor warns about an old directory and tells you to move it by hand; it never moves files. |
nav doctor lists every old name it finds.
Glossary
- Agent
- A role Cognition plays: its prompt, tools, access and an optional model and variant.
build,plan,vigie,lux,mousse,cartographe,sondeuror a custom one. - Beacon
- A short remark from the Metacognition layer, tied to a line, signed by Nav. It never changes your code.
- Captain
- You. The human writing the code, and the default author of every line.
- Catalogue
models.json: the gateways and models available. See Gateways and models.- Delegation
- You explicitly asking Nav to do a piece of work itself, instead of guiding you through it.
- Default agent
- The agent a new session starts with.
default_agent, orbuildwhen absent. - Editor session
- A session started by an editor client. It adds the pair-programmer prompt and the
intenttool. - Gateway
- An endpoint declared under
providersinmodels.json. The first part of a model’s name. - Intent record
.withnav/intent.mdin a project: what you are trying to do. Metacognition compares your code with it.- Layer
- One of four roles: Reflex, Instinct, Metacognition and Cognition. See Layers.
- Your single point of contact. It runs the
buildandplanagents. WithNav is the whole system. - Remark
- See Beacon.
- Subagent
- An agent that another agent hands a task to. See Agents.
- Suggestion
- Ghost text from the Reflex layer. It becomes code only when you accept it.
- Variant
- A named set of request fields, such as
reasoning_effort, that a model offers. See Variants. - Verdict
- Your rating of a remark: useful, noise or dismissed.
Changelog
All notable changes to this project. Versions follow Semantic Versioning;
before 1.0, a minor version may break compatibility. New entries go under Unreleased as pull
requests land in develop.
[Unreleased]
Changed
- The README is a short front page; the full user guide is an mdBook at withnav.dev/docs, with install, configuration, permissions and troubleshooting rewritten for clarity.
[0.7.8] - 2026-10-12
WithNav 0.7.8 prepares the public opening. Tagged releases now ship ready-to-install Linux
packages with nav inside, a toolchain you installed yourself (such as rustup’s cargo) is
trusted like a system one, and the HTTP and data dependencies move to their current majors.
Added
- A tagged release now ships ready-to-install packages for Linux. The GitHub Release holds a
VS Code package for x64 and one for arm64, each with a statically linked
navinside that the extension uses on its own, plus a universal package without a binary (it needsnavonPATHorwithnav.serverPath), the barenavbinaries as tarballs, andSHA256SUMS. A path you set inwithnav.serverPathstill wins over the bundled binary.scripts/package-vsix.shbuilds the same files locally.
Changed
- HTTPS connections use the system’s trusted certificates, including locally installed certificate authorities, through the platform verifier. TLS cryptography now uses aws-lc-rs instead of ring.
- The VS Code extension needs VS Code 1.91 or later (was 1.90). It now uses
vscode-languageclient10, which requires it. - A toolchain you installed yourself, such as rustup’s
cargo, is trusted like a system one. The permission gate used to trust only root-owned programs, so on a rustup machinecargo testwent to review every time. A program now qualifies when you or root own it, nobody else can write it or any directory above it, and it is outside the workspace. The trade-off: any such program you own is trusted by ownership, so an agent that can write one there (with the shell or a build script) can have it run as a known tool. Edits through thewriteandedittools to~/.cargo/binand~/.rustup/toolchains(or$CARGO_HOME/$RUSTUP_HOME) always ask you, and replacing the resolved executable (or rustup proxy) invalidates an approval for a command. A toolchain update behind an unchanged proxy does not.
Fixed
navbuilds for musl Linux. Writing a flow file that must not replace an existing one now callsrenameat2through the raw system call, becausenixonly wraps it with glibc. The behaviour is unchanged: one atomic rename that fails if the file exists.
[0.7.7] - 2026-10-11
WithNav 0.7.7 is a maintenance release with no new feature. A pass over the audit backlog fixes edge-case bugs, makes the permission gate stricter on git, paths and shell syntax, speeds up a few hot paths, and makes the test suite faster and steadier.
Fixed
- git pushes that delete or force always ask the Captain, however they are spelled. This
covers deletion refspecs (
:branch,+:branch,--delete), clustered or abbreviated options (-fvd,--delet,--forc), global options beforepush(git -C . -c x=y push,git --config-env …), and escaped or expanded spellings (\git push,git pu\sh,git $'\x70ush',git "$p"). Metacognition can no longer approve them on its own. - A
git diffthat can run a program is no longer treated as a read. This covers--ext-diff,--textconv,-c diff.external=…,--config-env,--exec-path,core.pagerandGIT_*assignments in the command. - Shell syntax can’t hide what a command does. A carriage return or another invisible space
before
#, a redirection target with$or an escape, a relativePATHentry, or acd/pushd/builtin cdbefore the real command now send the command to review. - Protected config and credential paths are recognised in more spellings. This covers quoted
paths containing a space,
~login/.config/withnav/…(the home directory comes from the system account database),~+/…, repeated slashes after~, and paths followed by a shell comment. - Ordinary commands with a comment, and symlinked workspaces, no longer go to review.
git status # checkis read asgit status. A workspace opened through a symlink or a relative path can again prove that an overwritten file is recoverable from git. nav auth setno longer erases other providers’ keys. Whenauth.jsonis unreadable or has unsafe permissions, the command reports the error instead of replacing the file.- Streaming answers end cleanly. An event spread over many
data:lines can’t grow past the size limit, and a network error mid-stream ends the stream. - An agent no longer stays “Running” after an error. A provider failure, a cancellation or the switch to compaction returns it to idle.
- An invalid
WITHNAV_MAX_STEPSno longer means “no limit”. It logs a warning and caps the turn at 500 steps (50 for subagents). Unset still means no cap for the main agent. - A tool call missing a required argument is refused with a clear message. It no longer
reaches the permission checks first. Optional and
nullarguments are still accepted. fetchoutput no longer ends with�when converted HTML is cut inside a character. The fallback HTML flattener decodes entities once (&lt;stays<).readandglobwith hugeoffset/limitvalues no longer overflow.- Flow expressions compare integers and decimals exactly, including beyond 2^53.
- Reloading flows reports each validation error once instead of twice.
- Git status is correct when WithNav runs from a git hook or a worktree script. Inherited
GIT_DIR,GIT_WORK_TREEandGIT_INDEX_FILEare ignored. - The Settings API answers a malformed JSON body with a 400 and the parser’s reason.
- The history store no longer silently drops an event. A different event written at an already-used sequence number is refused. An identical retry is still accepted, and a subagent can’t be finished with an invalid status.
- Compaction and skills handle limits safely. An out-of-range compaction boundary or an exhausted summary counter is refused without a model call. A skill header exactly at the size limit is no longer reported as truncated. An oversized agent definition is reported as too large even when it isn’t valid UTF-8.
Changed
- Flow run history is cheaper. Each capture is appended instead of rewriting a log of up to 32 MiB. The Studio run list reads only the requested page: listing 20 runs from a 33 MB log drops from about 1.4 s to 6 ms. Retention is unchanged (1000 runs, 32 MiB, 7 days).
- Agent steps no longer copy the whole conversation when nothing is injected at the step boundary.
- Smaller speed-ups:
- the event bus shares envelopes instead of copying them;
- a session summary takes one database read;
- the conversation digest and the picker search avoid repeated work;
- flow graphs reuse their validated node index.
- The hand-run Instinct setup probe now delegates to
nav doctor --layer instinct, like the Reflex probe. Permission-corpus calibration remains available throughcargo run --example permission_corpus_probe -- [optional-alias] [--raw] [--fast-only]. - The test suite is faster and steadier.
- Fixed sleeps and polling loops give way to signals and virtual time, always with a bounded timeout.
- Duplicated fixtures move to
tests/common/. - A missing golden fixture now fails instead of being created.
nav doctorhas acceptance tests for every layer, legacy names, trust files, the VS Code extension and its read-only promise.
[0.7.6] - 2026-10-11
WithNav 0.7.6 removes the theme row from the VS Code Settings page, where it had no effect: the panel follows the editor’s theme.
Fixed
- The VS Code Settings page no longer shows a theme setting. The panel follows the editor’s
theme, so that row had no effect there. Change the terminal and WebUI theme from the TUI
(
/themes) or the WebUI Settings.
[0.7.5] - 2026-10-11
WithNav 0.7.5 lets ordinary work run without waiting on review. Instinct alone decides confident
reads and recoverable edits in the project, so metacognition only reviews risky calls, with a
smaller input and one configurable deadline. Printing sed and filename globs count as reads. The
bash tool works on NixOS, nav doctor checks your setup and models without changing anything, and
subagent controls in VS Code look like the rest of the panel.
Added
nav doctorchecks your configuration and models without changing anything. Inspect assignments, credentials, legacy names, workspace/flow trust and installed binaries, then make small bounded live layer calls.--offlineavoids all network requests;--jsonreports stable diagnostic ids without provider bodies or secrets.
Fixed
- Ordinary work no longer waits 20–40 seconds for metacognition. Instinct alone decides
confident reads and recoverable workspace edits, including regex grep, executable lookups,
scheduling wrappers, secure mise-style symlinks and ordinary
nixcommands. Selecting Nav’s offered “Fais-le” option establishes that task. Destruction, credentials, opaque execution, unsafe executables and outside changes keep their safeguards. - Printing
sedand filename globs are reads.sed -n 250,285p fileandgrep … tests/*no longer go to review.sed -iand scripts that write or run files still do, and a glob that would reach a key or credential file (cat .ssh/id_*) is still treated as sensitive. - The bash tool works on NixOS. It finds bash through
/etc/shellsand runs the resolved, root-owned binary; on other systems/usr/bin/bashis still chosen first. - Subagent controls fit the VS Code panel. Stop sits beside the name and state, with themed buttons and a clear keyboard focus ring. Goals take two lines with the full text in a tooltip, and internal delivery labels no longer appear below them.
- The wire golden test no longer needs a git tag. It checks each fixture’s SHA-256 against its manifest, so it passes on a fresh clone and in CI.
Changed
- Risk review is smaller and has one configurable deadline. Ten of eleven real reviews timed
out with 45–54 KB of messages; one answer took 40.4 seconds and 3817 reasoning tokens out of a
4096-token cap. Reviews now keep the latest 16 KiB of conversation and three tool calls (512-byte
output excerpts), preserve the configured
lowvariant, and allow up to 16384 output tokens.permissions.review_timeout_secsdefaults to 40 seconds (5–300); the second five-second attempt is gone. Timed-out changes still ask you.
[0.7.4] - 2026-10-10
WithNav 0.7.4 installs on NixOS from the repository itself: a flake builds nav and the VS Code
extension, and its overlay adds both to a system configuration.
Added
- A Nix flake at the repository root builds
navand the VS Code extension (nix build .#withnav .#vscode-withnav), and itsoverlays.defaultadds both to a NixOS system.Cargo.lockis now committed so builds are reproducible.
[0.7.3] - 2026-10-10
WithNav 0.7.3 stops permission prompts from piling up in real sessions. Plain reads run when
Instinct is sure they only read, wherever they read; fd redirections such as 2>&1 no longer look
like writes. Permission review follows the conversation and your selected skill, so finishing one
task and starting the next in the same conversation works, and the input sent to Instinct is sized
from its context window instead of 16 KiB. One binary now does everything: the editor server is
nav lsp, and the deprecated navai command is gone. You can also answer Nav’s questions with a
choice in the terminal, VS Code or the browser.
Added
- Answer Nav’s questions with a choice in the terminal, VS Code or browser. Question cards offer single or multiple choices, optional other answers and recommendation hints. Answers remain Captain messages; replay shows the chosen options without changing permission authority, and plain-text asks keep working as before.
Removed
withnav-lspis gone: the editor server is nownav lsp. The extension launchesnav lspand migrates awithnav.serverPaththat pointed atwithnav-lsptonavin the same directory, with a notice to update the setting. Delete the leftover~/.cargo/bin/withnav-lspafter reinstalling.- The
navaicommand is gone: runnav. It only printed a deprecation notice and startednav, but every build still linked it as a third 37 MB binary. Update any script or shortcut that still callsnavai, and delete the leftover~/.cargo/bin/navaiafter reinstalling.
Fixed
- Read-only work no longer asks just because it is outside the workspace. Instinct decides ordinary reads, including fd redirections and read-filter pipes; credential reads and real writes keep their safeguards. A Captain-approved file edit covers subsequent edits of that same file until your next message.
- Starting another task in the same conversation keeps working. Permission review sees the conversation and your selected skill, not the first greeting as a permanent task. Its input budget follows the Instinct model’s window instead of squeezing everything into 16 KiB.
- Local-instruction ordering tests no longer fail on random temporary paths. Checks compare complete labelled sections and source order, rather than matching short content tokens that can also occur in a file’s path. Instruction discovery is unchanged.
[0.7.2] - 2026-10-10
WithNav 0.7.2 makes permissions smarter and long conversations sturdier. Instinct now tells a local, recoverable change from a destructive or public one, so commits, builds, tests and reads no longer wait for review; the review sees the ongoing task, recent work and the scripts a command runs, and remembers what you already approved in the conversation. You can allow WithNav config edits for a whole conversation, and a user skill’s scripts once per machine. Compaction is written by the metacognition model in rolling chunks, after Instinct prunes old tool outputs. The VS Code panel renders full Markdown, keeps text and tool calls in order, opens file references, follows your editor’s font size, and shows a clear Safe mode / Bypass active indicator.
Added
- Allow a user skill’s script once on this machine. A deliberate permission
answer persists the skill location and each approved script’s SHA-256. Reviews
still run; changes, rejects and protected targets still ask. Grants can be
listed and revoked through
/v1/skill-grants; Settings controls are follow-up. - Permission reviewers can read the local script a shell call runs. The host attaches bounded, untrusted script text without following links or reading protected configuration or credentials. Complete evidence is SHA-256 checked before dispatch; changes ask you again.
- Allow WithNav config updates for the rest of a conversation. Protected-config requests offer a session-only answer in the terminal, editor and browser. Reviews still run; credentials, review settings and changed scratch roots still require your answer.
- Metacognition writes compacted conversation memory in rolling chunks. Summaries use the assigned model’s window and fall back to the session model on provider errors; configuration can opt out or tune chunk size. Compaction events report the writer and successful passes.
Changed
- Ordinary recoverable work takes the fast Instinct permission path. Edits, commits, builds and publication of your task branch are graded separately from destructive or shared changes. Bounded git facts, recorded routing thresholds and a 56-call corpus make decisions auditable; protected configuration and credentials still ask you.
Fixed
- Permission review follows the ongoing task rather than just the latest small talk. Review sees bounded human intent and recent work with provenance, retries one eligible timeout, and reuses typed Captain approvals for the same safe command and scope without overriding rejects.
- Instinct recognizes ordinary repository work without unnecessary review. Described choices and confidence checks on only the deciding answers restore the fast permission path; destructive, shared, sensitive and host-unsafe calls retain their existing review floors.
- Long conversations compact with relevance pruning and learned message limits. Instinct
scores only old, unpinned tool outputs before metacognition writes its rolling summary;
unavailable scoring leaves the existing path intact. Provider size limits survive new sessions,
and failed
/compactcommands no longer duplicate their error in VS Code. - VS Code answers show text and tool calls in the order they happened. Each model step keeps its own Markdown and reasoning; consecutive tools share a foldable progress group. Permission, pause and question cards stay visible, and replayed tool groups start closed.
- VS Code command results stay concise. Selection changes no longer dump JSON; unknown command data stays available in a closed Details disclosure.
- File references in VS Code answers open at the referenced line. Bare and inline-code paths become keyboard-accessible links; relative paths try every workspace root, and missing or outside-workspace files explain why they cannot open.
- VS Code answers render GitHub-flavoured Markdown safely. Tables, quotes, nested and task lists, strikethrough and hard breaks render without interpreting raw HTML. Streaming redraws are coalesced per animation frame.
- Permission mode is visible without cluttering your conversation. Safe mode and Bypass active replace the ambiguous toggle labels; mode changes update the header without adding transcript lines in the editor, browser or terminal.
- Cognition and Settings text scales with your editor. Secondary text scales too, and conversation, tool and card content uses the readable theme foreground. Sidebar conversations retain the sidebar surface; child editor tabs use the editor surface.
[0.7.1] - 2026-10-09
WithNav 0.7.1 lets you delete a saved session, with its subagent sessions, from the terminal, VS Code and the WebUI. The WebUI now fits tablets and phones, and the VS Code panel shows in colour what waits on you, how subagents are doing and how big a change is. The decision layer is named Instinct everywhere, and about forty fixes land across tools, the terminal client, the language server, context budgeting and providers.
Added
- Delete saved sessions from the terminal session picker. Press
ctrl+d, then confirm permanent deletion with the attached subagent count. The list refreshes near the same row, and tabs for the deleted session and its subagents close. - Delete VS Code conversations from the command palette. Choose a workspace conversation, then confirm permanent deletion including its subagent count. The chat button uses the same confirmation; deleted conversations reset to an empty chat and their subagent views close.
- The WebUI can delete a session, with its subagent sessions. Each session in the sidebar has a delete button (shown on hover or focus; the Delete key works on the row), and the command palette has “Delete session…” for the open one. The confirmation lists the subagents that go with it and says it cannot be undone; deleting the open session moves to the next one.
Changed
- Permission review and supervision now use the Instinct layer name.
New events write
instinctsource, model, latency and guard metadata; clients still read storedjevevents and display them asauto · instinctuntil 0.8. - The VS Code panel colours what waits on you, how subagents are doing, and how big a change is.
Pause cards and pending permissions get the warning colour; a subagent’s state reads blue while
running, amber when it needs you, green when done, red when failed; a wake shows
+N −Mand its diff lines in your theme’s git colours. All from VS Code theme colours, AA contrast in Dark+, Light+ and High Contrast.
Fixed
- VS Code’s layer protocol now includes variant discovery diagnostics. The optional server field is represented in the extension’s types instead of being omitted from its contract.
- Unexpected VS Code chat message errors are reported instead of escaping the dispatcher. The host boundary now catches synchronous failures as well as the existing asynchronous errors.
- The example default agent has an explicit context window. Startup preflight no longer needs online catalogue discovery to determine its capacity.
- Brace glob alternatives can explicitly search normally skipped folders.
Patterns such as
{src,target}/**/*.rsnow entertarget, while broad wildcard alternatives still leave hidden and generated folders skipped by default. - Model variants cannot use the reserved name
inherit. Catalogue validation rejects a name that the CLI and commands interpret as selecting the model’s default variant. - Oversized model catalogue cache files are ignored without reading them whole. Disk reads are bounded to 2 MiB plus one byte, preventing corrupted caches from allocating unbounded memory.
- Provider error bodies are bounded and cancellable. Chat and non-streaming failures read at most 64 KiB, and Stop interrupts a slow error response rather than waiting for its end.
- Importing OpenCode models no longer requires an OmniRoute provider. Direct compatible providers can be imported; OmniRoute’s catalogue format is attached only when present.
- Invalid grep expressions return the same error on every host.
The ripgrep engine validates expressions before starting its process, returning
invalid_regexjust like the built-in fallback rather than a generic command failure. - Fetch explains how redirect query parameters are handled. A redirect to a new path without a query drops the original query under standard URL resolution; parameters, including credentials, are never implicitly forwarded.
- Closing a file now frees what inline completion kept for it. The completion service remembered every file it had ever completed in until the server stopped, so long editor sessions grew without bound. Closing a file now drops its entry and cancels any completion still in flight for it.
- Every folder of a multi-root VS Code workspace now counts as part of the workspace. The language server only knew the first folder, so the navigator’s read and search tools refused files in the others. It now knows all of them, follows folders added or removed while it runs, and keeps the first folder as the anchor for relative paths, the intent record and flows.
- The TUI header fits narrow terminals. In a split pane the agent, model and gauge ran past the
right edge and were cut off. The row now gives way in order (path, model, variant, branch,
gauge, pending switch, then
withnavshrinks to◆, then the model_id); the agent stays visible. - Pickers stay inside small terminals and stay usable. On a very short or narrow pane the model,
command or theme picker drew past the screen edge with overlapping rows. The popup now fits the
screen; as it shrinks it keeps the highlighted row first, then the filter, then
esc. - Sending a message from VS Code no longer breaks on a conversation id with special characters. The extension built the message URL from the raw id while every other call encoded it, so such an id produced a malformed request and a 404.
editnow refuses an emptyold_stringinstead of scrambling the file. An empty search text matches between every character, soreplace_allinterleaved the new text through the whole file and a single replace failed with a confusing “ambiguous match”. The call is now rejected up front.- One unreadable conversation no longer empties the session list. A session whose stored
metadata cannot be decoded made
GET /v1/sessionsfail with a 500, so the picker showed none of the workspace’s healthy conversations. The list now skips it and logs a warning. - Inline completion works on files with Windows line endings and at the end of a file. The cursor position was converted to a byte offset assuming one byte per line break, so in a CRLF file the code sent to the completion model drifted one byte per line; and a cursor on the empty line after a final newline got no completion at all.
- Remarks survive explanatory JSON before the final answer. The navigator skips unrelated objects in prose and code fences and uses the last answer-shaped object, while still rejecting malformed lines and empty remark text.
- Verbose shell commands drain excess output in larger chunks. Once the output buffer fills, larger discard reads reduce reader overhead without artificial delays or closing the pipe. Retained bytes never exceed the buffer cap.
- Shell cancellation only signals positive process group ids. Child ids are checked before conversion, and termination rejects nonpositive ids so an invalid id cannot signal WithNav’s own process group.
- The terminal client no longer fails to load skills or toggle bypass for a session whose id needs escaping. Those two requests put the id in the URL path as-is while every other one escaped it, so an id with a space or a slash gave a 404.
- VS Code keeps watchdog-paused subagents continuable without a root pause token. Removed an unreachable tooltip that incorrectly implied child continuation required a token; the server accepts tokenless child continuation.
- The intent tool can clear an obsolete section with an empty string. An explicit empty update now removes the previous text; omitted sections still retain their contents.
- Interrupted terminal turns no longer retain unfinished tool arguments. Turn and session boundaries discard streamed fragments so a later turn starts with an empty buffer.
- “Allow folder” grants from two conversations no longer cancel each other out. Two sessions
of one workspace saving a grant at the same moment could each overwrite the other’s entry in
grants.json, so a folder you had allowed asked again after a restart. Saves are now serialised with a lock file next to it. - Metacognition preserves home-relative and platform-absolute tool paths. Only ordinary relative paths are anchored to the workspace; home expansion and workspace confinement remain the responsibility of the shared path validator.
- The WebUI now fits tablets and phones. Below 1100 px the Intent / Crew / Extensions panel opens from a Details button in the top bar; below 700 px the page is one column and the workspace list opens from the ☰ button. Settings and the Studio work the same way. Esc closes a side panel and puts focus back on its button. The desktop layout does not change.
- Subagents no longer pause merely because instinct is unconfigured. A watchdog without a judge skips advisory checks; configured judges still escalate repeated failures, and the step cap remains enforced.
- Deleting lines now moves the edit point the navigator reports. A pure deletion left the “last edited line” on the previous edit, so remarks and context excerpts looked at stale code. The deletion point is now used, and the edit history records it.
- Creating files avoids resolving the same permission root twice. Path validation reuses its canonical ancestor when that ancestor is an allowed root. Other roots are still resolved freshly so changed symlinks cannot retain stale permissions.
- Relative file destinations can name folders that do not exist yet. Path validation anchors relative destinations to the first allowed root before resolving existing ancestors, while still refusing paths that escape the allowed roots.
- Metacognition call logs retain provider-reported token usage. Successful chat rounds now record input and output counts; tools and responses without usage keep these fields unknown.
- Code-heavy conversation summaries no longer exceed their cap during escaping. Angle brackets become same-size square brackets, keeping generic types and HTML summaries bounded without allowing summary text to close its data wrapper.
- A model with an 8k context window can be assigned again. The safety margin was never below 4096 tokens, so an 8k model with a 4k output limit was refused and one with the default reserve kept only a quarter of its window for input. Windows under 32k now use an eighth of their size as the floor (1024 tokens at 8k); windows of 32k and above are unchanged.
- You can switch off a failing model even when the conversation is too big for the new one.
/modelsummarises the conversation with the current model before moving to a smaller window, so a revoked key or a provider outage left you stuck on the broken model. When the current model cannot answer, the model you are switching to now writes the summary. It cannot rescue a conversation whose text that cannot be cut down (messages,askanswers, loaded skills; only other tool outputs can be) is larger than the new model’s window: the switch is then refused. - An OpenCode agent file named like a former WithNav agent no longer replaces a crew member.
~/.config/opencode/agents/explorer.md(orlibrarian,oracle,explore,review) configures OpenCode’s own agent; WithNav read it as the former id of Mousse, Vigie or Lux, which lost its built-in prompt. Those files are now skipped. The former ids still work in~/.config/withnav/agents/and.withnav/agents/.
[0.7.0] - 2026-10-08
Navai is now WithNav. Nav is your single point of contact, with a crew behind it: Vigie scouts
and cites its sources, Lux weighs hard decisions, Mousse handles small local edits, and the
Cartographe and the Sondeur come on demand. You are the Captain. The CLI is nav, the language
server withnav-lsp, the extension pixmants.withnav, and Tide Dark is the default theme. Your
config and data move to withnav on first start; every old name, directory, variable and agent id
keeps working with a warning until 0.8. This release also brings InstinctFlow: draw the flows the
instinct runs, in the new Studio, and the legacy decision pipeline is gone.
Rebrand to WithNav
Write new names throughout 0.7; legacy readers warn or give a migration hint. Removal of the deprecated names is planned for 0.8 (ADR 0014).
- Product and crate: Navai → WithNav (withnav.dev), crate
rust-agent→withnav. No old crate alias. The GitHub repository moves togithub.com/PixmaNts/withnav(the old URL redirects); the internalsrc/navigator/module stays. - Binaries:
navai→nav(deprecatednavaialias prints a notice and runsnav);navai-lsp→withnav-lsp(update the configured server path; no old LSP executable alias). - Project directory:
.navai//.navigator/→.withnav/, includingintent.md,agents/,flows/and legacypipeline.toml/prompts/. When.withnav/is absent, read.navai/then.navigator/. New Mermaid charts go in.withnav/charts/. - Config/data directories:
~/.config/navai/→~/.config/withnav/and~/.local/share/navai/→~/.local/share/withnav/. Move legacy files on first start without overwriting new ones; existing olderrust-agent/navigatormigrations remain. - Scratch paths:
$XDG_RUNTIME_DIR/navai,~/.cache/navai/scratchand<temp_dir>/navai→ the correspondingwithnavpaths, retaining old-path compatibility in 0.7. - Data filenames:
navai.db→withnav.db,navai.jsonl→withnav.jsonl; move on first start when only the old file exists, keeping database sidecars together. - Environment:
NAVAI_*→WITHNAV_*, with the old variable read and warned about when its new counterpart is unset. This includesNAVAI_CONFIG_DIR,NAVAI_SERVER,NAVAI_SERVER_TOKEN,NAVAI_TOKEN,NAVAI_WORKSPACES,NAVAI_MAX_STEPS,NAVAI_BASH_GRACE_MSandNAVAI_HTML_CONVERTER, each becoming the same suffix underWITHNAV_. Provider-specific API-key variables are unchanged. - VS Code:
vscode-navigator/→vscode-withnav/, extension idpixmants.navai→pixmants.withnav, view containernavigator→withnav; install the new extension (no duplicate folder or extension-id alias). Settingsnavai.*→withnav.*, falling back when the new setting is unset. Commandsnavigator.*/navai.*→withnav.*; use new command ids. The previously removednavai.modelAliasstays removed. - LSP:
navigator/serverInfo,navigator/remark,navigator/clear,navigator/status,navigator/feedback,navigator/diagnostics,navigator/intent,navigator/reflexShownandnavigator/inlineCompletion→ the same suffix underwithnav/. The server accepts both prefixes for requests/notifications and sends onlywithnav/*; clients use the new prefix. - Human and wire values: driver → the Captain;
driver→captainin event authors,approved_by, permission-gatesource,origin,selected_byand conversation digest authors. Readers accept both; new records writecaptain. - Flow action:
ask_driver→ask_captain; old TOML actions still parse. - Agent ids:
buildandplanremain Nav;explore/librarian→vigie,review/oracle→lux,explorer→mousse. Old ids resolve as deprecated aliases with warnings. Newcartographeandsondeurare on demand, with no old aliases. - Crew and labels: Nav · The Navigator, Vigie · The Lookout, Lux · The Expert Advisor, Mousse · The Lightweight Deckhand, Cartographe · The Cartographer and Sondeur · The Deep Diver. Metacognition remarks are Beacons signed by Nav. Layer technical names stay unchanged: Reflex · Completion, Instinct · Triage, Metacognition · Beacon, Cognition · Nav and the crew. Feature labels are Logbook · Activity history, Voyage · Session, Safe Harbor · Checkpoint.
- Default theme: Tide Dark when no theme choice is saved; saved choices remain intact.
Added
- InstinctFlow. Draw flows the instinct runs on WithNav’s events: a trigger, decisions
(noul, choice, score), expressions and actions, wired as a graph. Flows live in
.withnav/flows/(after you trust the workspace) or~/.config/withnav/flows/; Save writes the file and Reload makes it run. A blockingtool.pre_callflow can deny a tool call or ask you, never allow one past the normal permission review, and bypass never answers it.message.receivedandturn.endflows can inject a note or wake the cognition, marked as coming from a flow. See the Captain’s guide. - The Studio (
#/studioin the browser): canvas and TOML views of the same file, an inspector per node, Run sample against the instinct, Replay of recorded runs without a model call, and 24-hour health per flow. instinct-flow, a standalone engine crate and command (validate,run,replay) that runs without WithNav, andnav flows catalogue, which prints WithNav’s triggers, inputs and actions.
Removed
- The legacy navigator decision pipeline and fallback evaluator. Built-in editor flows now own
triggers exclusively; custom
pipeline.tomlfiles are converted once with originals retained. Offline replay uses flows only (--flowsremains accepted;--pipelineand--reaskare removed).
[0.6.1] - 2026-10-07
Navai gets a bypass mode: one session can skip ordinary permission prompts, from the terminal,
VS Code, the browser or navai run --yes, while Navai’s own configuration, credentials and keys
still ask you. The browser sidebar can now collapse every workspace and hide one, the transcript
uses one column, VS Code’s panel uses theme colours, and two permission gaps are closed. No
configuration change is needed to upgrade from 0.6.0.
Added
- Bypass ordinary permission prompts for one session. Turn it on with
/bypass on,navai run --yes, orPOST /v1/sessions/{id}/bypass:write,edit,bashandfetchcalls that would have asked run once instead, after the usual reviews, and the result saysapproved_by: bypass. A request already waiting is answered too. Requests for Navai’s own configuration, credentials and keys still ask you, andnavai run --yesstops with exit code 3 on one. It is off for every new or resumed session, never saved, and a session’s subagents follow it. It is not a sandbox:bashkeeps its access to your machine. - Bypass in the terminal. Alt+P (or
/bypass on//bypass off) turns bypass on or off for the open session. While it is on,⚠ bypassstays in the header and next to the tab, and each change appears in the conversation. Protected requests still ask. - Bypass in VS Code. “Navai: Toggle Bypass Permissions” (or the button in the Cognition panel header) turns bypass on for the open session, after a confirmation the first time. While it is on, a warning badge stays in the header, and each change appears in the conversation.
- Bypass in the browser. The “Bypass” button next to build/plan (or the command palette) turns bypass on or off for the open session. While it is on, a warning badge stays on the session in the sidebar and in the breadcrumb, and each change appears in the conversation. Protected requests still ask.
- Remove a workspace from the browser list. The
…menu of a workspace (or the command palette, for the open one) offers Remove from list, which keeps its sessions, and Delete workspace sessions…, which deletes every session of that folder with its subagents after you confirm the count. A removed folder stays out of the list after a restart and comes back when you open it again with “Open a folder” or start a session there. Folders passed tonavai webon the command line stay in the list while that server runs.
Changed
- VS Code’s Cognition panel uses theme colours to distinguish build and plan, tools, Navigator wakes, subagents and permission outcomes; Settings highlights saved changes in the same style.
Fixed
- Responses no longer stop midway when a model gateway sends
nullinstead of an empty list of tool calls or response choices. - Browser transcripts use one column. Driver messages and cognition replies share the full width, with bylines and a quiet Driver accent instead of right-aligned chat bubbles.
- Every workspace can collapse in the browser sidebar. Carets expand without switching workspaces, names navigate without reopening collapsed rows, and the browser remembers which folders are expanded. Collapse all and Expand all are also available in the command palette.
- Network commands always get a review. A bash call that runs
curl,wget,ssh,scp,sftp,rsync,nc,ncatorsocatis now treated as reaching outside the project, so it always goes to permission review instead of possibly running unreviewed. - A symlinked folder no longer slips past the Markdown exception. Writing a
.mdfile through a folder that is a link (for examplenotes/pointing elsewhere) now asks like any other write outside its real place; before, only a link on the file itself was caught.
[0.6.0] - 2026-10-07
Navai gets a browser interface and a Settings screen in every client. navai web opens the chat in
your browser; Settings lets you choose the model of each layer and agent, permissions, subagent
limits and the theme from the browser, the terminal (/config) or VS Code, all writing the same
config.json. No configuration change is needed to upgrade from 0.5.1.
Added
- Settings in VS Code. “Navai: Open Settings” (or the gear in the Cognition view) opens a
Navai Settings tab laid out like VS Code’s own settings editor: the same sections, saved at once
to
config.json, with a filter for modified settings and a link to the extension’s ownnavai.*settings. It still opens when the model or agent list cannot load, offers Open Folder when no folder is open, and Retry when the language server is not running. - Settings in the terminal.
/config(or the palette) opens Settings in the TUI: gateways (read-only,ttests the connection), the model of each layer and agent and its variant, the default agent, automatic review and scratch directories, subagent limits and watchdogs, and the theme. Changes wait until ctrl+s, which writes them toconfig.jsonone by one; if the file changed elsewhere it reloads and keeps your unsent changes. Leaving with unsaved changes asks write, discard or stay. - Settings in the browser. In
navai web, the gear in the top bar (or “Open settings” in the command palette) opens Settings: gateways (read-only, with a connection test), the model of each layer and agent and its variant, the default agent, automatic review and scratch directories, subagent limits and watchdogs, and the theme. Each change is checked against the catalogue and written toconfig.jsonat once; a change made elsewhere in the meantime is reloaded instead of overwritten. Layer models apply after restarting Navai, the rest to new sessions and subagents. API keys are never shown, only where they come from. - Favorite models. In both model pickers, Ctrl+F (or the star in VS Code) marks a model as a favorite; favorites are listed first in an always-open section. The terminal and VS Code keep separate lists.
- A chat in your browser.
navai webstarts Navai on a private local address, prints a link and opens it. The page lets you chat in any of the workspaces you pass with--workspace(repeatable; the current directory by default) and in the others you have worked in before; each one shows its git branch and whether it has changes, and a workspace’s intent record (.navigator/intent.md) can be read. Use--portto choose the port and--no-opento print the link without opening it. The link carries a secret that is new on every run and only works on this computer; press Ctrl-C to stop.
Fixed
- Two Navai windows no longer undo each other’s settings. The terminal’s theme and favorites
saves and every Settings change now take a lock on
config.json, so a save from one window cannot silently put back a value another window has just written.
[0.5.1] - 2026-10-06
Fixed
- Model pickers show the hierarchy. In the terminal
/modelpicker and the VS Code chat picker, gateways, providers and models are now indented by level. Search, collapse, keyboard navigation and API filtering are unchanged. - Fold or unfold every group at once. In both model pickers, Alt+A folds every gateway and provider, and pressing it again unfolds them.
[0.5.0] - 2026-10-05
Models now have explicit gateway-qualified names, while layers and agents keep their own choices. This is a breaking configuration and API change: migrate existing configuration by hand before starting this version. It does not convert your configuration automatically or create agent definitions.
Changed
- A catalogue, not aliases.
models.jsondescribes gateways, models, APIs, capabilities, limits and variants. Use names such asomniroute/combo/gpt-6-astra:omnirouteis the gateway andcombo/gpt-6-astrais the exact upstream id. Bare ids and the olddefault,fastandcognitionaliases are no longer supported. API selections and context/usage metadata usemodel,gatewayandupstream_id, notalias. - Assignments live in
config.json.layersselects one reflex (fim), instinct (systemone) and metacognition (chat) model; cognition runs agents rather than one singleton model.agentsassigns their models and variants, taking precedence over Markdownmodel:headers.default_agentisbuildwhen absent. An invalid default agent or unusable default chat model stops server/LSP startup with a clear error; an invalid nondefault assignment warns and falls back. Missing or incompatible lower layers are disabled with reasons and retain the existing fallbacks. - Editor context is not an agent role. Editor sessions add the pair-programmer prompt, intent tool and navigator conversation feed to the chosen agent. They start on the same default agent as terminal sessions; children do not inherit the editor attachment.
- Model choices stay with each agent in each conversation.
/model <gateway/id>changes the current agent’s model in place;/agent <id>restores that agent’s saved choice. Model and variant overrides survive reopening and never edit global assignments./new [agent]starts fresh without the old session’s overrides. Unassigned children inherit their parent’s effective model and variant; children with their own model assignment use it instead. - Variants have three choices: a name,
off(explicitly no variant), orinherit(remove the local variant choice). Changing model takes the destination model’s default variant. Metacognition preferslowwhen offered. The importer gives OmniRoute combo modelslow,mediumandhighrequest-body variants, not model-name suffixes, and preserves manually declared variants. - Model pickers in the terminal and VS Code group by gateway, then upstream provider. Groups
collapse, but search includes models inside collapsed groups. New conversations choose an agent
first, with an optional model override. The terminal’s right panel and VS Code’s header tooltip
show lower-layer assignments and the current agent’s choice, including override markers.
The old
navai.modelAliassetting is removed.
Added
- Readable archives for history whose model identity cannot be proved. These conversations remain available to read, with a reason why resume is disabled; they are never silently attached to a different model. Existing session ids are preserved. This is history handling, not automatic configuration migration.
- Assignment metadata for clients:
GET /v1/agentsreports configured/effective choices and, with a session id, local overrides;GET /v1/layersreports the three lower layers and disabled reasons. AlongsideGET /v1/models, these expose canonical identities and choice sources, not endpoint URLs, headers or credentials. - Example
oracleandlibrarianagents, with matching catalogue entries and assignments. Install their Markdown definitions separately; an assignment alone does not create an agent.
Fixed
- Agent changes apply the destination policy consistently: its prompt, tools, skill availability and context budget are measured together. Deferred selections cannot land on a different agent, and a refused change leaves the current choice intact.
- Compaction needed for a selection change is saved atomically with that change, including the agent, model, variant, per-agent overrides and ordered events. A failed save or cancellation before commit leaves the previous selection and checkpoint intact; resume restores the committed state.
[0.4.7] - 2026-10-04
Fixes from trying 0.4.6 in VS Code: subagents work there, reading a file outside the project is reviewed instead of refused, lists are searchable and compaction shows its progress.
Fixed
- VS Code conversations can start subagents. The agent of a VS Code conversation now has the
tasktool (andsteer_subagent,decide_subagent), like the terminal client. Subagents’ results reach the conversation as before; they never become the navigator’s conversation. - Reading secrets is never approved on Jev’s word. A read-only call that is risky, such as
reading a key or a credentials file, now goes to metacognition and then to you. Reading
~/.ssh,~/.gnupg,.envfiles,*.pem/*.key, cloud credentials or Navai’sauth.jsonwith thereadtool always asks you.grepandglobskip these files (without opening them) and say how many they skipped. Forbash, this detection is best effort. - VS Code lists are searchable. The model, skill, variant, command and conversation lists open
with a search field that matches names, model ids and descriptions, shows the number of matches
(“12 of 300”) and is chosen with the arrow keys and Enter. The
/list filters from the composer as you type. Choosing a model never touches your draft. - VS Code compaction no longer looks frozen.
/compactshows “Compacting the conversation…” until it ends or fails. You can keep writing and sending: your messages wait for the end of the compaction.
Changed
readoutside the project is reviewed likebashinstead of being refused: if Jev, metacognition or you approve it, it reads that one file once.grepandgloboutside the project are still refused, with a message pointing toreadorbash.- A
bashcommand that writes into Navai’s config directory (~/.config/navai) always asks you, even with options hidden in quotes, escapes or braces; only plain reads such ascat,lsorwcthere can be approved automatically. - “Allow folder” is only offered for
fetch; read, write, edit and bash requests offer allow once or deny. - Directory
grepuses ripgrep as before (fast, respects.gitignore); without ripgrep, its fallback is limited in size and time and can be cancelled. More than 1000 matches is reported as a partial result.
[0.4.6] - 2026-10-04
The VS Code chat gets the terminal client’s controls: slash commands, subagents and the context gauge. The server is unchanged.
Added
- VS Code chat: slash commands. Type
/to open a list of commands with their descriptions (arrow keys, Enter or Tab, Esc, or the mouse)./modelswitches the open conversation’s model, and the model picker now works on an open conversation too; while the agent is answering, the switch waits for the end of the turn and shows as pending. Also/variant,/agent(the current agent is shown near the composer),/compact,/context,/stop,/newand/help./skillslists the conversation’s skills;/skill <name> <request>sends your request with that skill. A command is never sent to the model as text, nor with the editor context;//textsends/textas a message. A refused command or skill keeps your draft. - VS Code chat: subagents. A Subagents section under the conversation lists each subagent with its goal and state. Open shows a subagent’s conversation in its own read-only editor tab, without leaving or changing your conversation. Stop and Continue (out of steps, or paused by the watchdog) ask for confirmation first. A subagent’s permission requests are still answered in the parent conversation.
Changed
- VS Code chat: the context meter shows what the next request will send, like the terminal
client (
~42k / 1M · 4%), instead of the last call’s usage. Hover or focus it for the breakdown (system, rules, skills catalog, tools, messages), the input budget, the output reserve and the provider’s count for the last request.
Fixed
- VS Code chat: commands, model switches and subagent controls always act on the conversation they were started from, including after switching conversations, a failed open, starting a new one or a server restart; a sent message always clears your draft, and a control is sent only once.
[0.4.5] - 2026-10-04
The VS Code chat can now answer the agent’s permission requests and continue a paused agent.
Fixed
- VS Code chat: permission requests can be answered. Before, the chat ignored them: a
conversation that needed your approval stayed blocked, and typing a message cancelled the
request. A card now shows the agent, the tool, the full reason and the call (the whole command;
a write or edit with its path, options such as
replace_all, and its content up to 4096 bytes), with Allow once and Deny (and an optional message). Subagents’ requests appear in their parent conversation. Answered, denied and expired requests stay visible when you reopen the conversation. Sending a message while a request is open asks you first, since it cancels it. - VS Code chat: subagent output is no longer shown as yours. Only your own messages are shown as you; a subagent’s result is labelled as untrusted data. A skill you selected shows beside your message.
- VS Code chat: paused agents can be continued. “Out of steps” offers Continue; a watchdog pause shows its reason with Continue and Stop. Refusals show the server’s reason, and pauses from a run that has ended stay as history, without buttons.
- VS Code chat: buttons always act on their own conversation, including after switching conversations, a failed open, a server restart or reopening the panel; an answer is never sent twice.
Added
- VS Code chat: tool rows show who approved a reviewed call (Jev or metacognition), with the reason and Jev’s verdicts in an expandable note, like the terminal client.
[0.4.4] - 2026-10-04
The per-call review now treats your own request as what you want, and the terminal client shows who approved each call.
Changed
- Your request counts in the permission review: Jev and metacognition receive your latest request as your intent, so a write you explicitly asked for (for example a file in your home folder) can be approved instead of refused as “untrusted”. Its text is never an instruction to the reviewers, and claims made by the agent, a subagent or a tool still prove nothing. Navai’s config directory still always asks you.
- Incomplete requests never authorize: your request is sent to the reviewers in full when it
fits; when it does not, every reviewed
bash,writeandeditcall asks you. gitcommands stay reviewed as changes: evengit statusorgit diffcan run programs configured in the repository.
Added
- Terminal client: a tool block says who approved a reviewed call (
auto · Jev,auto · metacognition,driver approved); expanded, it shows the reason, Jev’s verdicts and the plan role check. Copies include it. - Review evidence: the permission review records Jev’s verdicts, the deterministic checks that tightened the call, the reviewer models and their response times on the tool and permission events, also when you approved the call yourself.
Fixed
- Four tests that failed now and then on a loaded machine; when
git statusfor the Changes panel passes its deadline, Navai now ends and reaps the process itself.
[0.4.3] - 2026-10-04
Fixes from trying 0.4.2’s per-call permission review, including a security fix: metacognition could approve writes into Navai’s own configuration.
Fixed
- Security: writes and edits into Navai’s config directory (
~/.config/navai:auth.json,models.json,config.json, agent definitions) always ask you; metacognition can no longer approve them. This also holds through symlinks and when the directory does not exist yet. Abashcommand that names the config directory also asks you (best effort:bashis not confined). - Metacognition review no longer fails when the model reasons at length before answering: its answer budget is 4096 tokens. When it does fail, the permission request says why (timed out, unavailable, input too large, malformed answer and how, or output exhausted by reasoning), and a short excerpt of a malformed answer goes to the decision log. A reviewer answer with a duplicated field is rejected.
writeandeditoutside the project are reviewed likebashinstead of being refused: if approved (by metacognition or by you), the call runs once, for that exact file, without opening the folder to later calls.- Tool errors always carry a message.
- Terminal client: permission requests can be selected and copied like other messages, in full
(whole reason and command, no borders or truncation; a write’s content up to 4096 bytes). While a
request is open,
ystill allows it: drag with the mouse to copy it;ycopies an answered one.
Added
- Scratch folders: the agent can write temporary files without asking in two folders available
in every project: a private one,
$XDG_RUNTIME_DIR/navai(or~/.cache/navai/scratch), used first, and the shared/tmp/navai. Each must be a real directory owned by you with mode 0700; a folder that is replaced, made public or reached through a symlink loses this exemption.permissions.scratch_dirsinconfig.jsonsets the list of absolute paths;[]turns them off.
[0.4.2] - 2026-10-04
Every file change and shell command is now reviewed one by one, conversations no longer stop after 50 steps, skills can be picked from a list, and the conversation can be selected and copied.
Added
/skills: opens a searchable list of the conversation’s skills. Choosing one fills/skill <name>; add your request and send: the skill’s instructions are loaded into your message before the agent’s first model request, and the selection shows next to your message, also on replay. If the agent cannot use skills, the whole message is refused.- Select and copy the conversation: drag with the mouse to select conversation text, or
double-click a word; releasing copies it. Wrapping, the sidebar, borders and tool labels stay out
of the clipboard. A selection across several messages names each sender (
You:, the agent’s name,Navigator:…), once per run of consecutive messages; a selection inside one message copies only its text. With the conversation focused,ycopies the selection, or the whole selected message or tool output;ctrl+shift+cworks where the terminal sends it, andctrl+cstill stops. The wheel scrolls the conversation, and the lists of/model,/sessionsand other pickers. Copying uses OSC 52, so it works over SSH when the terminal supports it; under tmux setset -g set-clipboard on.shift+dragkeeps the terminal’s own selection.
Changed
- Per-call permission review: every
bash,writeandeditcall is reviewed before it runs, instead of an “allow folder” answer letting every later call through. Jev classifies the call: a read-only call, or a low-risk change inside the project, runs; a risky or outside-project change needs metacognition’s approval; anything else asks you, with the reason. “Allow folder” is gone for these tools and the bash/file grants saved by 0.4.1 are ignored;fetchkeeps its origin grants.permissions.automatic_review: falseinconfig.jsonasks for every such call; an invalid value fails closed. The review is advisory, not a sandbox. - No more 50-step limit: a conversation’s turn runs until it ends, is stopped, or the watchdog
stops it. Every 5 minutes (
agent.watchdog_interval_secs) Jev checks it is making progress. Stuck: the turn stops after its current tools finish, keeping the work done. In doubt: metacognition decides; if it hesitates too, the turn pauses and asks you (alt+ccontinues, a new message replaces the paused turn). With the watchdog off (0) or Jev unavailable, a 500-step cap applies, renewed only by you.NAVAI_MAX_STEPSstill sets an explicit cap. Subagents keep their 50-step budget.
Fixed
greptool: a pattern can no longer be read as a ripgrep option (such as--pre, which runs a program), and ripgrep configuration files are ignored.
[0.4.1] - 2026-10-04
Fixes from daily use of 0.4.0, mostly in the terminal client.
Changed
- Folder grants persist: an “allow folder” answer is saved per workspace in
<data dir>/grants.jsonand applies to later sessions in that workspace. Subagents keep theirs in memory. Delete the workspace’s entry in that file to revoke.
Fixed
- Terminal client: running out of agent steps shows “Out of steps · send a message to continue”, no longer a question awaiting an answer.
- Terminal client: a multiline draft grows to 20 lines, then scrolls with the cursor, and leaves room for the conversation on a small terminal.
- Terminal client: arrow keys and Home/End move the cursor in a draft, including between lines.
- Terminal client: a 1M context window no longer shows as
1000k. - Terminal client: moving or resizing the terminal window no longer closes the client with “cannot draw the screen: The cursor position could not be read within a normal duration”. The repaint after a resize no longer asks the terminal for the cursor position.
[0.4.0] - 2026-10-04
Background subagents: a conversation’s agent can delegate work to other agents that run on their own, report back, can be steered or interrupted, and are watched by Jev. The terminal client shows them under their conversation.
Added
- Subagents: a conversation’s agent can start a subagent with the
tasktool (task(agent, prompt, delivery?), for agents whose mode issubagentorall, such asexploreandreview). The subagent is a separate conversation that runs in the background; the tool answers at once with its id. At most 4 run per conversation (subagents.max_concurrentinconfig.json). A subagent gets the workspace, rules and skills but not the parent’s history, runs on the parent’s model and variant unless its definition names one, never getsaskortask, and cannot write when it or its parent is read-only orplan. - Results back to the parent: a finished subagent’s result is added once to the parent
conversation as a bounded message marked as untrusted subagent output, and published on the
parent’s stream as
message.receivedwithsource: "subagent". By default it arrives at the parent’s next turn;delivery: "steer"adds it to the running turn at its next safe point, anddelivery: "interrupt"restarts the parent’s turn on it. A parent waiting for your answer is never interrupted, and your own queued messages always come first. An idle parent starts a turn as soon as a subagent finishes. Results survive a restart and the deletion of the subagent. - Steering a subagent: the parent’s agent can use
steer_subagent(child_id, message, mode).steeradds guidance to the subagent’s running turn before its next model request, after all the tool results of its current step;interruptcancels that turn and starts a new one with the message, keeping the same subagent. - Jev watchdog: every 5 minutes (
subagents.watchdog_interval_secs, 0 turns it off) Jev checks each running subagent from its goal, how long it has been quiet, its last tool call and its step count.stuckstops it and hands its partial work to the parent;doubtpauses it and asks the parent’s agent, which answers withdecide_subagent(child_id, continue|stop). Jev failing to answer twice in a row counts as doubt. - You stay in control: a subagent’s permission requests appear in the parent conversation and
are answered there; approvals stay with that subagent. You can read and stop a subagent but not
write to it.
POST /v1/sessions/{parent}/children/{child}/continuegives a subagent that ran out of steps one more budget, or releases a watchdog pause. Stopping a conversation stops its subagents; deleting it deletes them. - Subagents in the terminal client: subagents are listed under their conversation with their
goal and state (running, waiting for permission, out of steps, in doubt, done, failed, stopped).
alt+sopens a searchable list, also below 80 columns;enteropens a subagent’s conversation read-only,alt+wgoes back. Stop andalt+c(continue) ask for confirmation. A finished subagent shows as one line in the parent conversation. - Events:
subagent.spawned,subagent.doneandsubagent.watchdogon the parent’s stream; the event catalog has 22 kinds. Session lists showparent_session_id,child_statusandpromptable: falsefor subagents.
[0.3.0] - 2026-10-03
Navai becomes a terminal coding agent as well as a navigator: a chat client with tabs, tool
permissions you answer from the terminal, a guarded plan agent, model switching inside a
conversation, and automatic compaction. Background subagents are planned for 0.4.0.
Added
- Terminal chat client:
navai tuistarts a private server, or--urlconnects to a running server (setNAVAI_SERVER_TOKENwhen it requires a bearer token). Open up to nine conversations; each keeps its draft and scroll position and continues streaming in the background. The left sidebar lists conversations and unread activity; the right panel shows agent, context, skills and changed files on terminals at least 140 columns wide (alt+rtoggles it). The sidebar is hidden below 80 columns.alt+1–alt+9switch tabs,alt+wcloses one,ctrl+nstarts a conversation,ctrl+oopens the session list,alt+bhides or shows the sidebar,f2picks a model,tabon an empty message switches build/plan,shift+tabchanges focus, andctrl+qquits. - Session commands:
/help,/model [alias],/new [alias],/stop,/context,/variant [name|off],/compact [focus]and/agent [id]. The TUI command palette combines these with local commands. The event catalog has 19 kinds. A line beginning//sends a literal slash, so//helpsends/help. - Model switching and variants:
/model <alias>switches the current conversation;/new <alias>starts another. A switch uses the target model’s default variant and waits until the current turn ends. Model lists andf2switch the live conversation;ctrl+nin the model picker starts a new conversation on that model./variantlists the session’s choices;/variant <name>selects one, and/variant offclears it. - Automatic compaction: when the next request will not fit the input budget, Navai summarizes
the older conversation and continues the same turn. If even the required history cannot fit, the
turn ends with an error recommending a model with a larger context window.
/compact [focus]also requests compaction manually. Compaction is not run while an answer or permission decision is pending. - Tool permissions: the eight registry tools are
read,write,edit,bash,grep,glob,fetchandask; sessions also provide askilltool to load a listed skill.navai rundoes not approve tool permissions automatically: it printspermission_required: <tool> <scope>and exits with code 3.--yesapproves each request once. Ctrl-C interrupts a run and exits with code 130. - Plan agent guard:
plancan write Markdown files without a permission prompt. Other writes, edits, shell commands and fetches are checked against its role; if the check cannot confirm the action, Navai asks for permission. The guard is advisory, not a sandbox. - Model catalog: when a model alias has no configured context window, Navai can discover one from
the provider’s model list and cache it under the data directory.
GET /v1/modelsreports the resolved window and its source. - Workspace tools:
globsearches for files without following links, with a default limit of 200 results (up to 1,000).fetchretrieves HTTP(S) text or HTML, caps the downloaded body at 5 MiB, and follows only same-origin redirects. - Terminal themes: Ayu Dark, Ayu Light, Nord Dark and Nord Light.
/themeopens the picker; the choice is saved in~/.config/navai/config.json.
Changed
- State locations: configuration is stored in
~/.config/navai/; data is stored in~/.local/share/navai/. Navai migrates supported files from the formernavigatorandrust-agentdirectories on first start without overwriting existing files. - Project identity: the crate and binaries are named
navaiandnavai-lsp; the VS Code extension is Navai (pixmants.navai). Environment variables use theNAVAI_*prefix.
Fixed
- A permission answer is tied to the checked destination. If a tool call resolves to a different destination when it runs, Navai asks again rather than using the earlier approval.
- A stopped or interrupted permission request is settled without running its held tool call.
- The terminal client keeps each open conversation’s model, variant and context display in its own tab, and ignores late events from a conversation that has been closed.
[0.2.0] - 2026-10-01
First usable release: the navigator, a pair-programming assistant in VS Code. The driver keeps the keyboard; four layers of models watch, complete, judge and discuss.
Added
- Language server and VS Code extension (
rust-agent-lsp,vscode-navigator/): one process, LSP over stdio for the editor, a local HTTP/SSE server with a bearer token for the chat. - Reflex: fill-in-the-middle ghost-text suggestions (Inception Mercury Edit 2), with a short context note kept current by the metacognition.
- Instinct: a decision pipeline in TOML and Markdown prompts, asking typed questions to a
System One model (Jev). Project
.navigator/overrides the user and built-in pipelines; hot reload on save. - Metacognition: short end-of-line remarks from a fast model, with read-only
readandgreptools under a budget, aware of the conversation, the intent record and recent edits. - Cognition: a chat panel on a frontier model with the full coding-agent toolset; it guides by default and does the work on explicit delegation. Editor context (focused file, cursor, selection) travels with each message.
- Wake: the instinct can bring a contradiction between the code and the conversation or the
intent record to the cognition, as a navigator-sourced message that never answers a pending
askin the driver’s place. - Conversation triggers: a driver turn in the chat re-checks the focused file against what was just agreed.
- Intent record (
.navigator/intent.md), written by the cognition as decisions settle. - Verdicts on remarks (useful, noise, dismissed, followed) and a JSONL decision log.
- Replay (
rust-agent-lsp replay): run a candidate pipeline over the logged triggers and compare its decisions with the rated remarks. - Conversation history in SQLite: every conversation per project, resumable under the same id,
pending
askincluded; list, reopen and delete from the panel.
Changed
- Data lives in
~/.local/share/navigator/(navigator.db,navigator.jsonl), migrated from~/.local/share/rust-agent/on first start. Configuration stays in~/.config/rust-agent/. - SQLite is the only history: a reconnect or a resume replays the store, then follows live events, with no cap on conversation length.
- Codebase restructured:
session/service,navigator/split by layer, one shared model client, a shared test harness. SeeARCHITECTURE.md.
Removed
- The WebSocket transport and the placeholder
tuicommand.
[0.1.0] - 2026-09-04
The coding-agent core: the agent loop with five tools and ask, OpenAI-compatible streaming
providers, the event bus and the local session server.
Contributing to WithNav
WithNav is a pair-programming system in Rust: you keep the keyboard, and several models of
different speeds watch, complete, judge and discuss. See the README for what it does
and ARCHITECTURE.md for how the code is laid out.
Questions and help
Ask in GitHub Discussions (Q&A), not in issues.
Reporting bugs
Use the bug report form.
A good report has the WithNav version (nav --version), your OS, how you installed it, the steps
that reproduce the problem, what you expected and what happened. Attach the output of
nav doctor --offline --json when configuration may be involved. It makes no network calls.
For a security problem, do not open an issue: see SECURITY.md.
Proposing features
Use the feature request form.
For a large idea, start a thread in
Discussions first, before writing code.
Read CONTEXT.md for the vocabulary and docs/adr/ for decisions
already taken.
Development setup
From the repository root.
Rust core (nav, nav lsp). The minimum supported Rust version is 1.89 (rust-version in
Cargo.toml).
cargo build --release --bins
Web client (webui/). Its build output webui/dist/ is committed and embedded in the binary,
so cargo build does not need Node. You only need it when you change the web client.
cd webui && npm ci && npm run dev
VS Code extension (vscode-withnav/).
cd vscode-withnav && npm ci && npm run compile
The repository has no .vscode/launch.json, so there is no ready-made F5 configuration. To try
the extension, build and install it as described in the install guide
(npm run package, then code --install-extension vscode-withnav/withnav-*.vsix). Releases also
publish platform-specific VSIX files for linux-x64 and linux-arm64 that bundle a static nav
binary, and a universal VSIX that uses nav from your PATH or the withnav.serverPath setting.
Native Windows is not supported yet; WSL or VS Code Remote works with the linux VSIX.
Branching
mainholds releases only, each taggedvX.Y.Z. Never commit to it.- Branch off
develop:feat/<name>,fix/<name>,refactor/<name>,docs/<name>ortest/<name>. One topic per branch, short kebab-case names. - Open a pull request into
develop.
More detail, including how releases are cut, is in AGENTS.md.
Commit style
Conventional commits, as in the history: feat:, fix:, refactor:, test:, docs:, chore:,
with an optional scope (fix(permissions): ...). Write in English, in the imperative mood
(“add”, not “added”).
Pull request checklist
A pull request is ready when these pass, run from the repository root:
cargo build --bins
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --check
If you changed the extension:
cd vscode-withnav && npm run compile
If you changed the web client:
cd webui && npm run typecheck && npm test
CI enforces this list. In the description, say what changed for the user and how you verified it.
Changelog
Add a line under Unreleased in CHANGELOG.md. Documentation-only changes
do not need one.
Code of Conduct and security
- Taking part means following the Code of Conduct.
- Report vulnerabilities privately, as described in
SECURITY.md.
Security policy
Supported versions
Only the latest release (the newest vX.Y.Z tag on main) receives security fixes. develop is
the integration branch, not a support channel.
Reporting a vulnerability
Report privately through GitHub Private Vulnerability Reporting: https://github.com/PixmaNts/withnav/security/advisories/new
Please do not open a public issue, pull request or discussion for a vulnerability.
Include as much of this as you can:
- the affected version and your operating system;
- how you installed WithNav (VSIX,
navbinary, built from source, Nix); - a description of the problem;
- steps to reproduce it;
- the impact, if you know it.
What to expect
- Acknowledgement within 72 hours.
- Triage (confirmed or not, severity, plan) within 7 days.
- Coordinated disclosure: we agree on a date with you, fix first, then publish an advisory. You are credited in the advisory unless you prefer to stay anonymous.
Scope
In scope:
- the Rust core (
nav,nav lsp); - the VS Code extension;
- the web client (
webui/); - the website and documentation;
- CI and release infrastructure.
Out of scope:
- vulnerabilities in third-party models or providers reached with your own API keys;
- issues that require physical access to your machine;
- theoretical issues with no concrete exploit path in a local-first tool.
Threat model
WithNav is a local, local-first tool for one person at a keyboard. Its realistic risks are
malicious content in files the agent reads, prompt injection, and supply-chain issues. The
permission gate asks before write, edit, bash and fetch and guards against drift; it is
not a sandbox, and bash is not confined to the workspace.
Facts that matter when you assess impact:
nav servebinds to loopback (127.0.0.1) by default. A non-loopback bind needs the explicit--allow-non-loopbackflag.nav tuiandnav webstart a private server on loopback with a per-process bearer token.- WithNav has no telemetry. It only contacts the model providers you configure, plus any URL the agent fetches with your approval.