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 restartNo 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.
Four 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. Prices and full model lists: providers.md.
| Variable | Default | Notes |
|---|---|---|
MODEL_PROVIDER |
ollama |
ollama (Ollama Cloud), opencode (OpenCode Go), openrouter (OpenRouter) or codex (OpenAI ChatGPT subscription). |
OLLAMA_API_KEY |
— | Key from ollama.com. |
OLLAMA_MODEL |
deepseek-v4-pro |
Any model on your Ollama Cloud plan. |
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_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_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. |
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.
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.
| 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. |
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.
| Variable | Default | Notes |
|---|---|---|
DEEPGRAM_API_KEY |
— | From console.deepgram.com. Transcribes voice notes, video circles and audio files. 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. |
| 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.
| 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.
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.
| 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, five 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. |