Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

LayerWhat it doesWhere you see it
ReflexCompletes what you type, fast.Ghost text in the editor (suggestions).
InstinctMakes 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.
MetacognitionCompares 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.
CognitionThe 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.

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:

MemberAgent idRole
Vigie, the LookoutvigieRead-only scouting of code, documentation and the web, with sources.
Lux, the Expert AdvisorluxRead-only analysis and recommendations.
Mousse, the Lightweight DeckhandmousseSmall local edits, after your approval.
Cartographe, the CartographercartographeOn demand. Draws maps as Mermaid charts in .withnav/charts/.
Sondeur, the Deep DiversondeurOn 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 doctor and nav auth: diagnostics and API keys.
  • The VS Code extension. It starts nav lsp, shows remarks and suggestions, and hosts the Nav chat. It can bundle nav.

Where to go next

  1. Install WithNav.
  2. Follow the Quick start to a first working session.
  3. 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:

FileUse it whennav
withnav-linux-x64-<version>.vsixLinux on x64Bundled (static binary). Nothing else to install.
withnav-linux-arm64-<version>.vsixLinux on arm64Bundled (static binary). Nothing else to install.
withnav-<version>.vsix (universal)macOS, Nix, developmentNot 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.

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: the nav command.
  • pkgs.vscode-withnav: the extension, for Home Manager’s programs.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

Using Nav

You can talk to Nav from four places. They all use the same agents, the same configuration and the same permission rules.

WhereStart itBest for
VS CodeInstall the extensionDaily work. It adds remarks and ghost-text suggestions to the editor, and hosts the Nav chat.
Terminalnav tuiA full chat client in your terminal.
Browsernav webA chat in your browser, with the Settings screen and the InstinctFlow Studio.
One tasknav 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.

  • build is the default. It implements, verifies and reports.
  • plan researches 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:

CommandWhat it does
/helpList 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.
/stopStop the session.
/contextShow 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+1 to alt+9. ctrl+n opens a new one, alt+w closes the tab and keeps the conversation, alt+b hides the tab bar.
  • Header. Workspace, git branch, agent and model.
  • Right column. Context breakdown, skills and git changes. alt+r shows or hides it.
  • Command palette. ctrl+p or /. It also holds /config (edit settings), /sessions, /skills, /theme and /quit.
  • Models. f2 opens the model picker, with variants. ctrl+f marks a favourite.
  • Sessions. ctrl+o lists past conversations to resume.
  • Subagents. alt+s opens a subagent’s read-only transcript. alt+c continues one that ran out of steps.
  • Bypass. alt+p turns 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 as tui.theme.
  • Help. f1 shows every shortcut. ctrl+q quits, esc or ctrl+c stops 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 to config.json with ctrl+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

Repository · Report an issue

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. /agent uses the selected agent’s own assignment or saved session override. /model and /variant override only that agent in that conversation; the server persists and restores the choice.

  • Canonical model catalog: searchable choices use exact gateway/upstream_id names, 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 that nav lsp runs 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, /new and /help. /skills fills a selected skill into your next request; command outcomes appear as notices. Commands never attach editor context; //text sends 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>.vsix or withnav-linux-arm64-<version>.vsix from the GitHub Release. It bundles a static nav; nothing else to install. A withnav.serverPath you set wins over the bundled binary.
  • Universal package (withnav-<version>.vsix, no binary; macOS, Nix, development): put a nav binary on PATH, or set withnav.serverPath to its absolute path. The extension appends lsp; do not put the subcommand in the setting. Old withnav-lsp and navai-lsp paths use nav in the same directory, with a one-time notice to update the setting.
  • Windows: nav does 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, file scheme only, excluding Nav’s own diagnostics), withnav/intent.
  • Settings use withnav.*; explicit navai.* values are honored when the new key is unset, with a one-time migration notice. Configuration lives under withnav.* settings, sent as initializationOptions and via workspace/didChangeConfiguration. Setting withnav.enabled to 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) WithNav with the reason in the tooltip — the extension stays alive.

Commands and keybindings

