Skip to content

Configuration files ​

Kiki keeps its long-term preferences in TOML (plain text with a clear structure) files. Settings live in three places, and the split is worth knowing before you edit anything:

FileHolds
~/.kiki/config.tomlAgent and runtime settings, plus your model and provider definitions
~/.kiki/credentials/credentials.tomlAPI keys, OAuth tokens and MCP credentials
~/.kiki/tui.tomlTerminal-side preferences: theme, editor, notifications, auto-update

config.toml is created on first run. Saved preferences persist across starts, and both config.toml and the credential file are watched while Kiki runs.

Config file location ​

To move the whole data directory, set the KIKI_HOME environment variable:

sh
export KIKI_HOME=/path/to/kiki-home

The config file then lives at $KIKI_HOME/config.toml — the name is always config.toml, wherever the directory is — and credentials at $KIKI_HOME/credentials/credentials.toml.

TIP

TOML field names always use snake_case, for example default_model and max_context_size. If a key contains ., you must quote it — for example [models."gpt-4.1"] — otherwise TOML treats . as a nested table separator.

Applying configuration changes ​

Kiki watches config.toml and credentials/credentials.toml on the host running Kiki. Save a valid edit and it reloads automatically after the file settles; you do not need to restart or run /reload for a request-concurrency rule. A periodic check recovers missed file notifications. An incomplete save is retried; a parse failure keeps the last valid configuration and reports a diagnostic. Fix the named file rather than overwriting it from Settings.

Reloading a file and applying a setting have different timing:

SettingWhen it takes effect
request_governanceRe-evaluates waiting requests and agent admissions as soon as the edit is loaded; active work finishes normally
Session defaults such as default_model, default_permission_mode, and default_plan_modeNew sessions; an existing session keeps its own choices
identityNext process start
tui.tomlNext start, or /reload-tui in the TUI

/reload remains available in the TUI to explicitly reload config.toml and tui.toml. Settings with a turn or session snapshot use the timing documented in their own section; automatic file reload does not replace an in-progress request.

Complete example ​

The following example covers the most commonly used configuration fields. You can copy it and adjust as needed:

toml
default_model = "kimi-code/k3"
default_permission_mode = "auto"
default_plan_mode = false
merge_all_available_skills = true

[providers."managed:kimi-code"]
type = "kimi"
base_url = "https://api.kimi.com/coding/v1"

[models."kimi-code/k3"]
provider = "managed:kimi-code"
model = "k3"
max_context_size = 1048576
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]
display_name = "K3"
support_efforts = [ "low", "high", "max" ]
default_effort = "max"

[models."kimi-code/kimi-for-coding"]
provider = "managed:kimi-code"
model = "kimi-for-coding"
max_context_size = 262144
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]

[models."kimi-code/kimi-for-coding-highspeed"]
provider = "managed:kimi-code"
model = "kimi-for-coding-highspeed"
max_context_size = 262144
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]

[thinking]
enabled = true
effort = "high"
keep = "all"

[loop_control]
max_attempts_per_step = 5
reserved_context_size = 50000

[background]
max_running_tasks = 4
keep_alive_on_exit = false

[nb_search.credential_slots."exa.default"]
provider_id = "exa"
env = "NB_SEARCH_EXA_API_KEY"

[nb_search.defaults]
search_lane = "exa.search"

[[permission.rules]]
decision = "allow"
pattern = "Read"

[[permission.rules]]
decision = "deny"
pattern = "Bash(rm -rf*)"

