Configuration
September 23, 2026 · View on GitHub
Iva is configured by one file: .env in the install directory. The setup wizard fills it in for you — run iva config any time to redo a step (cli.md). .env.example in the repo root is the template. This page documents every variable.
Every change needs a restart. Iva reads .env once at startup. After editing:
iva restart
No rebuild. Swapping a model, key or provider is edit → restart.
One exception: the reply language. The 🌐 Language button in /menu writes data/settings.json, which both processes re-read live — the switch takes effect immediately, no restart. See menu.md.
Model provider
Six providers. Pick one with MODEL_PROVIDER and fill only that block. ollama/opencode/openrouter are OpenAI-compatible API keys; codex rides your OpenAI (ChatGPT) subscription via OAuth — no key; claude rides your Claude Pro/Max subscription through the claude CLI signed in on the same server — no key either; custom is any OpenAI-compatible endpoint you point Iva at yourself. Prices and full model lists: providers.md.
| Variable | Default | Notes |
|---|---|---|
MODEL_PROVIDER | ollama | ollama (Ollama Cloud), opencode (OpenCode Go), openrouter (OpenRouter), codex (OpenAI ChatGPT subscription), claude (Claude Pro/Max subscription via the claude CLI) or custom (your own OpenAI-compatible endpoint). |
OLLAMA_API_KEY | — | Key from ollama.com. |
OLLAMA_MODEL | deepseek-v4-pro | Any model on your Ollama Cloud plan. |
OLLAMA_VISION_MODEL | gemma4:31b | Model that describes incoming photos. Your text model is usually text-only, so vision runs on its own model with the same key. Blank = the default. |
OLLAMA_CONTEXT_WINDOW | 131072 | See warning below. |
OPENCODE_API_KEY | — | Key from opencode.ai/auth. |
OPENCODE_MODEL | deepseek-v4-pro | Any Go model, bare ID (e.g. kimi-k3) — iva config shows the live list. |
OPENCODE_VISION_MODEL | qwen3.7-plus | Same for OpenCode Go, bare ID. Pick from the models that actually read images — many Go models answer 400 on a picture, and some mix their reasoning into the description. |
OPENCODE_CONTEXT_WINDOW | 131072 | Same warning. |
OPENROUTER_API_KEY | — | Key from openrouter.ai/keys (starts with sk-or-). |
OPENROUTER_MODEL | openai/gpt-5.1 | The model slug from openrouter.ai/models, form vendor/model (e.g. anthropic/claude-sonnet-4.5). iva config sends a live test request so a wrong slug can't slip through. |
OPENROUTER_VISION_MODEL | google/gemini-2.5-flash | Same for OpenRouter — any slug that accepts images. iva config does not test it live. |
OPENROUTER_CONTEXT_WINDOW | 131072 | Same warning — set the real window of the model you picked. |
CODEX_MODEL | gpt-5.5 | Model from your OpenAI plan. iva config lists what your subscription actually exposes. |
CODEX_CONTEXT_WINDOW | 272000 | Same warning — set the real window of the model you picked. |
CLAUDE_MODEL | claude-fable-5-1 | One of Fable 5.1 (claude-fable-5-1, default), Opus 5.5 (claude-opus-5-5) or Sonnet 5 (claude-sonnet-5). iva config lists the ones the CLI picker actually has. |
CLAUDE_CONTEXT_WINDOW | 1000000 | Same warning — 1000000 for Fable 5.1, Opus 5.5 and Sonnet 5. |
CLAUDE_COMMAND | (empty) | Path to the claude binary, or a command line with arguments; a path with a space is not supported. Empty means the claude found in the service's PATH (node directory, ~/.local/bin, /usr/local/bin, /usr/bin, /bin). |
CUSTOM_BASE_URL | — | Required for custom. The OpenAI-compatible base in full, including the /v1-style suffix — same convention as Ollama's https://ollama.com/v1. Empty stops the agent at startup naming this variable; it never falls back to a guess. |
CUSTOM_API_KEY | — | Optional. Empty means no Authorization header at all, which is what a self-hosted endpoint usually wants. iva doctor does not ask for it. |
CUSTOM_MODEL | — | Required for custom. There is no default: only you know what the endpoint serves. Empty stops the agent at startup, it does not borrow another provider's model. |
CUSTOM_VISION_MODEL | (empty) | Optional, no default — a chat model that reads images is asked directly and needs nothing here. Empty leaves the fallback path pointing at the chat model itself, so a blind model simply yields no description. The fallback call needs CUSTOM_API_KEY; without a key it is skipped. |
CUSTOM_CONTEXT_WINDOW | 131072 | Same warning — set the real window of the model you picked. |
CUSTOM_REASONING | (empty) | Set to 1 when the endpoint understands OpenAI's reasoning_effort (Meta Model API, vLLM serving a reasoning model): Iva then forwards THINKING_EFFORT to it. Empty sends nothing — an endpoint that does not know the field may answer 400 on every turn. |
THINKING_EFFORT | (empty) | Reasoning effort: minimal, low, medium, high, xhigh, max. Written by /model and /think in Telegram (cli.md), not by hand. codex takes it natively; ollama/opencode send low/medium/high over the OpenAI-compatible wire and ignore the rest; picking openrouter or custom in the wizard drops the line altogether, and a value typed in by hand is ignored there — except custom with CUSTOM_REASONING=1, which forwards low/medium/high like ollama does. |
*_VISION_MODEL is a fallback, not the main path. On the first incoming picture Iva asks the chat model itself with one tiny request whether it takes images. If it does, the picture goes straight to it — full pixels, no retelling — and the vision model is not called at all. If it answers with a refusal, the picture goes to *_VISION_MODEL as before and the model gets its description. The answer is remembered until the process restarts, so after a restart or a model change the question is asked again. One consequence of switching to a model without vision: pictures you sent earlier have no description saved for them, so send the photo again if the answer needs it.
Those six names, spelled exactly. Any other value — a typo, a different case — stops the agent at startup and prints the accepted names, instead of running Ollama under a name nobody configured; iva doctor and /menu → 📊 Status report the same thing (troubleshooting.md). No MODEL_PROVIDER line at all still means ollama.
For custom nothing is guessed: CUSTOM_BASE_URL and CUSTOM_MODEL are the two lines the provider cannot work without, and a blank one is a startup refusal naming the variable, not a silent default. THINKING_EFFORT is ignored on custom on purpose — OpenAI compatibility promises nothing about reasoning_effort, and sending it blind to an unknown endpoint risks an HTTP 400 on every turn. Full walkthrough: providers.md.
For codex there is no API key in .env: run iva login (device code, headless-friendly) or iva login --browser. The OAuth token lives in data/codex-auth.json (chmod 600, gitignored) and is auto-refreshed before it expires. Full flow: providers.md.
For claude there is no API key either: install the CLI on the server as the service user (npm install -g --prefix ~/.local @anthropic-ai/claude-code, no root) and sign it in once (claude auth login). Iva never sees a key — it calls that CLI, and iva doctor reports the plan from claude auth status. Keep ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_FOUNDRY_API_KEY, ANTHROPIC_BASE_URL and any CLAUDE_CODE_USE_* out of .env: each of them sends the CLI somewhere other than your subscription, so Iva refuses to use this provider and says which variable is in the way rather than quietly ignoring it. Full flow: providers.md.
Don't inflate the context window. Compaction triggers at 70% of this number. Set it above the model's real window and the compactor fires too late — the request overflows before history gets trimmed. When you switch models, enter the new model's actual window, not a rounder bigger one.
Telegram
| Variable | Default | Notes |
|---|---|---|
TELEGRAM_BOT_TOKEN | — | From @BotFather. |
TELEGRAM_BOT_USERNAME | — | Your bot's username. The wizard verifies the token via getMe and detects this itself. |
TELEGRAM_WEBHOOK_SECRET_TOKEN | — | Shared secret between the long-poll bridge and the local webhook. Any long random string. |
TELEGRAM_ALLOWED_USER_IDS | (empty) | Comma-separated numeric user IDs allowed to talk to Iva. |
TELEGRAM_DIGEST_CHAT_ID | — | Chat that receives the morning digest, nightly memory reports and one-time stable update offers. Usually your own chat ID. |
SUPPORT_CHAT_URL | (empty) | Invite link to the Telegram support chat the report-problem skill offers when a turn fails; empty means Iva says the address is not configured. |
TELEGRAM_RICH_REPLIES | auto | auto sends a reply as a Telegram rich message when it contains a table, task list, <details>, block formula, footnote, media block or a <tg-*> tag; never keeps every current-chat reply on the ordinary HTML/plain path except replies with buttons, which exist only as rich messages. Also switchable in /menu → Rich replies. Any other value stops startup. iva post is not affected. |
The allowlist is fail-closed: empty means Iva answers nobody. The wizard auto-discovers your ID the moment you message the bot; or ask @userinfobot. Why fail-closed matters: security.md.
At 10:00 in ASSISTANT_TIMEZONE Iva checks Git upstream without using the model. It sends nothing unless a higher stable MAJOR.MINOR.PATCH version exists, and offers each version only once. If TELEGRAM_DIGEST_CHAT_ID is empty, the first trusted ID is used.
Voice
| Variable | Default | Notes |
|---|---|---|
DEEPGRAM_API_KEY | — | Optional. From console.deepgram.com. Transcribes voice notes, video circles and audio files; without it recordings are saved but not transcribed. The wizard lets you skip it (Enter); add it later in /menu → 🎤 Voice or iva config. Free tier: providers.md. |
DEEPGRAM_LANGUAGE | multi | multi auto-detects the language per message (ru/uz/en and others). Pin a single code like en only if auto-detection trips on your mix. |
Search
| Variable | Default | Notes |
|---|---|---|
SEARCH_PROVIDER | tavily | tavily, exa, parallel or brave. |
TAVILY_API_KEY EXA_API_KEY PARALLEL_API_KEY BRAVE_API_KEY | — | Key for the matching provider. Keys can coexist; switching providers is just the flag. |
No key for the active provider means web_search returns a clear error — nothing crashes. Free tiers and the comparison table: providers.md.
Memory
| Variable | Default | Notes |
|---|---|---|
MEMORY_SEARCH_MODE | grep | grep = BM25 over Node's built-in SQLite FTS5 plus graph rerank. Zero external deps, zero keys, runs on a $4 box. hybrid adds dense embeddings — one external key. |
JINA_API_KEY | — | For hybrid. Jina jina-embeddings-v3: no-train policy, EU hosting. |
DEEPINFRA_API_KEY | — | For hybrid. Cheaper, serves BAAI/bge-m3. One of the two keys is enough. |
MEMORY_EMBED_PROVIDER | (auto) | Override auto-pick: jina or deepinfra. |
MEMORY_EMBED_MODEL | jina-embeddings-v3 | Embedding model name. |
MEMORY_EMBED_URL | — | Any OpenAI-compatible embeddings endpoint, e.g. a local Ollama at http://127.0.0.1:11434/v1/embeddings — then no external key at all. |
The nightly Brain pass builds the hybrid index; to build it now, run node --env-file=.env scripts/memory/embed-index.ts. How search actually works: memory.md.
Notices
What Iva says on her own lives in data/settings.json, not in .env — the 🔔 Notices screen in /menu writes these keys, and the report switch is read at the end of each nightly run, the digest switch when its schedule fires — so a tap applies without a restart.
| Key | Default | Notes |
|---|---|---|
memoryReports.enabled | false | The nightly memory report (daily 04:00 and weekly Mon 04:15) in Telegram. Off means the vault is still written. |
digestSchedule.enabled | false | The morning digest at 08:00. /digest works by hand either way. |
Alerts — problems (memory not backed up, a failed nightly pass) and new versions — always arrive: they cannot be switched off, but each one says what to do and repeats at most once a week per problem — ADR-0007.
System
| Variable | Default | Notes |
|---|---|---|
AGENT_LANGUAGE | ru | en or ru. Sets Iva's reply language, date locale, and which CORE.md seed init-vault uses. The 🌐 Language button in /menu overrides it at runtime via data/settings.json (read fresh every turn) and mirrors the choice back here, so the switch is instant — no restart (menu.md). |
ASSISTANT_TIMEZONE | Asia/Almaty | IANA name. Sets daily-transcript dates, two systemd watchdog timers, seven in-process eve schedules, and the date/time Iva sees each turn. Exported as TZ. |
ASSISTANT_VAULT_DIR | vault | The live memory: a separate private git repo, opens in Obsidian. |
ASSISTANT_DATA_DIR | data | Runtime data: tasks.json, token log usage.jsonl. |
IVA_PORT | 8723 | Local eve server port. Deliberately unfashionable — 3000/8000/8080 are usually taken on a stock VPS by docker and friends. Change it via iva config, not by hand: the systemd unit pins the port literally and must match (deploy.md). |
ASSISTANT_HOST | http://127.0.0.1:${IVA_PORT} | Where the poll bridge and memory scripts reach the server. Change only if the agent runs on another host. |
ASSISTANT_BEARER | (generated) | Shared secret required by Eve session routes. Setup/upgrades create it; local clients read it automatically. Keep it private. |
AGENT_BROWSER_MAX_OUTPUT | 24000 | Character cap on agent-browser output, so one page dump can't eat the context window. |