# Configuration (https://openknowledge.ai/docs/reference/configuration)

The .ok/config.yml schema and environment variables.

OpenKnowledge reads YAML config from three places:

- **Project**: `./.ok/config.yml` (this project, committed to git)
- **User**: `~/.ok/global.yml` (every project on your machine)
- **Project-local**: `./.ok/local/config.yml` (this project on this machine; gitignored). Holds per-machine-per-project preferences such as `autoSync.mode`. Maintained by the editor (auto-sync onboarding modal, sync popover, Settings pane Sync section); you don't normally hand-edit it.

All files are optional; defaults cover everything.

You can inspect and edit config three ways:

- **Settings pane** in the editor: Cmd/Ctrl-, or App menu → Settings…
- **Your IDE**: `.ok/config.yml` ships with a `$schema` magic comment for autocomplete and inline field descriptions
- **CLI**: `ok config validate` checks the merged result

Edits made anywhere reflect everywhere; an open Settings pane refreshes live.

## Schema

The **Scope** column says which file a setting belongs in:

- **project** → `.ok/config.yml` (shared in git)
- **user** → `~/.ok/global.yml` (personal, all projects)
- **project-local** → `.ok/local/config.yml` (this machine only, gitignored)

A key set in a file more specific than its scope (a user-scope key in `.ok/config.yml`, for example) is ignored.

