-
Notifications
You must be signed in to change notification settings - Fork 18
Configuration Wizard
make config (which runs python -m kai install config) is an interactive question-and-answer that gathers every setting Kai needs and writes them to install.conf in the project root. In protected mode a separate sudo make install step then reads that file and lays the deployment down on disk; in single-user mode the wizard writes the runtime files directly and there is no apply step. This page explains every prompt the wizard asks: what it controls, what to enter, and when each prompt appears or is skipped.
The wizard is re-runnable. If install.conf already exists from a previous run, its values become the new defaults, so re-running only asks about changes. Hit Enter at any prompt to accept the shown default.
A handful of prompts only appear under specific conditions. Five gates account for almost all the conditional behavior:
-
Deployment mode. The first prompt selects
protected(root-owned install,sudo make installapply step) orsingle_user(run from the cloned repo, no root). The Installation paths section only appears in protected mode, protected mode makes the per-user OS isolation prompts required rather than optional, and the wizard's closing instructions differ per mode. -
Client mode. The second prompt selects which client surfaces the install serves:
hybrid,workshop-only, ortelegram-only. Everything Telegram-specific (the bot token, the Telegram user setup, the transport prompts, voice, and TTS) is skipped when Telegram is disabled, and the Workshop LAN address prompt is skipped when Workshop is disabled. A fresh workshop-only install additionally gets the Initial Workshop admin block. -
users.yamlexists. When the canonicalusers.yamlis present (/etc/kai/users.yamlin protected mode,${XDG_CONFIG_HOME:-~/.config}/kai/users.yamlin single-user mode), the wizard skips the Telegram user setup block and leaves the file untouched. A strayusers.yamlin the project root is ignored with a note telling you to move or remove it. -
Default backend. Backend-specific prompts appear only for the selected backend: Codex auth mode, reasoning effort, and turn deadline override on
codex; the LLM provider on the multi-provider backends (goose,opencode,pi); the provider API key ongoose; autocompact and effort level onclaude. There is no binary-path prompt for any backend; the install step discovers binaries and records them in/etc/kai/backends.yaml. - Memory enabled and extraction enabled. Memory tunables are nested two layers deep: most appear only when semantic memory is turned on, and the extraction tunables appear only when extraction is also turned on.
Each section below notes the gate that controls it.
- Deployment mode
- Client mode
- Installation paths
- Telegram
- User setup
- Initial Workshop admin
- Telegram transport
- Default backend
- Agent
- Webhook server
- Workspaces
- Model catalogue
- PR review agent
- Optional features
- Semantic memory
- External services
Which installation shape to configure.
-
Default:
protected -
Valid:
protected,single_user -
Guidance:
protectedis the production shape: root-owned install at the install location, secrets under/etc/kai/, a service definition, and asudo make installapply step.single_userruns Kai directly from the cloned repo under your own account: the wizard writes.envto the project root andusers.yamlunder${XDG_CONFIG_HOME:-~/.config}/kai/, and you start the daemon withmake run. Picksingle_userto evaluate Kai or for a personal dev setup; pickprotectedfor an always-on service.
If you pick single_user on a host that still has a readable /etc/kai/env from a previous protected install, the wizard refuses and tells you to remove the protected leftovers first (sudo rm -rf /etc/kai and sudo rm -f /etc/sudoers.d/kai). The runtime would otherwise keep booting from the protected files and silently ignore the single-user ones.
Which client surfaces this install serves. Writes KAI_ENABLED_ADAPTERS.
-
Default: derived from the existing
KAI_ENABLED_ADAPTERSvalue;hybridon a fresh install -
Valid:
hybrid,workshop-only,telegram-only -
Guidance:
hybridserves both the Workshop browser client and Telegram.workshop-onlydrops the Telegram application entirely; the wizard notes that the bot token will be omitted, and no Telegram prompts appear.telegram-onlydisables the Workshop client routes. Changing modes later means re-runningmake config; see the Workshop Operator Guide for what each mode serves.
This answer gates large parts of the rest of the wizard: everything under Telegram, User setup, Telegram transport, and the voice prompts requires Telegram; the Workshop LAN address prompt requires Workshop.
Protected mode only. In single-user mode the install and data directories are the project root and the service user is your own account; none of these prompts appear.
Where Kai's source code, virtual environment, and launcher script live after sudo make install. Owned by root and read-only to the service user.
-
Default:
/opt/kai - Valid: any absolute path
-
Guidance: the default is fine on both macOS and Linux. The only reason to change it is if
/optis on a small partition or you have a site convention putting third-party services elsewhere. Whatever you pick,make installwrites there as root.
Where runtime data lives: the SQLite database (which holds the canonical Workshop event store), logs, conversation history, per-user memory and preference files, per-user home workspaces, file uploads.
-
Default:
/var/lib/kai - Valid: any absolute path
- Guidance: the default follows the FHS convention for service data on Linux and works equally well on macOS. The data directory is intentionally separate from the install directory so reinstalls cannot destroy your conversation history or memory. If you change it, make sure the chosen path is on a volume with enough room for logs and the database to grow.
The OS user the daemon runs as.
-
Default:
kai - Valid: any non-empty username
-
Guidance: use a dedicated, low-privilege account. Create it ahead of time (
dscl . -createon macOS,adduseron Linux) and letsudo make installset ownership of the install and data directories accordingly. In protected mode every interactive user's agent must run under a different account than this one; the wizard enforces that below.
Which service definition to install: a launchd plist on macOS, or a systemd unit on Linux.
-
Default: auto-detected from
sys.platform -
Valid:
darwin,linux - Guidance: accept the auto-detected value unless you are deliberately preparing a config for a different host. Cross-platform configs do not transplant cleanly because path conventions and service tooling differ.
Telegram-enabled modes only.
The auth token Telegram issues when you create a bot.
- Default: none (required)
-
Valid: the token string from BotFather, in the form
1234567890:ABC... -
Guidance: create a bot by messaging @BotFather and running
/newbot. Paste the token here. The token is a secret; the wizard writes it toinstall.confwith mode 0600 and the install step redeploys it to the runtime env file. In workshop-only mode this prompt does not appear and no token is stored.
Telegram-enabled modes, first run only. The wizard checks for the canonical users.yaml (/etc/kai/users.yaml in protected mode, ${XDG_CONFIG_HOME:-~/.config}/kai/users.yaml in single-user mode); when it exists the wizard skips this block and leaves the file untouched.
The numeric Telegram user ID of the admin who will be the first user authorized to talk to the bot.
- Default: none (required)
- Valid: a positive integer
- Guidance: message @userinfobot on Telegram to find your numeric ID. The wizard rejects anything that is not a positive integer; usernames are not accepted.
A short label for the admin entry in users.yaml.
-
Default:
admin - Valid: letters, digits, spaces, hyphens, underscores
- Guidance: any short name works. It shows up in listings and some log lines; it is not used for authentication.
Which OS account the admin's agent subprocess runs under. The shape of this prompt depends on deployment mode:
-
Protected mode: required, no default. The wizard prints "Protected mode requires a distinct OS account for each interactive user" and loops until you supply a valid username that differs from the service account. The service account can read exact protected Kai files through its sudoers rules; an agent running as that identity would inherit those capabilities, so protected mode has no same-user path. The account must exist by the time
sudo make installruns; the apply step validates it and aborts before making any change if it does not. -
Single-user mode: optional, behind a "Configure advanced user options" yes/no (default No). If you opt in, the default is your own
$USERand empty skips isolation entirely (the agent spawns in-process with no sudo wrap).
There is no prompt for home_workspace; every user lands in a per-user directory under the data directory, and an admin who wants a different home adds home_workspace: /absolute/path to their entry by hand.
Where the first-time users.yaml is written depends on deployment mode: protected mode stages it under ~/.cache/kai-install/ so sudo make install can copy it into /etc/kai/users.yaml; single-user mode writes the XDG path directly.
Fresh workshop-only installs only (Workshop enabled, Telegram disabled, no runtime policy yet). This block replaces the Telegram user setup as the way the first human comes to exist.
-
Default: your
$USER - Valid: letters, digits, spaces, hyphens, underscores
-
Protected mode: required, defaults to your
$USER, must differ from the service account; same rules as the Telegram path above. - Single-user mode: not prompted; the agent runs as the service user.
The wizard then records an initial-provisioning plan and prints what it means: the first human, their direct channel, and a protected runtime assignment will be created canonically at apply time, and Telegram can be linked later without creating another human. After install, the first enrollment token prints once; copy it before it scrolls away. You provision additional people with the workshop CLI, not the wizard.
Telegram-enabled modes only. How the bot receives updates from Telegram.
-
Default:
polling -
Valid:
polling,webhook -
Guidance:
pollingis the right choice for almost every deployment. The bot opens a long-poll connection to Telegram and receives updates over it; no inbound port is exposed to the public internet. Choosewebhookonly if you have a publicly-routable HTTPS endpoint (with a valid certificate) that Telegram can reach, and you specifically want push delivery instead of pull. See Exposing Kai to the Internet for the reverse proxy setup.
When webhook is selected, two more prompts follow:
The URL Telegram should POST updates to.
- Default: none (required when transport is webhook)
- Valid: an HTTPS URL ending in a path you control
-
Guidance: must be HTTPS with a real certificate (Telegram refuses self-signed). Typically this is
https://your-domain/webhook/telegramproxied via Cloudflare Tunnel or a public reverse proxy.
A secret that Telegram includes in the X-Telegram-Bot-Api-Secret-Token header on each delivery, used to verify the request is from Telegram.
- Default: empty
- Valid: any string
- Guidance: set a long random string. Left empty, the runtime generates a fresh one per process. Whatever you set must differ from the GitHub webhook secret below; the runtime refuses to start when any two ingress secrets match.
Which agent runtime drives the inner conversation.
- Default: the existing configured backend; with no prior value, Enter is only accepted when exactly one backend is installed
-
Valid: whichever of the five supported backends (
claude,codex,goose,opencode,pi) are actually installed; the choice list comes from the installed backend registry, or from discovered binaries on a fresh host -
Guidance:
claudeuses the Claude Code CLI and benefits from Anthropic's subscription auth.codexuses the OpenAI Codex CLI, also subscription-capable.opencodeandgooseare multi-provider runtimes driven over ACP;piis a multi-provider runtime with its own RPC transport and the widest provider list. See Multiple Backends for the trade-offs. If no supported backend is installed at all, the wizard exits with an error naming all five; install one and re-run.
The follow-up prompts depend on the chosen backend. None of them collects a binary path: the install step discovers binaries and writes /etc/kai/backends.yaml, the command-path authority for every backend, whether selected globally or per-user.
Codex backend only.
-
Default:
subscription -
Valid:
subscription,api_key -
Guidance:
subscriptionuses the ChatGPT plan login stored bycodex login; no API key is collected, and the wizard reminds you to runcodex loginafter install as each targetos_user(as that user directly, not via sudo from another account).api_keyprompts forOPENAI_API_KEY(required) and bills per token.
Multi-provider backends only (goose, opencode, pi).
- Default: none (must be selected)
-
Valid: the chosen backend's provider list:
anthropic,deepseek,google,ollama,openai,openrouterfor goose and opencode; pi addsgithub-copilotandopenai-codex -
Guidance: pick the provider whose credentials you have. This drives the per-role model registry lookup at runtime.
openrouterandollamaare open-ended (any model ID works);ollamaneeds no API key because it runs locally.
Goose backend only; the variable name shown matches the chosen provider. Skipped for ollama (no key) and skipped entirely for opencode and pi, whose credentials live in their own auth stores (opencode auth login; pi /login as each target OS user, which the wizard reminds you about after install).
- Default: none (required for paid providers on goose)
- Valid: the secret string the provider issued
-
Guidance: generate the key in the provider's console. The wizard stores it in
install.confwith mode 0600 and the install step writes it into the runtime env file.
If users.yaml contains per-user goose entries on other keyed providers, the wizard also prompts for each of those provider keys here (goose has no own auth store, so the daemon env must carry them; keys added to the env file by hand are wiped on regeneration).
Installation-wide agent defaults. There is no conversational default-model prompt anymore: the conversational role resolves through the model registry for the chosen backend and provider, per-user models: entries and runtime settings override it, and the retired DEFAULT_MODEL env var is dropped from regenerated configs.
Optional walk through the six non-conversational roles: PR review, issue triage, memory extraction, memory episode, behavioral judge, behavioral gen.
- Default: No on a fresh install; Yes when overrides were captured on a previous run
- Valid: y/n, then one model per role (a curated numbered choice on claude, codex, and goose with a curated provider; free text on opencode and the open-ended providers)
-
Guidance: answer No to use the registry defaults, which are tuned per backend (background roles generally run on a cheaper tier than chat). If you answer Yes, accepting the shown default for a role leaves it on the registry value; only values that differ from the registry default are persisted (as
DEFAULT_MODELS_JSON), which sits below per-usermodels:entries and above the registry in the resolution chain.
How long the inner agent subprocess can go without producing output on one response before Kai gives up on it.
-
Default:
120 - Valid: any positive integer
-
Guidance: 120 seconds is a sensible starting point for chat. Long-running tool use routinely needs more; bumping to 240-300 is reasonable on a fast machine. Going below 60 risks aborting the model mid-thought on legitimate work. Per-user
timeoutin users.yaml or runtime settings override it.
Total bound on one turn for every backend: the backstop that ends a turn trickling output without completing, since the idle guards reset on any output.
-
Default:
3600 - Valid: any positive integer
- Guidance: the generous default exists so legitimate long agentic turns never hit it. Lower it if you want a harder ceiling on how long any single turn can run.
Recycles a user's agent subprocess once the session is older than this, regardless of activity. Applies to every backend.
-
Default:
0(no limit) - Valid: any non-negative number
-
Guidance: long-lived CLI processes can accumulate memory; a 4-8 hour cap is a reasonable guard on memory-constrained machines.
0disables age-based recycling entirely. Recycling ends the session; the next message starts fresh.
Evicts a user's agent subprocess after this much inactivity. Applies to every backend.
-
Default:
1800 - Valid: any non-negative integer
-
Guidance: 30 minutes keeps the pool small without making users pay a cold start on every message. Raise it if cold starts annoy you and RAM is plentiful; set
0to keep subprocesses alive indefinitely.
The fraction of the active context window at which Claude Code automatically compacts conversation history into a summary. Claude backend only.
-
Default:
80 -
Valid:
0to100 -
Guidance: 80 is a good default. Lower values (60-70) compact sooner, which reduces token usage at the cost of losing raw context earlier in long sessions. Values above ~83 are silently clamped by Claude Code. Setting
0falls back to Claude Code's own default; pin a value here if you want predictable behavior across upstream changes.
Maps to Claude Code's --effort flag, which trades reasoning depth against latency and token spend. Claude backend only.
-
Default:
high -
Valid:
low,medium,high,xhigh,max -
Guidance:
highis the recommended default for everyday work.xhighandmaxproduce noticeably better answers on hard problems but are slower and burn more tokens. The wizard's allow-list mirrors the runtime allow-list, so it cannot accept a value the daemon would later reject. Claude Code's upstream default has changed over time, so pinning a value here insulates you from future shifts.
Codex backend only. Empty is a valid answer.
- Default: empty (codex default)
-
Valid:
minimal,low,medium,high,xhigh, or empty - Guidance: empty passes no override, so codex falls back to the per-OS-user codex config or the model default; that is usually right because codex config is per OS user and the highest tier is model-dependent. Set a value to pin the reasoning effort install-wide.
Codex backend only.
- Default: empty (follows the shared agent turn deadline)
- Valid: a positive integer, or empty
- Guidance: set only if the codex lane needs a different ceiling than every other backend.
These settings configure Kai's own HTTP server, which serves the /webhook/* and /api/* endpoints and the Workshop client. It always binds the loopback address; nothing here exposes it publicly.
The TCP port the embedded HTTP server binds to.
-
Default:
8080 -
Valid:
1to65535 - Guidance: 8080 works for almost every deployment because the public surface is fronted by Cloudflare Tunnel or a reverse proxy. Ports below 1024 require root, which Kai cannot bind, so anything in the 1024-65535 range is safe.
Workshop-enabled modes only. One private, non-loopback IPv4 address for a second listener carrying only the Workshop client routes, on the same port.
- Default: empty (loopback only)
- Valid: a private IPv4 address on this host; loopback, public, multicast, wildcard, and IPv6 addresses are rejected
- Guidance: set this to reach the Workshop from other devices on a trusted LAN. The wizard warns: Workshop bearer sessions will use plain HTTP on this trusted LAN address; use a TLS terminator for untrusted networks. Webhook ingress and the internal APIs stay on loopback regardless. See the Workshop Operator Guide.
The secret GitHub signs webhook deliveries with (HMAC-SHA256, verified on /webhook/github).
- Default: a freshly generated 64-hex-char random value (or the existing stored value on a re-run)
- Valid: any non-empty string that differs from the Telegram webhook secret
-
Guidance: accept the generated value and paste the same string into the GitHub webhook configuration. The wizard re-rolls the default and re-prompts if it matches the Telegram secret; the runtime exits when any two ingress secrets are equal. If a legacy unified
WEBHOOK_SECRETis present, the wizard notes it is no longer supported and omits it from the generated configuration.
The generic webhook secret (GENERIC_WEBHOOK_SECRET, for POST /webhook) is generated automatically with no prompt; read it from the generated env file when configuring generic webhook callers.
Installation-wide defaults; both prompts fire on every wizard run. users.yaml entries can override workspace_base per user.
A directory whose immediate subdirectories become valid workspace names. With this set, /workspace foo resolves to <base>/foo without needing the absolute path.
- Default: empty
-
Valid: any path (absolute or starting with
~; the wizard echoes the expansion) - Guidance: point this at your projects parent directory. Leave it blank if you always use absolute paths.
Pinned absolute paths that are always valid workspace destinations even if outside the base.
- Default: empty
- Valid: comma-separated absolute paths
- Guidance: use this for repos under non-standard locations. Each path becomes a one-tap option in the workspace picker. Leave it blank if the base directory covers everything you need.
Tunables for the durable model catalogue behind runtime model pickers; discovery adapters fetch model metadata per backend.
-
Default:
0(disabled) - Valid: any non-negative integer
-
Guidance:
0means the catalogue refreshes only when asked (from Workshop Settings orpython -m kai workshop model-catalogue refresh). Set an interval if you want newly released models to appear in pickers without manual refreshes.
-
Default:
30 - Valid: any positive integer
- Guidance: the cap on one backend's discovery call during a refresh. Raise it only if a backend's model listing is genuinely slow on your host.
There is no enable prompt here: whether reviews run at all is per person (pr_review in users.yaml, the Workshop Settings GitHub section, or /github reviews on|off), and the wizard prints a reminder saying so. Issue triage is likewise per person and has no wizard prompts.
-
Default:
300 - Valid: any positive integer
- Guidance: the wizard still prompts for and stores this setting, but it currently has no runtime effect; Kai dedupes duplicate webhook deliveries by GitHub delivery ID instead. Accept the default.
Hard cap on how long a single PR review run may take.
-
Default:
900 - Valid: any positive integer
- Guidance: 900 (15 minutes) accommodates large PRs without prematurely cutting off real work. If your PRs are typically small, 300-600 keeps a stuck process from running too long; if they are very large, 1200-1800 may be needed.
Telegram-enabled modes only. Enables inbound voice-message handling: voice notes get transcribed via whisper.cpp and the text is fed into the conversation as if you typed it.
- Default: No
- Valid: y/n
-
Guidance: turn this on if you talk to Kai from your phone and want voice input. Requires
ffmpegon the host and the whisper model (gitignored; the wizard does not download it). See Voice Setup.
Telegram-enabled modes only. Enables outbound voice replies via Piper TTS.
- Default: No
- Valid: y/n
- Guidance: complement to voice transcription. Off by default because most replies are long enough that text is more useful. See Voice Setup for the Piper model files.
After these prompts the wizard prints a reminder that per-user OS isolation lives in users.yaml (os_user); there is no global subprocess-user prompt.
Kai's semantic memory layer indexes facts and episodes in Qdrant via Mem0 and surfaces them on every turn. This whole section toggles that subsystem.
Master switch.
- Default: No
- Valid: y/n
- Guidance: turn it on if you want long-term memory of facts the user shares, prior decisions, and episodes (compact narratives of completed tasks). Off, the bot is purely conversational and forgets everything outside its rolling conversation context. See Memory for how retrieval works.
When memory is enabled, the prompts below appear.
Whether the bot runs a background one-shot model call after each turn to identify and store new facts. Appears when the chosen backend ships a one-shot reasoner (all five do).
- Default: No
- Valid: y/n
- Guidance: turn it on. Without extraction, memory only contains facts saved explicitly; with it, the bot proactively maintains its own memory and quality climbs over time. The extraction reasoner and model resolve per person from each person's effective backend via the model registry; there are no global reasoner or model prompts. The remaining prompts in this section all hang off this answer.
Hard cap on a single stage-1 extraction call.
-
Default:
10 - Valid: any positive integer
-
Guidance: the default of 10 is too aggressive for production. Real extractions routinely take 20-30 seconds, especially on the first call after a cold start, and silently abort at the boundary. Set this to
60or higher unless you know your extractions complete quickly.
How many existing similar facts to show the extractor as context, so it can decide whether a new observation is genuinely new, an update, or redundant.
-
Default:
8 - Valid: any non-negative integer
-
Guidance: 8 is the validated default. Lower values shrink the extraction payload at the risk of duplicate facts.
0is a valid kill-switch: the extractor falls back to all-new outputs, useful for debugging the consolidation path.
How many prior conversation exchanges are shown to the episode classifier as background.
-
Default:
3 -
Valid:
0to10 - Guidance: 3 is enough context for the classifier to recognize multi-turn workflows without ballooning the per-call payload. The 0-10 cap exists so a typo cannot blow past the model's per-call token limit.
Hard cap on a single stage-2 episode call.
-
Default:
120 - Valid: integer of at least 10
- Guidance: 120 seconds accommodates the model generating all seven episode fields without rushing. Bump to 180-300 if you see persistent timeouts. The 10-second floor exists because model warm-up alone routinely runs several seconds.
Write-time duplicate gate: a new fact whose cosine similarity to an existing fact meets this threshold is treated as a paraphrase and not stored again.
-
Default:
0.9 -
Valid:
0.3to1.01 -
Guidance: 0.9 catches near-identical rephrasings while letting genuinely new variants through.
1.01is the unambiguous disable sentinel (no cosine similarity can reach it).
Cap on how many tokens of retrieved memory are injected into each conversation turn.
-
Default:
2000 - Valid: any positive integer
- Guidance: 2000 balances useful background against displacing the actual conversation. Lower it if memory injection crowds out tool results and recent history; raise it if the bot keeps missing facts that should be in scope.
Maximum number of results returned by the vector search before the token-budget filter applies.
-
Default:
10 - Valid: any positive integer
- Guidance: 10 is the validated default. Increase to 15-20 only if you find specific facts are getting buried.
Lets the inner agent call Perplexity via the /api/services/perplexity proxy for web search and synthesis.
- Default: empty (Perplexity calls disabled)
- Valid: the key string from the Perplexity dashboard
-
Guidance: add the key if you want web-search grounded answers with citations. Remember that each person who should reach a service also needs it in their
allowed_serviceslist in users.yaml; see External Services.
The wizard writes install.conf to the project root with mode 0600 and prints a closing message that depends on deployment mode.
Protected mode:
Configuration written to <project>/install.conf
Review the file, then run: sudo python -m kai install apply
The next step is sudo make install, which reads install.conf, validates user isolation before touching anything, lays out the file system, generates the runtime env file under /etc/kai/, writes the backend registry, configures sudoers, generates the launchd plist or systemd unit, and starts the service. See Protected Installation for what that step does in detail. On backends with their own auth stores, the wizard's closing notes remind you to log in as each target OS user (codex login, opencode auth login, or pi then /login).
Single-user mode: the wizard additionally writes .env (mode 0600) to the project root and reports where users.yaml lives, then prints:
Single-user mode does not require 'sudo make install'.
Start the daemon with: make run
In both modes you can review install.conf before proceeding, but the right way to change a setting is to re-run the wizard so its validation logic gets to vet the new value.