Skip to main content
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]
engine = "sqlite"        # "sqlite" (default) or "grep"
async_index = true
index_window_ms = 200
index_batch_max = 50
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
FieldDefaultDescription
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.
ProviderRequired fields
openaiapi_key, model
ollamabase_url, model
cohereapi_key, model
voyageapi_key, model
httpurl (custom endpoint)
bedrockregion, model
vertexproject, location, model
ProviderNotes
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

[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"]
FieldDefaultDescription
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

[audit]
enabled = true
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

[schema]
enforce = false
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"
FieldDefaultDescription
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"
ValueBehavior
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.
Last modified on May 31, 2026