Newer
Older
navi-1 / docs / config.md

Configuration

All configuration is loaded from .env via pydantic-settings (navi/config.py). The global settings object is imported everywhere as from navi.config import settings.

General

Variable Type Default Description
NAVI_DEFAULT_PROFILE_ID str "" Default profile used when a client creates a session without specifying a profile_id. Empty string means "no default".

LLM

Variable Type Default Description
LLM_CONFIG_FILE str llm.json Path to the routing config (see LLM routing). Empty string disables it.
OLLAMA_HOST str http://localhost:11434 Ollama server URL (used when LLM_CONFIG_FILE is absent)
OLLAMA_API_KEY str "" Ollama Cloud API key (used when LLM_CONFIG_FILE is absent)
OLLAMA_DEFAULT_MODEL str gemma4:31b-cloud Default model (can be overridden per profile)
OLLAMA_NUM_CTX int 65536 Context window size in tokens (a route entry's context_window wins)
OLLAMA_THINK bool true Enable extended reasoning (thinking)
OLLAMA_REQUEST_TIMEOUT int 30 Seconds before Ollama request times out (affects fallback speed)
EMBEDDING_OLLAMA_HOST str "" Ollama server for embedding model (falls back to OLLAMA_HOST if empty)
EMBEDDING_OLLAMA_API_KEY str "" API key for embedding Ollama server
EMBEDDING_MODEL str nomic-embed-text:latest Embedding model for memory vector search
EMBEDDING_ROUTE str embedding llm.json route the embedding calls walk. Empty means "pass the model name only".
EMBEDDING_DIMENSIONS int 768 Vector dimensionality for embeddings
OPENAI_API_KEY str "" OpenAI API key (if using OpenAI backend)
OPENAI_MODEL str "gpt-4" Default model for OpenAI backend
OPENAI_BASE_URL str \ None None Custom base URL for OpenAI-compatible endpoints (e.g. vLLM, LM Studio)
ANTHROPIC_API_KEY str "" Reserved — no Anthropic backend implemented yet

Without llm.json (a bare dev checkout), Navi talks to one host built from OLLAMA_HOST/OLLAMA_API_KEY and the per-profile model list, exactly as before.

LLM routing (llm.json)

llm.json (repo root, committed — it holds no secrets) declares the hosts and the ordered chains of models to try on them. Backups are a property of the file, not of the profile: when the cloud is unreachable or out of quota, the next entry answers and a local model keeps Navi usable.

{
  "targets": {
    "cloud": { "host": "https://ollama.com", "api_key_env": "OLLAMA_API_KEY" },
    "home":  { "host": "http://192.168.1.129:11434" }
  },
  "routes": {
    "default": [
      { "model": "glm-5.3-flash:cloud", "target": "cloud", "context_window": 131072 },
      { "model": "qwen3.6:35b-128k", "target": "home", "context_window": 131072,
        "options": { "presence_penalty": 0.0 } }
    ],
    "embedding": [
      { "model": "nomic-embed-text:latest", "target": "home" },
      { "model": "nomic-embed-text:latest", "target": "cloud" }
    ]
  },
  "probe": { "interval_sec": 60, "timeout_sec": 5 }
}
  • targets — named hosts. api_key_env names an environment variable and is read from os.environ or from the .env file itself (the process environment does not receive it), so the deploy branch's .env does not change. Any name works — that is how a second Ollama Cloud account joins a route: give it its own target and its own variable, e.g. cloud2 with "api_key_env": "OLLAMA_API_KEY_2", then list both in the chain. A literal api_key is also accepted, but llm.json is committed: keep secrets in .env and reference them by name.
  • routes — ordered chains; the first entry is the primary, the rest are backups. context_window caps num_ctx for that model and drives the compressor's window, and options are merged over the global Ollama options. When every entry of a route serves the same budget, give them the same context_window: the compressor counts against whichever entry is active, so an uneven window silently changes how much history a failover can hold.
  • probe — how often the background prober re-checks a degraded target.

A profile joins a route with "llm_route": "default" (see profiles.md). No llm_route means the profile's model list is used, each name resolved to the target the config says serves it.

What fails over, and when

Failure Classification Engine behaviour
connection refused / timeout / first-chunk timeout LLMConnectionError target down for one probe interval, next entry
HTTP 429, or a "rate limit"/"quota" message LLMQuotaExceededError target cooling, at least the Retry-After and one interval
HTTP 401/403 LLMAuthError target auth, re-checked every 300 s (the key may be rotated)
HTTP 5xx LLMServerUnavailableError target cooling for one interval
HTTP 404 / model not found LLMModelNotFoundError only that (target, model) pair is marked missing
anything else (a malformed request) LLMBackendError not retried — every entry would say no

LLM_COMPLETE_RETRIES applies only to the last entry left: a dead cloud model must not cost retries × LLM_STREAM_FIRST_CHUNK_TIMEOUT on every call while a healthy local model sits one step down the chain.

For streaming calls the first chunk is awaited before anything is yielded, so a failure is caught before output reaches the client. A stream that breaks after the first chunk is not retried — the turn fails rather than mixing two partial answers from two hosts.

Switching back

Switch-back never happens mid-turn. RouteHealth probes only degraded targets (once per probe.interval_sec, with probe.timeout_sec), using list() for reachability/auth, show() for a missing model, and a one-token chat for a quota cooldown. When the probe reports the primary healthy again, the next user turn starts on it; the running turn keeps the host it began on.

/health reports which entry serves each route and why a target is degraded — see api.md. POST /admin/ollama/clear-blacklists forgets all recorded failures (and restarts every route at its primary) after a config fix.

If the file is broken

A missing llm.json (or LLM_CONFIG_FILE="") means "no routes" and the legacy single-host path. A malformed file raises LLMConfigError at startup, naming the file and the offending target or route: silently routing to the wrong host is worse than refusing to start.

Web search is not a Settings field: it is the navi-web MCP server. Its settings reach it through mcp_servers.d/navi-web.json, which maps SEARXNG_URL (and, optionally, BRAVE_SEARCH_API_KEY) from .env into the server's own environment. SEARXNG_URL is therefore a real .env setting — a self-hosted SearXNG, e.g. http://localhost:8888 — just not one Navi itself reads. Without it the server falls back to DuckDuckGo scraping. See mcp.md.

LLM Timeouts

Variable Type Default Description
LLM_COMPLETE_TIMEOUT int 120 Seconds before a non-streaming complete() call times out
PLANNING_LLM_TIMEOUT_SEC int 240 Seconds before a non-streaming planning phase call times out (plan tool / subagent pipeline). Wider than LLM_COMPLETE_TIMEOUT — cloud models prefill big planning prompts for up to a couple of minutes, and a timeout here silently kills the plan.
LLM_STREAM_FIRST_CHUNK_TIMEOUT int 90 Seconds to wait for the first token of a streaming call (prefill phase)
LLM_STREAM_CHUNK_TIMEOUT int 60 Max seconds between consecutive tokens in a streaming call
LLM_COMPLETE_RETRIES int 2 Attempts on the last remaining route entry after a connection error. Entries with a healthy alternative are tried once, so failover stays fast.
LLM_RETRY_BACKOFF_SEC float 2 Base sleep between those attempts (multiplied by the attempt number)

Large contexts can take 60–90 s to prefill; LLM_STREAM_FIRST_CHUNK_TIMEOUT=90 covers the common case for local Ollama. Increase (e.g. 180) for very large contexts or slow GPUs; reduce for fast cloud endpoints.

Security / Sandboxing

Variable Type Default Description
FS_ALLOWED_PATHS str "*" Comma-separated paths the filesystem tool can access. "*" = no restriction
TERMINAL_ALLOWED_COMMANDS str "*" Comma-separated allowed executables for terminal. "*" = allow all
SSH_HOSTS_FILE str ssh_hosts.json Path to JSON file with named SSH connections

settings.fs_allowed_paths_list and settings.terminal_allowed_commands_list are computed properties that parse the comma-separated strings into lists.

Variable Type Default Description
TERMINAL_USER_ALLOWED_COMMANDS str long allowlist Comma-separated allowed executables for non-admin users. Admin bypasses this restriction.

settings.terminal_user_allowed_commands_list — computed property parsed from the comma-separated string.

Database

Variable Type Default Description
DATABASE_URL str "" PostgreSQL URL (postgresql://user:pass@host:port/db). Required — Navi requires PostgreSQL 15+ with pgvector extension.

Server / deployment shape

Variable Type Default Description
NAVI_WEBCLIENT_ENABLED bool true Master switch for the web UI. false registers none of the web-facing routes (/, /assets, /images, /content-viewers, /content, /admin panel, /debug*) and skips the navi_ui MCP server — a pure API/WS server for terminal clients. The admin JSON API (/admin/*) stays, still gated by require_admin. Toggling requires a server restart.
NAVI_HOST str 127.0.0.1 Bind address for the navi-server launcher (uvicorn). 127.0.0.1 = local-only; remote clients connect via SSH tunnel or reverse proxy.
NAVI_PORT int 8099 Bind port for the navi-server launcher.

Swarm / hive registry

Navi instances can form a swarm: each announces itself to a tiny registry service ("hive", see hive/README.md) running on the main server; agents ask the book who is alive and where. The registry is never a blocking dependency — with HIVE_URL empty, or with the hive down, everything else works and the agent is simply told the book is unreachable.

Variable Type Default Description
HIVE_URL str "" Hive registry address (e.g. http://192.168.1.168:8087). Empty = standalone navi, no announcements.
ANNOUNCE_INTERVAL_SEC int 120 How often this navi re-announces itself to the hive.
SWARM_KEY_FILE str .swarm-key Shared PSK file (gitignored, 0600, generated by deploy/install.sh).
INSTANCE_FILE str instance.json This navi's identity for the swarm: generated name (single female name from Japanese/American/Spanish pools) + instance id. Rename = edit the file, restart.
PEER_ASK_PROFILE str server_admin Which profile answers incoming /peer/ask questions (one-shot agent run).
PEER_ASK_TIMEOUT_SEC int 120 Hard ceiling for a single peer ask — the LLM turn inside /peer/ask.

See deploy/README.md for the one-command server deployment (bash deploy/install.sh): dockerized PostgreSQL + systemd + navi-code in PATH, web UI and auth off by default.

Logging

Variable Type Default Description
LOG_LEVEL str INFO Python logging level (DEBUG, INFO, WARNING, ERROR)

Tools

Variable Type Default Description
TOOLS_DIR str tools Directory for user-defined tools (auto-discovered at startup)
CONTEXT_PROVIDERS_DIR str context_providers Directory for user-defined context providers (auto-discovered at startup)

MCP user_key (BYOK)

Per-server JSON config (mcp_servers.d/<name>.json) — where a user's personal credential is injected. Details in mcp.md.

// HTTP server: key goes into an Authorization-style header
{ "transport": "streamable_http", "url": "https://…",
  "user_key": { "header": "Authorization", "prefix": "Bearer " } }

// stdio server: key goes into an env var
{ "transport": "stdio", "command": "uvx", "env": { "API_KEY": "default" },
  "user_key": { "env": "API_KEY" } }

Keys are stored Fernet-encrypted (NAVI_AUTH_ENCRYPTION_KEY, see Authentication above). A user without a saved key is served the default credential from the config file only if their role is admin; every other role is refused, and the server is left out of their tool list entirely. See mcp.md.

Session files

Variable Type Default Description
SESSION_FILES_DIR str session_files Directory for uploaded session files
SESSION_FILES_MAX_SIZE_MB int 200 Max upload size per file in megabytes
SHARE_FILE_MAX_SIZE_MB int 1024 Max file size share_file may copy into session files, in megabytes
SESSION_MESSAGES_WINDOW int 1000 Max hot (non-archived) messages per session
WS_REPLAY_BUFFER_SIZE int 500 Max events retained per WebSocket turn for reconnect replay

Public URL

Variable Type Default Description
PUBLIC_URL str http://localhost:8099 Base URL used by share_file to build download links. Set this when behind a reverse proxy.

Context compression

Variable Type Default Description
CONTEXT_COMPRESSION_ENABLED bool true Enable/disable automatic context compression
CONTEXT_COMPRESSION_THRESHOLD float 0.90 Trigger compression at this fraction of OLLAMA_NUM_CTX
CONTEXT_KEEP_RECENT int 4 Number of recent conversation turns to keep verbatim
CONTEXT_SUMMARY_TEMPERATURE float 0.3 Temperature for the summarization LLM call
CONTEXT_SUMMARY_MAX_TOKENS int 6000 Max output tokens for the summary LLM call
OUTPUT_RESERVE_TOKENS int 2048 Headroom reserved for model response in context size checks
CONTEXT_MESSAGE_TOKEN_BUDGET int 0 Per-message token budget for the LLM context view. A single tool/assistant message whose estimated size exceeds this is head/tail-truncated in the built context only (stored history is never mutated) so one huge tool result cannot alone blow the window. 0 = auto (OLLAMA_NUM_CTX // 6).

Gmail

Variable Type Default Description
GMAIL_ADDRESS str "" Gmail address for the email_manager tool (IMAP/SMTP with App Password)
GMAIL_APP_PASSWORD str "" Gmail App Password (not the account password — generate at myaccount.google.com)

Authentication (gnexus-auth OAuth)

Variable Type Default Description
NAVI_AUTH_ENABLED bool true Master auth switch. Set false to disable OAuth/API-token auth entirely. Every request is treated as the local anonymous admin user. Use only for trusted single-user/local deployments.
GNAUTH_BASE_URL str http://gnexus-auth.local gnexus-auth server base URL
GNAUTH_CLIENT_ID str "" OAuth client ID
GNAUTH_CLIENT_SECRET str "" OAuth client secret
GNAUTH_REDIRECT_URI str http://localhost:8099/auth/callback Must match redirect URI registered in gnexus-auth
GNAUTH_ADMIN_ROLE_SLUG str navi_admin Role slug that maps to Navi admin role
GNAUTH_USER_ROLE_SLUG str navi_user Role slug that maps to Navi user role
GNAUTH_PROFILE_PATH str /account/profile Path appended to gnauth_base_url for profile links
NAVI_AUTH_ENCRYPTION_KEY str "" Fernet key (base64, 32 bytes). Generate once with python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())". Never change after first launch.
NAVI_AUTH_COOKIE_NAME str navi_auth_session Session cookie name
NAVI_AUTH_COOKIE_SECURE bool False Set True behind HTTPS
NAVI_AUTH_COOKIE_SAMESITE str lax SameSite policy
NAVI_AUTH_COOKIE_MAX_AGE_DAYS int 30 Cookie lifetime

Full auth setup guide: docs/auth.md.

Web push (PWA)

Variable Type Default Description
NAVI_PUSH_VAPID_PUBLIC_KEY str "" VAPID public key for web push. Empty = push fully disabled (endpoints 503, no notifications sent).
NAVI_PUSH_VAPID_PRIVATE_KEY str "" VAPID private key. Generate the pair once: pip install py_vapid && python -m py_vapid gen.
NAVI_PUSH_VAPID_SUBJECT str mailto:admin@navi.local Contact for the push service (a mailto: or https: URL).
NAVI_PUSH_COOLDOWN_SEC int 30 Minimum seconds between two pushes for the same session.

Push notifications fire when an agent turn (or a scheduled recall) completes and no browser tab is watching that session. See docs/push.md.

Synapse source

Variable Type Default Description
SYNAPSE_SOURCE_URL str "" GNEXUS Synapse base URL for the outgoing source API. Empty = nothing is ever emitted.
SYNAPSE_SOURCE_API_KEY str "" syn_* source key registered in the Synapse admin panel (write it into .env yourself).
SYNAPSE_SOURCE_NAME str navi Source name on Synapse's side.

Without both URL and key, Synapse-linked outgoing events are silently skipped and the UI disables Synapse-linked options (source_ready: false). See docs/synapse.md.

Persona

Variable Type Default Description
NAVI_PERSONA str "" Global personality prompt prepended to every profile's system prompt
NAVI_PERSONA_FILE str "" Path to a .txt file containing the persona (preferred over inline NAVI_PERSONA)

Recommended: use NAVI_PERSONA_FILE=persona.txt rather than inlining the persona in .env, because multi-line values don't parse reliably in .env files.

The _load_persona_from_file validator reads the file on startup if NAVI_PERSONA is empty and NAVI_PERSONA_FILE is set.

Parallelism and background tasks

Variable Type Default Description
TASKS_MAX_PER_SESSION int 5 Concurrent running background tasks per session
TASKS_MAX_GLOBAL int 20 Concurrent running background tasks server-wide
TASKS_MAX_SPAWN int 2 Concurrent background spawn_agent per session
TASKS_TTL_SEC int 3600 Finished-task retention before reaping
TASKS_EVENT_BUFFER_SIZE int 50 Per-task event ring (spawn tasks use 200)
TASKS_RATE_LIMIT int 10 Max task spawns per 5 min per session
TASK_NOTES_MAX_PENDING int 20 Pending completion notes per session
TASK_NOTES_PER_TURN int 5 Max notes coalesced into one turn injection
MESSAGE_QUEUE_MAX int 5 User messages queued while a run is active
BACKGROUNDABLE_TOOLS str terminal,ssh_exec,peer,spawn_agent,code_exec Tools accepting "background": true
PARALLEL_TOOL_CALLS bool false Execute a multi-tool-call batch concurrently (per-profile override: AgentProfile.parallel_tool_calls)

See docs/tasks.md for the full mechanism.

When a terminal (run), code_exec or ssh_exec call is detached and the agent did not pass timeout, the executor lifts it to 300 s for the detached run only — the tools' short foreground defaults (20/30/60 s) would otherwise mark long commands "completed" with partial output while the process is still running.

Example .env

# LLM — Ollama (primary)
OLLAMA_HOST=http://localhost:11434
OLLAMA_API_KEY=
OLLAMA_DEFAULT_MODEL=gemma4:31b-cloud
OLLAMA_NUM_CTX=65536
OLLAMA_THINK=true
OLLAMA_REQUEST_TIMEOUT=30

# LLM routing (hosts + backup chains). Committed; no secrets inside:
# LLM_CONFIG_FILE=llm.json

# LLM — OpenAI (optional)
# OPENAI_API_KEY=sk-...
# OPENAI_MODEL=gpt-4
# OPENAI_BASE_URL=https://api.openai.com/v1

# Database (PostgreSQL 15+ with pgvector)
DATABASE_URL=postgresql://user:pass@localhost:5432/navidb

# Security / sandboxing
FS_ALLOWED_PATHS=*
TERMINAL_ALLOWED_COMMANDS=*

# Misc
LOG_LEVEL=INFO
TOOLS_DIR=tools
SHARE_FILE_MAX_SIZE_MB=1024
SESSION_MESSAGES_WINDOW=1000
WS_REPLAY_BUFFER_SIZE=500

# Context compression
CONTEXT_COMPRESSION_ENABLED=true
CONTEXT_COMPRESSION_THRESHOLD=0.90
CONTEXT_KEEP_RECENT=4

# Persona
NAVI_PERSONA_FILE=persona.txt
NAVI_DEFAULT_PROFILE_ID=navi_code