> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kiwifs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Configure KiwiFS via .kiwi/config.toml — search, auth, versioning, vector embeddings, and more.

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

```toml theme={null}
[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

```toml theme={null}
[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

```toml theme={null}
[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.

```toml theme={null}
[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:

```toml theme={null}
[search.vector.chunk]
size = 512
overlap = 64
```

| Field     | Default | Description                              |
| --------- | ------- | ---------------------------------------- |
| `size`    | `512`   | Target tokens per chunk                  |
| `overlap` | `64`    | Token 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](/deploy/ollama-vector-search).

<AccordionGroup>
  <Accordion title="Embedder providers">
    | Provider  | Required fields                |
    | --------- | ------------------------------ |
    | `openai`  | `api_key`, `model`             |
    | `ollama`  | `base_url`, `model`            |
    | `cohere`  | `api_key`, `model`             |
    | `voyage`  | `api_key`, `model`             |
    | `http`    | `url` (custom endpoint)        |
    | `bedrock` | `region`, `model`              |
    | `vertex`  | `project`, `location`, `model` |
  </Accordion>

  <Accordion title="Vector store providers">
    | Provider     | Notes                                                  |
    | ------------ | ------------------------------------------------------ |
    | `sqlite-vec` | Zero-config, embedded (default when vector is enabled) |
    | `qdrant`     | Requires separate Qdrant server                        |
    | `pgvector`   | Requires PostgreSQL with pgvector extension            |
    | `pinecone`   | Cloud-hosted                                           |
    | `weaviate`   | Self-hosted or cloud                                   |
  </Accordion>
</AccordionGroup>

## Versioning

```toml theme={null}
[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

```toml theme={null}
[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

```toml theme={null}
[assets]
max_file_size = "10MB"
allowed_types = ["image/png", "image/jpeg", "image/gif", "image/webp", "application/pdf"]
```

## UI

```toml theme={null}
[ui]
theme_locked = false      # when true, users cannot change the theme
```

## Backup

```toml theme={null}
[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](/deploy/backup-sync) for the full workflow, opt-outs, and troubleshooting.

## Janitor

The janitor periodically scans for stale pages, orphans, and broken links.

```toml theme={null}
[janitor]
interval = "1h"
stale_days = 30
startup_scan = true
```

## DataView

```toml theme={null}
[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](/concepts/episodic-memory).

```toml theme={null}
[memory]
episodes_path_prefix = "episodes/"
```

## Drafts

```toml theme={null}
[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

```toml theme={null}
[workflow]
enforce_transitions = false
transitions = ["open->in_progress", "in_progress->done", "in_progress->blocked", "blocked->in_progress"]
```

| Field                 | Default | Description                                                                                     |
| --------------------- | ------- | ----------------------------------------------------------------------------------------------- |
| `enforce_transitions` | `false` | When `true`, `POST /workflow/advance` rejects transitions not listed in the workflow definition |
| `transitions`         | `[]`    | Global default transitions (workflow definitions can override per-workflow)                     |

## Audit

```toml theme={null}
[audit]
enabled = true
```

Requires `[audit] enabled = true` for `GET /api/kiwi/audit`. See [Optional features](/concepts/optional-features).

## Webhooks

```toml theme={null}
[webhooks]
enabled = true
max_workers = 4
max_retries = 3
```

See [Webhooks](/concepts/webhooks).

Register static webhooks at startup with `[[webhook_entries]]`:

```toml theme={null}
[[webhook_entries]]
url = "https://hooks.example.com/kiwifs"
events = ["file.created", "file.updated", "file.deleted"]
secret = "${WEBHOOK_SECRET}"
path_glob = "concepts/**"
```

## Lint

```toml theme={null}
[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

```toml theme={null}
[schema]
enforce = false
```

When `true`, writes are validated against `.kiwi/schemas/`. See [Schemas](/concepts/schemas).

## Import (Airbyte)

```toml theme={null}
[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_HOST`   | Docker socket for local Airbyte connectors                |
| `prefer_airbyte` | `true`           | Route network sources through Airbyte when available      |
| `image_prefix`   | `airbyte/`       | Prefix for connector Docker images                        |
| `pull_policy`    | `if-not-present` | Image pull policy: `always`, `if-not-present`, or `never` |

See [Airbyte import](/import/airbyte) and [Import connections](/import/connections).

## Tracing

Query tracing is enabled by default and writes structured JSON records of what KiwiFS did during each request.

```toml theme={null}
[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.

```toml theme={null}
[space]
visibility = "private"   # "private" (default), "unlisted", or "public"
```

| Value      | Behavior                                               |
| ---------- | ------------------------------------------------------ |
| `private`  | Requires authentication for all access                 |
| `unlisted` | Accessible to anyone with the URL, not listed publicly |
| `public`   | Browseable 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.

```toml theme={null}
[[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.

<Tip>
  You can also add spaces via CLI flags: `kiwifs serve --space eng=/data/eng --space product=/data/product`.
</Tip>

See [Multi-space](/concepts/multi-space) and [Spaces API](/api/spaces).
