KiwiFS is configured via .kiwi/config.toml inside your knowledge root directory. All settings are optional — KiwiFS runs with sensible defaults out of the box.
Environment variables in the form ${VAR} are expanded in all string values.
Server
[ server ]
host = "0.0.0.0"
port = 3333
cors_origins = [ "https://app.example.com" ]
public_url = "https://wiki.example.com"
[ server . cors ]
allowed_origins = [ "https://app.example.com" ]
allowed_methods = [ "GET" , "POST" , "PUT" , "PATCH" , "DELETE" ]
allow_credentials = true
max_age = 86400
The public_url is used for permalink generation. Override via KIWI_PUBLIC_URL env var.
Use cors_origins for a simple allowlist, or [server.cors] for fine-grained CORS control (methods, credentials, preflight cache).
Rate limiting
[ server . rate_limit ]
requests_per_minute = 600
burst_size = 50
When requests_per_minute is greater than zero, KiwiFS applies a token-bucket limiter keyed by Bearer token hash (or client IP when anonymous). Health, readiness, and metrics routes are exempt.
Search
[ search ]
engine = "sqlite" # "sqlite" (default) or "grep"
async_index = true
index_window_ms = 200
index_batch_max = 50
Vector search
Enable semantic search by configuring an embedder and a vector store.
[ search . vector ]
enabled = true
worker_count = 5 # default; lower for small local embedders
[ search . vector . embedder ]
provider = "openai" # openai | ollama | cohere | voyage | http | bedrock | vertex
model = "text-embedding-3-small"
api_key = "${OPENAI_API_KEY}"
dimensions = 1536
# timeout = "120s" # optional for Ollama; default is 30s
[ search . vector . store ]
provider = "sqlite-vec" # sqlite-vec | qdrant | pgvector | pinecone | weaviate
Chunking
Control how documents are split before embedding:
[ search . vector . chunk ]
size = 512
overlap = 64
Field Default Description size512Target tokens per chunk overlap64Token overlap between consecutive chunks
For local Ollama on a small CPU-only VPS, lower worker_count and raise the Ollama request timeout. See Ollama vector search on a small VPS .
Provider Required fields openaiapi_key, modelollamabase_url, modelcohereapi_key, modelvoyageapi_key, modelhttpurl (custom endpoint)bedrockregion, modelvertexproject, location, model
Provider Notes sqlite-vecZero-config, embedded (default when vector is enabled) qdrantRequires separate Qdrant server pgvectorRequires PostgreSQL with pgvector extension pineconeCloud-hosted weaviateSelf-hosted or cloud
Versioning
[ versioning ]
strategy = "git" # "git" (default), "cow", or "none"
async_commit = true
batch_window_ms = 200
batch_max_size = 50
max_versions = 100 # only for "cow" strategy
git — every write is a real git commit. Full history, blame, diff.
cow — copy-on-write snapshots without git. Lighter, limited history.
none — no versioning. Writes overwrite in place.
Authentication
[ auth ]
type = "none" # "none", "apikey", "perspace", or "oidc"
api_key = "kiwi_sk_..." # for type = "apikey"
# Per-space keys
[[ auth . api_keys ]]
key = "kiwi_sk_eng_..."
space = "engineering"
actor = "eng-team"
# OIDC
[ auth . oidc ]
issuer = "https://accounts.google.com"
client_id = "your-client-id"
Auth is hot-reloadable — send SIGHUP to the server process to reload auth configuration from disk without restarting.
Assets
[ assets ]
max_file_size = "10MB"
allowed_types = [ "image/png" , "image/jpeg" , "image/gif" , "image/webp" , "application/pdf" ]
[ ui ]
theme_locked = false # when true, users cannot change the theme
Backup
[ backup ]
remote = "git@github.com:org/knowledge.git"
interval = "5m"
branch = "main"
rebase_before_push = true # default: true
KiwiFS fetches and rebases onto the backup branch before pushing by default. This avoids routine non-fast-forward failures when another client pushed to the same backup branch first.
Override via env: KIWI_BACKUP_REMOTE, KIWI_BACKUP_INTERVAL, KIWI_BACKUP_REBASE_BEFORE_PUSH.
See Backup sync for the full workflow, opt-outs, and troubleshooting.
Janitor
The janitor periodically scans for stale pages, orphans, and broken links.
[ janitor ]
interval = "1h"
stale_days = 30
startup_scan = true
DataView
[ dataview ]
max_scan_rows = 10000
query_timeout = "5s"
max_auto_indexes = 20
computed_fields = true
[ dataview . custom_fields ]
age_days = "days_since(updated)"
is_long = "len(body) > 5000"
Memory
Episodic vs semantic conventions for agent memory. See Episodic memory .
[ memory ]
episodes_path_prefix = "episodes/"
Drafts
[ drafts ]
enabled = true
max_active = 10
auto_discard = "720h"
auto_discard is a duration string (for example "720h" for 30 days). Draft branches older than this threshold are discarded automatically.
Workflow
[ workflow ]
enforce_transitions = false
transitions = [ "open->in_progress" , "in_progress->done" , "in_progress->blocked" , "blocked->in_progress" ]
Field Default Description enforce_transitionsfalseWhen true, POST /workflow/advance rejects transitions not listed in the workflow definition transitions[]Global default transitions (workflow definitions can override per-workflow)
Audit
Requires [audit] enabled = true for GET /api/kiwi/audit. See Optional features .
Webhooks
[ webhooks ]
enabled = true
max_workers = 4
max_retries = 3
See Webhooks .
Register static webhooks at startup with [[webhook_entries]]:
[[ webhook_entries ]]
url = "https://hooks.example.com/kiwifs"
events = [ "file.created" , "file.updated" , "file.deleted" ]
secret = "${WEBHOOK_SECRET}"
path_glob = "concepts/**"
Lint
[ lint ]
auto_format = true
reject_errors = true
When auto_format is true (default), KiwiFS normalizes markdown on write (table alignment, trailing whitespace). When reject_errors is true (default), writes that fail lint checks with error severity are rejected.
Schema enforcement
When true, writes are validated against .kiwi/schemas/. See Schemas .
Import (Airbyte)
[ import ]
airbyte_api_key = "${AIRBYTE_API_KEY}"
airbyte_workspace_id = "${AIRBYTE_WORKSPACE_ID}"
docker_host = "unix:///var/run/docker.sock"
prefer_airbyte = true
image_prefix = "airbyte/"
pull_policy = "if-not-present"
Field Default Description docker_host$DOCKER_HOSTDocker socket for local Airbyte connectors prefer_airbytetrueRoute network sources through Airbyte when available image_prefixairbyte/Prefix for connector Docker images pull_policyif-not-presentImage pull policy: always, if-not-present, or never
See Airbyte import and Import connections .
Tracing
Query tracing is enabled by default and writes structured JSON records of what KiwiFS did during each request.
[ tracing ]
enabled = true # default true; set false to suppress
output = "stderr" # "stderr" (default) or "file"
file = "/var/log/kiwifs-trace.jsonl"
Space visibility
Control whether a space is accessible without authentication.
[ space ]
visibility = "private" # "private" (default), "unlisted", or "public"
Value Behavior privateRequires authentication for all access unlistedAccessible to anyone with the URL, not listed publicly publicBrowseable and searchable without authentication
Visibility can also be changed at runtime via PUT /api/kiwi/space/visibility.
Multi-space
Run multiple independent knowledge bases from one server.
[[ spaces ]]
name = "engineering"
root = "/data/eng-wiki"
visibility = "private"
[[ spaces ]]
name = "product"
root = "/data/product-wiki"
visibility = "unlisted"
Each space gets its own API under /api/kiwi/{name}/... or via the X-Kiwi-Space header. Administrators use /api/spaces to create and list spaces.
You can also add spaces via CLI flags: kiwifs serve --space eng=/data/eng --space product=/data/product.
See Multi-space and Spaces API .