| Field                                    | Type                                                                                                                    | Default                                                   | Scope         | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `content.dir`                            | string                                                                                                                  | `"."`                                                     | project       | Content directory, relative to project root. Defaults to `.` (the project root) even when `.ok/` is scaffolded at the git working-tree root from a sub-folder, so opened folder and content scope align by default. To narrow scope to a sub-folder, pass `--content-dir <dir>` to `ok init`, or uncomment `content.dir` in `.ok/config.yml` post-init. Excluded paths live in `.okignore`; see [Ignore patterns](https://openknowledge.ai/docs/features/ignore-patterns).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `content.attachmentFolderPath`           | string                                                                                                                  | `"./"`                                                    | project       | Where pasted and dropped assets are stored. `"./"` colocates beside the current document; `"/"` targets the content root; `"./subdir"` targets a subfolder under the current folder; `"folder"` targets a fixed folder under the content root. A configured folder also widens what counts as content: linkable files inside it (images, PDFs, media) are treated as project content without needing a Markdown document beside them, so in shared projects they auto-sync to teammates. The default `"./"` keeps the historical rule (assets sync only beside a document).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `contentRules.markdownlint.enabled`      | boolean                                                                                                                 | `false`                                                   | project       | Enable the [markdownlint](https://openknowledge.ai/docs/advanced/content-rules/markdownlint) content-rules plugin for this project (shared via git). Off by default — turn it on in **Settings → This project → Plugins**. The rules themselves live in your native `.markdownlint.*` file, not here.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `contentRules.frontmatter.enabled`       | boolean                                                                                                                 | `false`                                                   | project       | Enable the [Frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter) content-rules plugin for this project (shared via git). Off by default — turn it on in **Settings → This project → Plugins**.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `contentRules.frontmatter.schemas`       | array                                                                                                                   | `[]`                                                      | project       | Frontmatter schema mappings: each entry scopes one JSON Schema file to a set of docs — `file` (project-root-relative path), optional `appliesTo` (glob or list; leading `!` excludes; absent means every doc), optional `enabled: false` to park a mapping. Schema content lives in the mapped files, not here. See [Frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `validation.links`                       | `"off" \| "warning" \| "error"`                                                                                         | `"warning"`                                               | project       | How broken internal links are reported on the validation plane: `off` hides them, `warning` (default) or `error` sets their severity. Shared via git, like `contentRules`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `validation.fileTreeIndicators`          | boolean                                                                                                                 | `true`                                                    | project       | Whether the file sidebar tints and badges files that have validation problems. The badge is also the row's entry point into the Problems panel, so turning this off removes both the indicator and that shortcut (see [Where problems show up](https://openknowledge.ai/docs/advanced/content-rules/overview#where-problems-show-up)).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `appearance.theme`                       | `"light" \| "dark" \| "system"`                                                                                         | (unset)                                                   | user          | Editor light/dark theme.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `appearance.language`                    | `"system" \| "en" \| "zh-Hans" \| "zh-Hant" \| "hi" \| "es" \| "ar" \| "fr" \| "bn" \| "pt-BR" \| "id" \| "ur" \| "ko"` | (unset)                                                   | user          | Interface language. `system` (the effective value when unset) follows your operating system or browser, and keeps following it. A personal preference, not project-shared — and it applies to the app's own chrome only: your documents, titles, file and folder names, frontmatter values and tags stay in the language you wrote them in. **Settings → Preferences → Language** offers ten of them (English, 简体中文, 繁體中文, हिन्दी, Español, Français, বাংলা, Português do Brasil, Indonesia, 한국어). Every catalog is complete, but only `es` has been through a Spanish reader; the rest, `zh-Hans` included, are machine-translated and nobody who reads them has checked the wording. Treat it as provisional and [tell us what is wrong](https://openknowledge.ai/docs/contribute/translations). `ar` and `ur` are complete translations too, but they stay out of the picker until the interface lays out right to left; set either here by hand to run the app in it, and note the app will not select them from your OS language alone. `OK_LANG=<tag>` overrides the desktop app's native menus for a single run; the rest of the interface follows this field. |
| `appearance.colorThemeLight`             | theme id (`[a-z0-9-]`, 1–32 characters)                                                                                 | (unset)                                                   | user          | IDE color palette applied while the resolved mode is light. Built-in ids include `default`, `dracula`, `catppuccin-frappe`, `catppuccin-latte`, `monokai`, `gruvbox`, `solarized`, and `custom`; saved themes use their generated `saved-*` id. `default` (the effective value when unset or when no available palette matches an explicitly configured id) means no palette — the base stylesheet shows through. `custom` applies `appearance.customTheme`. Pick it in **Settings → Plugins → [Themes](https://openknowledge.ai/docs/plugins/themes)** with the tile's sun.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `appearance.colorThemeDark`              | theme id (`[a-z0-9-]`, 1–32 characters)                                                                                 | (unset)                                                   | user          | IDE color palette applied while the resolved mode is dark — the tile's moon. Built-in and saved-theme ids follow the same constrained string grammar. `appearance.theme` chooses between the two; on `system` your OS does, so the palette follows the OS appearance. Any available palette may sit in either slot, and it keeps forcing its own light/dark variant. An explicitly configured id that is not available falls back to `default`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `appearance.colorTheme`                  | theme id (`[a-z0-9-]`, 1–32 characters)                                                                                 | (unset)                                                   | user          | Legacy single-palette field, superseded by `colorThemeLight` / `colorThemeDark` above. A valid available id supplies the palette only for a slot that is truly unset; an explicit unknown per-mode id falls back to `default` instead of reviving this legacy value. Writing either slot from **Settings → Plugins → Themes** removes this key.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `appearance.colorThemeEnabled`           | boolean                                                                                                                 | `true`                                                    | user          | Whether the Themes plugin appears in **Settings → Plugins**. On by default (absent = enabled).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `appearance.customTheme`                 | object                                                                                                                  | (unset)                                                   | user          | The `custom` palette's [base16](https://github.com/tinted-theming/schemes) scheme: sixteen `base00`–`base0F` `#rrggbb` slots, plus optional `name`, `author` (a credit line carried through from an imported scheme), and `variant` (`"dark" \| "light"`, auto-detected from the tonal ramp when absent). A config written before base16 carried six semantic seed colors (`background`/`surface`/`foreground`/`primary`/`accent`/`border`) instead; that shape still parses and is upgraded to the sixteen slots automatically on read. Edit in **Settings → Plugins → Themes**.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `appearance.sidebar.showHiddenFiles`     | boolean                                                                                                                 | `false`                                                   | project-local | Show files whose path segments start with `.`. The sidebar otherwise lists every file on disk under the content directory; tooling internals (`.git/`, `.ok/`, `node_modules/`) stay hidden regardless. Toggled from the sidebar's right-click menu or the **View → Show Hidden Files** menu item.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `appearance.sidebar.showSkillsSection`   | boolean                                                                                                                 | `true`                                                    | project-local | Show the **Skills Studio** dock at the bottom of the file sidebar. Toggled from the sidebar's tree view options or the **Skills section** palette command. See [Skills](https://openknowledge.ai/docs/features/skills).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `appearance.sidebar.showSkillGroups`     | boolean                                                                                                                 | `true`                                                    | project-local | Group skills in the dock by where they came from: the publisher they were imported from, or the plugin that ships them. Skills you authored stay ungrouped at the top of their scope. Toggled with **Group skills by source** in the tree view options.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `appearance.sidebar.pinnedProjectSkills` | string\[]                                                                                                               | `[]`                                                      | project-local | Project-scope skills pinned to the top of the dock, by name. A pinned skill keeps its normal row too, so a source group still lists everything from that source.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `appearance.sidebar.pinnedGlobalSkills`  | string\[]                                                                                                               | `[]`                                                      | user          | Global-scope skills pinned to the top of the dock, by name. Stored per user rather than per project, so a pinned global skill follows you into every project.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `autoSync.mode`                          | `"off" \| "follow" \| "full" \| null`                                                                                   | `null`                                                    | project-local | Per-machine sync mode for this project: `off` (shown as **Manual** — nothing scheduled; the manual pull/push actions still work), `follow` (shown as **Auto (Pull only)** — pull remote changes on a schedule, never push; the earlier value `pull` is accepted as an alias), `full` (shown as **Auto (Pull and Push)** — bidirectional). `null` means "unanswered"; the editor's onboarding modal triggers on first remote-detected open. Supersedes `autoSync.enabled`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `autoSync.pullIntervalSeconds`           | `number`                                                                                                                | `30`                                                      | project-local | Seconds between scheduled pulls while `autoSync.mode` is `follow` or `full` (30–3600). Shown in **Settings → Sync** as **Check for updates every**. An unauthenticated follower is additionally floored to a 3-minute anonymous poll minimum, so a shorter value has no effect while signed out.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `autoSync.pushIntervalSeconds`           | `number`                                                                                                                | `60`                                                      | project-local | Seconds between scheduled pushes while `autoSync.mode` is `full` (30–3600). Shown in **Settings → Sync** as **Push my edits every**. Ignored in every other mode, which never pushes on a schedule. Each cycle with pending edits authors a commit, so a shorter interval means more, smaller commits in shared history.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `autoSync.enabled`                       | `boolean \| null`                                                                                                       | `null`                                                    | project-local | Legacy per-machine auto-sync toggle, superseded by `autoSync.mode`. Read only when `mode` is absent (`true` = full, `false` = off). `null` means "unanswered".                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `autoSync.default`                       | `"off" \| "follow" \| "full" \| boolean \| null`                                                                        | `null`                                                    | project       | Committed seed for each machine's first-open sync mode: `off` / `follow` / `full`, or the legacy boolean (`true` = full, `false` = off). `null` = ask. Lets a maintainer pre-answer the onboarding prompt for everyone who clones; a per-machine `autoSync.mode` overrides it. Compatibility note: a committed string value (`off` / `follow` / `full`) is rejected by app versions released before `autoSync.mode` existed, resetting that machine to config defaults until it updates (it never silently syncs). The legacy boolean seed (`true` / `false`) stays readable by older apps — commit `follow` only once collaborators are on a current version.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `server.bind`                            | list of addresses                                                                                                       | `["127.0.0.1"]`                                           | project-local | Addresses the server binds, e.g. `[127.0.0.1]` or `[0.0.0.0]`. The loopback default keeps everything off-machine out. A non-loopback bind additionally requires the `server.allowExternal` consent interlock. Per-machine — a value committed to `.ok/config.yml` is ignored (the exposing host would otherwise refuse to boot for every teammate who clones and runs locally, since the interlock needs per-machine consent that is never committed). Set it where the server is exposed: `OK_BIND` (space-separated), `ok start --bind <address>`, or `.ok/local/config.yml`. Lists replace, never merge. Read at server start.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `server.port`                            | number                                                                                                                  | (unset)                                                   | project       | TCP port the server listens on. Unset = a local start picks a free port dynamically (deployment platforms inject `PORT` instead). Read at server start.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `server.externalUrl`                     | string                                                                                                                  | (unset)                                                   | project       | Canonical external origin the server is reached at, e.g. `https://kb.example.com` — its host joins the Host/Origin allowlists and issued URLs. Setting it declares external exposure, which additionally requires the `server.allowExternal` consent interlock. Env: `OK_EXTERNAL_URL`; flag: `ok start --external-url <url>`. Read at server start.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `server.allowExternal`                   | boolean                                                                                                                 | `false`                                                   | project-local | Exposure consent interlock: a non-loopback `server.bind` (or external requests behind a proxy declared via `server.externalUrl`) is refused without it. Per-machine — consent never travels via git, clone, or share; containers consent via `OK_ALLOW_EXTERNAL=1` instead. There is no server-side auth: restrict who can reach the server at the edge.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `server.openBrowser`                     | boolean                                                                                                                 | derived: `true` loopback-only, `false` otherwise          | project-local | Open the editor in a browser once at start. The derived default means a laptop start pops the UI and an exposed or containerized server stays headless. Env: `OK_OPEN_BROWSER`; flag: `--no-open-browser`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `server.idleShutdown`                    | `"off"` or duration (`90s`, `30m`, `2h`)                                                                                | derived: `30m` loopback-only & unexposed, `off` otherwise | project-local | Shut the server down after this long with no connected editor (WebSocket) clients. Remote MCP sessions are plain HTTP the timer cannot see, so an exposed deployment (`server.allowExternal` + `server.externalUrl`) derives `off` automatically. Set `off` manually (or `OK_IDLE_SHUTDOWN=off`) only in the residual case the derivation misses — a loopback server reached by remote agents with no `externalUrl` (e.g. behind a same-box reverse proxy) — or it tears down under them mid-session.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `editor.wordWrap`                        | boolean                                                                                                                 | `true`                                                    | user          | Soft-wrap long lines in the source-mode CodeMirror editor. A personal preference, not project-shared. Toggle from the Settings pane.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `editor.previewTabs`                     | boolean                                                                                                                 | `true`                                                    | user          | Reuse one replaceable preview tab for single clicks in the Files tree and Skills sidebar, so browsing does not accumulate tabs. Double-click the tab title, pin it, or choose **Keep open** from its context menu to make it permanent. Set `false` to open every click in its own tab. Rename transitions still reuse the current tab; back/forward navigation focuses the target's existing tab, or opens a permanent one when it is no longer open. A personal preference; toggle it from **Settings → Preferences → Preview tabs**.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `appearance.preview.autoOpen`            | boolean                                                                                                                 | `true`                                                    | user          | Whether the agent should open or refresh the OpenKnowledge preview when it edits a doc through the MCP. Default `true` lets the agent route the preview by host capability: the host's in-app browser (Cursor preview pane, Codex's built-in browser, Claude Code Desktop) when one exists, the system browser otherwise. Set `false` to keep the agent's hands off your preview window. This is useful when you're already viewing the doc in OK Desktop, a browser tab on a second display, a non-default browser, or any flow where your extensions / accessibility tooling only work in your own browser. The agent then surfaces the URL on request but does not navigate. The change takes effect on the next preview-related tool call. Toggle from **Settings → Preferences → "Open preview when agent edits"**.                                                                                                                                                                                                                                                                                                                                              |
| `terminal.enabled`                       | `boolean \| null`                                                                                                       | `null`                                                    | project-local | Opt-out for the in-app terminal (a real OS shell at full user privilege). On by default; set `false` to disable it for this project on this machine.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `terminal.shell`                         | string                                                                                                                  | (unset)                                                   | project-local | Absolute Windows shell executable override, for example `C:\Program Files\PowerShell\7\pwsh.exe`. PowerShell, `cmd.exe`, and Git Bash (`bash.exe`) support OpenKnowledge-managed launches (agent chat tabs and fixed commands such as **Resolve in terminal** or **Install Slidev**) and dropped-file path insertion. A different existing executable remains active for plain terminal tabs and shows a capability notice; requested agent and command launches open only the plain shell without running the agent or command, and dropped-file paths are refused. Use `wsl` inside a supported shell rather than setting `wsl.exe` here. Agent-launch prompts are pasted without being submitted on Windows, so review the prompt and press Enter to send it. Empty or unset values use automatic discovery (PowerShell 7, Windows PowerShell, then `ComSpec` / `cmd.exe`). Unreadable, invalid, relative, or missing paths show a notice and fall back to discovery. Human-managed and never shared through git, clone, or sync.                                                                                                                                  |
| `slides.enabled`                         | boolean                                                                                                                 | `false`                                                   | user          | Whether the [Slidev plugin](https://openknowledge.ai/docs/plugins/slidev) appears in **Settings → Plugins**. Off by default. When on, a document whose frontmatter contains `slides: true` gets an **Open in Slidev** action that opens the deck in its own window, rendered by a [Slidev](https://sli.dev/) you install yourself — OpenKnowledge does not download or bundle one. Install it globally (`npm install -g @slidev/cli @slidev/theme-default`) or add those packages to the project (a project-local install takes precedence). Slidev themes ship separately from the CLI and a deck cannot open without the theme it declares. Desktop only; the action stays hidden until a `slidev` binary resolves.                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `agents.autoApproveOkTools`              | boolean                                                                                                                 | `true`                                                    | user          | Auto-approve OpenKnowledge's own MCP tools (and the `ok open` command on Claude) for agents launched from the built-in [docked terminal](https://openknowledge.ai/docs/features/editor#terminal), so reading and writing the knowledge base runs without a per-call approval prompt. Destructive tools (`delete`, `move`, `share_link`, `install`, `import`) still prompt; other shell commands and non-OpenKnowledge file edits are untouched. Per-machine preference; toggle from **Settings → Terminal**. Best-effort per agent — Claude uses an allow/ask list, Codex uses its per-server `approve` mode (and only when its OpenKnowledge entry is already configured).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `telemetry.localSink.enabled`            | boolean                                                                                                                 | `true`                                                    | project       | Write local diagnostic spans + logs under `.ok/local/` for `ok diagnose bundle` to collect. Local-only: nothing leaves the machine until you run `bundle`. Set `false` for sensitive workspaces.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `telemetry.localSink.spans.maxBytes`     | number                                                                                                                  | `52428800` (\~50 MB)                                      | project       | Max size of the local diagnostic spans file before it rotates.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `telemetry.localSink.logs.maxBytes`      | number                                                                                                                  | `26214400` (\~25 MB)                                      | project       | Max size of the local diagnostic logs file before it rotates.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `telemetry.localSink.attributeDenylist`  | `string[]`                                                                                                              | 8 credential keys                                         | project       | Attribute keys whose values are redacted (`[REDACTED]`) before any local span/log is written. Extends the built-in denylist (`authorization`, `password`, `cookie`, etc.).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `search.semantic.enabled`                | boolean                                                                                                                 | `false`                                                   | project-local | Add embeddings-based semantic ranking to the MCP `search` tool and a **By meaning** mode to the cmd-K omnibar. Default off. **When on and a key is set, the search query and matching page content are sent to the configured embeddings provider** (content egress). See [Semantic search](https://openknowledge.ai/docs/reference/configuration#semantic-search) below.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `search.semantic.baseUrl`                | string                                                                                                                  | `"https://api.openai.com/v1"`                             | project-local | Base URL of the OpenAI-compatible embeddings API. Override to point at a self-hosted server (Ollama / vLLM / LM Studio) or another provider. The API key is **not** stored here — set it with `ok embeddings set-key`; it is sent to whichever endpoint this names.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `search.semantic.model`                  | string                                                                                                                  | `"text-embedding-3-small"`                                | project-local | Embeddings model id. Must be served by the provider at `baseUrl`. Changing it re-embeds the corpus.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `search.semantic.dimensions`             | number                                                                                                                  | (auto)                                                    | project-local | Optional output vector size. Omit (recommended) and the size is detected from the endpoint's first response and reused across restarts — that is what lets a non-OpenAI model work without knowing its size up front. Set a smaller value to shrink the on-disk cache, trading a little quality; an endpoint that ignores the request parameter then fails loudly instead of silently.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `search.semantic.similarityFloor`        | number                                                                                                                  | (unset)                                                   | project-local | Optional hard cutoff (0–1): drops semantic matches whose cosine similarity is below it. Retrieval is rank-based, so most setups leave it unset; set it only for a provider/model whose cosine scale you know.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `linkPreviews.enabled`                   | boolean                                                                                                                 | `true`                                                    | project-local | Show a rich preview card (site name, page title, description, favicon) when you hover an external link in the editor. Default on (set to `false` to opt out). **When on, hovering an external link sends that link's URL to the destination site** to fetch its preview metadata — outbound egress, one request per previewed link. Previews of links to other documents in the project are read from the local index with no network request and are always on. See [Link previews](https://openknowledge.ai/docs/reference/configuration#link-previews) below.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |

## Removed keys

As new versions of OpenKnowledge are released, some keys are retired from the schema. If any of your config files contains a key the schema no longer reads, that key alone is dropped from the loaded config and reported with the setting that replaced it.

This applies to genuinely retired keys only. A file that cannot be trusted — invalid YAML, or a value that fails validation — is rejected and falls back to defaults.

Run `ok config validate` to see which keys are stale and what replaced each one. `ok config migrate` removes them for you; it defaults to every layer, so a bare run reaches whichever file the key lives in. Narrow it with `--scope project`, `project-local`, `user`, or `all` (the default), and preview with `--dry-run`. Migration deletes retired keys, but it never rewrites anything else on your behalf. A key whose replacement is not a one-to-one translation is removed and reported for you to recreate by hand.

## Folder properties

A folder's own metadata lives in that folder's `.ok/` directory, not in `config.yml`: its own open-shape frontmatter (any keys; `title` / `description` / `tags` are the conventional ones) in `.ok/frontmatter.yml`, and its templates in `.ok/templates/`. Folder properties describe only their own folder: they are not inherited by the docs inside it. (Templates do resolve leaf-to-root, so a root template is available in every subfolder.) Set them from the editor's **Folder properties** pane, or via the `edit({ folder })` / `write({ folder })` MCP tools. See [Folders and templates](https://openknowledge.ai/docs/advanced/folders-and-templates#folder-properties) for the full walkthrough.

## Ignore patterns

Path exclusions live in a project-root `.okignore` file using gitignore syntax. `.gitignore` rules are honored automatically alongside it, and a leading `!` re-includes a file `.gitignore` excluded. Edit patterns from the **Ignore patterns** section of the Settings pane, the right-click menu in the file tree, or by hand in any editor; the running server picks up changes without a restart. See [Ignore patterns](https://openknowledge.ai/docs/features/ignore-patterns) for the full UX.

## Precedence

CLI flags (when running the bundled `ok` CLI) > environment variables > project-local config > project config > user config > defaults. Leaf values at higher levels override the same leaf below; arrays replace, they don't concatenate.

## Environment variables

Most users never set these — the schema settings above cover the common cases. These have no `config.yml` key or Settings toggle; they are environment-only.

**Common:**

| Variable           | Sets                                                                                                                          |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `HOST`             | Server bind host. The `--bind` flag wins over this.                                                                           |
| `PORT`             | Server bind port. The `--port` flag wins over this.                                                                           |
| `OK_MCP_AUTOSTART` | Set to `0` to stop MCP tool calls from auto-starting the project's OpenKnowledge server; tools error until you run `ok start` |
| `OK_LOG_LEVEL`     | Log verbosity (`info`, `debug`, `trace`). Falls back to `LOG_LEVEL` if unset.                                                 |

**Advanced / operator:**

| Variable                          | Sets                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPEN_KNOWLEDGE_GITHUB_CLIENT_ID` | Override the GitHub OAuth App client ID                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `OK_SHOWALL_MAX_ENTRIES`          | Max entries the sidebar's disk-walk listing returns per level before it stops and marks the list truncated (default `50000`)                                                                                                                                                                                                                                                                                                                                                                                               |
| `OK_BRIDGE_TOLERANCE_TELEMETRY`   | Set to `1` to record each bridge-tolerance-class fire as a JSONL line in `<project>/.ok/local/tolerance-telemetry.jsonl` (opt-in, local-only, off by default; unstable diagnostic for triaging markdown-fidelity incidents). **Records doc paths in cleartext** — independent of `telemetry.localSink`'s `attributeDenylist`, which redacts span/log attributes, not this file. Deliberately outside the `.ok/local/telemetry/` subtree `ok diagnose bundle` harvests, so the unredacted paths never ship in a bug bundle. |
| `OTEL_SDK_DISABLED`               | OpenTelemetry OTLP push gate. Inverted sense: set to `false` to **enable** push (it is off by default).                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `OK_BUG_REPORT_INTAKE_URL`        | Base URL of the bug-report intake the desktop app's **Help → Report a bug…** upload posts to (e.g. `https://openknowledge.ai`). Must be `https:` — plain `http:` is accepted only for loopback hosts (local testing). Unset (the default), Send makes no network request — it opens a prefilled email draft to [support@inkeep.com](mailto:support@inkeep.com) that you send yourself.                                                                                                                                     |
| `OK_FEEDBACK_INTAKE_ORIGIN`       | Origin the optional [uninstall feedback](https://openknowledge.ai/docs/reference/what-open-knowledge-writes#what-leaves-your-machine) submission posts to (default `https://openknowledge.ai`). Must be `https:` — plain `http:` is accepted only for loopback hosts (local testing). An unusable value drops the submission rather than falling back to the default.                                                                                                                                                      |
| `OK_EMBEDDINGS_API_KEY`           | Fallback embeddings key for [semantic search](https://openknowledge.ai/docs/reference/configuration#semantic-search), used when no project key is stored **and only for the default OpenAI endpoint** (a machine-wide env key is never sent to a custom host). Prefer `ok embeddings set-key` for normal use; the env var is convenient for CI / scripted runs against OpenAI.                                                                                                                                             |

## Semantic search

The MCP `search` tool can fuse an **embeddings-based semantic signal** into its ranking, so an agent's query surfaces conceptually-related pages even when they share no keywords (a query about "auth retries" can surface a page titled "Session Token Refresh"). It is **off by default** and additive — with it off, search everywhere (the MCP tool and the cmd-K omnibar) is purely lexical and stays on-machine. With it on, the omnibar gains a **By meaning** mode alongside its default lexical mode: typing never embeds; pressing Enter fires one semantic search, sending the query to the embeddings provider.

> **Warn**
>
> **Content egress.** When semantic search is enabled **and** a key is set (or the endpoint is a keyless local server), the search query and the matching page content are sent to whichever OpenAI-compatible endpoint the project has configured (OpenAI by default). Only content that is already in your corpus is embedded — anything excluded by `.okignore` / `.gitignore` is never sent — and embedding is lazy: nothing leaves the machine until a semantic search actually runs. Keys live only in a 0600 `~/.ok/secrets.yml` file, keyed by project + endpoint so a key never travels to a host it wasn't set for; never in `config.yml`, the project tree, logs, or telemetry.

To turn it on (per project, per machine):

1. Enable it for the project: the **Settings → This project → Search** toggle, or `ok embeddings enable`. Either sets `search.semantic.enabled: true` in project-local config (`<project>/.ok/local/config.yml`), picked up live by a running server; `ok embeddings disable` turns it off.
2. Set the API key right there on the same screen (or `ok embeddings set-key` in the project). Keys are **per project** — one project's key is never shared with another unless both point at the same endpoint. A **localhost** endpoint (Ollama / LM Studio) needs no key at all — the field shows "not required". `ok embeddings list` shows every stored key (redacted); `ok embeddings clear-key` removes this project's.
3. (Optional) point at your own OpenAI-compatible endpoint (a self-hosted Ollama / vLLM / LM Studio server, or another provider): open **Custom endpoint** in that same section, set the endpoint URL and the model id, and press **Test connection** — it runs one throwaway embed and reports either the detected vector size or the specific reason it failed. The same knobs are available from the CLI (`ok embeddings set-url <url>` / `clear-url`, `ok embeddings set-model <id>` / `clear-model`) and in project-local config (`search.semantic.baseUrl` / `model`). The vector size is detected automatically, so `dimensions` normally stays unset. Changing the endpoint or the model discards the cached vectors and re-embeds the corpus.

The first semantic search kicks off a background embed of the corpus (cents for a whole vault with `text-embedding-3-small`); vectors are cached incrementally under `.ok/local/` and only changed docs re-embed. If the key is missing, the provider errors, or you're offline, search degrades to lexical — it never blocks or errors. Because that degradation is quiet, **Test connection** is the way to tell a working custom endpoint apart from one that is failing. Each MCP `search` response reports embedding coverage so an agent knows when vectors are still filling in.

## Link previews

Hovering a link in the editor shows a preview card. For **internal links** (other documents in your project) the card — title, folder, tags, last-edited time, backlink count, and a short excerpt — is built entirely from the local index and file contents: always on, no configuration, and nothing leaves the machine.

For **external links**, a card shows the destination's site name, page title, description, and favicon. It is **on by default**; turn it off per machine in Settings.

> **Info**
>
> **Desktop app:** external link previews are not yet available in the packaged desktop app (its `file://` renderer is rejected by the preview route's anti-proxy gate), so this default applies to the browser (`ok start`) surface. Internal document-to-document previews work everywhere.

> **Warn**
>
> **URL egress.** When `linkPreviews.enabled` is on, hovering an external link sends that link's URL to the destination site — one metadata request per previewed link, from your machine, with no cookies or credentials attached. Responses are size- and time-capped, results are cached locally under `.ok/local/`, and requests that resolve to private or internal addresses are refused. Nothing else is sent: internal-link cards never touch the network.

To turn it off (per machine): the **Settings → This project → Link previews** toggle, which sets `linkPreviews.enabled: false` in project-local config (`<project>/.ok/local/config.yml`), picked up live by a running server. The setting is enforced by the local server, so with it off no external request is made. A preview that can't be fetched (offline, timeout, the site blocks it) quietly falls back to the plain URL pill.

## Verifying changes

The Settings pane validates `.ok/config.yml` against the schema as you edit and surfaces errors inline. The **Ignore patterns** section shows a live count of files each pattern matches, so you can confirm scope changes without leaving the editor.