Skip to main content

Configuration reference

Korvun reads one JSON file, passed with --config (default korvun.json):

korvun serve --config /etc/korvun/korvun.json

The field shape is a contract: once you write a config, the names and structure are stable across releases. Validate any file offline with korvun config check <file> (add --preflight to also resolve secrets and reach the providers).

Secrets are environment variables, by NAME — never by value. Fields ending in _env (token_env, api_key_env) hold the name of an environment variable; Korvun reads the value at boot. A secret is never read from the command line, the config file, logs, or error messages. A missing secret is a loud, named boot error.

Top level

FieldTypeRequiredMeaning
channelsarrayyes (≥1)Messaging channels to run.
brainsarrayyes (≥1)Orchestrating brains.
routesarrayyes (≥1)Bindings of a channel to a brain.
storageobjectnoDurable conversation store. Absent ⇒ stateless.
observabilityobjectnoAdmin HTTP server. Absent ⇒ ON (loopback).
adminobjectnoThe write surface + the builder. Absent ⇒ read-only.

The asymmetry is deliberate: no storage means off (stateless), no observability means on with safe loopback defaults, no admin means read-only — each the safe default.

channels[]

FieldTypeRequiredValues / meaning
typestringyestelegram, discord, or webhook.
modestringconditionaltelegrampolling; discordgateway; webhook takes no mode.
token_envstringyesName of the env var holding the channel's secret (bot token, or the webhook's inbound Bearer secret).
webhookobjectwebhook onlyThe webhook block (below).

A channel registers under its type as its name — that is the value routes reference.

{ "type": "telegram", "mode": "polling", "token_env": "TELEGRAM_BOT_TOKEN" }
{ "type": "discord", "mode": "gateway", "token_env": "DISCORD_BOT_TOKEN" }

Discord needs one manual switch in the Developer Portal — the Discord guide walks through it.

The webhook block

FieldTypeRequiredValues / meaning
bindstringnoListen address. Default 127.0.0.1:8090 (loopback). A non-loopback bind warns at boot.
pathstringnoInbound POST path. Default /webhook.
outbound_urlstringyesWhere brain replies are POSTed.
outbound_token_envstringnoName of the env var holding an outbound Bearer secret. If named, it must resolve at boot.
mappingobjectnoYour JSON field names → Korvun's message fields.

Edge validation on every request: POST only (405), application/json (415), body ≤ 1 MiB (413), full buffer answers 503 (retry later). The webhook guide is the zero-to-round-trip path.

brains[]

FieldTypeRequiredValues / meaning
namestringyesUnique name, referenced by routes.
sensitivitystringyespublic | private. private drops cloud models before dispatch — sensitive payloads never leave the box.
dispatchstringnofanout (default: all models in parallel) | sequential (in order, stop at first success — a paid provider is only contacted if the local one failed).
policyobjectyesThe reducer that picks the reply (below).
modelsarrayyes (≥1)The provider catalog for this brain.
agentobjectnoMounts a bounded tool-use agent instead of the default orchestrator.

brains[].policy

FieldTypeRequiredValues / meaning
kindstringyespriority | consensus.
orderarraynoProvider priority list both reducers use.
  • priority — the reply from the highest-priority provider that answered, in order.
  • consensus — the answer a strict majority of successful providers agree on (needs at least two; a tie or a lone success ⇒ no consensus).

brains[].models[]

FieldTypeRequiredValues / meaning
providerstringyesollama | groq.
model_idstringyesThe provider's model name (e.g. llama3.2).
localitystringyeslocal | clouddeclared, not derived; the privacy selector routes on it.
base_urlstringnoOverride the adapter default (Ollama: http://127.0.0.1:11434).
api_key_envstringcloud onlyName of the env var holding the API key. Required for groq.

brains[].agent (optional)

Present ⇒ the brain is a bounded tool-use agent. tools (required, ≥1) picks from the built-in safe set time, echo, calc; max_iterations caps the loop; system_prompt appends operator instructions.

routes[]

FieldTypeRequiredMeaning
channelstringyesA configured channel's type name.
brainstringyesA configured brain's name.
{ "channel": "telegram", "brain": "assistant" }

storage (optional)

FieldTypeRequiredMeaning
pathstringnoSQLite file. Empty ⇒ <os user config dir>/korvun/korvun.db.

Present ⇒ durable per-conversation memory that survives restarts. Absent ⇒ stateless. Under the hardened systemd unit, use /var/lib/korvun/korvun.db.

observability (optional)

FieldTypeRequiredMeaning
enabledboolnoUnset ⇒ true.
addrstringnoBind address. Empty ⇒ 127.0.0.1:2112.

The admin server exposes /metrics (Prometheus), /healthz, the read-only control API, and the live view at /ui. It binds loopback by default so a fresh boot exposes nothing to the network; binding 0.0.0.0 is a conscious choice that puts auth/TLS/firewall on you.

admin (optional)

FieldTypeRequiredMeaning
token_envstringyes (when present)Name of the env var holding the admin bearer token.

The admin block turns on the write surface and the visual builder at /builder. No block, or the variable unset ⇒ read-only: the builder is not mounted at all. The token travels as Authorization: Bearer (constant-time checked, never a cookie) and is only safe over the default loopback bind or behind TLS.

{ "admin": { "token_env": "KORVUN_ADMIN_TOKEN" } }