CommandKeyAction
withnav.markUsefulCtrl+Alt+YRate the current remark “useful”, then clear it
withnav.markNoiseCtrl+Alt+NRate the current remark “noise”, then clear it
withnav.dismissCtrl+Alt+DSend “dismissed”, then clear it
withnav.setIntentclick status barTell Nav your current goal
withnav.focusChatcommand 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 --noEmit type check only.
  • npm test — deterministic event projection, replay and extension-host control tests.
  • Output channel “WithNav” shows the LSP traffic log.

Settings

SettingDefaultDescription
withnav.enabledtrueEnable Nav
withnav.serverPathnavBinary (PATH or absolute), launched with lsp; when unset, the bundled bin/nav is used if present
withnav.idleMs3000Idle delay before Nav may speak
withnav.minChangedLines3Minimum changed lines to consider speaking
withnav.cooldownMs20000Cooldown between remarks on one file
withnav.reflex.enabledtrueOffer 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.

CommandWhat it does
nav tuiTerminal chat client.
nav webBrowser chat client.
nav runRun one task and exit.
nav serveThe session server alone (HTTP and SSE).
nav authStore API keys.
nav doctorRead-only diagnostics.
nav lspThe language server the editor extension launches.
nav flows cataloguePrint the flow schema of this build as JSON.

Add --version to any of them to print the version.

nav tui [--workspace <dir>] [--model <gateway/id> | --session <id>] [--models <file> | --url <server>]
FlagMeaning
--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 [--workspace <path>]... [--models <file>] [--port <n>] [--no-open]
FlagMeaning
--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-openPrint the address without opening the browser.
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.

FlagMeaning
-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-thinkingShow the model’s reasoning.
--yesUnsafe. Turn on bypass for the run: ordinary permission prompts run once. Protected ones still stop the run with exit code 3.

Exit codes:

CodeMeaning
0Done.
1Error.
2The agent asked a question.
3A tool call needs approval (permission_required).
130Interrupted.
nav serve [--bind <addr>] [--allow-non-loopback] [--models <file>]

Runs the session server alone, with no client: an HTTP API with Server-Sent Events.

FlagMeaning
--bind <addr>Address to listen on. Default 127.0.0.1:7310.
--allow-non-loopbackAllow 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 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 [--offline] [--json] [--workspace <dir>] [--layer <reflex|instinct|metacognition>]

Checks your configuration. It is covered in Troubleshooting.

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

VariableMeaning
WITHNAV_SERVER_TOKENBearer token for nav tui --url.
WITHNAV_MAX_STEPSCap 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 variablesThe 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.json is 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.json makes 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.

PageWhat it covers
Gateways and modelsmodels.json, API keys, a full example.
LayersChoosing the models for Reflex, Instinct and Metacognition.
AgentsChoosing models for build, plan and the crew; custom agents.
Editor context and session overridesWhat /model, /variant, /agent and /new do.
Rules and skillsAGENTS.md, rules.md and SKILL.md files.
Import a catalogue from OpenCodeBuild 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/.

