-
Notifications
You must be signed in to change notification settings - Fork 17
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. Four 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, and the wizard's closing instructions differ per mode. -
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 only the User setup block and prints a summary instead. Installation-wide defaults (model, timeout, workspaces, PR review limits) prompt on every run regardless, because users.yaml entries without a per-user override inherit them. Per-user features that used to have global wizard prompts (PR review enable, issue triage enable, GitHub notification routing, OS isolation) are managed inusers.yamlor via Telegram commands, not here. -
Default backend. Backend-specific prompts appear only for the selected backend: Codex auth and binary path on
codex, the OpenCode binary path onopencode, the Goose binary path ongoose, the LLM provider (and API key on goose) on multi-provider backends, autocompact and effort level onclaude. All four backends ship a one-shot reasoner, so the memory extraction prompt appears on every backend. - 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
- Installation paths
- Telegram
- User setup
- Telegram transport
- Default backend
- Agent
- Webhook server
- Workspaces
- 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.
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 (e.g.,/usr/local/kai). Whatever you pick,make installwrites there as root.
Where runtime data lives: the SQLite database, logs, conversation history, the per-user MEMORY.md files, file uploads, and crash-recovery flags. Owned by the service user and writable to it.
-
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. Running as your everyday user works in development but is not the right shape for an always-on service.
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.
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.
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. If it exists, this entire block is skipped and the wizard prints a one-line summary (user and admin counts) plus a reminder that user changes go through Telegram commands or by editing the file directly. A stray users.yaml in the project root is ignored with a note telling you to move or remove it. The prompts below only appear on a fresh install.
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 like
@kaiare not accepted. The admin role grants access to every command including the per-user settings management commands.
A short label for the admin entry in users.yaml.
-
Default:
admin - Valid: letters, digits, spaces, hyphens, underscores
-
Guidance: any short name works. This is shown in
/userslistings and in some log lines; it is not used for authentication.
Whether to expose per-user advanced fields during this wizard run.
- Default: No
- Valid: y/n
-
Guidance: answer No unless you want to set the admin's
os_user(process isolation account) here. You can always add advanced fields later by editingusers.yamldirectly. There is no prompt forhome_workspace; every user lands in a per-user directory under the data directory, and an admin who wants a different home addshome_workspace: /absolute/pathto their entry by hand.
Only shown when advanced options are enabled. Sets os_user for the admin entry, so the inner agent process for the admin runs as that OS user via sudo -u.
-
Default: the current
$USER - Valid: letters, digits, dots, hyphens, underscores (empty to skip)
- Guidance: leave it at the default unless you have created a dedicated account for the admin's agent subprocesses. An empty value, or a value equal to the bot's own user, spawns the agent in-process with no sudo wrap. Process isolation is most useful when several humans share the deployment.
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.
How the bot receives updates from Telegram. This prompt follows User setup; it has no section banner of its own.
-
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.
An optional 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 (no verification)
- Valid: any string
- Guidance: set a long random string. Without it, anyone who learns the URL can POST fake updates. If you are running behind Cloudflare, this complements the IP-allowlist enforcement.
Which agent runtime drives the inner conversation.
-
Default:
claude -
Valid:
claude,codex,goose,opencode -
Guidance:
claudeuses the Claude Code CLI and benefits from Anthropic's subscription auth.codexuses the OpenAI Codex CLI, also subscription-capable.opencodeis a multi-provider runtime driven over ACP;gooseis Block's multi-provider runtime. See Multiple Backends for the trade-offs. The choice here is the global default; individual users can overridebackendin theirusers.yamlentry, and the wizard does not collect setup for per-user overrides (install and authenticate those backends out of band).
The follow-up prompts depend on the chosen backend.
Codex backend only.
-
Default:
subscription -
Valid:
subscription,api_key -
Guidance:
subscriptionuses the ChatGPT plan login stored bycodex login; no API key is collected.api_keyprompts forOPENAI_API_KEY(required) and bills per token.
Codex backend only. Persisted so the install step writes both the runtime env and the sudoers rule from the same value.
-
Default:
which codex, falling back to/opt/homebrew/bin/codex - Valid: an existing executable path
-
Guidance: accept the detected path. In subscription mode the wizard reminds you to run
codex loginafter install as each targetos_user(run as that user directly, not via sudo from another account).
OpenCode backend only. Persisted so the install step writes both the runtime env and the sudoers rule from the same value.
-
Default:
which opencode, otherwise empty (you must type the path) - Valid: an existing executable path
-
Guidance: OpenCode has no single canonical install location, so the wizard will not guess. After install, authenticate at least one provider as the service user with
opencode auth login; Kai passes the active model at process spawn and OpenCode resolves credentials from its own auth store.
Multi-provider backends only (goose, opencode).
- Default: none (must be selected)
-
Valid:
anthropic,deepseek,google,ollama,openai,openrouter -
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); the others have curated model lists in the Agent prompt below.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 for opencode entirely, whose auth is managed by opencode auth login rather than by Kai.
- 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.
Installation-wide agent defaults. These prompt on every wizard run, even when users.yaml exists, because users.yaml entries without a per-user override inherit them; skipping the prompts on re-runs used to let a stale global model survive a backend switch.
The global default model for the conversational role. Curated surfaces present a numbered choice ("Default model"); open-ended surfaces ask for a free-text "Default model ID".
-
Default: the existing value when it is still valid for the chosen backend; otherwise the backend-aware default:
opus(claude/anthropic),gpt-5.5(codex),gpt-5.5-pro(openai),gemini-2.5-pro(google),deepseek-v4-pro(deepseek) -
Valid (claude):
opus,sonnet,haiku -
Valid (codex): the Codex CLI's own curated list (
gpt-5.5,gpt-5.4, ...), which is independent of the OpenAI API surface -
Valid (goose on a curated provider): the provider's curated list, e.g.
gpt-5.5-pro/gpt-5.5/gpt-5.4-family (openai),gemini-2.5-pro/gemini-2.5-flash/gemini-2.5-flash-lite(google),deepseek-v4-pro/deepseek-v4-flash(deepseek) -
Valid (opencode, and goose on openrouter/ollama): free text; opencode takes a full
provider/modelID (e.g.deepseek/deepseek-v4-pro) -
Guidance: the defaults follow the rule that the conversational role gets the provider's strongest model; non-conversational roles run on cheaper tiers via the registry. If a previously configured model is invalid for the newly selected backend, the wizard says so and prefills the backend-aware default instead. Per-user overrides via
/modelalways win at runtime, so this is the global fallback, not a hard ceiling.
Optional walk through the six non-conversational roles, in prompt order: pr_review, issue_triage, behavioral_judge, behavioral_gen, memory_extraction, memory_episode.
- Default: No on a fresh install; Yes when overrides were captured on a previous run
- Valid: y/n, then one model per role (curated choice or free text, same surface rules as the default model)
-
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 run 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 (large file edits, broad searches, multi-step refactors) 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. If you set it too low you will see synthetic timeout entries in history. Per-user
timeoutin users.yaml or/settings timeoutoverrides it at runtime.
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 to free room for new turns. Claude backend only; other backends skip this prompt.
-
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, so setting
100does not actually disable compaction. Setting0falls back to Claude Code's own ~83% 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; other backends skip this prompt.
-
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.lowandmediumare appropriate for quick lookups or tight tool loops. 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.
These two settings configure Kai's own HTTP server, which serves the /webhook/* and /api/* endpoints. They are unrelated to the Telegram-webhook transport (which is about how Telegram delivers messages to Kai); these are about the server Kai itself runs locally.
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. Pick a different port only if 8080 is already in use on the host. Ports below 1024 require root, which Kai cannot bind, so anything in the 1024-65535 range is safe.
The shared secret required by the X-Webhook-Secret header on every /webhook/* and /api/* request.
- Default: auto-generated 64-hex-char random string on first run
- Valid: any non-empty string (required)
- Guidance: accept the auto-generated value. It is cryptographically random and long enough to resist brute force. Only change it if you have a reason to rotate (for example, a previous value leaked). Note that any external service that calls Kai's API needs this secret in its config too, so a rotation requires updating those callers in lockstep.
Installation-wide defaults; both prompts fire on every wizard run. Users.yaml entries can override workspace_base per user, and users manage additional allowed workspaces at runtime via /workspace allow and /workspace deny.
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
~) -
Guidance: point this at your projects parent directory (commonly
~/Projects). Leave it blank if you always use absolute paths in/workspacecommands.
Pinned absolute paths that are always valid workspace destinations even if outside WORKSPACE_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
/workspaceskeyboard. Leave it blank ifWORKSPACE_BASEcovers everything you need.
There is no enable prompt here: whether reviews run at all is per-user (pr_review in users.yaml, or /github reviews on|off in Telegram), and the wizard prints a reminder saying so. The two prompts below are global resource limits that apply to any review subprocess regardless of which user triggered it. Issue triage and GitHub notification routing are likewise per-user (issue_triage / /github triage, github_notify_chat_id / /github notify) and have no wizard prompts at all.
Minimum time between automatic review runs on the same PR.
-
Default:
300 - Valid: any positive integer
- Guidance: 300 (5 minutes) is calibrated to absorb rapid back-to-back pushes without flooding the PR with reviews. Lower it (60-120) if you want faster feedback during active development; raise it (600+) if reviews are noisy on your setup.
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 (many files, deep refactors), 1200-1800 may be needed. The bot kills the subprocess at the boundary.
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 themodels/ggml-base.en.binwhisper model (148 MB, gitignored). The wizard does not download it for you; see Voice Setup for installation.
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. Worth turning on if you primarily use voice. See Voice Setup for the Piper model files.
After these two 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 as a [Relevant memories...] block. 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 ("I live in Toronto", "use Celsius"), 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.
- 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-user from each user's effective
backendvia the model registry (all four backends ship a one-shot reasoner); there are no global reasoner or model prompts. On codex, opencode, and goose installs the wizard re-prompts for the backend binary path here if it was not already collected. 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 dataclass 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. The extractor uses these to decide whether a new observation is genuinely new, an update of an existing fact, 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; higher values give the model more context per call.
0is a valid kill-switch: the extractor falls back to all-newoutputs and the EXISTING FACTS data block is omitted, useful for debugging the consolidation path or forcing a re-extraction sweep.
How many prior conversation exchanges are shown to the episode classifier as background. The classifier decides whether the just-completed turn warrants generating a stage-2 episode.
-
Default:
3 -
Valid:
0to10 -
Guidance: 3 is enough context for the classifier to recognize multi-turn workflows without ballooning the per-call payload. Lower it to
0to disable the windowing and classify on the current turn alone. Raise it (toward 10) if your tasks span many short exchanges and the classifier is missing them. The 0-10 cap exists so a typo (3000 instead of 3) 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; a sub-floor timeout would surface as systematic timeouts that mask real model failures as configuration errors. The episode model resolves per-user from the registry; there is no model prompt.
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. Lower it if your store accumulates paraphrase duplicates; raise it toward 1.0 if distinct facts are being swallowed.
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. Filters the search results before injection.
-
Default:
2000 - Valid: any positive integer
- Guidance: 2000 is a balance between giving the model useful background and not displacing the actual conversation. Lower it (1000-1500) if memory injection is crowding out tool results and recent history; raise it (3000-5000) if you have rich memory and the bot keeps missing facts that should be in scope.
Maximum number of results returned by Qdrant before the token-budget filter applies.
-
Default:
10 - Valid: any positive integer
- Guidance: 10 is the validated default. Lower values reduce noise and shave embedding-search cost per turn; higher values pay slightly more compute but catch more relevant facts. Increase to 15-20 only if you find specific facts are getting buried.
Lets the inner agent call Perplexity Sonar 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. The bot prefers Perplexity over generic
WebSearch/WebFetchbecause results are cleaner and citations are returned. Free tier is enough for casual use. See External Services for the request shape and the available service catalog.
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 /path/to/kai/install.conf
Review the file, then run: sudo python -m kai install apply
The next step is sudo make install, which reads install.conf, lays out the file system, generates the runtime env file under /etc/kai/, configures sudoers, generates the launchd plist or systemd unit, and starts the service. See Protected Installation for what that step does in detail.
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.