# 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`.

## LLM

| Variable | Type | Default | Description |
|---|---|---|---|
| `OLLAMA_HOST` | str | `http://localhost:11434` | Ollama server URL (used when `OLLAMA_BACKENDS_FILE` is not set) |
| `OLLAMA_API_KEY` | str | `""` | Ollama Cloud API key (used when `OLLAMA_BACKENDS_FILE` is not set) |
| `OLLAMA_DEFAULT_MODEL` | str | `gemma4:31b-cloud` | Default model (can be overridden per profile) |
| `OLLAMA_NUM_CTX` | int | `65536` | Context window size in tokens |
| `OLLAMA_THINK` | bool | `true` | Enable extended reasoning (thinking) |
| `OLLAMA_BACKENDS_FILE` | str | `""` | Path to JSON file with multi-server config (see below). When set, overrides `OLLAMA_HOST`/`OLLAMA_API_KEY`. |
| `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_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 |

For direct Ollama Cloud access without fallback: set `OLLAMA_HOST=https://ollama.com` and `OLLAMA_API_KEY=<key>`.

## Web Search

| Variable | Type | Default | Description |
|---|---|---|---|
| `BRAVE_SEARCH_API_KEY` | str | `""` | Brave Search API key (free tier: 2000 req/month). Used when DuckDuckGo returns no results. |
| `SEARXNG_URL` | str | `""` | Self-hosted SearXNG meta-search URL, e.g. `http://localhost:8888` |

### Multi-server fallback (`OLLAMA_BACKENDS_FILE`)

When `OLLAMA_BACKENDS_FILE` points to a JSON file, Navi uses `FallbackOllamaBackend` instead of the single-server backend. The file contains an ordered list of servers:

```json
[
  { "host": "https://ollama.com", "api_key": "ollama_..." },
  { "host": "http://localhost:11434" }
]
```

Each profile's `model` field is also a **priority list** (see `docs/profiles.md`). The fallback algorithm tries all combinations in a nested loop:

```
for each server (in order):
    for each model (in order):
        try → success: use this server+model
        LLMConnectionError → blacklist server, skip remaining models, try next server
        LLMModelNotFoundError → blacklist (server, model) pair, try next model
raise if all exhausted
```

**Blacklisting is in-memory** (module-level sets in `navi/llm/fallback.py`) and persists until server restart. A dead server or missing model is only probed once per process lifetime, avoiding latency spikes from repeated retries.

For streaming calls, the first chunk is awaited before yielding, so connection/model errors are caught before any output is sent to the client — clean retry with no partial response.

## LLM Timeouts

| Variable | Type | Default | Description |
|---|---|---|---|
| `LLM_COMPLETE_TIMEOUT` | int | `120` | Seconds before a non-streaming `complete()` call times out |
| `LLM_STREAM_FIRST_CHUNK_TIMEOUT` | int | `180` | 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 |

Large contexts can take 60–90 s to prefill; `LLM_STREAM_FIRST_CHUNK_TIMEOUT=180` is a safe upper bound for local Ollama. Reduce if using a fast cloud endpoint.

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

## 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) |

## 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:8000` | 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.70` | Trigger compression at this fraction of `OLLAMA_NUM_CTX` |
| `CONTEXT_KEEP_RECENT` | int | `8` | 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 | `3000` | Max output tokens for the summary LLM call |
| `OUTPUT_RESERVE_TOKENS` | int | `2048` | Headroom reserved for model response in context size checks |

## 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:8000/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`](auth.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.

## Example `.env`

```dotenv
# 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

# Multi-server fallback mode (overrides OLLAMA_HOST/API_KEY):
# OLLAMA_BACKENDS_FILE=ollama_backends.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.70
CONTEXT_KEEP_RECENT=8

# Persona
NAVI_PERSONA_FILE=persona.txt
```