WhereWhat
~/.config/withnav/models.jsonThe 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.jsonAPI keys, mode 0600. Write them with nav auth set <provider>.
~/.config/withnav/config.jsonlayers, agents, default_agent, and other settings such as tui.theme.
~/.config/withnav/agents/*.md, <project>/.withnav/agents/*.mdAgent 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.mdThe 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.dbConversation history.
~/.local/share/withnav/withnav.jsonlDecision log.
~/.local/share/withnav/grants.jsonFolder approvals you saved for fetch. See Permissions.
~/.local/share/withnav/tui.logLog 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.

KeyDefaultMeaning
layers.reflexnone (layer off)Model for Reflex. Must use the fim API.
layers.instinctnone (layer off)Model for Instinct. Must use the systemone API.
layers.metacognitionnone (layer off)Model for Metacognition. Must use the chat API.
default_agentbuildThe primary agent a new session starts with. See Agents.
agents.<id>.model, agents.<id>.variantnoneModel and variant of an agent. See Agents.
compaction.writermetacognitionWho writes compaction summaries: metacognition, or session for the session’s own model. If Metacognition is unavailable, the session model is used.
compaction.chunk_tokens64000Size of one rolling summary chunk, capped at half of the writer’s input budget. A positive integer.
permissions.automatic_reviewtruefalse asks you for every bash, write and edit call. See Permissions.
permissions.review_timeout_secs40Deadline 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_dirsprivate runtime directory, then a shared temp oneAbsolute paths where the agent may write temporary files without asking. [] turns it off. See Permissions.
subagents.max_concurrent4How many subagents one session may run at once. An integer from 1 to 64.
subagents.watchdog_interval_secs300How often Instinct checks that a background subagent makes progress. 0 turns it off.
agent.watchdog_interval_secs300How often Instinct checks that the main conversation makes progress. 0 turns it off. See Long turns.
tui.themetide-darkTerminal 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:

  • omniroute is the gateway, declared in models.json.
  • combo/gpt-6-astra is 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
  }
}
KeyMeaning
base_urlThe OpenAI-compatible endpoint.
api_key_envThe name of the environment variable that holds the key. Never put the key itself in models.json.
headersExtra HTTP headers.
connect_timeout_msDefault 5000.
request_timeout_msDefault 600000.
catalog_formatOptional. "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.

KeyMeaning
providerThe gateway name.
modelThe upstream id.
apichat (default), fim or systemone. Each layer needs a specific one.
context_windowTokens the model accepts. A usable chat model needs one, from here or from the gateway’s own model list.
max_input_tokens, max_output_tokensOptional limits.
max_message_tokensOptional. Bound for one serialised compaction message. Must be positive.
supports_toolsWhether the model can call tools. Default false.
supports_parallel_tool_callsOptional.
reasoning_historypreserve (default) or omit.
default_variantThe variant a new conversation starts with. If you leave it out, there is no variant.
variantsNamed 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:

  1. The key stored in ~/.config/withnav/auth.json. Write it with nav auth set <provider>.
  2. 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, medium and high as request-body reasoning_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.

LayerAssignmentRequired apiJob
Reflexone model, editor onlyfimInline completion as you type (Mercury in the example).
Instinctone modelsystemoneTyped decisions and supervision: when to speak, permission review, progress checks.
Metacognitionone modelchatRemarks beside your code, and review of risky calls. Prefers the low variant when the model offers one, otherwise the model’s default.
Cognitionagents, each with its own role and modelchatThe 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 api the layer requires.
  • variant is optional. Leave it out for the model’s default. Use null or "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 offEffect
ReflexNo inline suggestions.
InstinctPermission review asks you when it cannot decide. Supervision of long turns falls back to a visible, renewable 500-step cap.
MetacognitionNo 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.

AgentKindWhat it does
buildprimaryThe default. Implements, verifies and reports.
planprimaryResearches and writes Markdown plans. Gated calls are checked by Instinct; see Permissions.
vigiesubagentRead-only scouting of code, documentation and the web, with sources.
luxsubagentRead-only analysis and expert advice.
moussesubagentSmall local edits, after your approval.
cartographesubagent, on demandWrites Mermaid charts to .withnav/charts/.
sondeursubagent, on demandRoot-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_agent is the agent a new session starts with. It is build when 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.json wins over a model: in an agent definition’s header.
  • Adding an assignment does not create an agent definition.
  • variant follows 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:

  1. ~/.config/opencode/agents/*.md (only read; shared with OpenCode)
  2. ~/.config/withnav/agents/*.md
  3. <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 keyMeaning
descriptionWhat the agent is for.
modeprimary, subagent or all. all when absent.
modelOptional model. An assignment in config.json wins.
toolsA map of tool name to true or false.
accessread-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:

CommandEffect
/modelReport 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.
/variantReport the effective variant, its source and the choices.
/variant <name>Set a named variant.
/variant offDisable variants explicitly.
/variant inheritRemove 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:

  1. Your global file: ~/.agents/AGENTS.md, or ~/.claude/CLAUDE.md if the first is missing.
  2. The optional global supplement ~/.agents/rules.md.
  3. For each directory from the repository root down to the workspace: AGENTS.md, or CLAUDE.md if there is no AGENTS.md, then the optional rules.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:

  1. For each directory from the workspace up to the repository root: .agents/skills, .claude/skills, .opencode/skills.
  2. 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
FlagMeaning
--dry-runPrint a summary and write nothing. Try this first.
--config-dir DIRWhere to write the WithNav files.
--opencode FILEThe OpenCode config to read. Default ~/.config/opencode/opencode.jsonc.
--with-windowsCopy context windows from OpenCode instead of letting WithNav read them from the gateway.
--self-testRun 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 the omniroute provider. 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, medium and high variants 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-windows copies them instead.

API keys

  • A literal apiKey goes to auth.json (mode 0600).
  • An {env:NAME} key becomes "api_key_env": "NAME" in models.json. Any key stored in auth.json for 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 a baseURL that 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. bash is 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

ToolGate
write, edit, bashEvery call is reviewed before it runs.
fetchAsks first.
read outside the project, or of credentialsReviewed like bash.
grep, glob outside the projectRefused. Use read or bash for that.
read, grep, glob inside the projectRun without asking.

How a call is decided

  1. Fixed checks look at the call: redirections, commands that change things, the real destination of a path. They only ever make a call stricter.
  2. 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.
  3. 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.
  4. 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 push that deletes or forces, in any spelling.
  • Shell syntax that hides what a command does, such as cd before the real command, a relative PATH entry 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

AnswerMeaning
Allow onceRun this call.
DenyDo not run it. You can add a message that the model sees.
Allow for this folder / siteOnly 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 sessionOffered for edits to WithNav’s own config. Reviews still run, and credentials still ask.
Allow this skill’s scripts on this machineOffered 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+p in the terminal, the WithNav: Toggle Bypass Permissions command in VS Code, the Bypass button in the browser, or nav run --yes.
  • With it on, write, edit, bash and fetch calls 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 --yes stops 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: ⚠ bypass in the terminal, a badge in VS Code and the browser.
  • It is not a sandbox: bash keeps 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 write and edit tools to ~/.cargo/bin and ~/.rustup/toolchains (or $CARGO_HOME and $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/scratch if 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+c in 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.

  1. Open the Studio from your workspace in the WebUI. Its route is #/studio?ws=<absolute-workspace>&flow=<name>&loc=workspace.
  2. Choose New flow, name it message-test, choose This workspace, and set Event it listens to to message.received. Create writes an initial flow containing a log action; it does not reload it.
  3. Select the trigger card. In its inputs, select event.text: the chip declares the alias text. Declare the input first, then use its alias in when. Set when to text == "test". Do not write event.text == "test" there.
  4. Select the log action and set its text parameter to Matched test. The trigger’s event port must be wired to this action.
  5. 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.
  6. Save, then Reload. Check the reload result; saved · not reloaded means the file is newer than the running definition.
  7. Click the sample payload in the bottom bar and replace it with:
    {"command_id":"sample","author":"captain","text":"test"}
    
  8. Choose Run sample. The path proposes log; application says not applied. This is correct: samples never apply actions. Change text to Test or test\n and run again: the path says skipped: when is false.
  9. 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

WhereWhat to do there
Left: flow listSelect 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 headerCheck location, path, unsaved/not-reloaded status, validation errors and health. Set blocking and its budget; Save, Discard or Reload.
Centre: CanvasAdd a decision, expression or action from the toolbar; arrange cards and connect ports.
Right: inspectorSelect the trigger or a node to edit its fields, declared inputs, instructions, parameters and ports. Inspect rendered state and run detail.
Centre: TOMLEdit the same definition as text. Errors show locations; invalid text leaves the last valid canvas visible and locked.
Bottom: run barEdit sample JSON, Run sample, choose a record, View run, Replay, Clear run. Inspect path, proposed actions, failure, fallback and application status.
HealthHeader/list counters for the last 24 hours of live runs: runs, failures, fallbacks applied, cost. Samples and replay do not count.
GestureResult
——
Click a cardSelect its node and open its inspector.
Drag a cardMove it; positions are saved as layout, not behaviour.
Drag an output port to another card’s inputAdd a wire. One port can feed several nodes. Cycles and invalid connections are refused.
Click a wire, then DeleteRemove the selected wire. Backspace also works with canvas focus.
Click a card, then DeleteRemove that node and its wires. The synthetic trigger cannot be removed. Check inputs referring to the removed node.
Drag canvas background / scrollPan / zoom the React Flow canvas.
Escape in New flowClose the creation dialog.
Clear runLeave 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.

TypeConfigurationAnswer and active port
noulinstructions, threshold (finite 0–1 number or "constant.NAME")Yes probability; yes when answer ≥ threshold, otherwise no.
choiceinstructions, [node.criteria]: 2–16 named options, each with nonempty instructionsOption string; the matching criteria key is the port.
scoreinstructions, levels: 2–10 distinct labels, weakest first; at_least: exact labelFractional 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.

ReferenceValue
event.<field>One exact payload field offered by this trigger (section 7); no arbitrary nested traversal.
Host name, e.g. session.agentContext collected by WithNav, only when declared and needed.
constant.MIN_YESScalar from [constants]: string, finite number, integer or boolean.
answer.requestedUpstream decision answer: noul number, choice string, score number.
answer.kind.confidenceUpstream choice/score confidence.
answer.kind.probabilities.optionChoice probability for an option; score probability keys are zero-based index strings.
result.gateUpstream expression’s boolean condition result.
variable.gate.THRESHOLDVariable 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 inputSemantic type
——
session.idstring
session.agentstring
session.modelstring
session.workspacestring
conversation.last_driverstring
conversation.digeststring
conversation.last_turnsarray
intent.recordstring
workspace.rootstring
workspace.branchstring
workspace.dirty_filesarray
editor.open_filesarray
editor.cursorobject
editor.selectionstring
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.

SyntaxMeaning
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.

TriggerPayload fieldsCan blockAllowed actions
tool.pre_calltool:s, args:o, call_id:syestool
message.receivedcommand_id:s, author:s, text:s, selected_skill?:oyescommon
turn.endturn_id:s, text:s, status:snocommon
saveuri:s, language_id:s, version:i, diff:s; editor extras belownoeditor
idleuri:s, language_id:s, version:i, diff:s, idle_ms:i; editor extrasnoeditor
diagnosticsuri:s, language_id:s, version:i, diagnostics:a, diff:s; editor extrasnoeditor
conversationturn_id:s, text:s, uri?:s; editor extrasnoeditor

Every editor trigger also offers these optional payload fields, used by the built-ins and migrated definitions (not host-input names):

TypeExact optional fields
stringtemplate_file, template_language, template_intent, template_diff, template_context, template_diagnostics, template_live_state, template_conversation
integerchanged_lines, since_last_remark_ms, since_last_wake_ms
booleancalled_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.

ActionParametersKindWhat WithNav does
no_objection{}verdict, priority 0Continue normal tool permission review, unchanged; not permission to run.
ask_captainnoteverdict, priority 1Hold the call for a Captain-only permission request, skipping model reviews.
denynoteverdict, priority 2Do not run the tool; return the note as the denied tool result.
remarkprompteffectAsk metacognition for an editor remark using inline rendered text, not a prompt-file lookup.
wakenoteeffectBring the note to cognition’s attention; wake delivery rules/cooldown still apply.
inject_notetexteffectDeliver a flow-labelled note at a safe boundary.
logtexteffectAnnotate 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:

KeyUsed for
on_failureGeneral failure; expression, input, cancelled and limit failures.
on_timeoutExhausted deadline.
on_provider_errorProvider unreachable, rejected the request (HTTP 4xx) or failed (HTTP 5xx).
on_invalid_answerMissing/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

LocationPath / 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-inShipped 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

SymptomCauseFix
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 falsePayload does not satisfy exact comparison.Inspect text, case and whitespace; edit sample JSON or correct the condition.
Unknown name in whenAlias 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 failsUnsupported expression syntax.Use double-quoted string literals and `
Workspace flow cannot sample/replay or does not runWorkspace is untrusted.Inspect definitions; grant trust explicitly, then reload. Global/built-in flows remain available.
Saved but live behaviour is unchangedSave 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 flowReplacement is all-or-nothing, including eligible disabled/shadowed definitions.Fix every reported error; reload again. The previous set remains running.
Sample/replay says not appliedIntentional test behaviour.Inspect proposed actions/path; use View run on a live record to inspect real application.
Async live failure says not applied, fallback noneAsync runs have no default fallback.Fix the recorded failure or declare an allowed effect fallback.
large file: limit failureContext 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 noticeFirst 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 failureNode 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 valuesA recorded/sample run overlay is still displayed.Choose Clear run; it does not delete records or edits.
Cannot delete wire/cardCanvas 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 revisionAnother edit changed the file on disk.Preserve your draft separately, then Reopen the file; reopening loses unsaved edits.
Replay is not comparableRequest 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
FlagMeaning
--offlineMake 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.
--jsonPrint 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_agent names a primary agent (build or plan, or a custom primary one).
  • agents.<id>.model is a name that exists in models.json, in the form <gateway>/<id>.
  • The gateway has a key: nav auth list, or its api_key_env variable is exported. For example, model 'openai/gpt-4.1' authentication unavailable for provider 'openai'.
  • The model has a context window, from models.json or 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.

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:

OldNew
navai commandnav
.navai/, .navigator/ in a project.withnav/. When .withnav/ is missing, the old folders are read, with a hint.
NAVAI_* environment variablesThe same name with WITHNAV_. The old one is read when the new one is unset.
navai.* VS Code settingswithnav.*. An old value is honoured when the new key is unset, with a notice.
withnav-lsp or navai-lsp in withnav.serverPathnav. The extension uses nav in the same directory and asks you to update the setting.
Agent ids explore, librarian, review, oracle, explorervigie, 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, sondeur or 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, or build when absent.
Editor session
A session started by an editor client. It adds the pair-programmer prompt and the intent tool.
Gateway
An endpoint declared under providers in models.json. The first part of a model’s name.
Intent record
.withnav/intent.md in 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 build and plan agents. 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 nav inside that the extension uses on its own, plus a universal package without a binary (it needs nav on PATH or withnav.serverPath), the bare nav binaries as tarballs, and SHA256SUMS. A path you set in withnav.serverPath still wins over the bundled binary. scripts/package-vsix.sh builds 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-languageclient 10, 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 machine cargo test went 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 the write and edit tools to ~/.cargo/bin and ~/.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

  • nav builds for musl Linux. Writing a flow file that must not replace an existing one now calls renameat2 through the raw system call, because nix only 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 before push (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 diff that can run a program is no longer treated as a read. This covers --ext-diff, --textconv, -c diff.external=…, --config-env, --exec-path, core.pager and GIT_* 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 relative PATH entry, or a cd / pushd / builtin cd before 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 # check is read as git status. A workspace opened through a symlink or a relative path can again prove that an overwritten file is recoverable from git.
  • nav auth set no longer erases other providers’ keys. When auth.json is 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_STEPS no 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 null arguments are still accepted.
  • fetch output no longer ends with � when converted HTML is cut inside a character. The fallback HTML flattener decodes entities once (&amp;lt; stays &lt;).
  • read and glob with huge offset / limit values 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_TREE and GIT_INDEX_FILE are 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 through cargo 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 doctor has 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 doctor checks 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. --offline avoids all network requests; --json reports 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 nix commands. Selecting Nav’s offered “Fais-le” option establishes that task. Destruction, credentials, opaque execution, unsafe executables and outside changes keep their safeguards.
  • Printing sed and filename globs are reads. sed -n 250,285p file and grep … tests/* no longer go to review. sed -i and 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/shells and runs the resolved, root-owned binary; on other systems /usr/bin/bash is 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 low variant, and allow up to 16384 output tokens. permissions.review_timeout_secs defaults 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 nav and the VS Code extension (nix build .#withnav .#vscode-withnav), and its overlays.default adds both to a NixOS system. Cargo.lock is 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-lsp is gone: the editor server is now nav lsp. The extension launches nav lsp and migrates a withnav.serverPath that pointed at withnav-lsp to nav in the same directory, with a notice to update the setting. Delete the leftover ~/.cargo/bin/withnav-lsp after reinstalling.
  • The navai command is gone: run nav. It only printed a deprecation notice and started nav, but every build still linked it as a third 37 MB binary. Update any script or shortcut that still calls navai, and delete the leftover ~/.cargo/bin/navai after 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 /compact commands 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 instinct source, model, latency and guard metadata; clients still read stored jev events and display them as auto · instinct until 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 −M and 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}/**/*.rs now enter target, 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_regex just 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 withnav shrinks 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.
  • edit now refuses an empty old_string instead of scrambling the file. An empty search text matches between every character, so replace_all interleaved 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/sessions fail 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. /model summarises 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, ask answers, 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 (or librarian, 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 to github.com/PixmaNts/withnav (the old URL redirects); the internal src/navigator/ module stays.
  • Binaries: navai → nav (deprecated navai alias prints a notice and runs nav); navai-lsp → withnav-lsp (update the configured server path; no old LSP executable alias).
  • Project directory: .navai/ / .navigator/ → .withnav/, including intent.md, agents/, flows/ and legacy pipeline.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 older rust-agent / navigator migrations remain.
  • Scratch paths: $XDG_RUNTIME_DIR/navai, ~/.cache/navai/scratch and <temp_dir>/navai → the corresponding withnav paths, 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 includes NAVAI_CONFIG_DIR, NAVAI_SERVER, NAVAI_SERVER_TOKEN, NAVAI_TOKEN, NAVAI_WORKSPACES, NAVAI_MAX_STEPS, NAVAI_BASH_GRACE_MS and NAVAI_HTML_CONVERTER, each becoming the same suffix under WITHNAV_. Provider-specific API-key variables are unchanged.
  • VS Code: vscode-navigator/ → vscode-withnav/, extension id pixmants.navai → pixmants.withnav, view container navigator → withnav; install the new extension (no duplicate folder or extension-id alias). Settings navai.* → withnav.*, falling back when the new setting is unset. Commands navigator.* / navai.* → withnav.*; use new command ids. The previously removed navai.modelAlias stays removed.
  • LSP: navigator/serverInfo, navigator/remark, navigator/clear, navigator/status, navigator/feedback, navigator/diagnostics, navigator/intent, navigator/reflexShown and navigator/inlineCompletion → the same suffix under withnav/. The server accepts both prefixes for requests/notifications and sends only withnav/*; clients use the new prefix.
  • Human and wire values: driver → the Captain; driver → captain in event authors, approved_by, permission-gate source, origin, selected_by and conversation digest authors. Readers accept both; new records write captain.
  • Flow action: ask_driver → ask_captain; old TOML actions still parse.
  • Agent ids: build and plan remain Nav; explore / librarian → vigie, review / oracle → lux, explorer → mousse. Old ids resolve as deprecated aliases with warnings. New cartographe and sondeur are 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 blocking tool.pre_call flow can deny a tool call or ask you, never allow one past the normal permission review, and bypass never answers it. message.received and turn.end flows can inject a note or wake the cognition, marked as coming from a flow. See the Captain’s guide.
  • The Studio (#/studio in 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, and nav 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.toml files are converted once with originals retained. Offline replay uses flows only (--flows remains accepted; --pipeline and --reask are 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, or POST /v1/sessions/{id}/bypass: write, edit, bash and fetch calls that would have asked run once instead, after the usual reviews, and the result says approved_by: bypass. A request already waiting is answered too. Requests for Navai’s own configuration, credentials and keys still ask you, and navai run --yes stops 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: bash keeps 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, ⚠ bypass stays 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 to navai web on 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 null instead 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, ncat or socat is 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 .md file through a folder that is a link (for example notes/ 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 own navai.* 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, t tests 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 to config.json one 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 to config.json at 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 web starts 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 --port to choose the port and --no-open to 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 /model picker 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.json describes gateways, models, APIs, capabilities, limits and variants. Use names such as omniroute/combo/gpt-6-astra: omniroute is the gateway and combo/gpt-6-astra is the exact upstream id. Bare ids and the old default, fast and cognition aliases are no longer supported. API selections and context/usage metadata use model, gateway and upstream_id, not alias.
  • Assignments live in config.json. layers selects one reflex (fim), instinct (systemone) and metacognition (chat) model; cognition runs agents rather than one singleton model. agents assigns their models and variants, taking precedence over Markdown model: headers. default_agent is build when 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), or inherit (remove the local variant choice). Changing model takes the destination model’s default variant. Metacognition prefers low when offered. The importer gives OmniRoute combo models low, medium and high request-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.modelAlias setting 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/agents reports configured/effective choices and, with a session id, local overrides; GET /v1/layers reports the three lower layers and disabled reasons. Alongside GET /v1/models, these expose canonical identities and choice sources, not endpoint URLs, headers or credentials.
  • Example oracle and librarian agents, 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 task tool (and steer_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, .env files, *.pem / *.key, cloud credentials or Navai’s auth.json with the read tool always asks you. grep and glob skip these files (without opening them) and say how many they skipped. For bash, 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. /compact shows “Compacting the conversation…” until it ends or fails. You can keep writing and sending: your messages wait for the end of the compaction.

Changed

  • read outside the project is reviewed like bash instead of being refused: if Jev, metacognition or you approve it, it reads that one file once. grep and glob outside the project are still refused, with a message pointing to read or bash.
  • A bash command 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 as cat, ls or wc there can be approved automatically.
  • “Allow folder” is only offered for fetch; read, write, edit and bash requests offer allow once or deny.
  • Directory grep uses 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). /model switches 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, /new and /help. /skills lists 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; //text sends /text as 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, write and edit call asks you.
  • git commands stay reviewed as changes: even git status or git diff can 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 status for 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. A bash command that names the config directory also asks you (best effort: bash is 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.
  • write and edit outside the project are reviewed like bash instead 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, y still allows it: drag with the mouse to copy it; y copies 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_dirs in config.json sets 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, y copies the selection, or the whole selected message or tool output; ctrl+shift+c works where the terminal sends it, and ctrl+c still stops. The wheel scrolls the conversation, and the lists of /model, /sessions and other pickers. Copying uses OSC 52, so it works over SSH when the terminal supports it; under tmux set set -g set-clipboard on. shift+drag keeps the terminal’s own selection.

Changed

  • Per-call permission review: every bash, write and edit call 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; fetch keeps its origin grants. permissions.automatic_review: false in config.json asks 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+c continues, 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_STEPS still sets an explicit cap. Subagents keep their 50-step budget.

Fixed

  • grep tool: 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.json and 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 task tool (task(agent, prompt, delivery?), for agents whose mode is subagent or all, such as explore and review). 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_concurrent in config.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 gets ask or task, and cannot write when it or its parent is read-only or plan.
  • 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.received with source: "subagent". By default it arrives at the parent’s next turn; delivery: "steer" adds it to the running turn at its next safe point, and delivery: "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). steer adds guidance to the subagent’s running turn before its next model request, after all the tool results of its current step; interrupt cancels 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. stuck stops it and hands its partial work to the parent; doubt pauses it and asks the parent’s agent, which answers with decide_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}/continue gives 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+s opens a searchable list, also below 80 columns; enter opens a subagent’s conversation read-only, alt+w goes back. Stop and alt+c (continue) ask for confirmation. A finished subagent shows as one line in the parent conversation.
  • Events: subagent.spawned, subagent.done and subagent.watchdog on the parent’s stream; the event catalog has 22 kinds. Session lists show parent_session_id, child_status and promptable: false for 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 tui starts a private server, or --url connects to a running server (set NAVAI_SERVER_TOKEN when 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+r toggles it). The sidebar is hidden below 80 columns. alt+1–alt+9 switch tabs, alt+w closes one, ctrl+n starts a conversation, ctrl+o opens the session list, alt+b hides or shows the sidebar, f2 picks a model, tab on an empty message switches build/plan, shift+tab changes focus, and ctrl+q quits.
  • 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 //help sends /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 and f2 switch the live conversation; ctrl+n in the model picker starts a new conversation on that model. /variant lists the session’s choices; /variant <name> selects one, and /variant off clears 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, fetch and ask; sessions also provide a skill tool to load a listed skill. navai run does not approve tool permissions automatically: it prints permission_required: <tool> <scope> and exits with code 3. --yes approves each request once. Ctrl-C interrupts a run and exits with code 130.
  • Plan agent guard: plan can 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/models reports the resolved window and its source.
  • Workspace tools: glob searches for files without following links, with a default limit of 200 results (up to 1,000). fetch retrieves 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. /theme opens 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 former navigator and rust-agent directories on first start without overwriting existing files.
  • Project identity: the crate and binaries are named navai and navai-lsp; the VS Code extension is Navai (pixmants.navai). Environment variables use the NAVAI_* 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 read and grep tools 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 ask in 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 ask included; 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. See ARCHITECTURE.md.

Removed

  • The WebSocket transport and the placeholder tui command.

[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

  • main holds releases only, each tagged vX.Y.Z. Never commit to it.
  • Branch off develop: feat/<name>, fix/<name>, refactor/<name>, docs/<name> or test/<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

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, nav binary, 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 serve binds to loopback (127.0.0.1) by default. A non-loopback bind needs the explicit --allow-non-loopback flag.
  • nav tui and nav web start 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.