[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.kiki/hooks/check-bash.mjs"
timeout = 5

External harness defaults ​

An external harness (the program running the agent) can use its own configuration without a Kiki profile. For the main-agent execution selection, set reusable Kiki overrides under [agent_executor_overrides.<id>.defaults]; a session override wins over an explicitly declared profile value, which wins over these settings. Anything left unset stays with the harness's own default. Native execution keeps Kiki's existing model and profile defaults.

toml
[agent_executor_overrides."claude-acp".defaults]
# Optional: use the harness's model ID, not a native Kiki model alias.
model_alias = "YOUR_HARNESS_MODEL"
thinking_effort = "high"
permission_mode = "auto"
kiki_context = []
allow_kiki_subagents = false

Omit model, effort and permission fields to let the harness choose them. kiki_context accepts memory, board, cron, threads, history and hooks; [] explicitly disables all groups, and allow_kiki_subagents = false explicitly disables delegation. An omitted profile field inherits the harness settings in this execution path. Changing settings does not rewrite an existing binding: select the execution again or explicitly rebuild its context to adopt them.

native_agent selects the vendor's own agent. It is a session override or a value in these defaults, not a profile field. JSON null on the session override clears that choice and inherits the defaults value. Omit it, or set engine-default, to keep the vendor's default agent; Kiki then does not send a custom agent selection. A newly chosen value must be one the executor has already advertised. Missing discovery keeps an existing choice when the executor, that agent, and the descriptor revision still match; a discovery list that omits the value is rejected and the current binding stays.

Kiki's own executor accepts only omission or engine-default. AGY CLI's menu currently offers only engine-default, so its default main agent stays AGY's own and is not passed as a custom --agent.

inject_kiki_mcp opens or closes the Kiki tool bridge. true opens the bridge and does not grant a tool the binding already denies. false closes the bridge, including credentials already issued. JSON null on the session override inherits the profile value or these defaults. The bridge rules are in Kiki tools in an external harness.

defaults.executor_prompt accepts delivery (append, replace or preamble), body, append, include, and per_engine.<id> overrides. These fields merge individually with the selected profile, so setting only delivery does not erase the default body. Omitted include inherits; include = [] turns off the additional sections. A profile body is used when no effective executor_prompt.body is set. External execution does not automatically add Kiki cognition or shared prompt fields; request the desired fields explicitly in include.

Through the REST API, update these settings with POST /api/config and an agent_executor_overrides object. A defaults field set to JSON null removes that saved setting; defaults: null removes the whole defaults block. TOML has no null, so remove the corresponding key when editing the file. Launch settings such as bin_path, home_dir, env and args remain separate from defaults.

Display preferences are separate from execution settings. An external engine becomes a new profile or execution choice only when [agent_executor_overrides.<id>] sets show_in_profile_list = true. A missing field leaves that engine on the settings page and out of new choices. show_in_profile_list = false stays off: refresh, a new check, and another import all keep that off. externals_visible = false under [agent_executor_display] blocks every external engine from new choices. Omitting it, or setting true, only opens that gate; engines already set to false stay off. Reopening the gate brings back only the engines that are still true. discovery_initialized = true records that Settings has already run the one-time import check. It does not enable an engine. Native stays in the choices. A profile or session already bound to a hidden or unavailable engine keeps that identity as its current value. Those engines stay available there and do not return to the list of new choices. Hiding a choice does not uninstall an engine, rewrite an existing execution binding, or cancel a task.

toml
[agent_executor_overrides."claude-acp"]
show_in_profile_list = true

[agent_executor_display]
externals_visible = true
discovery_initialized = true

Send the same snake_case fields in POST /api/config. JSON null removes only that display preference; launch settings and defaults stay. GET /api/config and the write response expose these fields in raw.agent_executor_overrides and raw.agent_executor_display. raw.agent_executors contains configured descriptors, not the shipped engine catalog. GET /api/executors reads the latest check and does not probe the program or the login. Check one engine with POST /api/executors/{id}/check. Catalog status: "unknown" means that engine has no check yet. After 60 seconds the response still carries the previous check_status and checked_at, with check_stale: true. A timeout or failure is recorded on that engine alone. Settings lists every engine for configuration and Check. The first time Settings opens while discovery_initialized is not true, each external engine is checked once. A ready or warning result turns on an engine that has no explicit on or off yet, by writing show_in_profile_list = true. A failure, a timeout, and an engine already set to false stay as they are. Once that import records discovery_initialized = true, opening Settings again does not repeat it. A bare execution entry needs all three: a ready program, a confirmed login or the engine's declared API-key environment, and show_in_profile_list = true. A saved profile, launch preference, or native Kiki model on its own does not add the engine to new choices.

Custom executor descriptors can read model IDs from a metadata-only command under [agent_executors.<id>.model_probe]. For a command whose JSON response contains command.data.models, configure:

toml
[agent_executors.example.model_probe]
command = "vendor.exe"
args = ["--output-format", "json", "models"]
format = "json"
json_path = ["command", "data", "models"]

Use the vendor's actual metadata subcommand and flag order; a print, prompt or slash-command invocation is not a guarantee that no model request will run. Arguments are passed directly as an array, not through a shell. format accepts lines (the default), tab, or json; json_path selects the JSON array of strings or objects with an id. IDs stay unchanged. Invalid JSON or a missing array leaves the catalog partial rather than proving that saved model IDs are invalid.

Continuity reminder cues ​

Kiki can remind the agent to record standing instructions, find earlier decisions, update unfinished todos, and preserve working notes before or after context compaction. Reminders are appended to conversation history; they do not automatically save memories or change the system prompt. Progress reminders require TodoList; instruction and history reminders can also operate with approved memory access when TodoList is unavailable.

Configure the human-turn cadence and Chinese/English cue lists under loop_control. Assistant messages, tool polling, and forwarded agent messages do not count as human turns. When memory is unavailable, reminders do not suggest MemoryWrite.

Provider credentials ​

Provider credentials — the API keys Kiki uses to call each provider — are stored in ~/.kiki/credentials/credentials.toml ($KIKI_HOME/credentials/credentials.toml when you override the data directory). Ordinary configuration stays in config.toml; credential values keep the TOML paths they had there, so a provider's api_key simply moves out of its [providers."<name>"] table in config.toml and into the same table here.

toml
# ~/.kiki/credentials/credentials.toml
[providers."managed:kimi-code"]
api_key = "YOUR_API_KEY"

The file is optional. When it is missing, Kiki treats it as empty and falls back to any credential still present in config.toml. When both files carry a value for the same provider credential, the value in credentials.toml wins.

The field-level priority between api_key and the [providers.<name>.env] fallback is covered in Config overrides.

Migrating existing keys ​

An older root-level credentials.toml is moved unchanged into credentials/credentials.toml at startup, and the old location stays readable until that succeeds; if the two hold different bytes, Kiki stops rather than choosing — check both and retry. Credentials still sitting in an older config.toml are moved into credentials/credentials.toml, and the old config.toml is kept as a unique config.toml.bak-<YYYY-MM-DD>-<uuid>. That backup still holds the original plaintext keys, so delete it once you have checked the migration and no longer need it. Loading again after a successful migration creates no second backup and changes nothing already moved; the same key path holding different values in the two files stops the migration for you to resolve, and an interrupted one may leave a backup to inspect.

File permissions ​

On POSIX systems, Kiki requests owner-only permissions (0600) when creating and rewriting credentials.toml. On Windows, effective access depends on the file and parent-directory ACLs; Kiki does not currently verify or repair the ACL of the general credentials.toml. Check those permissions before relying on the file to exclude other local users.

Secret handling ​

Provider read APIs return locally stored API keys so you can view and edit them in the GUI; environment-backed credentials expose the variable name, not its value. Treat access to the server API as access to those stored keys. Keep credentials.toml and any migration backup private.

Top-level fields ​

Fields in the config file fall into two categories: top-level scalars that directly control default behavior, and nested tables (providers, models, thinking, etc.) that each have their own structure, described individually in the sections below.

FieldTypeDefaultDescription
default_modelstring—Default model alias; must be defined in models
default_permission_modestringautoDefault for new sessions: manual asks for unapproved actions; auto approves routine work but asks for protected actions; review routes those requests to a reviewer first; yolo approves most tool actions, though Git-control paths may still ask
default_plan_modebooleanfalseWhether new sessions start in Plan mode (produce a plan before executing) by default
merge_all_available_skillsbooleantrueWhether to merge Agent Skills from all available directories
extra_skill_dirsarray<string>—Extra skill search directories, layered on top of the default directories
extra_agent_dirsarray<string>—Extra custom agent search directories, layered on top of the default directories
skip_builtin_profile_installationarray<string>—Built-in template names not to install under agents/builtin/ at startup. Already managed copies remain active and continue receiving safe updates; this is not a runtime disable switch
disabled_named_profilesarray<string>[]Profile names to hide from subagent discovery and dispatch, regardless of file source. The default main agent binding remains available
builtin_product_skillsbooleantrueWhether Kiki's product skills are offered to the model: kiki-ops for product usage and configuration, and kiki-profile for agent profile authoring. Turning them off removes both names and descriptions from the system prompt, at the cost of those guided workflows
providerstable{}API provider table → providers
modelstable—Model alias table → models
model_switchtableask, then switch directlyWhat a model change does with the current context → model_switch
thinkingtable—Default parameters for Thinking mode → thinking
loop_controltable—Agent loop control parameters → loop_control
retrytable—Error-specific step retry policies → retry
request_governancetableNo rulesModel-request and agent-execution concurrency and waiting budgets → request_governance
token_countingtable—Which context token count is reported externally → token_counting
transcript_memorytable—Server-side transcript history memory budgets → transcript_memory
backgroundtable—Background task runtime parameters → background
subagenttable—Subagent run defaults and limits → subagent
agentstable—Delegation-notice defaults → agents
thread_communicationtableunsetAllow peer-thread tools; default stays off → thread_communication
mcptable—Global MCP server timeout defaults → mcp
toolstable—Global tool switch → tools
imagetable—Image compression parameters → image
session_titletable—Which model writes AI session titles → session_title
experimentaltable—Persistent overrides for experimental-feature flags → experimental
nb_search_sourcetable—Host option controlling whether the built-in search module reuses the server's local nb-search configuration → nb_search
nb_searchtable—Built-in search and retrieval module behind WebSearch and FetchURL → nb_search
permissiontable—Permission rules and reviewer → permission
interactiontable—Whether agent questions block the turn → interaction
hooksarray<table>—Lifecycle hooks; see Hooks
identitytable—Custom agent identity → identity
prompttable{}Prompt field overrides and custom variables → prompt

The following sections detail the nested tables listed above.

providers ​

Each entry in the providers table defines an API provider, keyed by a unique name. Store an API key in credentials.toml, or set api_key_env in config.toml to read a named environment variable from the Kiki process. Without that explicit reference, exporting a provider key in the shell has no effect.

FieldTypeRequiredDescription
typestringYesProvider type: kimi, anthropic, openai, openai_responses, google-genai, vertexai
api_keystringNoAPI key. Stored in credentials.toml; a value in config.toml is used only when credentials.toml has none for that provider
api_key_envstringNoNon-empty environment variable name, not a key value. Stored in config.toml; consulted only when api_key is empty. See credential priority and example
base_urlstringNoAPI base URL
oauthtableNoOAuth credential reference (storage and key fields); injected automatically by the login flow — normally no need to write this by hand
envtable<string, string>NoProvider environment values; see below
custom_headerstable<string, string>NoCustom HTTP headers attached to each request

env sub-table: You can write provider-conventional key names (such as KIMI_API_KEY) inside [providers.<name>.env] as a fallback source for api_key / base_url. It also takes priority over the process environment for the variable named by api_key_env, including an explicitly empty value. This sub-table is read only from the config file and does not modify the shell environment:

toml
[providers.kimi.env]
KIMI_API_KEY = "sk-xxx"
KIMI_BASE_URL = "https://api.moonshot.ai/v1"

If the variable named by api_key_env is missing or empty, Kiki does not fall back to another credential variable. See Config overrides for the full resolution order. type = "vertexai" can omit api_key when the env sub-table sets both GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION. An API key set alongside those coordinates is still sent. See Vertex AI.

models ​

Each entry in the models table defines a model alias (the name used in default_model or the -m flag), keyed by a unique name.

FieldTypeRequiredDescription
providerstringYesName of the provider to use; must be defined in providers
modelstringYesModel identifier sent to the server when calling the API
pricing_modelstringNoCanonical model used to estimate usage cost, independent of the upstream model ID; for example, a proxy alias can bill as gpt-6-sol. An unavailable price remains unknown; this never changes the model sent to the provider
max_context_sizeintegerYesMaximum context length in tokens; must be at least 1
max_input_sizeintegerNoDeclared per-request input limit when it sits below the total window (e.g. gpt-5: 400k window, 272k input). Compaction, context-overflow checks, and usage ratios prefer it; completion budgeting keeps the total window. Resolution clamps it to max_context_size
max_output_sizeintegerNoPer-request output token cap (maps to max_tokens). Currently only the anthropic provider honors it. When set for a Claude model, this explicit value overrides the built-in server-side maximum
capabilitiesarray<string>NoCapability tags to add explicitly: thinking, always_thinking, image_in, video_in, audio_in, tool_use. Unioned with the capabilities auto-detected by the provider — entries can only be added, never removed
support_effortsarray<string>NoThinking effort levels the model accepts. For kimi, selecting another value at runtime fails; when model resolution carries an unsupported configured or previous value, the session falls back to the target model's default_effort and reports that effective value to the UI. A Thinking-capable Kimi model without this field uses boolean on / off. Other providers pass concrete values unchanged when their protocol has a native effort field; protocols that expose only levels or token budgets perform the required format conversion. Managed and open-platform refreshes may rewrite this field; to pin it manually, set [models."<alias>".overrides] support_efforts instead
default_effortstringNoDefault thinking effort for the model. Managed and open-platform refreshes may rewrite this field; to pin it manually, set [models."<alias>".overrides] default_effort instead
service_tierstringNoService tier for every request using this model: auto, default, flex, or priority. Overrides profile, route, and per-request tiers, including main-agent and subagent requests. Only openai_responses encodes it; other protocols ignore it. Omit to retain the requesting profile or request's tier
request_paramstableNoExtra request parameters merged into every request for this model (for example temperature, top_p); values may be strings, numbers, or booleans. Layered values merge by key
context_budgetintegerNoUpper bound (tokens) applied to the model's effective context window; never above the model's real capacity. Layered values take the smallest
auto_compactintegerNoAutomatic compaction point in absolute tokens for this model. A closer profile or session value takes precedence; this does not change the model's context limit. Also accepted under this model's overrides table
max_completion_tokensintegerNoUpper bound on per-request completion tokens. Layered values take the smallest, within the model's output cap
off_effortstringNoEffort value sent on the wire to disable thinking (e.g. none for xai grok). Only meaningful for models that declare such an encoding (catalog imports set it): turning thinking Off then sends this value instead of omitting the effort field — the only way to actually stop reasoning on models that reason by default
protocolstringNoRequest format for this model: anthropic, openai, openai_responses, or google-genai. Resolution uses this field, then the provider type when that type is one of these four, then the vendor base format. A value that differs from the provider type is a valid gateway choice. Not accepted in overrides; writing it there raises a warning and is not applied
beta_apibooleanNoanthropic transport only: route requests through the beta Messages API endpoint instead of the standard one. Not accepted in overrides
base_urlstringNoPer-model endpoint. Resolution prefers it over the provider's base_url even when protocol is omitted, and it does not by itself change the request format. Not accepted in overrides; writing it there raises a warning and is not applied
display_namestringNoName shown in the UI; falls back to model when unset
aliasesarray<string>NoExtra routing keys for this model. An exact match on any entry resolves to this table key, including names that contain /. This is the supported way to keep old names working after you rename a key. The same alias string on two models is an error
reasoning_keystringNoopenai provider only. Override the field name used for reasoning content when the gateway returns it under a non-standard name; by default reasoning_content, reasoning_details, and reasoning are auto-detected
adaptive_thinkingbooleanNoanthropic provider only. Force adaptive thinking on or off, overriding the version inference based on the model name. Omit to infer automatically (Claude ≥ 4.6 uses adaptive)
prompt_overridestableNoPrompt field overrides for this model alias, with optional files and fields; see prompt
cognitiontableNoPer-alias prompt bodies or file references that condition this model → Model cognition

When an alias contains ., use a quoted key:

toml
[models."gpt-4.1"]
provider = "openai"
model = "gpt-4.1"
max_context_size = 1048576

Legacy model parameter migration ​

Older configurations put temperature / top_p and max_completion_tokens / service_tier in different places. Settings → Models & providers → Providers & accounts → Maintenance → Legacy model parameter migration can copy them into a model's parameters table.

Preview migration first, which lists the model aliases, parameter names, review reasons, a revision and the backup name it would use, and writes nothing. Unambiguous values are copied; ambiguous or conflicting ones are left for you to decide. Your existing fields are not removed, so read the preview instead of assuming anything changed.

Apply previewed changes then writes a byte-exact backup next to config.toml as config.toml.generation-backup-<uuid> before touching the config, and refuses to write if the config changed since the preview. That backup contains your original secrets — protect it like config.toml and credentials.toml, and delete it once you no longer need it.

Restore backup asks for its own confirmation, and only restores while the current config is still exactly what that backup produced. Any other edit blocks it. Restore soon after a migration: the revision is a byte comparison, so a later edit that happens to return the config to identical bytes is not detected, and an old backup can undo newer work.

Model alias resolution ​

The key of each [models] entry is the model's alias — the name used by default_model, -m, and an agent's model_alias. A request is resolved in this order:

  1. Exact table key
  2. Exact match against a model's aliases list
  3. Bare name that uniquely matches a table key or a record's model field (equal to the name, or ending in /<name>)
  4. Provider-qualified name (<prefix>/<name>) whose last segment is a table key and whose prefix is consistent with that record's provider

A bare name and a provider-qualified name resolve to each other when the match is unique. If more than one configured model matches, resolution fails and asks you to use a full model id to disambiguate.

When you shorten a table key, put the previous name in aliases so sessions and agent profiles that still store the old name keep working:

toml
[models.fast-model]
provider = "openai"
model = "fast-model"
max_context_size = 1048576
aliases = ["openai/fast-model"]

The same pattern applies to a review model whose key used to be qualified:

toml
[models.k3-review]
provider = "openai"
model = "k3-review"
max_context_size = 262144
aliases = ["openai/k3-review"]

Model overrides ​

Use [models."<alias>".overrides] for user overrides that must survive provider-model refreshes. Runtime consumers read the effective value: the override when present, otherwise the top-level field.

toml
[models."kimi-code/kimi-for-coding"]
provider = "managed:kimi-code"
model = "kimi-for-coding"
max_context_size = 262144

[models."kimi-code/kimi-for-coding".overrides]
max_context_size = 131072
display_name = "Kimi for Coding (custom)"

[models."<alias>".overrides] accepts ordinary model fields such as max_context_size, max_input_size, max_output_size, capabilities, display_name, reasoning_key, adaptive_thinking, support_efforts, default_effort, off_effort, service_tier, request_params, context_budget, auto_compact, and max_completion_tokens. It does not accept identity or routing fields: provider, model, protocol, beta_api and base_url. Writing one of those fields there raises a warning when the config loads and does not change the model; set it on the model itself. The model settings page names that warning and shows whether the effective format, endpoint, and credential come from the model, the provider, or an environment variable. It does not show the key or the endpoint address.

Recipes can change supported model prompts and generation parameters, but not the model's provider, request format, endpoint, or credentials. A routing field in a Recipe is rejected; set it on the model itself instead. This also applies to programmatic model-settings overlays, including nested routing fields in overrides.

Layering works in this order: resolve the alias configuration including its overrides, then apply model alias → top-level profile → matching model_profiles entry. request_params merge by key and the last explicit service_tier wins. context_budget and max_completion_tokens are limits, so the smallest value across layers applies, within the model's capacity and output cap; omitting one adds no restriction.

You can also switch models temporarily without touching the config file — by setting KIKI_MODEL_* environment variables, the CLI synthesizes a temporary provider in memory that does not persist after restart. See Define a model from environment variables.

Model cognition ​

[models."<alias>".cognition] attaches prompt text to a single model alias without changing an agent profile. Each text slot accepts { text = "..." } for a complete body stored with the model, or a relative Markdown path or path array for author-managed files. Text is saved when the model binds; later injections reuse that snapshot rather than rereading edits on disk. No default body is injected when a slot is unset.

FieldTypeDefaultDescription
overlay{text: string}, string or array<string>—Body merged into the bound model's system prompt. Multiple file paths are joined with a blank line in declaration order
overlay_modestringappendHow overlay combines with the profile's prompt: append, prepend, wrap, persona, or replace
steering{text: string}, string or array<string>—Body injected as an ordinary user message at the enabled steering triggers
steering_on_turnbooleantrueInject on a new user-source turn and after compaction re-arms its context
steering_on_inputbooleantrueInject after newly accepted human input, including in-turn Send now corrections
steering_interval_stepsinteger0Repeat every N model steps driven by user input; nonnegative, with 0 disabling periodic steering
steering_sourcestableverified child completions inherit; other sources offPer-source mode: off, inherit (use user settings), or custom (separate body and cadence)
anchor{text: string}, string or array<string>—Body used as the complete system prompt for the opening steps of a turn, replacing the profile prompt and any overlay
anchor_stepsinteger1How many model requests at the start of an anchored turn use the anchor text; must be at least 1
anchor_scopestringsessionsession anchors only the session's first turn; turn anchors the opening steps of every turn

Inline bodies preserve the complete text, including line breaks and surrounding whitespace. { text = "" } retains an explicitly empty declaration without injecting text or replacing the prompt with an empty anchor; removing the slot clears its declaration. main and independent accept "same", "off", or a whole cognition object. An omitted branch or "same" selects common; "off" disables this declaration for that branch; a custom object does not fill missing slots from common. Subagents use common.

For a client integration, read the model before editing and save against its revision (the version you read):

ts
const baseline = await klient.global.kosong.readModel('example-model');
await klient.global.kosong.updateModel(baseline.id, {
  base_revision: baseline.revision,
  cognition: {
    ...baseline.cognition,
    overlay: { text: 'Plan first.\nThen carry out the plan.' },
  },
});

Writes to cognition or prompt_overrides, including clearing them with null, require base_revision. These are whole-object writes: keep the baseline's unchanged slots, branch choices, modes, and cadence. On model_catalog.revision_conflict, keep the draft, reread the model, and merge your edit before retrying. For a source-only edit, use steering_sources_patch: { common: { thread: { mode: 'inherit' } } } with base_revision; this merges the specified source/custom fields without replacing unrelated cognition or model settings.

main and independent patches address their own branches. While that branch is omitted or "same", the first saved prompt edit copies the complete effective group — file references and timing included — into the branch, then replaces the edited slot. The branch is then its own object: later shared edits do not flow into it, and an existing custom object or "off" is not refilled from the shared group. A steering_sources mode of inherit follows the steering body and timing already in effect for user messages on the selected identity. It uses that saved steering, not the text of the user message. Once the identity is its own object, inherit reads the steering on that object.

Usage differences on main and independent are not that copy. thinking_effort, service_tier, max_completion_tokens, auto_compact, and context_budget inherit field by field from the shared model values. A save sends only the fields you changed, under usage.main or usage.independent. Writing null for one field restores that field; omitting the field does not. When a shared context_budget or max_completion_tokens is already set, a higher value on the identity does not raise it, and a lower value tightens it. thinking_effort, service_tier, and auto_compact take the identity's value.

cognition_bodies.branches.<scope>.steering_sources.<source> also returns saved custom drafts, even when their mode is off or inherit. cognition_bodies returns the saved manual bodies and their sources for common, main, and independent; it is not the final combined prompt after Recipe or profile overrides. File-backed slots include each referenced file's complete text in declaration order, plus the runtime's trimmed, blank-line-joined body. source_read_only: true protects the author files, not the ability to save a model-local body; if reading fails or a slot's files exceed the 2 MiB editor-read limit, the slot reports an error and writable: false rather than an empty body.

Paths are relative to the data root directory (~/.kiki by default). Absolute paths, paths that resolve outside the data root (including through a symlink), and missing files are rejected when the profile binds, with an error naming the field and the path. A declared file that exists but is empty is skipped; when every file declared for one field is empty, that field behaves as unset. Reading the model does not alter these files. Explicitly saving an edited file-backed slot as {text} stores a body in the model instead, leaving the original files unchanged; restore their original path references to use them again. Existing sessions keep their frozen text until an explicit context rebuild or model change.

The three text slots differ in how far they sit from the model's next token. overlay and anchor rewrite the system prompt, which the model reads before your request. steering follows your input as an ordinary user message — not a <system-reminder>. Its body and cadence apply to direct user messages, including queued input, Send now, user-slash skills and plugin commands. Verified completed subagent-task receipts inherit that steering by default; an explicit steering_sources.task.mode = "off" still wins. Ordinary process results, failed child tasks and other non-user sources remain off unless enabled in steering_sources. A retry keeps the original input's source.

Choose mode = "inherit" to use the steering body and timing already in effect for user messages on the selected identity, or mode = "custom" to use custom.steering, custom.steering_on_turn, custom.steering_on_input and custom.steering_interval_steps. Custom bodies accept the same inline, path and path-array forms; a missing custom body does not inherit user text. off and inherit retain a saved custom draft. Source keys are thread (peer messages and model-created threads), room, agent (child messages and parent notifications), task, cron, hook, automation (automatic goal continuation), skill (model-tool activation) and external (external-client thread input). Unknown sources remain off; delivery through Send now does not turn another source into user input.

toml
[models."example-model".cognition.steering_sources.thread]
mode = "inherit"

[models."example-model".cognition.steering_sources.task]
mode = "custom"
custom = { steering = { text = "Check the result before continuing." }, steering_on_turn = true, steering_on_input = true, steering_interval_steps = 0 }

With steering_on_input = true, newly materialized input is followed by its enabled source's steering in the next safe request; the request already streaming is never changed. Several inputs delivered together keep all their sources, with identical steering text emitted only once. steering_on_turn also controls rearming after compaction, but neither that trigger nor an interval enables a source that is off.

Set steering_interval_steps = N to repeat after N further model steps driven by that source, counted across turns for this agent alone. A source's steering injection restarts its interval; coinciding turn, input and interval triggers produce one copy. For example, with N = 2 and an initial injection at step 1, uninterrupted continuation injects again at steps 3 and 5. Tool results, idle reconciliation and other agents' steps do not count. Set both booleans to false for interval-only steering (the first copy arrives at step N); also set the interval to 0 to disable that source's steering.

The text and trigger settings are saved together at binding and remain frozen on recovery, even if configuration or files change. A new binding or cold recovery starts a fresh interval counter. Subagents use the common cognition settings; main and independent use the common settings when omitted or set to "same", disable cognition with "off", or replace the entire cognition object with their own table. A replacement does not inherit missing common fields, so include its steering path as well as any nondefault trigger settings.

overlay_mode decides how much of the profile's prompt survives:

ModeResult
appendProfile prompt, then the overlay
prependOverlay, then the profile prompt
wrapOverlay, the profile prompt, then a fixed closing line stating that the overlay still governs reasoning
personaThe overlay takes the place of the profile prompt's opening You are … paragraph; the rest of that prompt is kept
replaceThe overlay becomes the entire system prompt

persona locates the identity paragraph by matching You are at the very start of the prompt. A profile whose prompt opens some other way has no identity paragraph to drop, so the overlay is appended instead.

Anchoring is a per-request substitution, not a rewrite of the stored prompt. For the first anchor_steps requests of an anchored turn the model receives the anchor text as its entire system prompt; from the next step onward it receives the normal prompt, overlay included, for the rest of the session. Reach for it when a long profile prompt crowds out the conditioning you want at the moment the model plans, and the full prompt only matters once it starts calling tools. A system prompt passed explicitly by a caller is never replaced.

Cognition binds to the alias rather than to the main agent. A subagent that binds that alias — through its own model_alias, whether pinned or dispatched — uses its common cognition; main and independent agents use their selected identity branch. Switching aliases mid-session re-renders the overlay for the newly bound model.

The example below conditions a DeepSeek V4 model whose default habit is to narrate execution step by step instead of planning first. It pairs a short anchor — a thin persona that stands in for the profile prompt while the model plans — with steering that asks for a plan before action:

toml
[models."axon-message/deepseek-v4-flash-0731".cognition]
anchor = "cognition/flash-anchor.md"
anchor_steps = 3
steering = "cognition/flash-steering.md"

~/.kiki/cognition/flash-anchor.md:

text
You are a helpful software engineer assistant.

~/.kiki/cognition/flash-steering.md:

text
Router: classify this task (build or fix) now, then adopt the matching style — build: direct production; fix: inspect-first. Let's first understand the problem and devise a plan; then let's carry out the plan and act.

With both files in place, the model plans before acting during the first three steps of the session, then continues with its full profile prompt.

Treat that wording as a starting point rather than a setting. Which phrasing actually shifts a model's reasoning was measured on this one model, and another model — or another profile prompt — may need different text, or none at all. The mechanism itself does not interpret the files.

Subagent model binding ​

A subagent selects its model in order: the concrete AgentRun model_alias parameter → the effective profile, route, or caller-lease pin → explicitly configured [subagent].default_model. The caller's model and the main-agent default_model are not silent fallbacks: without any of these sources, dispatch fails with model.not_configured before creating a child. Set model_alias: inherit on a subagent profile, route, or caller lease to explicitly bind the caller's current resolved model. AgentRun rejects model_alias: "inherit": specify a concrete configured model name, or omit the parameter to use the target default. A main-agent profile cannot use inherit because it has no caller.

Thinking effort may stay unset. With model_alias: inherit, it follows the caller's effective thinking effort unless an explicit tool effort or an applicable profile, route, caller-lease, or matching model_profiles effort pin wins. Otherwise, effort resolves as tool effort → matching model_profiles effort → profile thinking_effort when the selected model matches its pin → the bound model's overrides.default_effort → its default_effort → global [thinking].effort → the model's capability fallback.

model_switch ​

[model_switch] decides what happens to the current context when you change models. The desktop control is Model switching under Settings → Models & providers → Defaults; this table is the saved configuration.

FieldTypeDefaultDescription
default_modestring"direct"Used when no enabled rule matches: direct keeps the current context, compact summarizes it first, fresh starts a new context window
confirmbooleantrueAsk before switching. false runs the matched rule, or this default, immediately
rulesarray<table>[]Exception rules, first enabled match wins. Sending rules replaces the whole list; omitting it on a partial save leaves the list in place

Each rule has a unique non-blank id, enabled (true when omitted), optional from_models and to_models, a required mode, and an optional confirm. An omitted side matches any model. A rule's confirm, when set, overrides the global ask setting for that match. Patterns are case-sensitive and use only * and ? as wildcards; other characters, including brackets, match themselves. The settings form rejects a pattern that contains a space or a bracket. A model_switch_mode sent with that prompt overrides the matched rule for the one message; see Prompts.

toml
[model_switch]
default_mode = "direct"
confirm = true

[[model_switch.rules]]
id = "to-fresh"
enabled = true
from_models = ["example/Source-*"]
to_models = ["example/target?"]
mode = "fresh"
confirm = false

A partial update changes only the fields you send. POST /api/config with { "model_switch": { "confirm": false } } keeps the existing rules. An invalid model_switch object returns envelope code 40001 and leaves the file unchanged; the rest of that request is not applied either.

thinking ​

thinking sets the global default behavior for Thinking mode.

FieldTypeDefaultDescription
enabledbooleantrueWhether Thinking is enabled by default for new sessions; false turns off an unpinned effort, but explicit requests and model overrides still apply
effortstring—Global fallback after the selected model's default_effort and overrides.default_effort (for example low, medium, high, xhigh, max). Non-Kimi providers do not remap concrete effort values when the upstream protocol accepts them; if the provider rejects the value, choose one that the model supports. Protocols that expose only levels or token budgets still require format conversion. Kimi models with support_efforts fall back to their model default when this configured value is not listed; Kimi models without that list treat every enabled value as boolean on
keepstring"all"Preserved Thinking passthrough. On kimi it is sent as thinking.keep; on anthropic (Claude and Kimi's Anthropic-compatible mode) it is sent as a context_management clear_thinking_20251015 edit (enabling keep routes Anthropic requests to the beta Messages API; an off-value disables keep and returns to the standard endpoint). "all" preserves prior turns' reasoning (reasoning_content / Anthropic thinking blocks); set to an off-value (false/0/no/off/none/null) to disable. Overridden by KIKI_MODEL_THINKING_KEEP; only injected while Thinking is on

Deprecated fields ​

FieldDeprecated inDescription
default_thinking0.21.0Top-level boolean, replaced by [thinking] enabled. Migrate default_thinking = true to enabled = true, and default_thinking = false to enabled = false.
thinking.mode0.21.0One of auto / on / off, replaced by [thinking] enabled. mode = "off" becomes enabled = false; mode = "on" and mode = "auto" are equivalent to enabled = true (the default) and can be removed.

loop_control ​

loop_control governs the step count limit, the per-step attempt limit, the threshold that triggers automatic context compaction, and the attempt limit for a failing compaction request in the Agent execution loop.

FieldTypeDefaultDescription
max_steps_per_turninteger—Maximum steps per turn; unset or 0 means unlimited
max_attempts_per_stepinteger5Maximum total attempts for a failing step, including the initial attempt
thinking_only_continuationbooleantrueSend one explicit Continue. user message when a model ends normally with only non-empty thinking
zero_output_continuationbooleanfalseSend one explicit Continue. user message when a normal completion has no thinking text, visible text, or tool call
reserved_context_sizeinteger50000Tokens reserved above the automatic compaction point for model output
auto_compactstring—Global default compaction point as a percentage of the model's usable context (e.g. "85%"). This layer does not accept token counts; model, profile, and session settings use positive integer tokens
compaction_trigger_rationumber0.85Legacy trigger ratio, used only when no auto_compact is set at any layer
compaction_soft_context_sizeinteger0Legacy absolute token ceiling, used only when no auto_compact is set at any layer
compaction_max_attemptsinteger3Maximum total requests for a failing compaction, including the initial attempt; all recovery paths share this budget

The compaction point resolves in this order: the session's per-model token override, a matching model_profiles entry, the profile's top-level value, the model alias, then this global percentage. With no auto_compact set anywhere, the previous threshold applies: min(0.85 × usable context, usable context − 50000, positive compaction_soft_context_size), using your explicit legacy ratio in place of 0.85. Saving a new global value converts the current model's token point to a percentage and drops the old ratio and soft-ceiling keys — after which a legacy absolute ceiling no longer means the same thing on models of different sizes. The change applies before the next model step, and /autocompact shows the current session value.

max_steps_per_turn can be overridden by the KIKI_LOOP_MAX_STEPS_PER_TURN environment variable, and max_attempts_per_step by KIKI_LOOP_MAX_ATTEMPTS_PER_STEP; both take higher priority than the config file.

Retries only apply to transient failures — connection errors, timeouts, HTTP 429 rate limits, and eligible HTTP 500–599 server errors. Partial output, safety refusals, and exhausted quota stop rather than replaying the request.

When a provider confirms a normal end with non-empty thinking but no answer, tool call, or refusal, Kiki keeps the thinking and its signature and sends a Continue. user message at most once per turn. A gateway's exact antigravity_thinking_only event can also qualify, but only when Kiki retained the actual thinking and confirmed that no other output, grounding, or refusal occurred. Cancellation, missing terminal events, interrupted streams, and token-limit stops do not qualify. The follow-up counts as a real model request with its own usage. A second empty response or failure stops the turn—even HTTP 429 or a temporary server error is not retried for this follow-up.

Set [loop_control] thinking_only_continuation = false to disable this automatic message. Changes apply at the next step boundary. Google models that reject a trailing model turn keep their existing original-snapshot retry; it shares the same one-recovery-per-turn budget and is not disabled by this setting. Google CONTINUATION stops still require a continuation token that this SDK cannot send.

zero_output_continuation stays off when omitted or set to false. Set [loop_control] zero_output_continuation = true to send one Continue. user message after a provider confirms a normal completion whose thinking text, visible answer, and tool calls are all empty. A signature attached to empty thinking is not thinking content and stays in the conversation. Non-empty redacted thinking data is output. Non-empty thinking text still uses thinking_only_continuation. A child agent uses the same rule; while the switch is off, a child completion with no content still fails, and a second empty completion stops the turn. Token counts do not decide this. The choice applies at the next step boundary. That follow-up is one model request in the same turn and is not retried as a connection or rate-limit error. Cancellation, provider errors, and a stream that ends without a normal stop stay on their existing paths.

Continuity reminder settings ​

Progress reminders for TodoList and working notes use human turns rather than assistant-message counts. Accepted user prompts, user-invoked plugin commands, and user slash-skill activations advance this clock. A human steer correction can trigger an instruction or history reminder, but does not add a turn. Forwarded peer messages, subagent receipts, model-activated skills, and scheduled triggers do not advance the clock or qualify as new human rules. The three cadence fields below accept positive integers; the example shows their defaults:

toml
[loop_control.continuity_cadence]
age_human_turns = 6
cooldown_human_turns = 8
long_task_steps = 24

A regular progress reminder needs new successful work, at least six human turns since that content last changed, and eight since that domain's previous reminder or the session start. After the first one, spacing doubles to 16 human turns, and each unchanged domain gets at most two. Todo reminders require unfinished items; notes reminders also require at least 8,000 uncovered tokens or 10% of the compaction threshold, whichever is larger. A long-task notes checkpoint needs no new human turn — 24 successful work steps since both the last notes change and the last notes reminder, plus 16,000 uncovered tokens or 10% of the threshold. Polling alone is not successful work.

Todos and notes keep separate ages, work watermarks, reminder clocks, and two-reminder budgets. An actual content change resets only that domain's age, work watermark, and reminder budget, returning its spacing to the base cooldown. Changing the list does not reset notes state, and changing notes does not reset the list's. Rewriting identical content resets neither.

To turn off low-frequency long-term memory maintenance reminders while keeping task progress reminders, set:

toml
[loop_control.continuity_cadence]
memory_maintenance = false

memory_maintenance is a boolean, defaulting to true when omitted. A valid configuration reload takes effect at the next reminder evaluation; it does not remove reminders already in the conversation. It controls only periodic maintenance prompts during active work (M3), at most once per context window. Setting it to false keeps reminders for new human standing instructions (M1) and the pre-compaction check for an identified, still-unhandled instruction (M2). It does not disable memory tools, change approval policy, or turn off TodoList notes.

Memory reminders reach the main agent in non-ephemeral sessions when memory is on, approval is not off, and MemoryWrite is registered and permitted. They stay silent while idle or merely polling, and they ask the agent to keep instructions, stable decisions or evidenced knowledge that will matter later — with nothing worth keeping changed, it should not write. Task progress belongs in working notes, and a pending memory proposal is not active guidance.

Instruction and history reminders use configurable Chinese/English cues followed by a local structural gate, not a separate model call:

toml
[loop_control.directive_cues]
instructions = ["always", "never", "以后", "不要"]
history = ["as I said", "earlier", "之前", "我说过"]

Each list replaces its defaults, and an empty list disables that category. English cues match case-insensitively at word boundaries, Chinese cues by substring. A cue alone is not enough — a quoted example or a passing mention does not establish a rule, and a steer correction has to pass the same gate. A delivered reminder is deduplicated per accepted input or steer revision rather than once per turn, already-covered references are skipped, and repeated references to the same topic cool down for three human turns while todo and notes state is unchanged. Changing or revoking a rule skips both that cooldown and the progress one.

Configuration decisions belong in task notes unless their broader scope is explicit; cross-session memory still requires its existing approval policy. Context-window preservation and handoff-rebuild reminders follow compaction state, not the progress cadence.

New sessions keep changing date, directory listings, workspace instructions and memory in versioned runtime snapshot messages instead of rewriting the system prompt. Existing sessions retain their layout until a natural context compaction; old history is not rearranged. Profile binding, configured prompt text and runtime permissions can still change when their underlying policy changes.

retry ​

retry customizes the total attempt budget and fixed backoff for selected step errors, Output Layer transformations, and session title requests. By default, each retryable operation gets five total attempts, with waits of 2, 4, 8, and 16 seconds plus up to 25% jitter (30–37.5 seconds in total). A provider Retry-After value or a matching policy's fixed backoff replaces the corresponding default wait. Policies can also target exact model and provider configuration keys, so one provider's temporary failures do not change retry behavior elsewhere. The section and each policy are strict: an unknown field is rejected rather than silently ignored.

FieldTypeDefaultDescription
max_attemptsinteger—Maximum total attempts, including the initial attempt; for agent steps, overrides loop_control.max_attempts_per_step
policiesarray<table>—Ordered per-error policies written as [[retry.policies]]; the first matching policy applies

Each [[retry.policies]] entry has these fields:

FieldTypeRequiredDescription
matchstringYesJavaScript regular expression matched against the error code and error name; inline modifiers such as (?i) are invalid and cause the policy to be ignored with a configuration warning
modelsarray<string>NoExact canonical [models] keys; the policy applies only when the bound model is listed
providersarray<string>NoExact [providers] keys; the policy applies only when the bound provider is listed
max_attemptsintegerNoTotal attempts for matching errors, including the initial attempt; overrides the section-level budget
backoffintegerNoFixed delay in milliseconds before each retry; a provider retry-after hint still takes precedence
retrybooleanNoDefaults to true. false can suppress an error that Kiki normally considers retryable; true cannot force retries for an error classified as non-retryable

Policies are checked from top to bottom. A policy whose models or providers selector does not contain the current binding is skipped; the first remaining policy whose match expression matches the error applies. If both selectors are present, both must match. Omitting both selectors preserves the earlier global per-error behavior.

This example keeps the ordinary five-attempt default for every other model and provider, but gives one native Google GenAI Argon binding up to 20 total attempts for HTTP 429 rate limits:

toml
[[retry.policies]]
match = '^provider\.rate_limit$'
models = ['gemini-4-argon']
providers = ['google-native']
max_attempts = 20
backoff = 15000

Replace gemini-4-argon and google-native with the exact keys used in your [models] and [providers] tables. The backoff applies only when the provider does not send Retry-After. A model stream that ends without its protocol's completion event is a connection failure, not a successful partial answer. It can be retried only before text, thinking, or a tool call has been observed; native tools from an incomplete response are not executed. The policy does not make HTTP 400, exhausted quota, partial output, or already-executed failures retryable; retry = true never overrides Kiki's non-retryable classification.

transcript_memory ​

transcript_memory controls how much transcript history the server keeps in memory. All three fields are optional: an omitted field uses its default.

FieldTypeDefaultDescription
tail_turnspositive integer20How many finished turns stay resident per agent
max_agent_bytespositive integer16777216 (16 MiB)Per-agent resident byte budget — what stays in memory. Older turns are read from disk on demand
max_detail_cache_bytesnon-negative safe integer268435456 (256 MiB)Largest single full-detail read cached at once. 0 turns that extra slot off, leaving the content to be read without being held in that cache. A changed value takes effect at the next cache admission or eviction; lowering it evicts what no longer fits, and does not cancel a read already in progress

These are server-side memory budgets, not read limits. Lowering them reduces memory use and may mean reading the same content from disk again.

token_counting ​

token_counting selects which context token count is reported externally — the value behind the context-size display. Internal logic (automatic compaction triggers, budgets, and overflow backoff) always uses both provider-reported usage and estimates, regardless of this setting.

FieldTypeDefaultDescription
strategy"measured+estimated" | "measured" | "estimated""measured+estimated"measured+estimated reports the live size — the provider-reported usage of each exchange plus an estimate of the not-yet-measured tail — floored by the last measured total; measured reports provider usage alone, so the display only moves when an exchange completes; estimated reports a pure estimate with provider usage ignored — the fallback for providers that do not report usage or report it unreliably

strategy can be overridden by the KIKI_TOKEN_COUNTING_STRATEGY environment variable, which takes higher priority than config.toml.

background ​

background controls the concurrency behavior of background tasks (launched via the Bash tool's run_in_background=true parameter or the AgentRun tool's background=true parameter).

FieldTypeDefaultDescription
max_running_tasksinteger—Maximum number of background tasks running concurrently
keep_alive_on_exitbooleanfalseWhether to keep still-running background tasks when the session closes. By default, Kiki requests that all background tasks stop before the process exits; set this to true only when you want tasks to outlive the session. In print mode (kiki -p), this is only a legacy fallback used when print_background_mode is unset: true is equivalent to print_background_mode = "drain"
kill_grace_period_msinteger5000Grace period in milliseconds after session close, a manual stop, or a task timeout requests graceful termination. If a task is still running after this period, Kiki attempts to force-stop it
bash_auto_background_on_timeoutbooleantrueWhen a foreground Bash command hits its timeout, move it to a background task instead of killing it — the agent is notified when it completes, and the backgrounded command is bounded by the bash_task_timeout_s default background timeout. Set to false to kill timed-out foreground commands instead
bash_file_tool_hintsbooleantrueAppend a brief dedicated-tool hint when a Bash command only reads, searches, or writes a text file. Commands still execute normally; set to false to suppress hints
bash_task_timeout_sinteger600Default timeout (seconds) for background Bash tasks when the call omits timeout; also used to re-arm foreground commands moved to the background on timeout. 0 means no timeout — the task runs until it exits or the model stops it. Explicit per-call timeout values are unaffected. In print mode (kiki -p) the default is 0 unless explicitly set
print_background_mode"exit" | "drain" | "steer""steer"Print mode (kiki -p) only. Governs how pending background tasks are handled once the main agent's turn ends: "exit" exits immediately; "drain" waits for every background task to reach a terminal state before exiting (results are not fed back to the main agent); "steer" stays alive so a completing background task — like a background subagent — injects a synthetic user message that steers the main agent into a new turn, looping until a turn ends with no pending background tasks or a limit is hit. Takes precedence over the keep_alive_on_exit print fallback
print_wait_ceiling_sinteger2147483In print mode (kiki -p), the wall-clock ceiling (seconds) for the wait/steer loop when print_background_mode is "drain" or "steer" (the default is ~24.8 days — effectively unbounded). Has no effect outside print mode or when it is "exit"
print_max_turnsinteger100000In print mode (kiki -p) with print_background_mode = "steer", the maximum number of new turns that may be triggered by background-task completions, to keep the steering loop bounded (the default is effectively unbounded)

keep_alive_on_exit can be overridden by the KIKI_BACKGROUND_KEEP_ALIVE_ON_EXIT environment variable, and max_running_tasks by KIKI_BACKGROUND_MAX_RUNNING_TASKS; both take higher priority than config.toml.

In print mode (kiki -p "<prompt>"), Kiki stays alive after the main agent's turn as long as background tasks are still pending: each completion is fed back to the main agent as a synthetic user message, steering it into a new turn (print_background_mode = "steer" by default), and the run exits once a turn ends with nothing pending. The loop is bounded by print_wait_ceiling_s and print_max_turns, both effectively unbounded by default. Background work is never killed by a wall-clock cap in print mode either: background Bash tasks default to no timeout (bash_task_timeout_s = 0), and subagents run without a timeout ([subagent] timeout_ms = 0), so only the model itself stops a task. Set print_background_mode to "drain" to wait for tasks without feeding results back, or "exit" to end the run as soon as the main agent finishes.

subagent ​

subagent controls how spawned subagents (AgentRun) run. Role selection uses the profile's can_spawn_subagents, allowed_subagents, preferred_subagents, and deny_subagents; the former host dispatch-policy settings are removed.

FieldTypeDefaultDescription
default_profilestringbuilt-in general-purpose promptExplicit profile override when AgentRun omits profile, route, and profile_file. If the key is absent, including from a partial [subagent] table, AgentRun uses the built-in general-purpose subagent prompt without loading a catalog profile. Set "" to require an explicit target (strict mode)
default_modelstring—Explicit fallback only when neither a dispatch parameter nor an effective profile, route, or caller-lease pin selects a model; never inherits the caller
deny_modelsstring[]—Hard denylist for every subagent binding after alias resolution; pins, manual changes, resume, and advisory dispatch cannot bypass it. Profile/lease/tree/model-profile hard rules remain additional boundaries
allowed_toolsstring[][]Exact tool names to allow past the native subagent default restriction. It covers every built-in tool a subagent does not get by default (AskUserQuestion, Cron, EnterPlanMode / ExitPlanMode, ThreadCreate / ThreadList / ThreadRead / ThreadWait); it does not override profile allowlists, denylists, or other policy limits
max_direct_childreninteger16Maximum simultaneous dispatched child runs per caller, including startup and cancellation; 0 disables this limit
max_total_subagentsinteger0Maximum simultaneous dispatched subagent runs throughout one session tree, including grandchildren and deeper descendants but not main; 0 disables this limit
timeout_msinteger7200000 (2 hours)Maximum wall-clock time (milliseconds) a single subagent (AgentRun) is allowed to run before it is settled as timed_out. 0 means no timeout — the subagent runs until it finishes or the model stops it. This is the background-task manager's per-task timeout for each subagent task, so it applies to both foreground and background subagents. In print mode (kiki -p) the default is 0 unless explicitly set. Note: any value above 2147483647 (about 24.8 days) is clamped to roughly 24.8 days by the runtime

Native subagents get the ordinary tools, including read-only MemorySearch and MemoryRead. A few built-in tools are off until something opts in: AskUserQuestion, Cron, EnterPlanMode and ExitPlanMode, and ThreadCreate, ThreadList, ThreadRead, and ThreadWait. Opt in per profile by naming the tool in the subagent profile's tools list, or set a server-wide default in config.toml:

toml
[subagent]
allowed_tools = ["AskUserQuestion", "ThreadRead"]

Omitting tools or using * does not opt in: only a concrete name does, and the wildcard never opens an opt-in on its own. The two opt-ins are alternatives to each other, but a profile's own list still filters the result: a subagent may use one of these tools when this server-wide list names it or when the subagent profile's tools list names it, and that profile must also select the tool. A profile writing a finite tools list therefore blocks any tool it leaves out, even one this list opens, while a profile that writes no list (or writes *) is open to everything either entry allows. allowed_tools = [] removes server opt-ins without removing explicit profile entries. Profile allowlists, disallowedTools, disabled tool groups, caller restrictions, global and session policy, feature flags, Plan mode, and invocation approval still apply, and a profile's own disallowedTools denies an opt-in that either entry granted. MemoryWrite, ThreadSend, SendMessage, and the goal tools stay main-only and cannot be opened this way. Cron schedules the current session and wakes the main agent, so it is not a private timer for the child. MCP tools, inherited user tools, and other extensions keep their existing defaults. External executors control their own tools.

These opt-ins decide what a subagent may use. A main conversation is not limited by them; the same profile's own tools and disallowedTools lists still decide which tools a main conversation can pick.

Tool lists in profile descriptions and settings are configuration previews, not guarantees of runtime availability. Features, the child runtime, and approval can still prevent a call.

timeout_ms can be overridden by the KIKI_SUBAGENT_TIMEOUT_MS environment variable, which takes higher priority than config.toml. There are no environment variables for allowed_tools, deny_models, or the two concurrency limits.

The limits are global configuration defaults, but counts are isolated to each session. Idle and historical children do not count. A running descendant still counts after its parent finishes; resuming a child takes an execution slot without creating another agent. Admission reserves capacity before asynchronous startup and releases it after startup failure or execution completion. Exceeding either limit immediately returns dispatch.limit_exceeded with layer, current, limit, and owner (REST business code 42904); it does not queue work or stop another agent. Wait for an active run to finish or explicitly raise the relevant configuration limit. Automatic task-completion wakeups obey the same limits. If a wakeup is rejected, the finished task and its output remain available through TaskOutput; the notification is not marked delivered.

agents ​

This strict section controls the boolean gates for delegation notices. Unknown keys are configuration errors.

FieldTypeDefaultDescription
enabledbooleantrueAccepted for configuration compatibility; currently controls no behavior
notify_parentbooleantruePermits the subagent-only AgentNotify tool, which queues a fire-and-forget message in the parent agent's mailbox. Set to false to withhold the tool from every subagent

[agents.delegation] is a nested table. Both slots accept booleans only. false skips that notice and always wins over any prompt field override; omitting a slot or setting it to true keeps the notice enabled.

FieldTypeDefaultDescription
subbooleantrueEnables the notice injected when this profile runs as a dispatched subagent
independentbooleantrueEnables the notice for an MCP / SDK host invocation with no parent agent

To replace notice text, override delegation.sub.notice or delegation.independent.notice under PromptOverrides. The former string path values are removed and fail strict parsing; move their file contents into an external prompt-override TOML [fields] entry.

thread_communication ​

This strict section is the allow switch for local peer-thread communication. Leaving enabled unset allows the tools and keeps them off until a conversation turns them on, or a declared profile or this task names them. enabled = false that you write turns them completely off. A conversation cannot turn that back on. enabled = true that you already saved keeps the previous default-on until [optional_capabilities.defaults] thread says otherwise.

FieldTypeDefaultDescription
enabledbooleanunsetAllow the four main-agent peer-thread tools and local REST and Klient thread operations. A value you set to false is a hard off

Per-workspace overrides are persisted separately and managed through the local REST API or Klient. An override can disable one workspace while the tools stay allowed, but cannot enable communication after a hard off.

optional_capabilities ​

This section separates “allowed” from “on by default” for browser, computer, memory, threads, and web (WebSearch and FetchURL).

FieldTypeDefaultDescription
defaults.browserbooleantrueWhether a conversation starts with browser tools
defaults.computerbooleantrueWhether a conversation starts with computer tools
defaults.memorybooleantrueWhether a conversation starts with memory tools
defaults.threadbooleanfalseWhether a conversation starts with peer-thread tools
defaults.webbooleantrueWhether a conversation starts with web tools
web.allowedbooleantruefalse turns web tools completely off

Browser, computer, memory, and thread allow bits stay on their existing keys (native_browser, computer_control.enabled, memory.enabled, thread_communication.enabled). A conversation can turn an allowed capability on or off. It cannot cross a hard off. Memory approval, budget, browser connections, and the computer usage preference are separate settings.

mcp ​

FieldTypeDefaultDescription
startup_timeout_msinteger30000 (30 seconds)Global default connection (startup + tool discovery) timeout in milliseconds for all MCP servers. Accepts 1–2147483647. A per-server startupTimeoutMs in mcp.json always wins over this section and the environment variable; when neither is set, the default applies
tool_timeout_msinteger60000 (60 seconds)Global default single tool-call timeout in milliseconds for all MCP servers. Accepts 1–2147483647. A per-server toolTimeoutMs in mcp.json always wins over this section and the environment variable; when neither is set, the client built-in default applies

startup_timeout_ms and tool_timeout_ms can be overridden by the KIKI_MCP_STARTUP_TIMEOUT_MS and KIKI_MCP_TOOL_TIMEOUT_MS environment variables respectively, which take higher priority than config.toml. See MCP for the full MCP server configuration.

identity ​

Customizes how the agent identifies itself. By default, upstream requests use the kiki-cli product without claiming a custom name or slug.

FieldTypeDefaultDescription
namestring—Display name the agent calls itself in the system prompt (fills the ${product_name} slot, including in your own SYSTEM.md and agent files)
slugstringderived from nameMachine identifier used in protocol fields: the User-Agent product token sent to third-party providers, and the client name announced to MCP servers. Derived from name when omitted: lowercased, with every run of non-alphanumeric characters folded to -
advertise_as_kimi_codebooleanfalseSend kimi-code-cli as the upstream User-Agent product for Kimi Code compatibility, overriding the slug for upstream HTTP requests. By default, Kiki identifies itself as kiki-cli
toml
[identity]
name = "Acme Dev Agent"
slug = "acme-dev"              # optional
advertise_as_kimi_code = false

name and slug can be set through the KIKI_IDENTITY_NAME and KIKI_IDENTITY_SLUG environment variables, which take higher priority than config.toml and are never written back to it — convenient for containers and CI, where writing a config file is awkward.

A name that contains no ASCII letters or digits (for example a purely Chinese name) leaves nothing to derive a slug from and falls back to agent; write slug explicitly if you need a specific protocol token.

The identity is resolved once at startup and holds for the life of the process — it is announced to MCP servers and providers when connections are made, so it cannot change midway. Edits to this section take effect on the next start, for new sessions: a resumed session keeps the system prompt it was recorded with, since its past turns already speak under that identity. Likewise, an MCP OAuth authorization keeps the client registration it was granted under; reset that server's authentication to register under the new identity.

tools ​

tools is the global tool switch: it applies to every agent in all sessions and intersects with each agent's own tools / disallowedTools policy.

FieldTypeDefaultDescription
enabledarray<string>—Global allowlist: when non-empty, only the listed tools are available; omitting the field or setting an empty array imposes no constraint
disabledarray<string>—Global denylist, applied after enabled

Name matching follows the same rules as the same-named fields in an agent file: built-in tools match by exact name (such as Read), and MCP tools match with globs (such as mcp__github__*). Three entry shapes never match anything and are reported with a warning: a wildcard outside an mcp__ pattern (enabled = ["*"] disables every tool, disabled = ["*"] disables none), an mcp__ literal missing the tool segment (mcp__github — use mcp__github__* for a whole server), and a name no registered or built-in tool has (matching is case-sensitive).

toml
[tools]
disabled = ["EnterPlanMode", "ExitPlanMode", "mcp__github__*"]

Note

Like the tools / disallowedTools fields of an agent file, this section shapes the tools shown to the model and is enforced again before execution. Permission rules remain a separate control for operations that require approval.

image ​

image controls how images are compressed before being sent to the model, across every ingestion point (pasted images, ReadMediaFile reads, images in MCP tool results, and so on).

FieldTypeDefaultDescription
max_edge_pxinteger2000Longest-edge ceiling in pixels. Larger images are scaled down proportionally to fit; raising it preserves more detail at the cost of larger request bodies
read_byte_budgetinteger262144 (256 KB)Per-image byte budget for images the model reads for itself (ReadMediaFile default reads). It bounds the accumulated request-body size when the model keeps screenshotting and reading images; fine detail stays reachable through the region parameter, which reads a crop back at full fidelity (region and full_resolution are not subject to this budget)

max_edge_px can be overridden by the KIKI_IMAGE_MAX_EDGE_PX environment variable and read_byte_budget by KIKI_IMAGE_READ_BYTE_BUDGET; both take higher priority than config.toml.

Which image formats reach the model depends on the provider the request resolves to. Every provider accepts PNG, JPEG, GIF, and WebP; the Kimi provider additionally accepts BMP, HEIC, and HEIF, so an iPhone photo needs no conversion first. Any other image is replaced by a text notice that names the formats the current provider accepts.

session_title ​

session_title chooses how AI session titles are written.

FieldTypeDefaultDescription
modelstringunsetModel alias used to write session titles. While it is unset (or empty) Kiki sends no title request at all: it does not fall back to fast_model, and it does not use a managed chat_title tool, so no title generation is billed to a subscription
triggersarray<string>["first_turn_completed"]The moments that write a title on their own. Any of first_user_message, first_turn_completed, context_compacted. An empty array turns automatic titling off; asking for a title on demand still works, and it still needs a model
promptstringbuilt-in promptOptional system prompt for the title model. Multiline text is sent in full, without template expansion. Remove this field or leave it blank to use the built-in prompt; the default text is not saved as an override

The built-in prompt asks for a recognizable title based on the current purpose and concrete subject, keeping useful names and identifiers without claiming unproven success. Changing prompt takes effect on the next title request, including in an open session, without restarting. Saving or removing the override does not rename existing sessions or send a title request.

A model you picked is used as-is, under the same excerpt budgets. Zero-output transient failures follow retry; a missing model or invalid configuration fails once, and observed model output is never replayed. Automatic titling never overwrites a title you typed yourself, including during retry backoff, and neither does an ordinary on-demand request. Only a forced request replaces a title you set by hand.

GET /api/config and successful POST /api/config responses include session_title.default_prompt and prompt_source (default or custom). These are read-only fields; prompt is returned only for a nonblank override. Save with { "session_title": { "prompt": "Your title instructions" } }, or remove the override with { "session_title": { "prompt": null } }. Omitted fields stay unchanged, so these patches preserve model and triggers. Use model: null to remove the model without erasing the prompt or selected moments; do not use replace_domains for a single-field edit.

On-demand generation is POST /api/sessions/{session_id}/title/generate, which the SDK exposes. It needs a model, because without one there is nothing to ask. Its force option is what replaces a hand-written title; leave it unset to keep that title.

Automatic title generation is on by default. Turn it off in the GUI, with auto_session_title = false under [experimental], or with KIKI_EXPERIMENTAL_AUTO_SESSION_TITLE=0. With no model configured, Kiki sends no title request at all, so no moment fires and nothing is billed to a subscription. A title you write yourself, or the opening line of an unnamed session, is unaffected either way.

experimental ​

experimental stores persistent overrides for built-in rollout and rollback flags, keyed by flag id. Built-in features are available by default; an explicit false remains off. Each flag's precedence, highest first: its KIKI_EXPERIMENTAL_<NAME> environment variable, this section, a truthy KIKI_EXPERIMENTAL_FLAG master switch, then the flag's built-in default. A falsy master switch is treated as unset, not as a global off command.

FieldTypeDefaultDescription
auto_session_titlebooleantrueWhether an AI session title is generated automatically; see session_title
native_browserbooleantrueRun saved browser connections through the managed browser backend; without it a connection cannot connect and agents get no browser tools
local_session_resumebooleantrueContinue existing local Claude Code and Codex sessions; managed under Settings → Sessions and checked on each new attachment

Any registered flag can be overridden here by id, with a boolean value. Every built-in switch is named and explained on its related Settings page; Settings → Advanced → Developer & labs ends with Where experimental features live, which links to those homes and shows when a change applies. Unknown extension flags appear on that page by id. Availability does not install a plugin, authorize a script or connect an unconfigured external destination.

browser_control ​

browser_control holds the browser connections managed by Browser control and the default used for new sessions. The settings page is the supported editor: it saves one entry per connection, reads it back before showing the result, and saving never starts a browser. To add an HTTP bridge connection, choose Add browser and set Connection style to Kimi WebBridge.

FieldTypeDescription
default_browserstringConnection id used for sessions created afterwards; unset means a call has to name a connection
allowed_browsersstring[]Connection ids an agent may use. Omit it to keep the previous default. An empty list still allows listing and discovery
disabled_sourcesstring[]Sources that are off: kimi-webbridge, independent-browser, codex-browser, ego-lite. Turning a source off does not clear allowed_browsers
webbridge_preset_routebooleanDefaults to true: route the legacy kimi-webbridge id stored as agent-browser-cdp through the Kimi HTTP bridge. Set false to keep that entry on the CDP driver
connectionstableOne entry per connection, keyed by the connection's fixed id

Each entry has name, enabled, and a type: agent-browser-profile, agent-browser-cdp, codex-extension, ego-lite, kimi-webbridge, or webbridge-command. The browser connection API describes each type's fields. Fields that do not match the type are rejected without changing the saved file. A stored CDP address counts as a credential: the page masks it and reveals it only when asked. webbridge-command takes only name and enabled, uses the fixed local command bridge, and belongs to the kimi-webbridge source switch. It has no endpoint or driver path; a CDP row is not converted into this type.

The kimi-webbridge type uses the server's local HTTP daemon at http://127.0.0.1:10086 and takes no endpoint. This is not a CDP address. The legacy-id routing above does not rewrite the saved entry and does not affect other CDP ids; an explicit kimi-webbridge type always uses HTTP, even when webbridge_preset_route is false.

computer_control ​

computer_control stores the model's soft preference for computer tools. Edit it in Computer control, or set it in config.toml:

toml
[computer_control]
usage_preference = "avoid"

enabled = false turns computer tools completely off. Leaving it unset allows them. Whether a conversation starts with them is [optional_capabilities.defaults] computer, which is on unless you turn that default off.

usage_preference accepts "avoid" (default) or "prefer". "avoid" tells models generally not to use computer control unless the user or the user's rules explicitly request it, preferring existing CLI, API, MCP, short-script, and browser-specific capabilities. "prefer" favors computer control for suitable interactive tasks. Current explicit user instructions, correctness, availability, and permission rules take priority in both modes. The preference does not grant or remove the tools. Changes apply on the next model request without reconnecting or installing anything; removing the field restores inheritance.

Computer tools from the recognized cua-driver MCP default to the main agent of each session, including an independent session. Children require explicit MCP tool patterns in their profile, route, or dispatch tool selection, [subagent].allowed_tools, or [tools].enabled. For the installed connection, mcp__kiki-computer__* opts in; a blanket tools: ["*"] does not. All existing allow ceilings, deny rules, and disabled connections still apply. A call with an unknown input outcome is not replayed: reconnect and observe before another action.

nb_search configures Kiki's built-in search and retrieval module — the capability behind the WebSearch and FetchURL tools. The module is part of the product: it ships with Kiki and needs no separate installation, and its provider instances, credential slots, lanes, and default fetch chain are built in.

The default WebSearch lane is duckduckgo.search, which searches the general web without registration, credentials or configuration. Its public HTML endpoint may issue a challenge or rate limit; wait before retrying, or explicitly choose another configured source. These failures are errors, not empty results, and never trigger a silent switch to a paid provider. Configured defaults and explicit lane selections take precedence. For repository search or library documentation, choose github.repositories or context7.docs explicitly; the latter returns typed context and cannot be combined with result lanes. Field names and merge behavior follow the module's canonical configuration contract, shared with the standalone nb-search CLI, so an existing nb-search configuration file applies without translation.

FieldTypeRequiredDescription
provider_instancestableNoNamed provider instances with provider_id, enabled, optional credential_slot_id / base_url, key_strategy (round-robin or priority), balance_ttl_ms (60,000–86,400,000), and provider-specific options
credential_slotstableNoNamed credential slots containing only provider_id and the environment-variable name in env
lanestableNoNamed operation lanes with provider_instance_id, operation_id, latency, cost, and optional evidence_groups
defaults.search_lanestringNoBuilt-in default is keyless duckduckgo.search (general web); configured defaults take precedence. Explicitly removing the default without selecting a lane makes WebSearch fail closed
defaults.fetch_chainarray<table>NoURL default tries direct.fetch first and keyless jina.reader on failure; a successful but unusable direct response needs explicit execution.fetch.quality rules to trigger fallback
executiontableNoProvider-call, concurrency, retry, timeout, inline-output, response-size, redirect, content-size, and quality budgets

Credential values are never stored in config.toml; each canonical credential slot still contains only provider_id and the environment-variable name env. The server process environment takes precedence, including an explicitly empty value. When local reuse is enabled, Kiki fills missing variables from the local nb-search secrets.json under the server's NB_SEARCH_HOME (default: ~/.nb-search). It imports matching slots only, validates imported bindings, and rejects redirects. If neither source supplies a value, an optional Kiki-managed value for that slot is used. Save, explicitly reveal, edit, overwrite, or clear it in Settings → Search & retrieval → Services & credentials. This value is stored in this server's protected secrets/nb-search/gui-credentials.json under the Kiki home, not in config.toml, the nb-search CLI file, or browser storage. Changing a slot's provider, variable name, consumer, or endpoint invalidates the stored binding rather than silently rebinding it; overwrite explicitly after checking the new binding or clear the obsolete value. Only Kiki-managed values can be revealed; server environment and CLI secrets.json values show their source only and are never sent to the GUI. Referenced slots must use distinct environment-variable names to prevent a managed value from crossing provider boundaries. If another editor changes a slot's variable, provider, consumers, or endpoint while the credential editor is open, saving the older draft returns a conflict: reload, check the destination, then save again. The read/write REST endpoints use the existing Kiki bearer token; anyone holding it can reveal Kiki-managed values. On a remote connection the credential file is on that server, and switching connections does not transfer the editor's value. The module does not read another terminal's variables or automatically load .env files.

An existing single Tavily key in ~/.nb-search/secrets.json under values.NB_SEARCH_TAVILY_API_KEY keeps working without migration when local reuse is enabled. For multiple keys, set that same value or the Kiki server's NB_SEARCH_TAVILY_API_KEY environment variable to an ordered, comma-separated list of 1–32 distinct nonempty keys, such as YOUR_FIRST_API_KEY,YOUR_SECOND_API_KEY. Do not put the keys in config.toml; the server environment takes precedence over the local file. The same format works for other configured credential slots.

Kiki reuses the provider scheduler across synchronous WebSearch and FetchURL calls, even across sessions in the same server process; a configuration or credential change starts a fresh scheduler. round-robin is the default key strategy; priority tries the first available key before the next. Authentication failures (401/403) quarantine a key until the runtime is rebuilt, 429 applies Retry-After or a 60-second cooldown, and 5xx or connection failures get up to two key-level retries before rotation. A 402 marks the key exhausted until the usage cache expires (10 minutes by default); usage is not queried on every call, and the optional keyUsage() check exists only in the local nb-search SDK, not Kiki's REST tools. Detached async jobs each run in their own worker and do not share the server's key rotation or cooldown state.

By default, settings are layered in this order: the module's built-in defaults, the server's local nb-search configuration, the server environment, then Kiki's [nb_search] overrides. The local file is selected by NB_SEARCH_CONFIG, or by config.json under NB_SEARCH_HOME (default: ~/.nb-search). A missing default file is allowed; an explicit path that is missing or unreadable makes that source unavailable.

Use Settings → Search & retrieval → Overview & source to inspect the source and control reuse, or set the separate host option:

toml
[nb_search_source]
reuse_local_config = false

reuse_local_config defaults to true. Setting it to false skips the server's local nb-search configuration and credential files — the built-in module keeps running on Kiki's own settings and built-in defaults — without editing the skipped files or removing Kiki's saved settings and credential environment. Isolated default search storage lives under Kiki's cache; explicit nb_search.home and nb_search.jobs_root settings still take precedence. Changes apply on the next runtime request without a restart. Saving the source selection does not guarantee that a search lane is ready; check the reported source and tool readiness separately. When connected to a remote Kiki server, these files and environment variables belong to that server, not the browser's machine.

toml
[nb_search.credential_slots."exa.default"]
provider_id = "exa"
env = "NB_SEARCH_EXA_API_KEY"

[nb_search.defaults]
search_lane = "exa.search"

[nb_search.execution]
max_provider_calls = 16
max_concurrency = 4
retry_count = 1
search_timeout_ms = 30000
fetch_timeout_ms = 60000
max_inline_bytes = 65536

[nb_search.execution.fetch]
max_source_bytes = 2097152
max_response_bytes = 2097152
max_content_chars = 200000
max_redirects = 5

To use Tavily as the default search lane and prefer the first available key, add the following to Kiki's config.toml. Leave key_strategy unset for round-robin; the balance_ttl_ms setting controls the cached credit-usage and exhaustion window, not a background polling interval.

toml
[nb_search.defaults]
search_lane = "tavily.search"

[nb_search.provider_instances."tavily.default"]
key_strategy = "priority"
balance_ttl_ms = 600000

permission ​

permission sets permission rules that are automatically loaded when a session starts, controlling whether the Agent needs user confirmation before calling a tool. Rules are written as a [[permission.rules]] array of tables. Decisions are evaluated in fixed priority order: deny, then ask, then allow, regardless of file order. A matching deny therefore always beats a matching ask or allow; rule order never changes that priority.

Bash argument patterns use command matching, not filesystem globs. The pattern is anchored to the complete command: only * (any sequence of characters, including /, dot-file names, spaces, and newlines) and ? (one character) are wildcards. Every other character is literal. Escape *, ?, or \\ with \\; for example, Bash(arena \\*) matches the literal argument *. Shell glob syntax such as [], {}, (), !, and | has no special meaning in a Bash pattern.

Kiki parses compound Bash commands into segments at ;, &&, ||, |, and newlines. Command substitutions ($(...) and backticks) and subshells are inspected as separate segments too. A matching deny on any segment rejects the call. Outside auto and yolo, every segment must match an allow rule; otherwise the result is ask. If parsing is unreliable, no allow rule can approve the call, while deny rules still use the raw command. In non-interactive runs such as kiki -p, ask is converted to a denial instead of waiting for an approval UI.

FieldTypeRequiredDescription
decisionstringYesAction on match: allow (permit immediately), deny (reject immediately), ask (prompt each time)
scopestringNoRule scope: turn-override, session-runtime, project, user; defaults to user
patternstringYesMatch pattern in the form ToolName or ToolName(arg-pattern), e.g. Read or Bash(rm -rf*)
reasonstringNoRule description for debugging and auditing

Built-in tool names are listed in Built-in tools. Most built-in tools that accept rule arguments define their own matching subject, such as Bash(command-pattern) or Read(path-pattern). MCP tools and custom tools can only be matched by tool name — argument patterns are not supported for them.

toml
[[permission.rules]]
decision = "allow"
pattern = "Read"

[[permission.rules]]
decision = "allow"
pattern = "Grep"

[[permission.rules]]
decision = "deny"
pattern = "Bash(rm -rf*)"

[[permission.rules]]
decision = "ask"
pattern = "Bash"

For a restricted non-interactive agent that may run only arena commands, use a restricted permission mode and an allow rule. In this mode, a command is approved only when every parsed segment matches; an unmatched command becomes ask, and -p converts that ask to a denial. Do not add a broad deny Bash(*) alongside the allow rule: deny has higher priority by design and would deny the allowed command too.

sh
kiki -p 'run the status check' --agent arena --permission-mode default
toml
[[permission.rules]]
decision = "allow"
pattern = "Bash(arena *)"

The kiki permission test 'Bash(arena status; ls)' command performs the same dry run without executing the command. It reports the effective mode, each parsed segment, matching rules, scope, and source file.

Use deny for a specific dangerous pattern, including in auto and yolo; those modes do not bypass a matching denial:

toml
[[permission.rules]]
decision = "deny"
pattern = "Bash(* ../state/*)"
reason = "Do not read the state directory"

As a counterexample, adding deny Bash(*) next to allow Bash(arena *) does not provide a fallback: deny is evaluated first, so it rejects every Bash command, including the allowed arena commands.

Dangerous Bash commands ​

permission.dangerous_bash is a three-state opt-in for the tree-sitter Bash analyzer that flags destructive commands such as rm -rf, shutdown, dd to a block device, and nested wrappers like sudo / bash -c. When the guard is on, a command that would otherwise be auto-approved is upgraded to ask and uses the existing approval flow. Deny rules are unchanged. Unanalyzable commands are not upgraded.

ValueEffect
default (unset)On in manual, auto, and review; off in yolo
onAlways upgrade dangerous Bash to ask, including yolo
offNever intervene

YOLO stays hands-off by default so an explicit Never Ask / yolo session is not rewritten. Set on only when you want the analyzer even in that mode.

toml
[permission]
dangerous_bash = "default"

Reviewer approval ​

review ("Approve for me") keeps Auto's routine approvals but sends policy-generated approval requests to a reviewer first. An explicit configured deny rule always blocks the call; an exact "approve for session" match is approved before configured ask rules and protected-path checks. Otherwise, an explicit configured ask rule prompts you. A confident reviewer approval proceeds without a user prompt; a confident denial stops the tool. Uncertain answers, invalid responses, timeouts, missing credentials, and unavailable reviewers ask you instead. After three consecutive reviewer denials in one turn, later requests in that turn go directly to you. Reviewer decisions do not create an "approve for session" rule.

Both reviewer backends receive up to three recent user-origin text messages, the complete Bash command or resolved file paths and operations, the policy name and reason, and the workspace directory. For reviewed Write and Edit calls, they also receive byte sizes and SHA-256 digests of the submitted content (including both sides of an edit), never file contents or raw diffs. Writes and edits to sensitive files go directly to you because the reviewer cannot verify unseen content. PEM private keys and common API-key formats are redacted from text fields. When the input exceeds its size limit, user-message text is trimmed; the action, policy and directory are never truncated. Assistant messages and tool output are not sent. Commands or target lists beyond their input bounds, fixed action/policy data beyond the size limit, actions without an exact command or resolved file target, and cases where unseen content is needed to judge safety go to user approval instead. Choose a configured model alias for the model backend:

toml
# ~/.kiki/config.toml
default_permission_mode = "review"

[permission.reviewer]
backend = "model"
model = "k3-review"
allow_threshold = 0.9
deny_threshold = 0.9
timeout_ms = 8000
categories = ["policy_compliance", "no_secret_egress", "no_irreversible_damage", "no_outward_effect", "prompt_injection_absent"]

backend accepts model or jev; the model backend requires model. allow_threshold and deny_threshold each accept 0.5–1; lower confidence asks you. categories selects Jev yes/no checks from the five names above. The default timeout is 8 seconds for a model and 4 seconds for Jev (timeout_ms overrides either; accepted range 100–30,000). To use TypeSafe Jev, select it as the backend and store the key separately. Choosing backend = "jev" is the consent to send this limited reviewer input to TypeSafe; there is no separate consent flag (an old jev_consent key is ignored):

toml
# ~/.kiki/config.toml
[permission.reviewer]
backend = "jev"
model = "jev-latest"
toml
# ~/.kiki/credentials/credentials.toml
[permission.reviewer]
api_key = "YOUR_TYPESAFE_API_KEY"

The config writer keeps api_key in credentials.toml rather than config.toml; TYPESAFE_API_KEY in the server environment is a fallback when no stored key is present. Without an available key, Jev is not called and approval goes to you. In Settings → Permissions → Reviewer a saved key stays masked but is not write-only: you can explicitly reveal, copy, edit, or clear it, and a key taken from the environment shows its source and can be overridden with a value saved in Kiki. Anyone holding the Kiki bearer token can reveal the stored key.

TIP

MCP server declarations are configured in ~/.kiki/mcp.json or the project-local .kiki/mcp.json, not in config.toml. The legacy .kimi-code/mcp.json path is not read. The interactive configuration entry point is the built-in kiki-ops skill (Kiki's product-usage and configuration Skill): type /kiki-ops help me configure MCP; see Model Context Protocol.

interaction ​

interaction.ask_user_question controls whether AskUserQuestion blocks the agent's current turn. The default, background, lets the agent request a background question and continue working; blocking makes every question wait for your answer, even if a tool call passes background = true. In blocking mode the tool description and input schema do not advertise the background option. If there is no connected interaction consumer, the question is dismissed instead of waiting indefinitely.

toml
[interaction]
ask_user_question = "blocking" # or "background" (default)

prompt ​

prompt overrides stable text fields without forking an agent profile. Field values replace the named text unit; they do not append, prepend, or wrap it.

FieldTypeDefaultDescription
variablesrecord<string, string>{}Named variables referenced as ${name} from an override field. Names must match [A-Za-z_][A-Za-z0-9_]*; built-in runtime names are reserved and cannot be redefined here
overridestable{}Global PromptOverrides, with optional files and fields

Useful built-in field ids include system.language, system.reply_style, system.coding, system.shared, tool.web-search.description, tool.web-search.guidance, delegation.sub.notice, and delegation.independent.notice. System fields replace sections of the built-in prompt. system.shared is the only shared outer addition and is appended once when non-empty. A tool description replaces its static description while preserving runtime-generated details; its guidance is appended under the existing User-configured guidance: label when non-empty.

${name} substitution is a single, literal pass with no recursion or script execution. A field may use its declared built-in variables and names from [prompt.variables]; an unknown name is rejected. Whole-prompt composition variables such as ${base_prompt} and ${parent_prompt} are not allowed in fields.

toml
[prompt.variables]
search_guidance = "Favor recent results unless the question asks for historical context."

[prompt.overrides]
files = ["prompt/team.toml"]

[prompt.overrides.fields]
"system.shared" = "When citing web sources, link the exact URL you opened."
"tool.web-search.guidance" = "Use ${search_guidance} when results span multiple years."

Prompt override format ​

Every override surface uses the same object:

text
files?: string[]
fields?: record<string, string>

Paths in files are relative to the Kiki home directory (~/.kiki by default). Absolute paths, .. traversal, paths that escape through symbolic links, missing files, malformed TOML, duplicate keys, and unknown field ids fail validation. Each external file is strict TOML with schema_version = 1 and one [fields] table; it cannot include other files:

toml
schema_version = 1

[fields]
"system.language" = "Reply in the user's language unless they request another language."
"tool.web-search.description" = "Search public web sources through Kiki's configured search runtime."

Within one surface, files are applied in listed order and inline fields apply last. Across surfaces, precedence from low to high is global [prompt.overrides], model [models."<alias>".prompt_overrides], agent or SYSTEM.md frontmatter prompt_overrides, then the matching model_profiles[].prompt_overrides. A missing key inherits the lower value; an empty string explicitly clears only fields that allow empty values.

toml
[models.fast-model.prompt_overrides]
files = ["prompt/fast-model.toml"]

[models.fast-model.prompt_overrides.fields]
"system.reply_style" = "Keep answers compact and action-oriented. ${reply_style_guide}"

For agent and SYSTEM.md frontmatter, use the equivalent YAML mapping:

yaml
prompt_overrides:
  files:
    - prompt/reviewer.toml
  fields:
    system.coding: Prefer minimal, verified patches.

The active profile, model, field registry, configuration, and loaded override files are frozen for each turn when its first model request is prepared. A valid watched-file update applies on the next turn, never halfway through the current turn. If a refresh fails, Kiki reports prompt-fields-refresh-failed and keeps the last valid snapshot rather than partially applying the broken update.

A whole-body SYSTEM.md or agent replacement, or a model cognition replace, shadows the corresponding system.* fields. A cognition anchor is sent byte-for-byte, so system and delegation fields are inactive while the anchor applies; tool fields remain effective. These statuses are diagnostic only and are never inserted into the prompt.

Removed

The old [prompt] shared and [prompt.tools] keys have been removed and now fail strict configuration parsing. Move shared to the "system.shared" key under [prompt.overrides.fields]; move each tool entry to tool.<kebab-case-name>.guidance (for example, WebSearch becomes tool.web-search.guidance).

In the desktop GUI, open Settings → Agents → Prompt fields to edit this section. The card is collapsed by default — expand it before editing.

[request_governance] ​

request_governance limits either native model requests (resource = "model_request") or running agents (resource = "agent_execution"). No rule means no concurrency cap; existing rules without resource remain model-request rules. In the GUI, Usage → Live shows running and queued requests and includes Concurrency limits to add, edit, pause, or delete rules. History, the default tab, shows token usage and estimated cost. See Usage for the diagnostic workflow.

A global rule shares capacity across sessions in the same Kiki service process. An each_session rule gives each session its own bucket. These limits do not span independent Kiki processes, set requests-per-minute, or impose token or spending budgets. For search and fetch calls, use nb_search.execution.

Model-request rules count provider generation attempts, including compaction and OAuth replay. External harness requests are unmanaged, not zero. Agent-execution rules cover both Kiki and external executors launched through Kiki, including main agents, subagents, and externally delegated independent agents. One agent holds one slot throughout its execution, including tool work, approval waits, cancellation cleanup, and finalization. Idle agents, background shell tasks, and child task receipts do not add agent slots. External model identities remain unknown when the harness has not supplied a binding; Kiki never substitutes its native default model.

For example, cap all models using one provider at two simultaneous requests across sessions. Replace example-provider with the exact key from [providers]:

toml
[request_governance]
schema_version = 1
max_wait_ms = 300000
max_queue_size = 1024

[[request_governance.rules]]
id = "shared-provider"
resource = "model_request"
scope = "global"
providers = ["example-provider"]
max_concurrent = 2
overflow = "queue"
enabled = true

To give subagents using one model a separate cap of one per session, add this rule instead, or alongside the provider rule. Replace example-model with the canonical [models] table key, not its display name, upstream model value, or an alternate entry in aliases:

toml
[[request_governance.rules]]
id = "session-model-children"
scope = "each_session"
models = ["example-model"]
subagents_only = true
max_concurrent = 1
max_wait_ms = 60000
overflow = "queue"

To limit one executor to two running subagents across sessions, use its registered executor ID:

toml
[[request_governance.rules]]
id = "executor-children"
resource = "agent_execution"
executors = ["example-executor"]
roles = ["subagent"]
max_concurrent = 2
overflow = "queue"
FieldTypeDefaultMeaning
schema_versioninteger1Configuration version; only 1 is accepted
max_wait_msinteger300000Positive local waiting budget in milliseconds; cumulative across model-request retries, or per agent admission
max_queue_sizeinteger1024Positive maximum number of pending admissions across both resources
rulesarray<table>[]Concurrency rules, written as [[request_governance.rules]]
Rule idstringRequiredNonempty, unique rule identifier
Rule resourcestringmodel_requestmodel_request or agent_execution; counts requests or agents, respectively
Rule scopestringglobalShared service capacity, or each_session capacity per session
Rule modelsarray<string>AllNative canonical model IDs or external bound model strings; selected IDs share one cap
Rule providersarray<string>AllExact native provider configuration IDs; external provider identity is unknown
Rule executorsarray<string>AllAgent rules: registered executor IDs; native means Kiki
Rule profilesarray<string>AllAgent rules: bound profile names
Rule rolesarray<string>AllAgent rules: main, subagent, or independent; select main and subagent explicitly to include both
Rule subagents_onlybooleanfalseMatch only subagent requests or executions; agent rules exclude independent agents
Rule max_concurrentintegerUnlimitedPositive cap; omit for no cap. Zero is invalid
Rule overflowstringqueuequeue waits for capacity; reject fails immediately when this rule is full
Rule max_wait_msintegerSection limitOptional positive budget; the shortest of the section and all matching enabled rules wins
Rule enabledbooleantruefalse pauses the rule without deleting it or applying its waiting budget

All matching enabled rules apply together. Different selectors in one rule are AND; IDs within one selector are OR. Omit a selector to match all; an empty list is invalid. Unknown identities do not match named selectors. To give each model or executor its own cap, write one rule per identity. Unknown fields and duplicate rule IDs are rejected.

Saving a rule in the GUI or editing the file re-evaluates pending admissions. Raising a cap or pausing a rule can release waiters; lowering a cap does not terminate active work, so it may temporarily exceed the new limit. Model-request slots release after stream cleanup, before tool work or retry backoff. Agent slots remain held throughout the execution. Waiting admissions hold no slot; Stop cancels the waiting execution without starting provider or harness work.

Queue errors and provider 429 ​

Use the error code to distinguish local waiting from provider throttling:

ErrorMeaning and action
request.limit_rejectedA matching full rule uses overflow = "reject". Wait for active requests or change that rule
request.queue_fullThe service-wide queue reached max_queue_size. Let it drain before retrying
request.queue_timeoutThe request or agent admission exhausted its local wait budget. Retry after capacity frees up, or adjust the cap or waiting budget
request.agent_ancestor_limitA running parent occupies the matching agent limit. Select subagents only, separate main and subagent rules, or increase the reported rule limit; waiting for the parent would risk a deadlock
provider.rate_limit / HTTP 429The provider throttled a request that was sent. Check its message and account limits; a concurrency cap can reduce overlap but cannot guarantee a requests-per-minute or token rate

Local request.* errors do not automatically retry. Provider transient 429 retries follow retry, including Retry-After; exhausted quota or insufficient balance fails without retry. In Usage → Live, expand Request details to inspect the queued model, blocking rule IDs, and wait time before changing a limit. Stale counts show the last received snapshot, not current capacity.

tui.toml ​

Alongside config.toml, the CLI keeps terminal-UI and client preferences in a companion tui.toml in the same directory (~/.kiki/tui.toml, or $KIKI_HOME/tui.toml when overridden). It is created with defaults on first run, and the interactive commands /config, /theme, and /editor write to it for you — so you rarely need to edit it by hand. If the file is malformed, the CLI falls back to defaults and shows a notice instead of failing to start.

FieldTypeDefaultDescription
themestringautoColor theme: auto (follow the terminal), dark, light, or the name of a custom theme
render_latexbooleantrueRender LaTeX math expressions ($…$, $$…$$) in Markdown messages as Unicode text; false keeps the raw source
disable_paste_burstbooleanfalseDisable the non-bracketed paste-burst fallback that keeps rapid multi-line pastes from submitting line by line
cache_expiry_hintbooleantrueShow a dialog when resuming a long-idle session or submitting after a long idle stretch, warning that the context cache has likely expired and offering to compact or start a new session
[editor].commandstring""External editor command for composing long input; empty falls back to $VISUAL / $EDITOR
[notifications].enabledbooleantrueWhether desktop notifications are sent
[notifications].notification_conditionstringunfocusedWhen to notify: unfocused (only when the terminal is not focused) or always
[status_line].itemsstring[][]Built-in slots to show on the first footer line and their order: mode, goal, model, tasks, cwd, git, tips. Unset keeps the default layout; unknown ids are skipped with a warning
[status_line].commandstring""Custom status line command. Its first stdout line replaces the first footer line, with a JSON snapshot (model, cwd, git branch, permission mode, plan mode, context usage, session id, version) passed on stdin. Runs are capped at 300ms and throttled to once per second; failures fall back to the built-in layout
toml
# ~/.kiki/tui.toml
theme = "auto" # "auto" | "dark" | "light" | custom theme name
render_latex = true # false keeps LaTeX math in messages as raw source
disable_paste_burst = false # true disables non-bracketed paste-burst fallback
cache_expiry_hint = true # false disables the "cache expired" dialog on resume / idle submit

[editor]
command = "" # empty uses $VISUAL / $EDITOR

[notifications]
enabled = true
notification_condition = "unfocused" # "unfocused" | "always"

# [status_line]
# items = ["mode", "goal", "model", "tasks", "cwd", "git", "tips"]
# command = "~/.kiki/statusline.sh"

Changes apply on the next start, or immediately with /reload-tui (which reloads only tui.toml); /reload reloads both config.toml and tui.toml.

Project-local configuration ​

In addition to the user-level files under ~/.kiki, Kiki reads a project-local configuration file at <project-root>/.kiki/local.toml. It holds settings that are specific to one project checkout and typically should not be shared with teammates. The legacy .kimi-code/local.toml path is not read.

The file is created automatically when you add an extra workspace directory with /add-dir and choose to remember it for the project. You rarely need to edit it by hand. The directories recorded here only load when the workspace is trusted: an untrusted checkout does not read additional_dir at startup, and persisting a new directory requires trusting the workspace first.

[workspace] ​

The [workspace] table groups project-level workspace settings:

FieldTypeRequiredDescription
additional_dirarray<string>NoAdditional workspace directories, stored as absolute paths. Written automatically when you confirm "remember this directory" in /add-dir; read back on startup so the directories are available in every session of this project. Only loaded for a trusted workspace
toml
[workspace]
additional_dir = ["/absolute/path/to/shared"]

Because directories are stored as absolute paths, which are specific to your machine, we recommend adding .kiki/local.toml to your project's .gitignore so it is not committed.

Next steps ​