# Translate the interface (https://openknowledge.ai/docs/contribute/translations)

Community driven translations

The app is natively written in English and we did our best to provide machine generated translations for some of the most spoken languages in the world.

OpenKnowledge's interface is available in many languages. The language selection can be found at **Settings → Preferences → Language**.

Language is an app appearance setting and can be set directly in the config [configuration reference](https://openknowledge.ai/docs/reference/configuration#schema). That table is the source of truth, so this page doesn't repeat it.

## How you can help

- Fix errors, improve existing translations
- Add new language sets

## Run OpenKnowledge in the language you're checking

Set the language in the UI setting or set it by hand in `~/.ok/global.yml`:

```yaml
appearance:
  language: bn # <-- set to the language you are testing
```

Use the tag exactly as it appears in the configuration reference (`zh-Hans`, `pt-BR`, and so on). The editor picks the change up without a restart.

Reading the interface in your own language is the best review there is, and the one nobody here can do for you.

On the desktop app, the `OK_LANG` environment variable additionally overrides the **native menus** for a single run, which is the quickest way to check menu translations without touching config. The rest of the window follows `appearance.language`, so set both if you want everything in one language at once.

## Make the change

Translations live in [`inkeep/open-knowledge`](https://github.com/inkeep/open-knowledge), one gettext catalog per language:

```
packages/app/src/locales/<locale>/messages.po
```

1. **Edit the `msgstr` values.** Leave every `msgid` alone — that is the English source string, and the key the app uses to look the translation up. An empty `msgstr` means "untranslated" and falls back to English, so don't blank one out to flag a problem; open an issue instead.

2. **Follow the glossary.** [`GLOSSARY.md`](https://github.com/inkeep/open-knowledge/blob/main/packages/app/src/locales/GLOSSARY.md), in the same folder, pins one form per language for the words that recur everywhere (*document*, *folder*, *branch*, and so on) and lists what is never translated at all: the product name, format names such as Markdown and YAML, and placeholders like `{count}`. If a glossary entry is wrong for your language, change that row and sweep every message that uses it in the same pull request. A catalog holding two words for one concept is worse than either word on its own.

3. **Regenerate the compiled catalogs.** The app loads a compiled `messages.json` beside each `.po`, and CI fails when the two disagree:

   ```bash
   cd packages/app && pnpm run i18n
   ```

   Commit both files.

4. **Open a pull request.** Say which language you read and whether you speak it natively.

[CONTRIBUTING.md](https://github.com/inkeep/open-knowledge/blob/main/CONTRIBUTING.md) covers the rest: toolchain, `pnpm run check`, and the Contributor License Agreement.

## What is and isn't translated

- **The application strings ARE translated.**
- \*\*User Generated content is NEVER translated: \*\*Document bodies, titles, file and folder names, frontmatter values and tags stay exactly as you wrote them, in every language.
- **The CLI's commands, flags and machine-readable output REMAIN English ONLY** on purpose, so scripts keep working whatever the machine's locale is.
- **This documentation site is English only** for now.

## A language that isn't on the list

Open an [issue](https://github.com/inkeep/open-knowledge/issues) before starting work on one. Then follow the instructions above to provide a complete language set. Don't forget to add the language to the list of available UI languages so other users can select your translation set from within the desktop application!

# Agent activity (https://openknowledge.ai/docs/features/agent-activity)

See every file an agent edits in a session, with per-edit diffs and selective rollback.

Agent activity shows all edits made by an agent during its current editing session. It is built for quick review: see which files changed, inspect each edit, and undo any agent work you do not want to keep.

## Open agent activity

Avatar icons will appear in the top right of the editor whenever an agent or a human has the document open. Agents working in other documents appear in an adjacent group. You can click on any agent's avatar icon to open the activity panel for that agent.

The activity panel displays the list of files that the agent has edited in the current session.

## What you can see

The activity panel groups edits by file. Each file row shows the total additions and deletions made by the agent.

Expand a file to see individual edits. Each edit has its own additions, deletions, timestamp, and diff.

## Undo agent edits

While an agent's session is still active, you can undo:

- The agent's latest edit in a file
- All edits made by an agent in a file at once

Activity history lives only as long as the agent's session: shortly after the agent disconnects, the panel and the agent's avatar clear. Use the [timeline](https://openknowledge.ai/docs/features/timeline-and-recovery) to review and roll back changes from a closed session.

## How attribution works

Every edit is attributed to its author — a specific agent, you, an upstream Git sync, or the file system — and recorded in a local edit history. The activity panel is a live view of a single agent session; the timeline is the durable record of who changed what.

# Assets and embeds (https://openknowledge.ai/docs/features/assets-and-embeds)

Drop files into the editor and reference them with wiki-style embeds.

OpenKnowledge treats assets (images, PDFs, video, audio) as first-class content alongside markdown.

## Adding files

Drag files into the editor or paste them — screenshots paste straight from the clipboard, and any file type is accepted. The file uploads into the project and the matching block is inserted. Uploads land beside the document by default; point [`content.attachmentFolderPath`](https://openknowledge.ai/docs/reference/configuration) at a different folder to change that. A configured attachment folder is also a sync scope: in shared projects, linkable files inside it auto-sync to teammates even when no document sits beside them. Dropping a file whose bytes already exist in the target folder reuses the existing file rather than writing a duplicate. You can also insert an image, video, or audio block from the `/` insert menu and upload from its settings.

AI agents add assets through the MCP [`write`](https://openknowledge.ai/docs/reference/mcp) tool's `asset` target, passing exactly one of `content` (the bytes as base64, for small files) or `source` (a local filesystem path the server reads — use it for large files).

## How assets render

- **Images render inline** with click-to-zoom. Select one to align it left / center / right from the bubble menu, or set alt text and dimensions in its settings.
- **Video and audio render native players.** The video block also takes YouTube, Vimeo, and Loom URLs and renders each host's own player.
- **PDFs and other documents** (`.docx`, `.zip`, `.csv`, …) insert a file attachment row — icon, name, size — linking to the file. To render pages in the doc, use the **PDF** block instead: a multi-page viewer with thumbnails, page navigation, and zoom; set `page=3` in its `anchor` setting to open at that page.

In the markdown source, dropped media serializes as `<img>` / `<video>` / `<audio>` and file attachments as wiki-style embeds (`![[report.pdf]]`). You can author the wiki form directly: `![[diagram.png]]` on its own line follows the type rules above, inside a sentence it stays a link, and a fragment (`![[report.pdf#page=3]]`) is preserved on the rendered link. Standard `![alt](./chart.png)` image syntax works too.

## Embedding web pages

The **Embed** block renders an external page in an inline iframe — docs, demos, Figma, CodeSandbox. Drag its handles to resize; the dimensions persist into the markdown. Only `http://` / `https://` URLs load, never the editor's own origin; a site that forbids framing renders an empty pane — that's the remote site's policy. YouTube, Vimeo, and Loom watch URLs are rewritten to their embeddable player URLs at render.

## Live HTML embeds

A code block fenced as ` ```html preview ` renders its HTML in a live sandboxed iframe instead of showing the code. The editor's theme tokens (`var(--foreground)`, `var(--chart-1)`, …) are injected into the preview so embeds track light and dark mode; toggle any HTML block between code and preview from its controls.

Type `/embed` for starters: a blank **HTML** canvas plus **Chart**, **Stat cards**, **Custom SVG**, and **Interactive control** templates. Agents get the same starters from the MCP [`palette`](https://openknowledge.ai/docs/reference/mcp) tool.

## Diagrams

A ` ```mermaid ` fence renders as a diagram — flowchart, sequence, class, state, ER, gantt, or pie; the **Mermaid** entry in the `/` menu inserts one. Rendered diagrams carry buttons for zoom, pan, and reset; mouse-wheel zoom and drag panning are off, so a diagram holds still while you scroll the doc. Standalone Mermaid files (`.mmd`, `.mermaid`) open the same way — rendered as a diagram in their own pane, with a toggle to read or edit the raw source directly. For a standalone Mermaid file, switching from the rendered diagram to raw source does not by itself discard diagram-label history. That history belongs to the current document session: closing the file's last tab, closing the app, or opening enough other documents that the file drops out of your recently opened set are examples of events that start it fresh. A full replacement from disk also starts it fresh, so an older diagram edit cannot be replayed into the replacement.

## Sharing across apps

Local-path images (`![chart](./Q3-sales.png)`), wiki-embeds (`![[diagram.png]]`), and any other reference whose URL only resolves on your machine cannot render in apps that don't have access to your filesystem. When you copy a section containing one of these and paste into Gmail, Outlook, Notion, Slack, Discord, or Google Docs, OpenKnowledge degrades visibly instead of silently:

- **Non-portable URLs surface as a styled code block.** Recipients see the literal markdown source (e.g. `![chart](./Q3-sales.png)`) in monospace, not a broken-image icon. They can read what the section was meant to include and ask for the file or accept the textual reference.
- **Inline images in paragraphs surface as inline source.** A paragraph with a local-path image keeps its prose flow; only the `<img>` collapses to inline source markup, and the surrounding sentence is unaffected.
- **Wiki-links surface as anchor links.** `[[OtherDoc]]` cross-app becomes a clickable `<a href="#otherdoc">OtherDoc</a>`. The link shape signals that something was referenced, though the fragment href will not resolve in the destination.

Two affordances are preserved across the cross-app boundary, not degradations themselves:

- **Markdown-aware destinations get the source.** Linear, Outline, Obsidian, and similar apps that read `text/plain` first receive clean canonical markdown; the cross-app degradation only affects the `text/html` payload that destinations like Gmail render.
- **OpenKnowledge → OpenKnowledge paste round-trips images and wiki-links.** Copying out of one OpenKnowledge doc and pasting into another preserves the original markdown image and wiki-link shape. Wiki-embeds round-trip when the copied selection carries other markdown signals (a heading, a list, bold); a selection that is only a wiki-embed — or plain prose around one — pastes as literal source text, not a rendered embed.

Source-mode copy (the CodeMirror toggle) works the same way: the clipboard's `text/html` payload is always a styled code block of the raw markdown bytes, never the rendered output. Pasting a `# Heading` from source-mode into Gmail produces a code block, not a rendered heading. This is consistent with VS Code, Obsidian source mode, GitHub textareas, and CodeMirror itself.

If you want recipients to see rendered images cross-app, host the file at a public URL (`https://...`) and reference it that way; public URLs are passed through unchanged. Public IP literals (`https://1.2.3.4/img.png`, `https://[2001:4860:4860::8888]/img.png`) are also passed through; only private, loopback, link-local, and other non-public ranges trigger the source-fallback shape.

# Comments (https://openknowledge.ai/docs/features/comments)

Annotate your knowledge base with comments and resolve them with your agent

> **Info**
>
> For comments you want to keep *inside* a markdown file using `<!-- … -->` or `%%…%%`, see [markdown comments](https://openknowledge.ai/docs/features/editor#markdown-comments).

## Write a comment

Select text, a code block, or a diagram, then press `Cmd+Shift+M` on macOS or `Ctrl+Shift+M` on Windows and Linux, or click **Comment** in the selection toolbar.

![Writing a comment on selected text in the OpenKnowledge editor](https://openknowledge.ai/screenshots/write-comment.gif)

## Review and send

Comments are highlighted in the document, and each open comment gets a  marker in the right-hand margin. Click either one to open the comment in the document panel (`Cmd+Option+B` on macOS or `Ctrl+Alt+B` on Windows and Linux).

![Comments panel](https://openknowledge.ai/screenshots/comments.png)

You can control which comments are sent to your agent by checking or unchecking them. Then click the send button or use `Cmd+Shift+Enter` on macOS or `Ctrl+Shift+Enter` on Windows and Linux while the Comments tab is open.

> **Info**
>
> If the text a comment points to is deleted or rewritten enough that OpenKnowledge can no longer find it, the comment becomes orphaned. The comment is still there, but it is no longer attached to live text in the document. If you send it to your agent, it includes the original quote for context. To reattach it, select the new text in your document and click **Re-place on selected text**.

## Resolve and reopen

After comments are sent to an agent, they are marked as resolved. To see resolved comments again, use the **Show resolved** control in the Comments tab.

![Show resolved comments control in the Comments tab](https://openknowledge.ai/screenshots/show-resolved-comments.png)

If you are unhappy with the way the agent made edits, or just want to reopen a comment, **Reopen** puts it back in the unresolved state so you can send it to an agent again.

## Agent access to comments

Agents can view unresolved comments when they read a file via MCP. Each comment includes the note along with the passage it refers to.

When an agent lists files, it can also see how many unresolved comments each file has.

## Comments are local

Comments stay local to the machine that created them. They are not committed and not pushed, so they are not visible to your team members.

> **Info**
>
> Comments are stored in `.ok/local/comments/`. Read more on [what OpenKnowledge writes to your system](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

## See also

- [Editor](https://openknowledge.ai/docs/features/editor) — the selection toolbar, the Properties panel, and the rest of the editing surface.
- [Agent activity](https://openknowledge.ai/docs/features/agent-activity) — reviewing what the agent changed after it acts on your comments.

# Editor (https://openknowledge.ai/docs/features/editor)

The OpenKnowledge editor. WYSIWYG markdown, source toggle, content blocks, link authoring, inline frontmatter, and the file sidebar.

The editor is where you read, write, and collaborate on your docs, and where AI agents land when they edit. WYSIWYG by default with a source-mode toggle, a frontmatter properties pane, real-time CRDT collaboration, and a file sidebar that organizes the project.

## Open a single file

Want the editor on one loose markdown file without setting up a project? Run `ok <file>`:

```bash
ok notes.md            # open a single file in the editor
ok ./specs/foo/SPEC.md # a file inside a project opens that project, focused on the doc
```

When the file is inside an existing OpenKnowledge project, this opens that project focused on the doc. When it's a loose file, it opens an **ephemeral single-file session**: a throwaway editor scoped to just that one file, with no project scaffolding, no agents, and no git. Your edits save straight back to the original file, and **nothing is written into the file's directory** — all session state lives in a temporary directory that's removed when you close the window. Opening a file you don't edit leaves it byte-for-byte unchanged.

It opens in the desktop app when it's installed, and falls back to your browser otherwise (press Ctrl-C in the terminal to end a browser session). Single-file sessions show just the file and the editor — no sidebar, tabs, or project switcher. Discover it any time with `ok --help`.

## Document body

Open any doc from the sidebar. The body renders as WYSIWYG markdown with familiar conventions: bold / italic / headings / lists / tables / code fences / links / images. Toggle to source mode from the editor controls or with `Cmd+Option+M` to edit the raw markdown directly; both views are bound to the same CRDT document, so they stay in sync across users and AI agents. Switching modes keeps your place — the block you were looking at stays in view either way.

A slash command (`/`) opens an insert menu for headings, lists, tables, code blocks, callouts, footnotes, inline math, and the other content shapes the editor supports.

When a doc ends in a table, list, or code block, click the empty space below it to start a new paragraph there. A **+** appears in the gutter as you hover, showing where the line will land. On a touch device there is no hover to read, so the **+** stays visible whenever that space is there to use, and on a narrow screen it sits at the left edge of the text rather than out in the gutter. The space is only a click target, so nothing is written to the file until you use it, and a doc that ends in a table stays ending in a table.

A doc written before this behavior may still carry a blank line at the end that nobody typed. Open Knowledge leaves trailing blank lines alone rather than removing ones it did not write, so clear it with **Auto-fix** in the Problems panel, with `ok lint --fix`, or by hand. See [markdownlint rules](https://openknowledge.ai/docs/advanced/content-rules/markdownlint).

Markdown problems (hard tabs, heading increments, and the rest) are flagged inline as you write, with a **Problems** panel in the document panel for the whole doc or project. See [Content rules](https://openknowledge.ai/docs/advanced/content-rules/overview).

### Undo in source mode

This section is about the Markdown source pane only. The visual editor keeps its own undo history, and none of what follows changes it.

Undo in source mode covers the editing you did there. Switching to the visual editor for a look and coming straight back leaves that history intact. But if you leave source mode, by switching to the visual editor or by moving to another tab, and anything writes to the document while you are away, source-mode undo and redo start fresh when you come back. Many things count as a write, not only the obvious ones: your own edits in the visual editor, an agent, a collaborator, a change on disk, a field you change in the **Properties** panel, a fix applied from the **Problems** panel or from the visual editor, or the tail of a large paste you started in source mode that was still landing as you flipped away. Restoring an earlier version from the [Timeline](https://openknowledge.ai/docs/features/timeline-and-recovery) counts as a write too, so if you restore while you are out of source mode, undo there starts fresh when you come back. Source-mode undo history belongs to the current document session: navigating through enough other documents to evict it, or closing and reopening the app, starts it fresh even if no one rewrote the file.

No text is removed when that happens. One case is still not covered. If a rewrite lands while you are sitting in Markdown source rather than away from it, the history is left as it is, so a single undo can still take back more than you expect.

To go further back, the [Timeline](https://openknowledge.ai/docs/features/timeline-and-recovery) keeps earlier versions of the whole document. Each entry is a snapshot of the whole document. Agent edits, human edits, and file-system changes are batched into an entry once activity settles. Upstream syncs get their own entry when the sync lands. Restoring an entry brings back that version's content. Restoring is append-only, so the edits you made since then stay on the timeline and you can restore back to them.

### Tables

Insert a table from the slash menu. The header row stays visible as you scroll down a long table, the first column stays pinned as you scroll sideways, and dragging a row or column handle reorders it. Cells are single-line in the stored markdown: pressing Enter inside a cell — or pasting a table whose cells hold lists or paragraphs — joins the lines with `<br />`, the GFM line-break idiom, so the table stays valid for everyone else opening the file. `<br>` tags in existing files render as real line breaks and round-trip byte-for-byte.

### Mermaid diagrams

A `mermaid` code fence renders as a live diagram (the slash menu can insert one). On-diagram buttons pan, zoom, and reset the view; mouse-wheel zooming and drag panning are disabled so a large diagram never moves while you scroll past it.

### Live HTML embeds

An `html preview` code fence renders its contents live in a sandboxed iframe instead of showing the source. The slash menu's `/embed` group inserts starters — a blank theme-aware page, a chart, stat cards, custom SVG, an interactive control — that open straight into the preview.

### Underline

`Cmd+U`, or the Underline button in the selection bubble menu, underlines the selection. Markdown has no underline syntax of its own, so it saves as inline HTML: `<u>underlined</u>`. `<ins>…</ins>` — what Joplin and Logseq write — is read as underline too, whether it arrives on the clipboard or is already sitting in a file you open, and each file keeps the spelling it arrived with instead of being rewritten under you. Both underline in OpenKnowledge and in any renderer that passes inline HTML through. GitHub is the exception worth knowing: it renders `<ins>` underlined but strips `<u>` back to plain text, so the words survive and the underline doesn't.

### Markdown Comments

Notes that stay in the file and out of what a reader sees. (For review comments attached to a passage from outside the file, and sent to an agent as a batch, see [Comments](https://openknowledge.ai/docs/features/comments).) OpenKnowledge reads two syntaxes. There's no button or shortcut for either — you type them, inline inside a paragraph or as a standalone block spanning several lines:

| Syntax          | Hidden from readers in                                                                                                      |
| --------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `<!-- note -->` | Everywhere. It's an HTML comment, so no markdown renderer ever shows it to a reader.                                        |
| `%%note%%`      | OpenKnowledge and Obsidian, which share the convention. Everywhere else it renders as ordinary text, percent signs and all. |

So: reach for `<!-- -->` when the file will be read outside OpenKnowledge — pushed to GitHub, built into a docs site, opened in another editor. Reach for `%%…%%` when the file round-trips through Obsidian, whose own comment syntax this is. The syntax you type is the syntax that gets saved: OpenKnowledge never converts `<!-- -->` into `%%…%%` or back. Two tidyings happen on save, and only these two. A multi-line `%%` block takes the padded shape, with a blank line after the opening delimiter and before the closing one. And an HTML comment gets one space of inner padding, so `<!--note-->` saves as `<!-- note -->`. The `%%` inline form keeps its body exactly as typed, spaces included.

In the editor, comments stay visible and editable: dimmed and italic under a dashed underline, with a dashed left rail on the block form. The underline is what tells a comment apart from ordinary emphasis without relying on colour. The delimiters themselves aren't drawn, the same way the editor doesn't draw the asterisks around bold — switch to source mode to see or edit them. That means the two forms look identical in the editor: which one a given comment uses is a real decision, per the table above, and source mode is where you check it. The body stays on screen either way, which is deliberate: text you typed should never disappear from the surface you typed it into.

Two limits are worth knowing. The first applies mid-sentence only: a comment inside a line of prose whose body is *nothing but* a formatted word — `%%**bold**%%` — stays ordinary prose rather than becoming a comment, because there is no way to record whether the emphasis sat inside the comment or the comment inside the emphasis. Give it any plain text alongside (`%%note **bold**%%`) and it is recognized normally. The same comment alone on its own line is recognized either way, as a comment block.

The second is a budget: a paragraph, heading, or table cell can carry **one** comment whose body contains formatting. Write two in the same one and neither is recognized — both stay literal `%%`, and worse, the word between them can be claimed as a comment instead and disappear from published output. Split them across separate paragraphs, or keep one of the two bodies plain.

Readers don't see them. Wherever OpenKnowledge renders a doc to HTML rather than to the editor, both forms come out as real HTML comments, which browsers don't display. Copying a passage out of OpenKnowledge leaves the comments behind too, so an annotation can't ride along into an email or a chat message. In-app read-only views — the rendered diff, the skill viewer — do keep showing them dimmed: there the whole point is to show you everything the document contains.

### Find and replace

`Cmd+F` opens find in the visual editor; `Cmd+Option+F` (`Ctrl+H` on Windows / Linux) adds the replace controls. `Cmd+G` and `Shift+Cmd+G` step through matches.

## Links

Links work the way they do in the editors you already know:

- **Type a URL, then press space or enter.** A full URL (`https://…`, `www.…`, or an email address) becomes a link when you press space or enter after it. Plain words and filenames like `AGENTS.md` are left alone, and one `Cmd+Z` turns a conversion back into plain text.
- **Paste or drag in a URL.** Pasted (or dropped) on its own, it becomes a link. Pasted over selected text, it links that text instead — the text stays, the URL becomes its destination. `Cmd+Shift+V` pastes plain, with no linking.
- **Type `[text](url)`.** Closing the parenthesis converts the markdown shorthand into a real link; `Cmd+Z` restores the literal text.
- **Press `Cmd+K` with text selected.** The link popover opens with the URL field focused, pre-filled from your clipboard when it holds a URL. With the cursor inside an existing link, `Cmd+K` opens that link for editing. With no selection, `Cmd+K` keeps opening the command palette, as everywhere else. To reach the command palette while text is selected, use `Cmd+P` (`Ctrl+P` on Windows / Linux), which always opens it.

## Right-click menu and spell check

In the desktop app, right-clicking editable text opens a native context menu. Any editable field gets **Cut / Copy / Paste / Select All**; when there's a selection or a flagged word under the cursor, **Search with Google** joins it — plus, on macOS, **Look Up** (the dictionary panel).

Over the document body, the menu also carries **View in Source `</>`** — the desktop equivalent of `Cmd+Option+E`, opening the Markdown behind the block you right-clicked in the visual editor.

Spell checking uses the native macOS spellchecker on macOS (language follows your macOS settings) and Chromium's built-in spellchecker on Windows and Linux, so misspelled words get the familiar red underline. Right-clicking a flagged word adds the spelling section:

| Item                    | Behavior                                                                                                             |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------- |
| *Suggestions*           | The spellchecker's corrections, one row each; pick one to replace the word.                                          |
| **Add to Dictionary**   | Teaches your system dictionary the word — persistent and, on macOS, shared with your other Mac apps (Safari, Notes). |
| **Disable Spell Check** | Turns spell checking off app-wide. The choice persists across relaunches.                                            |

While spell check is off, right-clicking any editable text offers **Enable Spell Check**, and the menu bar carries the same toggle as **Edit ▸ Check Spelling While Typing** (see [Edit menu](https://openknowledge.ai/docs/features/editor#edit-menu)) — flip either to bring the squiggles back.

Surfaces with their own specialized right-click menus — asset and wiki-link chips, the [file sidebar](https://openknowledge.ai/docs/features/editor#right-click-context-menus), editor tabs — keep them; the editor menu never doubles up on top. In the web build the browser's own context menu fills this role.

## Ask AI

An **Ask AI** composer is docked at the bottom of the editor — in the desktop app and in a regular browser — whenever a doc or folder view is open. It's a slim, single-line entry field that expands as you type to accept a fuller prompt (and `@`-mentions); in the desktop app, `Cmd+L` opens and focuses it. Type an instruction — "condense this doc", "research X", "make a spec from this user story" — and press Enter to hand it to an agent. `Shift+Enter` inserts a newline; the field clears once the prompt is sent.

It's the primary in-editor way to hand work to an agent, and the freetext companion to the menu-driven [Open with AI](https://openknowledge.ai/docs/features/editor#open-with-ai) (whole file / folder / project). Submitting dispatches the typed prompt to your **default agent**, scoped to the current doc (or, in a folder view, the folder), which the agent receives as an `@`-mention. Until you pick something else, that default is an **In app** agent, so the conversation stays inside OpenKnowledge; it falls back to a **Terminal** CLI — preferring one detected on this machine — and then to an **External app**, when no higher option is available here. The agent picker beside the field overrides the default per send, listing In app agents first, then the [Terminal](https://openknowledge.ai/docs/features/editor#open-with-ai) CLIs available on this machine, then the external agent apps detected here. Whatever you pick is remembered on that machine and stays your default until you change it or turn it off in **Configure agents**. Success and failure use the same handoff toast as Open with AI.

The composer stays out of the way where it doesn't belong: it's hidden inside an AI editor's embedded view, while the [docked terminal](https://openknowledge.ai/docs/features/editor#terminal) is open (the terminal is its own AI entry point), and when no doc or folder is open (the empty state already offers project handoff). It's also dismissible — an **Ask AI** badge in the status bar brings it back.

### From a text selection

Select text in the WYSIWYG editor and the selection bubble menu shows a **Comment** button (Sparkles icon) alongside the formatting controls. Clicking it — or pressing `Cmd+Shift+M` — opens a composer on the selection. What you write there is a [comment](https://openknowledge.ai/docs/features/comments): it goes into the project's comment queue rather than to an agent, and you hand a batch of comments over as a separate, deliberate step from the **Comments** tab.

## Terminal

The desktop app on macOS, Windows, and Linux has a built-in terminal with real interactive shells inside the editor window. On Windows, the terminal requires Windows 10 version 1809 (build 17763) or later, Windows 11, or Windows Server 2019 or later. On Linux, OpenKnowledge inherits Electron's supported distribution range: releases of the major distributions that both Chromium and the distribution vendor still support. Toggle it with `Cmd+J` on macOS or `Ctrl+J` on Windows and Linux, with `` Ctrl+` `` on any desktop platform (which also works from inside a focused terminal), or with **View ▸ Show / Hide Terminal**. It opens at the placement saved for this installation (the bottom dock by default). Right-click a bottom-dock tab and choose **Move to right panel**, use the matching command in the command palette or **Terminal** menu, or use the right-panel header button to move it back to the bottom. Moving or hiding the Terminal keeps its live shells, tabs, and scrollback intact.

The terminal is also the editor's AI-chat surface: opening it starts your remembered CLI pick, or a plain shell when you have none, and the tab strip's **+** button opens another tab with whatever you picked last — a CLI chat or a plain shell. Its dropdown switches that pick and opens a tab in the thing you picked, in one action. A CLI pick sticks and is shared with the Ask AI composer; the plain-shell pick is remembered for the terminal only. In-app agents run in the Agents panel, which has its own tab strip and **+** button; picking an agent there sticks the same way and also becomes your Ask AI default. With text selected in the editor, `Cmd+J` on macOS, `Ctrl+J` on Windows or Linux, and `` Ctrl+` `` on any desktop platform stage the passage into the active CLI's input (or open a new CLI tab when none is running) instead of toggling — they share one handler. `Cmd+Shift+J` on macOS or `Ctrl+Shift+J` on Windows or Linux always stages it into a new CLI tab; the passage sits in the input for you to add to, and nothing is submitted until you press Enter. **Terminal ▸ New Terminal Window** (see [Terminal menu](https://openknowledge.ai/docs/features/editor#terminal-menu)) opens a dedicated window with the same multi-session tabs, switched with `Cmd+<number>` on macOS or `Ctrl+<number>` on Windows and Linux, and shells starting at the project root.

On Windows, type `wsl` in any terminal tab to enter your default WSL distribution; OpenKnowledge's CLI detection and bundled `ok` command stay Windows-side, so install agent CLIs on Windows for them to appear in the launcher.

Rename a tab by double-clicking it or pressing `F2`; the name you set pins over whatever title the running program reports, and clearing it falls back to that title. Reorder tabs by dragging, or move the active tab with `Cmd+Shift+←` / `Cmd+Shift+→` — untitled tabs keep a number that stays with their session as the strip is rearranged.

The desktop app restores the Terminal's placement, right-panel width, tab order, active tab, and tab names after a full app restart. A full quit ends the shell processes and discards their scrollback; the restored tabs start new shells. This differs from moving, hiding, or reloading the editor window, which keeps the existing live sessions.

On a narrow window, the right-side Terminal and Agents panel cannot remain open together. Opening either one closes the other and shows an in-app notice; closing the visible panel does not reopen the one that was displaced.

The shells run at full user privilege. To turn the terminal off for a project on this machine, use **Settings ▸ Terminal** or set `terminal.enabled: false` in `.ok/local/config.yml` (see the [configuration reference](https://openknowledge.ai/docs/reference/configuration)); the setting is project-local, never shared via git or sync, and not agent-settable — only a human can flip it. On Windows, set `terminal.shell` in the same file to an absolute shell executable if you want to override automatic PowerShell and `cmd.exe` discovery. 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. Enter WSL by running `wsl` inside a supported shell rather than setting `wsl.exe` as the override.

## Properties

Every doc can carry a bit of structured information at the top: a `status`, an `owner`, a `tags` list, a date. OpenKnowledge calls these its **properties**.

In the raw markdown file, these properties live in a fenced block at the very top of the file, between two `---` lines. Markdown tools call that block *frontmatter*: it's just a small chunk of YAML, the doc's "labels." You never have to write it by hand: the **Properties** panel on the right of the editor shows each field as an ordinary form input (text, number, checkbox, date, or list) and saves your changes back into the file. Edit the panel or the raw block in source mode; changes propagate live either way.

A folder can describe itself (its own properties, just like a doc's) and offer templates that give new docs their starting properties. See [Folders and templates](https://openknowledge.ai/docs/advanced/folders-and-templates#folder-properties) for how that works.

## File sidebar

The sidebar lists every file in your project on disk, including files excluded by `.gitignore` / `.okignore`, except dot-prefixed entries and tooling internals (`.git/`, `.ok/`, `node_modules/`). Turn on **Show Hidden Files** to reveal the dot-prefixed entries. Folder rows expand and collapse; file rows open in a tab on click. A file with validation problems shows a tinted row and a count badge, and clicking the badge (or tabbing to it and pressing Enter) opens that file with the Problems panel on it, see [Where problems show up](https://openknowledge.ai/docs/advanced/content-rules/overview#where-problems-show-up). The toolbar at the top of the sidebar has buttons to create files, create folders, and search. At the bottom of the sidebar, the collapsible **Skills Studio** dock lists your skills next to your files, so a file and a skill can be open side by side; see [Skills](https://openknowledge.ai/docs/features/skills#the-skills-studio-dock).

### Right-click context menus

Right-click anywhere in the sidebar to get a context-aware menu. Three shapes share consistent ordering so the muscle memory carries across surfaces:

| Item                         | Empty space                     | Folder row            | File row              |
| ---------------------------- | ------------------------------- | --------------------- | --------------------- |
| New File                     | yes                             | yes                   | no                    |
| New from template            | yes                             | yes                   | no                    |
| New Folder                   | yes                             | yes                   | no                    |
| Reveal in Finder             | yes (project root)              | yes                   | yes                   |
| Open with AI ▸               | yes (project scope)             | yes (folder scope)    | yes (file scope)      |
| Share                        | yes (project root)              | yes                   | yes                   |
| Copy full path / Copy Path ▸ | yes (full path of project root) | yes (Full / Relative) | yes (Full / Relative) |
| Show Hidden Files            | yes                             | yes                   | no                    |
| Expand all / Collapse all    | yes (tree-scoped)               | yes (subtree-scoped)  | no                    |
| Duplicate                    | no                              | yes                   | yes                   |
| Rename                       | no                              | yes                   | yes                   |
| Hide folder / Hide this file | no                              | yes                   | yes                   |
| Delete                       | no                              | yes                   | yes                   |

Items that depend on the desktop app (revealing in your file manager, Delete via the OS trash) are hidden in the web build. Asset rows (images, PDFs, other non-markdown files) suppress Open with AI, Share, Duplicate, and Hide; they keep Reveal, Copy Path, Rename, and Delete.

Expand all and Collapse all only appear when they have work to do: Expand all is hidden when no folder is collapsed; Collapse all is hidden when no folder is expanded. New from template appears only when templates are available for the target folder, and Share only when the project has a GitHub remote.

### Creating files and folders

Pick **New File**, **New from template**, or **New Folder** from any of the three menus. The sidebar shows an inline rename field at the target location; type a name and press Enter. **New from template** opens the [template picker](https://openknowledge.ai/docs/advanced/folders-and-templates#templates) (the same dialog the toolbar's create buttons use) and respects the folder's `templates_available` cascade.

Right-click on empty space drops the new item at the project root. Right-click on a folder drops it inside that folder.

### Hiding files and folders

**Hide this file** on a file row, or **Hide folder** on a folder row, appends a matching pattern to `.okignore`. See [Ignore patterns](https://openknowledge.ai/docs/features/ignore-patterns) for the full pattern syntax. The item disappears from the tree, search, and the agent's MCP tools immediately; nothing is deleted on disk.

### Show Hidden Files

The sidebar always lists every file on disk under the content directory (including files excluded by `.gitignore` / `.okignore`), except synthetic system docs. A single **Show Hidden Files** toggle on the empty-space and folder menus controls whether dot-prefixed entries are shown. It persists per-project per-machine in `.ok/local/config.yml` under `appearance.sidebar`, and also lives in the **View** menu.

| Toggle                | Effect                                                                                                                                                                                                                      |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Show Hidden Files** | Off by default. When on, stops the client from hiding paths that start with `.` (recovers allowed dotfiles like `brain/.archived/note.md`). Tooling internals (`.git/`, `.ok/`, `node_modules/`) stay hidden in every mode. |

On very large trees the disk walk is capped per level, and the sidebar shows a notice telling you the list is partial.

### Deleting via Trash

**Delete** moves the item to the system Trash via the same `shell.trashItem` call VS Code and Finder use: recoverable, not destructive.

### Open with AI

**Open with AI ▸** dispatches the project, the folder, or the file to one of your agents — in app, in the terminal, or in an external app. The agent receives a short text prompt asking it to open the target in the OpenKnowledge web preview, then grounds the rest of the conversation via the [OpenKnowledge MCP](https://openknowledge.ai/docs/reference/mcp). Three scopes, three prompt shapes:

| Scope   | Triggered from                            | Prompt template                                                                              |
| ------- | ----------------------------------------- | -------------------------------------------------------------------------------------------- |
| Project | Empty-space menu (no doc / folder active) | `Let's work on this project using OpenKnowledge. Open the OK editor in web view.`            |
| Folder  | Folder row menu (folder view active)      | ``Let's work on the `<folder>` folder using OpenKnowledge. Open the OK editor in web view.`` |
| File    | File row menu (doc active)                | ``Let's work on `<path>` using OpenKnowledge. Open the OK editor in web view.``              |

The trailing `Open the OK editor in web view.` directive is dropped when the [`appearance.preview.autoOpen`](https://openknowledge.ai/docs/reference/configuration) preference is off, so the receiving agent honors the same "leave my preview alone" choice on its first turn; only the `Let's work on … using OpenKnowledge.` opener is sent.

To send your own freetext instruction along with the handoff, use the bottom [Ask AI](https://openknowledge.ai/docs/features/editor#ask-ai) composer (or the empty-state create composer on a fresh project) rather than these one-click submenus.

**Install detection** supplies the defaults for all three sections: Terminal and External apps rows probe for the CLI or app itself, and In app rows probe for the agent's underlying CLI — and for an agent whose sign-in lives where OpenKnowledge can read it (Codex today, through the namespace it shares with the Codex CLI), an existing sign-in counts on its own even with no CLI installed. **Settings → Configure agents** overrides those defaults in both directions — turn off something detected, or turn on something you haven't installed yet. **Terminal** rows always use the desktop app's own PATH probe. For **External apps** the desktop app checks the OS protocol handlers locally, while the web build asks the server (`GET /api/installed-agents`), so that answer reflects the machine the server runs on — exact on `localhost`, but against a remote server (a hosted preview, SSH tunnel, remote dev) it's the server's installs that count. The **In app** signal is always the server's: it rides the agent catalog, not either probe. When nothing is enabled or detected, the submenu is just the **Configure agents** row.

See the [per-integration pages](https://openknowledge.ai/docs/integrations/claude-code) for what each agent receives once dispatched.

The menu is organized into three sections, in the order the default follows. **In app** leads: a row for each in-app agent you've enabled or that detection turned up, which opens the conversation in OpenKnowledge's Agents panel without leaving the app. **Terminal** (desktop app) follows: a row for every agent CLI OpenKnowledge can launch — **Claude**, **Codex**, **OpenCode**, **Cursor**, **GitHub Copilot**, **Pi**, **Antigravity**, **OpenClaw**, and **Hermes** — each running its own command in OpenKnowledge's [docked terminal](https://openknowledge.ai/docs/features/editor#terminal) with the same scoped prompt. On Windows, OpenKnowledge pastes that prompt without submitting it because a generic ConPTY readiness signal cannot safely identify the active input; review the prompt, then press Enter to send it. A CLI the PATH probe reports absent is hidden until you turn it on in **Configure agents**. **External apps** comes last, listing the agent apps detected on this machine — **Claude Desktop**, **ChatGPT Desktop** (Codex's desktop app is branded ChatGPT), and **Cursor Desktop** — plus any you turned on in **Configure agents** (those route to the app's installer); selecting one launches it via its deep link and hands the work out of OpenKnowledge. Because the sections can carry the same brand name, the **Desktop** suffix is what tells a Terminal **Claude** (the CLI) apart from an External apps **Claude Desktop** (the app) on screen; screen readers additionally get "Claude CLI" as the Terminal row's accessible name. Each section renders only when it has something to launch, so on the web build, which has no docked terminal, the Terminal section is absent, and a saved CLI pick degrades to the same order the default follows: an In app agent when one is available, otherwise an external app. A **Configure agents** row is always last.

The empty-state create composer on a fresh project carries the same three-way split: its In app rows pick the in-app agent the **Create** button uses, its Terminal rows (desktop only, disabled until the workspace resolves) launch any of those CLIs with the brief you typed, and its External apps rows hand that brief to an agent app installed here.

When Claude or Codex launches from the docked terminal, OpenKnowledge auto-approves its own MCP tools, so the agent searches, reads, and edits the knowledge base without a per-call approval prompt. Claude additionally auto-approves the `ok open` command. Every other CLI launches unchanged.

Five tools stay gated: `delete` and `move` (knowledge-base-wide blast radius), `share_link` (publishes content), `install` (writes executable skill scripts into the agent's own config directory), and `import` (acquires skill content from an external source). For Claude, everything outside OpenKnowledge still prompts too, including shell commands and non-OK file edits. The guarantee is best-effort per agent: Claude uses an explicit allow/ask list, while Codex uses its own **approve** mode (auto-approve except potentially-unsafe actions) and only gets it once its OpenKnowledge entry is already configured, so run `ok init` for Codex if it keeps asking.

Turn it off in **Settings → Terminal → “Let agents use OpenKnowledge without asking”** ([`agents.autoApproveOkTools`](https://openknowledge.ai/docs/reference/configuration)). It applies to every project on this machine and defaults on.

## Tabs and split panes

Tabs share the main editor header and shrink as the row fills, keeping every open target visible without a horizontal scrollbar. Drag a tab to reorder it. Opening a file from the sidebar uses a lightweight preview tab by default: the next single-clicked file replaces it, while double-clicking the tab title, pinning, or choosing **Keep open** from its context menu keeps it open. Turn previews off with **Settings → Preferences → Preview tabs** ([`editor.previewTabs`](https://openknowledge.ai/docs/reference/configuration)).

To work side by side, drag any document, folder, or **New tab** to the left or right edge of the editor. The blue drop indicator shows where the new vertical pane will open. You can also right-click a tab and choose **Move to new pane left** or **Move to new pane right**; from the keyboard, focus the tab, press `Shift+F10` (or the Menu key), then choose the same command. Drag the divider to resize panes. Each pane keeps its own active tab, while the document panel follows the pane you most recently focused. Moving or closing a pane's last tab removes the pane automatically.

Open tabs, pane order, sizes, and the focused pane are restored with the project after a clean restart.

## Command palette

`Cmd+K` opens the command palette in both the desktop app and the browser, and `Cmd+P` (`Ctrl+P` on Windows / Linux) does the same from anywhere — including while text is selected in the visual editor, where `Cmd+K` adds a link instead. Type to jump to any file or folder, run create commands, open the graph or settings, or dispatch to an agent; recently opened entries surface first. In the desktop app it also switches projects and worktrees.

In the browser, `Cmd+P` is normally the print shortcut; OpenKnowledge takes it for the palette, so print from the browser's own menu instead.

Remove a recent project from the list by hovering its row and clicking the ×, or right-clicking for a **Remove from recent projects** menu — the same controls are on the sidebar's **Switch project** dropdown and in the Project Navigator. A recent whose folder was deleted or moved off disk isn't greyed out; it's cleared automatically the next time you open it, with a brief notice, instead of opening a broken window.

Every word you type has to appear somewhere in a command's name or its search terms, but the order does not matter and the words do not have to sit next to each other: `branch switch` and `switch branch` both reach **Switch worktree**. Commands also carry the words people search by rather than only the words in their names, so `delete file` reaches **Move to Trash** and `open settings` reaches **Settings**. Because commands are listed above file results, a query that matches both will highlight the command — arrow down to reach the file, or keep typing to narrow.

A `tag:` prefix searches by tag: `tag:` alone lists every tag, `tag:fr` narrows the tag list, and `tag:frontend` lists the docs tagged `#frontend` (including its hierarchy children). When semantic search is configured for the project, a **By meaning** pill switches the palette to meaning-based search — press Enter to run it; typing alone never fires a query.

## Sidebars and window width

The editor has two sidebars: the file sidebar on the left and the document panel on the right (Outline, Links, Properties, Timeline). Both adapt to the window:

- At comfortable widths (1280px and up) both sidebars open by default.
- Between 1024px and 1279px the document panel defaults to collapsed so the editor keeps its breathing room; the file sidebar stays open.
- Below 1024px both default to collapsed so the editor canvas isn't squeezed off-screen.
- When OpenKnowledge is opened inside an AI editor's webview (Cursor, Codex, Claude Desktop), both sidebars start collapsed regardless of width; the embedded view is for reading the content you were sent, not for file management.

The defaults only apply until you toggle. OpenKnowledge remembers your most recent Show / Hide for each sidebar in your browser and survives reloads. When you come back to the same layout context (narrow window, comfortable window, or embedded view) you used last, your toggle is what you'll see. Toggling in a different context replaces that memory; only your most recent choice sticks, so a stale preference from a wide-monitor session can't quietly override what you just chose on a narrow screen.

Keyboard shortcuts (on Windows / Linux, use `Ctrl` in place of `Cmd` and `Alt` in place of `Option`; rows that already use `Ctrl` are unchanged):

| Shortcut                       | Action                                                                                                                                                                                                                                                                                                                                                                                                           |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Cmd+T`                        | Open a new editor tab.                                                                                                                                                                                                                                                                                                                                                                                           |
| `Ctrl+Tab`                     | Move to the next editor tab.                                                                                                                                                                                                                                                                                                                                                                                     |
| `Ctrl+Shift+Tab`               | Move to the previous editor tab.                                                                                                                                                                                                                                                                                                                                                                                 |
| `Cmd+1` ... `Cmd+8`            | Jump to one of the first eight editor tabs.                                                                                                                                                                                                                                                                                                                                                                      |
| `Cmd+9`                        | Jump to the last editor tab.                                                                                                                                                                                                                                                                                                                                                                                     |
| `Cmd+Shift+T`                  | Reopen the most recently closed editor tab.                                                                                                                                                                                                                                                                                                                                                                      |
| `Cmd+K`                        | Command palette — or, with text selected in the visual editor, add a link (cursor inside a link edits it).                                                                                                                                                                                                                                                                                                       |
| `Cmd+Option+S`                 | Show / Hide the file sidebar (left). Matches Apple's standard sidebar accelerator.                                                                                                                                                                                                                                                                                                                               |
| `Cmd+Option+B`                 | Show / Hide the document panel (right). Matches VS Code's Secondary Side Bar accelerator.                                                                                                                                                                                                                                                                                                                        |
| `Cmd+Option+M`                 | Toggle between the visual and source editors, keeping your place.                                                                                                                                                                                                                                                                                                                                                |
| `Cmd+Option+E`                 | **View in source** — jump from the visual editor to the Markdown for the block at the cursor, centered and highlighted (visual editor only).                                                                                                                                                                                                                                                                     |
| `Cmd+J` / `Ctrl+J`             | Show / Hide the Terminal in its current placement (macOS / Windows and Linux desktop apps). With text selected, stages the passage into the AI CLI's input instead — nothing runs until you press Enter.                                                                                                                                                                                                         |
| `` Ctrl+` ``                   | Show / Hide the Terminal too — the VS Code / Zed chord, literal `Ctrl` (desktop app). Works even while a terminal holds focus. With text selected it stages the passage like `Cmd+J` instead of toggling.                                                                                                                                                                                                        |
| `Cmd+Shift+J` / `Ctrl+Shift+J` | Open an additional terminal tab (macOS / Windows and Linux desktop apps). With text selected, stages the passage into the new tab's AI CLI input.                                                                                                                                                                                                                                                                |
| `Cmd+P`                        | Open the [command palette](https://openknowledge.ai/docs/features/editor#command-palette) from anywhere — unlike `Cmd+K`, it opens the palette even while text is selected in the visual editor.                                                                                                                                                                                                                 |
| `Cmd+L`                        | Open and focus the [Ask AI](https://openknowledge.ai/docs/features/editor#ask-ai) composer (desktop app).                                                                                                                                                                                                                                                                                                        |
| `Cmd+Shift+D` / `Ctrl+Shift+D` | Start a bug report (desktop app). The screenshot it attaches is the screen exactly as it looks when you press the chord — open menus, tooltips and hover states included — plus a marker drawn where your pointer was, since a screenshot never includes the cursor. No marker appears if the pointer has not moved since the window loaded, or has since left the window. Nothing is sent until you press Send. |
| `Cmd+Shift+M`                  | [Comment](https://openknowledge.ai/docs/features/comments) on the selected text (visual editor).                                                                                                                                                                                                                                                                                                                 |
| `Cmd+Shift+Enter`              | Send the checked comments the open **Comments** tab is showing to an agent. Does nothing while that tab is closed.                                                                                                                                                                                                                                                                                               |
| `Cmd+F`                        | Find in the visual editor.                                                                                                                                                                                                                                                                                                                                                                                       |
| `Cmd+Option+F`                 | Find and replace (`Ctrl+H` on Windows / Linux).                                                                                                                                                                                                                                                                                                                                                                  |
| `Cmd+G` / `Shift+Cmd+G`        | Next / previous find match.                                                                                                                                                                                                                                                                                                                                                                                      |

In the desktop app the sidebar and terminal toggles also live in the **View** menu (see below), where each label flips between **Show …** and **Hide …** based on current state.

Inside an AI editor's embedded view, the **Open with AI** menus, the bottom Ask AI composer, and the empty-state agent grid are hidden; you're already inside an agent, so the handoff would loop back into the same surface. OpenKnowledge's MCP tools and the document itself stay fully available; only the launchers are gone.

## Application menus

The OpenKnowledge desktop app keeps its menu bar in sync with the active editor target: File menu items enable / disable based on whether a doc, folder, or nothing is currently selected. On macOS this is the system menu bar; on Windows and Linux the same menus live in the app window's title bar, with `Ctrl` in place of `Cmd`.

### File menu

Alongside the project items (Recent project, New project…, Switch Project… `Cmd+Shift+P`, Open Folder… `Cmd+O`), the worktree pair (New worktree…, Switch worktree…), **Set up OpenKnowledge integrations…** (re-runs the first-launch integrations setup), and Close tab, the File menu surfaces the most-used sidebar affordances so they're reachable from the keyboard:

| Item                   | Accelerator   | Behavior                                                                                                                                                                                               |
| ---------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **New File**           | `Cmd+N`       | Creates a new file at the project root (or under the active folder if one is selected).                                                                                                                |
| **New Folder**         | `Cmd+Shift+N` | Creates a new folder.                                                                                                                                                                                  |
| **New from Template…** | (none)        | Opens the template picker.                                                                                                                                                                             |
| **Duplicate**          | `Cmd+D`       | Duplicates the active file or folder. Disabled when nothing is selected.                                                                                                                               |
| **Rename**             | (none)        | Inline-renames the active file or folder. Disabled when nothing is selected.                                                                                                                           |
| **Move to Trash**      | `Cmd+Delete`  | Sends the active file or folder to the OS trash (with the same confirm modal as the sidebar). On Windows the item reads **Move to Recycle Bin**. Disabled when nothing is selected.                    |
| **Reveal in Finder**   | (none)        | Reveals the active target in your file manager. The label adapts per platform — **Reveal in File Explorer** on Windows, **Open containing folder** on Linux. Empty selection reveals the project root. |
| **Open with AI ▸**     | (none)        | Same submenu as the sidebar, dispatched at the active scope.                                                                                                                                           |
| **Copy Path ▸**        | (none)        | Full / Relative path of the active target.                                                                                                                                                             |

### Edit menu

Alongside the standard Undo / Redo / Cut / Copy / Paste / Select All items:

| Item                            | Accelerator | Behavior                                                                                                                                                                                                                                                                                                                      |
| ------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Check Spelling While Typing** | (none)      | Checkbox reflecting whether spell check is on. Toggling applies app-wide and persists across relaunches — it's the same switch the editor right-click menu's Disable / Enable Spell Check rows flip (see [Right-click menu and spell check](https://openknowledge.ai/docs/features/editor#right-click-menu-and-spell-check)). |

### View menu

Before the existing Zoom and Toggle Fullscreen items, the View menu gets:

| Item                           | Accelerator        | Bound to                                                                                                                                                                                                              |
| ------------------------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Show / Hide Sidebar**        | `Cmd+Option+S`     | Toggles the file sidebar (left). Label flips between Show and Hide based on current state, matching Finder.                                                                                                           |
| **Show / Hide Document Panel** | `Cmd+Option+B`     | Toggles the document panel (right).                                                                                                                                                                                   |
| **Show / Hide Terminal**       | `Cmd+J` / `Ctrl+J` | Toggles the Terminal in its current placement. With editor text selected, stages the selection into the AI CLI's input instead of toggling. (`` Ctrl+` `` toggles it too, though that chord has no menu accelerator.) |
| **Show Hidden Files**          | `Cmd+Shift+.`      | `appearance.sidebar.showHiddenFiles`; same toggle as the sidebar's right-click menu.                                                                                                                                  |
| **Expand All**                 | (none)             | Expands every folder in the tree. Hidden when nothing is collapsed.                                                                                                                                                   |
| **Collapse All**               | (none)             | Collapses every folder in the tree. Hidden when nothing is expanded.                                                                                                                                                  |

Toggling the menu checkboxes flips the underlying config field, which propagates back to the sidebar via the standard CRDT subscription; both surfaces stay synchronized.

### Terminal menu

| Item                                | Accelerator | Behavior                                                                                                                                                                              |
| ----------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **New Terminal**                    | (none)      | Opens a new terminal tab, revealing the Terminal in its current placement if it's hidden.                                                                                             |
| **New Terminal Window**             | (none)      | Opens a terminal in its own window — the same multi-session tabs, switched with `Cmd+<number>` on macOS or `Ctrl+<number>` on Windows and Linux, shells starting at the project root. |
| **Move Terminal to right / bottom** | (none)      | Moves the live Terminal workspace to the opposite placement. The label names the destination.                                                                                         |
| **Kill Terminal**                   | (none)      | Closes the active terminal tab and kills its shell.                                                                                                                                   |

## Real-time collaboration

Everything in the editor (body, frontmatter, sidebar toggles) is backed by a CRDT. You and multiple AI agents writing through MCP can edit the same doc concurrently. Per-user presence is shown in the editor header; the **agent activity** view ([Agent activity](https://openknowledge.ai/docs/features/agent-activity)) groups every burst of agent edits with per-burst diffs and selective undo.

For recovery + history, see [Timeline and recovery](https://openknowledge.ai/docs/features/timeline-and-recovery).

## See also

- [Ignore patterns](https://openknowledge.ai/docs/features/ignore-patterns): `.okignore` syntax and Hide-from-tree mechanics
- [Configuration reference](https://openknowledge.ai/docs/reference/configuration): `.ok/config.yml` schema including `appearance.sidebar.*`
- [Agent activity](https://openknowledge.ai/docs/features/agent-activity): review and undo per-session agent edits
- [Timeline and recovery](https://openknowledge.ai/docs/features/timeline-and-recovery): version history and restore

# GitHub sync (https://openknowledge.ai/docs/features/github-sync)

Clone repos from GitHub, auto-sync changes with your team, and resolve conflicts.

OpenKnowledge can connect a project to a GitHub remote repository and keep it synced over time. When GitHub sync is enabled, OpenKnowledge keeps your copy current with the remote.

The sync mode controls what runs automatically:

- **Manual** — nothing moves until you ask. Pull, push, or both from the sync menu whenever you choose.
- **Auto (Pull only)** — updates flow in from the remote on a schedule; your edits stay on this computer and are never pushed.
- **Auto (Pull and Push)** — your edits are committed and pushed automatically, and remote changes pull in.

The mode only chooses the schedule. The manual **Pull**, **Push**, and **Pull and Push** buttons in the sync menu do the same thing in every mode: **Pull** brings in remote changes without ever committing your work, **Push** commits and sends your work, and **Pull and Push** does both in one pass. If your GitHub account can't push to the repository, **Push** and **Pull and Push** are hidden rather than offered and rejected — see [GitHub-side blockers](https://openknowledge.ai/docs/features/github-sync#github-side-blockers).

> **Warn**
>
> Only enable Auto (Pull and Push) for repositories where you are comfortable with OpenKnowledge writing commits to the remote history. If you are worried about automated commits cluttering your Git history, use Manual or Auto (Pull only) for that repository.

## What GitHub sync is for

Use GitHub sync when you want to collaborate with your team on an OpenKnowledge project.

It helps you:

- Keep your project in sync with updates from your team
- Resolve conflicts between your edits and your team's edits

## Clone an existing GitHub repository

These steps use the desktop app; from a terminal, [`ok clone`](https://openknowledge.ai/docs/reference/cli#clone-from-github) does the same.

### Open the clone dialog

From the Navigator window, click **Clone from GitHub**. You can open the Navigator window from the editor by clicking on your project name in the bottom left and then clicking **Switch project**.

### Sign into GitHub

If you are already signed in with the GitHub CLI, OpenKnowledge will use those credentials by default. If not, click **Connect GitHub** and complete the device authentication flow.

### Choose a repository

Paste a repository URL or `owner/repo` shorthand. You can also filter and select from a list of repositories your account can access.

### Open the project

When the clone completes, the cloned project opens in a new editor window and the Navigator closes. Re-summon the Navigator from inside the editor via the sidebar Switch Project pill, the File menu, or the Command Palette.

## Publish a local project

You can publish your local project to GitHub directly from the OpenKnowledge app by opening a folder or file and clicking the **Share** button in the top right of the editor.

This will open a dialog where you can choose an owner and repository name. You can also choose to make the repository public or private.

## Enable GitHub sync

### How often sync runs

Both automatic modes run on a timer, and the two directions have their own cadence: OpenKnowledge checks for updates every **30 seconds** and pushes your edits every **60 seconds** by default. They differ on purpose. A pull is a read, so checking more often only means you see other people's work sooner. A push is a commit, so pushing more often leaves more of them in the history everyone reads.

Change either one in **Settings → Sync → Advanced** (the disclosure is collapsed by default), under **Check for updates every** and **Push my edits every**. The controls appear only while an automatic mode is active — in Manual nothing runs on a schedule, so there is no cadence to set. Both accept 30 seconds, 1 minute, 5 minutes, 15 minutes, or 1 hour, and both are per-machine — your choice stays on this computer and is not committed to the repository. **Push my edits every** appears only in Auto (Pull and Push), since no other mode pushes on a schedule.

After repeated failures, OpenKnowledge slows each direction down independently: 3 failures in a row hold the next attempt for at least 5 minutes, 5 failures for at least 15 minutes, and 8 failures for at least 1 hour. These are floors, not replacements: if your configured interval is already longer than the tier, the interval stands. The two directions back off on separate counters — push failures do not slow down pulls, and pull failures do not slow down pushes. A successful sync in a direction resets that direction's backoff; a manual trigger from the sync indicator (or clicking Pull/Push) resets both directions immediately. One cross-direction case: if pushes were backing off because the network was unreachable, the next successful update check releases that backoff straight away — it is direct proof the remote is reachable again. A backoff from a rejected push (a protected branch, say) is left alone, since reaching the remote says nothing about whether it will accept the write.

While you are signed out, **Auto (Pull only)** checks for updates at most every 3 minutes no matter what you choose here — a signed-out client is anonymous to GitHub, and the limit protects the repository from the combined polling of everyone following it. Auto (Pull and Push) is unaffected: pushing requires being signed in to begin with.

When you open a freshly cloned project, you will be prompted to enable sync. If you have push access to the repository, you will be prompted to enable **Auto (Pull and Push)**. If you have read-only access, you will be prompted to enable **Auto (Pull only)**. You can change the sync mode from the sync popover (the Sync icon) or from **Settings → Sync** at any time.

If you want to set a default sync setting for future collaborators, go to the project settings page and navigate to the **Sync** → **Shared default** section. This writes [`autoSync.default`](https://openknowledge.ai/docs/reference/configuration) — one of `off` (Manual), `follow` (Auto, pull only), or `full` (Auto, pull and push) — to the project's committed `.ok/config.yml`, pre-answering the enable-sync prompt for everyone who clones the repo; each machine's own auto-sync choice overrides it.

### Manual

Nothing runs on a schedule. The sync menu's **Pull**, **Push**, and **Pull and Push** buttons cover everything on demand, and the menu's status section shows what each would do before you click: which files a pull would bring in, which files a push would include, and which changed files sit outside OpenKnowledge's commit scope and would be skipped. When sync is paused because your local edits overlap an incoming update, the same menu grows a **Changed here and on the remote** section listing the blocked files with a **Commit and sync** button — see [Conflicts and pending changes](https://openknowledge.ai/docs/features/github-sync#conflicts-and-pending-changes).

### Auto (Pull and Push)

With push-and-pull sync active, OpenKnowledge:

- Fetches commits from the remote
- Commits your edits locally using the configured Git identity
- Pushes those commits back to the remote so collaborators see your changes

If your Git identity isn't set, OpenKnowledge commits under a default "OpenKnowledge" author; the sync status indicator reminds you to set one so teammates see your name.

### Auto (Pull only)

With pull-only sync active, OpenKnowledge:

- Fetches commits from the remote
- Never commits or pushes your edits on its own — if you have push access, the manual **Push** button is still there when you want to send something. Collaborators who can't push to the repository don't see it; see [GitHub-side blockers](https://openknowledge.ai/docs/features/github-sync#github-side-blockers).

Your own edits stay on this computer. You can keep editing: when an update arrives, non-overlapping changes combine automatically, and an edit to the same lines the remote changed surfaces in the [conflict view](https://openknowledge.ai/docs/features/github-sync#resolving-a-conflict) where you choose which side to keep. Your branch always tracks the remote tip and never forks. The same applies to the manual **Pull** button in every mode — a pull never commits your in-progress work.

## Authentication

If you are logged in with the GitHub CLI, OpenKnowledge will use those credentials for GitHub operations. If not, you can complete device authentication to generate an OAuth token. OpenKnowledge stores this token in the keychain and reuses it for syncing, cloning, and publishing.

Manage the connection from **Settings → Account**. **Connect GitHub** authorizes OpenKnowledge to browse and sync your repositories; **Disconnect** clears OpenKnowledge's GitHub token only. From a terminal, the [`ok auth`](https://openknowledge.ai/docs/reference/cli) subcommands manage the same credentials: `ok auth signout` matches **Disconnect**, `ok auth pat` stores a Personal Access Token when device authentication isn't an option, and `--host` targets GitHub Enterprise.

When a project's `origin` remote points at a GitHub Enterprise Server host, OpenKnowledge resolves credentials and identity for that host automatically: the account shown in settings, the sync token, the push-permission check, and the `ok auth` default `--host` all follow the workspace's remote instead of assuming github.com. Share links work with enterprise remotes too — a recipient opening a GHES share is asked to confirm the server unless they are already signed in to it.

### Which GitHub account is used

If you are signed into multiple accounts with the GitHub CLI, OpenKnowledge will use the account that is active by default. If you want to set a specific account for a project, you can do so by adding a username to the remote URL or a `credential.<url>.username` entry to your [Git configuration](https://github.com/git-ecosystem/git-credential-manager/blob/main/docs/multiple-users.md).

OpenKnowledge will choose the GitHub credential to use for a project in the following order of priority:

| Priority | Declaration                                                          | Where it lives                                                             | How to set it                                                       |
| -------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| 1        | A username in the remote URL — `https://alice@github.com/owner/repo` | that clone's `.git/config`                                                 | `git remote set-url origin https://alice@github.com/owner/repo.git` |
| 2        | A `credential.<url>.username` entry                                  | git config, any scope                                                      | `git config --global credential.https://github.com.username alice`  |
| 3        | Neither                                                              | the GitHub CLI's active account, or the account connected in OpenKnowledge | —                                                                   |

The username entries above apply to HTTPS remotes only. SSH projects follow the GitHub CLI's active account (switch it with `gh auth switch`), and that account decides only which identity the push-permission check runs as — the push itself authenticates with your SSH key, chosen by your SSH configuration. To pin an SSH project to a declared account, switch it to an HTTPS remote.

## Sync status and conflicts

The sync status indicator in the editor opens the sync menu: the mode selector, the manual **Pull** / **Push** / **Pull and Push** buttons, and a live status section showing how far ahead or behind the remote you are, which files a pull would bring in, which files a push would include (and which it would skip), unresolved conflicts, and when a sync last ran. Opening the menu refreshes the remote counts with a read-only fetch — nothing in your copy moves until you press a button. From a terminal, `ok sync` commits, pulls, and pushes in one pass; `ok pull` and `ok push` cover one direction each. All three work whether or not the editor is open (see the [CLI reference](https://openknowledge.ai/docs/reference/cli)).

## Resolving a conflict

When a remote update conflicts with your local edits, OpenKnowledge surfaces the conflict in three places at once: the conflicted file gains a `⚠` badge on its tab, a pinned **Conflicts** section appears at the top of the file tree listing every conflicted file, and the editor area swaps from the normal editor to a unified diff view.

Click the badged tab or any row in the Conflicts section to focus the conflict.

You can't edit a page with conflicts until they're resolved; the same applies to agents writing over MCP.

## Work on multiple branches

In the desktop app you can open several branches of the same project at once, each in its own window. Click your project name in the bottom left and pick a branch or worktree from the project switcher (open worktrees also appear in the Command Palette); **New worktree** starts a fresh branch or checks out an existing one. Opening a branch that has no worktree yet creates one on demand under `.ok/worktrees/`, kept out of `git status`, so each window runs its own editor and server without touching your main working copy.

## Common failure modes

A few situations stop sync from completing successfully. In each case the sync status indicator changes color and tells you what's wrong. Except where noted, sync stays paused until the underlying issue is fixed, then picks back up on its own.

### Conflicts and pending changes

- **Unresolved conflicts.** If even one file has a conflict between your edits and your team's, sync waits. You can keep working on every other doc; only the conflicted files are frozen until you choose a side from the diff view. See [Resolving a conflict](https://openknowledge.ai/docs/features/github-sync#resolving-a-conflict) above.
- **Local changes would be overwritten by an incoming update.** A teammate's update is ready to pull, but a file it touches has uncommitted local changes. Doc edits are committed automatically before merging, so this usually means other tracked files, like configs. Sync pauses rather than clobbering your work. The sync menu lists every blocked file under **Changed here and on the remote**, and **Commit and sync** commits exactly those files and resumes — nothing else in your working tree is touched. If you would rather stash the changes or throw them away, **Resolve in terminal** (desktop app only) opens a terminal on the project so you can run that yourself. OpenKnowledge never discards uncommitted work for you.
- **Edits made outside the app aren't ready yet.** If you (or another tool) edited a file directly on disk while sync wanted to merge, OpenKnowledge waits until those external edits settle before merging. Usually this clears in a second or two on its own.

### Project isn't on a normal branch

- **The project is parked on a specific commit, not a branch.** This usually means someone checked out an older commit from the terminal to look around. There's no branch to push to, so sync pauses. Switch back to your normal branch (`main`, your working branch, whichever) and sync resumes.
- **Diverged history.** When the remote has been rewritten (force-push, rebase, branch reset) in a way OpenKnowledge can't reconcile automatically, sync stops and shows the error. This one usually needs a fix from the command line. Ask whoever rewrote the history for help, or restore from the [Timeline](https://openknowledge.ai/docs/features/timeline-and-recovery).

### GitHub-side blockers

- **Authentication errors.** Your stored credentials expired, were revoked, or don't have access to this repository anymore. Reconnect from the sync indicator (or from **Settings → Account**) to clear the error.
- **You don't have permission to push to this repo.** Someone shared a project backed by a GitHub repo where your account isn't a collaborator (or has read-only access on a private repo). OpenKnowledge checks this up front and offers **Auto (Pull only)** instead. In that mode the manual **Push** and **Pull and Push** buttons are hidden, since GitHub would reject them.
- **Protected branches and rejected pushes.** If the target branch requires reviews, signed commits, or status checks, GitHub rejects the push. OpenKnowledge turns sync off automatically so it stops retrying, and it stays off until you turn it back on from the sync indicator or **Settings → Sync**. Switch to a branch you can push to, or use a pull request for the protected branch instead.

### Temporary problems

- **Network or service hiccups.** Flaky Wi-Fi, a VPN drop, or a brief GitHub outage. OpenKnowledge retries automatically, slowing down progressively as described in [How often sync runs](https://openknowledge.ai/docs/features/github-sync#how-often-sync-runs). No action needed; a manual trigger from the sync indicator resets the backoff immediately if you want sync to run sooner.

# Ignore patterns (https://openknowledge.ai/docs/features/ignore-patterns)

Hide files and folders from search and your AI agents.

OpenKnowledge indexes the markdown in your project so it can power search and the tools your AI agents call. When you don't want a file in that picture (drafts, scratch notes, vendor-pasted material), hide it with an ignore pattern. Hidden items don't show up in search and don't reach AI agents through MCP; they stay visible in the sidebar, which lists what's on disk, not what's indexed. Nothing on disk is deleted.

## Hide a file

Right-click any file in the sidebar and pick **Hide this file**. OpenKnowledge appends a matching pattern to `.okignore` and confirms with a toast pointing at Settings; the row stays in the sidebar.

Right-click a folder and pick **Hide folder** to hide every file inside it.

The hidden item is now ignored in search, agent context, and the document list returned by MCP tools. To bring it back, remove the matching row from Settings.

## Manage patterns from Settings

Open Settings (`Cmd/Ctrl-,` or App menu → Settings…) and stay on the **This project** tab. The **Ignore patterns** section lists every pattern in your project, one per row.

You can:

- **Add a pattern.** Type a folder name (`drafts/`) or a file shape (`*.draft.md`) and press Enter.
- **Edit a row.** Click into it and type. Changes save when you blur or press Enter.
- **Reorder rows.** Drag the handle on the left.
- **Remove a row.** Click the × on the right.

There is no Save button; every edit auto-saves to `.okignore` on disk.

### Pattern preview

Each row shows a **matches N files** preview as you type. Files already hidden by another row, or excluded by `.gitignore`, don't count toward it. A pattern that reports `matches 0` is usually a typo, but can also mean the file is already hidden by another rule.

### Heuristic warnings

A small amber warning icon appears next to a row when the pattern has a syntax oddity (a stray `[`, a trailing backslash, leading whitespace, a lone `!`, or an embedded line break). These warnings are non-blocking nudges, not errors. The pattern still saves; the icon is a hint that you may have meant something different.

## Edits take effect immediately

Adding, editing, or removing a pattern updates the running editor in under a second. Search re-indexes and the next MCP tool call sees the new scope. No restart, no manual refresh.

External edits work the same way: open `.okignore` in another editor, save, and the running editor picks up the change.

## Advanced patterns

`.okignore` uses [gitignore syntax](https://git-scm.com/docs/gitignore) parsed by the [`ignore`](https://www.npmjs.com/package/ignore) npm library, a JavaScript implementation of the gitignore pattern spec. The list editor handles common cases (folder-prefixed paths, glob patterns, plain filenames). For full syntax (negation with `!`, anchoring with `/`, the `**` recursive glob), click **Show advanced** under the list to edit `.okignore` as raw text.

```text title=".okignore"
# Exclude a directory
drafts/

# Exclude any file matching a pattern
*.draft.md

# Re-include a file .gitignore excluded
!keep.md
```

## Where the patterns live

Patterns are stored in a `.okignore` file at your project root, alongside `.gitignore`. If `content.dir` points somewhere other than the project root, Settings manages the `.okignore` at the content directory root instead. The file is checked into version control and shared with anyone who clones the repo. Hand-edit it directly in any editor; OpenKnowledge picks up changes the same way it picks up Settings edits. To check the resulting scope from the terminal, run `ok preview`; it shows what content OpenKnowledge will track, no server required.

OpenKnowledge scaffolds a starter `.okignore` with a commented header the first time it sets up a project, so the file is there from day one.

## Nested files

Drop additional `.okignore` files at any folder depth. Each file scopes to its directory, the same way nested `.gitignore` files do. Nested files are honored at runtime but aren't currently editable from Settings; open them in your IDE.

If OpenKnowledge can't reload your ignore files (rare; usually a filesystem error), it surfaces a toast with the path that triggered the reload. The workspace keeps using the previous patterns until the next change.

## Working with `.gitignore`

`.gitignore` exclusions are honored automatically; no need to re-list them in `.okignore`. To re-include a file that `.gitignore` excluded, add a negation pattern:

```text title=".okignore"
!secret.md
```

`.gitignore` itself is git's contract; OpenKnowledge never edits it. If you want a file out of git but visible in OpenKnowledge, that's where the negation goes.

The negation is an OpenKnowledge behavior; `git` itself only reads `.gitignore`, so an `!` line in `.okignore` does not change what git tracks.

## Temporarily surfacing hidden files

The sidebar always lists every file on disk under the content directory, including files excluded by `.gitignore` / `.okignore` (listings truncate at 50,000 entries, deepest paths first). Dot-prefixed entries are the exception, so the sidebar's right-click menu and the **View** menu carry a project-local **Show Hidden Files** toggle for when you need to see them without permanently editing `.okignore`:

- **Show Hidden Files** stops the client from hiding paths that start with `.` (recovers allowed dotfiles like `brain/.archived/note.md`). Tooling internals (`.git/`, `.ok/`, `node_modules/`), secret-bearing paths (`.env` files, private keys, credential files, `.ssh/`), and OS junk (`.DS_Store`) stay hidden in every mode.

The setting persists per-project per-machine. See [Editor → Show Hidden Files](https://openknowledge.ai/docs/features/editor#show-hidden-files) for the full UX.

## See also

- [Configuration reference](https://openknowledge.ai/docs/reference/configuration): the project config schema and `content.dir`.
- [Editor → Hiding files and folders](https://openknowledge.ai/docs/features/editor#hiding-files-and-folders): the right-click flow.

# Share links (https://openknowledge.ai/docs/features/share)

Copy a link to any doc or folder and send it to a teammate. OpenKnowledge handles the GitHub round-trip, the deep link, and the branch on the other end.

OpenKnowledge has a Share button in the editor toolbar. Click it and the app copies a `https://openknowledge.ai/d/…` URL to your clipboard. Anyone you send it to lands on a splash page that knows the project and target, and a single click opens it in their copy of OpenKnowledge.

Share links give each recipient their own copy, kept in sync through GitHub. For real-time editing where everyone works on one live server instead, see the [Remote Control overview](https://openknowledge.ai/docs/remote-control/overview).

## Sending a share link

Click the Share button to copy a deep link to the current doc or folder to your clipboard. The sharing functionality is backed by GitHub and requires that your project has a remote origin.

### Without a remote

If the project has no `origin` configured, the Share button opens the **Publish to GitHub** wizard. The wizard:

1. Lists the GitHub accounts and orgs you can push to.
2. Validates the repo name as you type; name collisions surface inline.
3. On submit, creates the repository through GitHub's API, sets it as `origin`, and runs the initial `git push`. SAML SSO blocks and other GitHub errors surface as a banner.
4. Once the push lands, shows the share URL with a **Copy share link** button.

If the push fails after the repo was created (auth lost mid-flow, network blip), the wizard shows a **Retry push** affordance. The GitHub-side repo isn't recreated, only the push is retried.

### Freshness warnings

A share link points at GitHub, but your local edits only reach GitHub through **Auto (Pull and Push)** or a manual push — in Manual and Auto (Pull only), nothing is pushed on a schedule. When you click Share, OpenKnowledge checks the shared doc or folder against its last-known state on GitHub and, if the link won't yet show what you're looking at, adds a non-blocking warning row to the popover:

- **Not on GitHub yet.** The target has never been pushed, so the link won't resolve until it is. When the project isn't on Auto (Pull and Push), the row offers **Enable Auto (Pull and Push)** and **How to push manually**; when it is, it offers **Sync now** and clears itself once the push lands.
- **Unpushed changes.** The target is on GitHub but has local edits that aren't, so recipients see the last pushed version. On Auto (Pull and Push) this stays silent — the next scheduled push closes the gap.
- **Empty folder.** The folder holds nothing Git can track — it has no documents, or everything inside it is ignored — so the link won't resolve, and unlike the never-pushed case no push will fix it. The row names the only remedy, adding a document, and offers no sync actions at all. Only folders reach this state: a doc is trackable at any size, including zero bytes.

If you don't have write access to the repo, the row explains that instead of offering a push action. The check is local, adds no network round-trip, and never blocks sharing: if it can't run, the link still copies with no warning. Folder shares get the same treatment as docs, and can additionally hit the empty-folder case above, which a doc never can. Links minted by an agent get the same check: the [`share_link` MCP tool](https://openknowledge.ai/docs/reference/mcp) prepends the warning to its response so the agent can relay it with the link.

## Opening a share link

The recipient clicks the URL and lands on a splash page showing the target (file or folder) name, the `owner/repo` path, and — for shares from a non-default branch — the branch name.

The splash offers two ways to receive:

- **Desktop app.** Click **Open in desktop app** to fire the deep link into the OpenKnowledge desktop app — on the shared doc, folder overview, or project root depending on what was shared. **Download the app** routes recipients to the matching macOS, Windows, or Linux download flow, with a download page listing all available builds when the browser cannot identify the platform. The download remembers the share (a first-party cookie, valid for seven days), and the app opens the shared target on its first launch.
- **CLI (any platform).** Copy the pre-filled `npm install -g @inkeep/open-knowledge` then `ok clone <owner/repo> -b <branch>` from the splash to clone the repo locally and open the editor. The clone command is always branch-pinned, but if the feature branch has been deleted, it will fall back to the default branch. See the [CLI reference](https://openknowledge.ai/docs/reference/cli#clone-from-github) for `ok clone` and related commands.

## What the desktop app does when it receives the link

The deep link arrives at the desktop app's handler, which runs a three-question receive flow:

1. **Q1: Do you already have this repo?** The app scans recent projects for one whose `gitRemoteUrl` matches the share's `owner/repo`. A hit silently routes to that project (no dialog, the window just opens and navigates to the shared doc, folder, or project root).
2. **Q2: Clone or locate?** If Q1 misses, a dialog asks "Clone to a new folder" or "I already have it locally". The locate path lets you point at an existing folder.
3. **Q3: Clone.** On the clone path, the app runs an auth check, asks where to put the folder, then runs `git clone -b <share-branch>` so the new checkout lands on the right branch before opening.

### Receiving on a different branch

If you have the repo open locally but on a different branch, the app opens a branch-switch dialog instead of silently showing the wrong content. It compares the shared target (file or folder) against your current branch and against GitHub, then offers the action that matches what it finds:

- **On your current branch.** Open it where you are, or switch to the share's branch and open.
- **Not local yet, but on GitHub (your local copy is behind).** **Switch and update branch** fast-forwards your local branch to GitHub's tip and opens the doc. This is fast-forward-only — the receive flow never merges. A branch that has diverged from GitHub instead gets a plain switch with a note that the doc will appear once the branch syncs.
- **Moved.** If GitHub shows the target was renamed, the dialog offers to open it at its new path.
- **Removed, or never pushed.** A deleted target is messaged as removed from the branch; a path that never existed on the branch is messaged as "may not have been pushed yet." The two are distinct, and neither drops you into an empty create-mode editor.

If uncommitted changes conflict with the switch, switching is disabled until you commit or stash them; if the share's branch is already checked out in another worktree, the dialog offers to open that worktree instead.

If OpenKnowledge can't reach GitHub to make this call, the dialog falls back to the plain switch.

### When the shared target is not on your local copy

Even on the right branch, a shared target (file or folder) can be renamed or deleted after a link is minted. In this case, OpenKnowledge offers to open the renamed target at its new path, a plain "removed from this branch" message with a link back to the parent folder, or "not pushed yet" when the path never existed.
If the target was moved, renamed, or deleted in your own local copy, OpenKnowledge tells you to commit your changes manually or enable Auto (Pull and Push).
If the target is on GitHub but hasn't reached your local copy yet, OpenKnowledge alerts you that your local copy is behind. The **Pull latest changes** button on this message fetches from GitHub and fast-forwards your copy in place. It only ever pulls: nothing is committed or pushed on your behalf, and your uncommitted edits ride on top of what comes in. If the pull can't run — another sync operation is already in flight, or GitHub is unreachable — the reason appears under the message and the button stays available to retry.

### Receiving without push permission

If the shared repo is one your GitHub account can't push to (you're not a collaborator on a public repo, or you have read-only access on a private one), OpenKnowledge offers **[Auto (Pull only)](https://openknowledge.ai/docs/features/github-sync#auto-pull-only)** at first open instead of the full "Enable Auto (Pull and Push)?" prompt: your copy keeps up to date by pulling the latest from GitHub automatically, while your own edits stay on your computer. You can keep editing — an incoming update combines with your non-overlapping changes automatically, and an edit to the same lines surfaces as a conflict to resolve.

See [GitHub sync](https://openknowledge.ai/docs/features/github-sync) for how to automatically sync changes to GitHub, and [Timeline and recovery](https://openknowledge.ai/docs/features/timeline-and-recovery) for per-doc history once the doc is open.

# Skills (https://openknowledge.ai/docs/features/skills)

Create, import, version, and install Agent Skills across your editors from one OpenKnowledge source.

A skill in OpenKnowledge is content, not a config file: authored or imported in the WYSIWYG editor, versioned with your base, and installed into every editor from one source.

```html preview
<div style="padding:18px">
  <div class="flow">
    <div class="col">
      <div class="lbl">edit in the WYSIWYG editor</div>
      <div class="ed">
        <div class="h3">Log a fishing trip</div>
        <div class="p">Capture <b>date</b>, spot, and catch, then link the spot.</div>
        <div class="meta">same editor as your docs &middot; versioned</div>
      </div>
    </div>
    <div class="mid"><div class="arr">→</div><div id="inst" class="pill">install</div></div>
    <div class="col">
      <div class="lbl">symlinked into every editor</div>
      <div id="eds" class="eds"></div>
    </div>
  </div>
  <div class="cap">One canonical skill folder, mirrored into each editor. Edit once, no re-install, no drift.</div>
</div>
<style>
.flow{display:grid;grid-template-columns:1.15fr auto 1fr;gap:16px;align-items:center}
@media(max-width:620px){.flow{grid-template-columns:1fr}.mid .arr{transform:rotate(90deg)}}
.lbl{font:11px ui-monospace,monospace;text-transform:uppercase;letter-spacing:.04em;color:var(--muted-foreground);margin-bottom:6px}
.ed{border:1px solid var(--border);border-radius:12px;background:var(--card);padding:13px;box-shadow:0 1px 3px rgba(0,0,0,.06)}
.ed .h3{font-weight:600;font-size:14.5px}
.ed .p{color:var(--muted-foreground);font-size:12.5px;margin:5px 0}
.ed .meta{font-size:11.5px;color:var(--accent-ink)}
.mid{display:flex;flex-direction:column;align-items:center;gap:7px;color:var(--muted-foreground)}
.pill{font:700 11px ui-monospace,monospace;text-transform:uppercase;letter-spacing:.04em;border-radius:999px;padding:4px 11px;border:1px solid var(--border);transition:all .3s}
.pill.on{background:var(--accent-soft);color:var(--accent-ink);border-color:var(--primary)}
.eds{display:flex;flex-wrap:wrap;gap:7px}
.eds .chip{border:1px solid var(--border);border-radius:9px;padding:6px 11px;font-size:12.5px;color:var(--muted-foreground);opacity:.5;transition:all .5s}
.eds .chip.on{opacity:1;border-color:var(--primary);background:var(--accent-soft);color:var(--accent-ink)}
.cap{margin-top:14px;color:var(--muted-foreground);font-size:12px}
.cap code{font-size:11.5px}
</style>
<script>
var eds=["Claude Code","Cursor","Codex","OpenCode","Pi"],box=document.getElementById("eds"),inst=document.getElementById("inst"),chips=[];
eds.forEach(function(e){var c=document.createElement("div");c.className="chip";c.textContent=e;box.appendChild(c);chips.push(c);});
var reduce=matchMedia("(prefers-reduced-motion:reduce)").matches;
function run(){
  inst.classList.remove("on");chips.forEach(function(c){c.classList.remove("on");});
  setTimeout(function(){inst.classList.add("on");},250);
  chips.forEach(function(c,i){setTimeout(function(){c.classList.add("on");},500+i*130);});
}
run();
if(!reduce)setInterval(run,3400);
</script>
```

## Edit skills like docs

A project skill is not a raw text file you hand-edit in a dotfile. It is a document in your base: written in the same WYSIWYG editor, versioned, with the same backlinks and live preview as the rest of your knowledge. Improving a skill is the same flow as improving any page.

## One source, every editor

A skill is a **real folder** in your agent's skills directory — `.claude/skills`, `.cursor/skills`, `.codex/skills`, `.opencode/skills`, or the vendor-neutral `.agents/skills` hub. OpenKnowledge keeps one real folder per skill name and mirrors it into your other editors. A new location is a symlink to that source by default, since it duplicates no bytes and cannot drift; you can convert any location to an independent copy when you want one, and a new location otherwise follows the form the skill's existing locations already use. Edit the source once and every agent has the change.

## Create, import, and install skills

Skills live in **Skills Studio**, which shows up in two places: a dock at the bottom of the file sidebar, where you author and browse, and a page in Settings, where you install the skills OpenKnowledge ships and decide which folders your AI tools share. The Settings side is covered in [Skills Studio in Settings](https://openknowledge.ai/docs/features/skills#skills-studio-in-settings).

### The Skills Studio dock

The dock sits under the file tree as a collapsible **Skills Studio** section, so a file and a skill can be open side by side. It starts collapsed and remembers its state per machine. Expanded, it takes up to 45% of the window and scrolls on its own, so the file tree always keeps a usable body; drag the handle above it to resize, or focus the handle and press Up / Down. Its toolbar shows on hover or keyboard focus, with two actions:

- **New skill** creates a project-scope skill named `new-skill` (then `new-skill-2`, and so on) and opens its `SKILL.md` as an editor tab. Rename it and fill in the description: the name is the id agents use to invoke it, and the description is what agents match on to decide when to use it.
- **Explore skills** opens the import dialog. **Explore** searches skills.sh, previews a skill before it lands in your base, and imports it with one click. **Upload** imports from skills.sh, GitHub, a git URL, a local path, a `.zip` / `.skill` archive, or a folder containing `SKILL.md`.

The palette's **Skills** command and an unresolved `/skill-name` link reveal the dock rather than opening a separate page. To hide the dock, untick **Skills Studio** in the sidebar's tree view options, run **Skills section** from the command palette, or set `appearance.sidebar.showSkillsSection` to `false`.

Agents use the same marketplace flow over MCP: `skills({ query: "..." })` searches skills.sh, then `import({ source, skill: name })` imports the selected result.

A skill is live for its agent the moment it lands. There is no separate "install" step to flip. The skill's **install** menu instead controls *where* it is available: add or remove editors, flip any editor between a copy and a symlink, or relocate the canonical source. If a source bundles several skills, OpenKnowledge asks which one to import; scripts are stored as versioned content for review and never run during import.

### Grouped by where they came from

Within each scope, the dock groups skills by source: the skills.sh publisher or GitHub repo they were imported from, or the Claude plugin that ships them. Skills you wrote yourself stay ungrouped at the top of their scope, the way your own files carry no label in the file tree; only content from elsewhere gets a row naming its origin. A source with a single skill collapses onto that skill's row instead of earning a group, and grouping is per scope, so one source can group under Project and sit flat under Global. OpenKnowledge's own built-ins publish to `inkeep/open-knowledge-skills` and group under it like anything else.

A group row leads with the publisher's avatar or the plugin's glyph; when the source resolves to a URL, that mark is a link to it. The group's menu offers **Update all from this source**, which re-pulls every skill in the group in one request (the source is cloned once for the whole batch) and reports per skill how many updated, how many were already current, and which failed and why. One skill that cannot be written does not stop the rest.

A skill row's menu offers **Pin to top** to keep it above the groups. Pins follow the skill's scope: a project pin stays with the repo (`appearance.sidebar.pinnedProjectSkills`, per machine), a global pin follows you into every project (`appearance.sidebar.pinnedGlobalSkills`, per user). A pinned skill keeps its normal row too, so its group still lists everything from that source. Turn grouping off with **Group skills by source** in the tree view options (`appearance.sidebar.showSkillGroups`).

## Skills Studio in Settings

**Settings → Skills Studio** exists at both scopes. It carries the skills OpenKnowledge ships, not the ones you author (those live in the dock), and the folders your AI tools read skills from.

**User scope** (under User) lists **Skills from OpenKnowledge**, the machine-wide bundles: `open-knowledge-discovery` (how to set up new projects with OpenKnowledge) and `open-knowledge-write-skill` (how to write a new skill and install it). Each row shows a one-line description written for a person, the AI tools it reaches, and an Install / Uninstall control behind a confirmation that names every path it will touch. Installing one adds it to every AI tool detected on this machine (`~/.agents/skills/` plus each editor's own skills folder). **Manage** opens the skill's preview tab, which shows the full `SKILL.md` (including the agent-facing trigger text the row leaves out), links to its source, and offers **Update** when the published copy differs. Built-ins never update on their own.

Only the discovery skill is offered at first launch. The first time you open Skills Studio, a short dialog explains the page and offers the authoring skill once, with the same disclosure as the install confirmation. **Not now** is remembered on this machine and the row underneath keeps the skill available; a skill you declined at setup is never re-offered.

**Project scope** (under This project) lists the project's own `open-knowledge` skill: how to use OpenKnowledge and its MCP tools. It lives in the project folder (`.claude/skills/`, `.cursor/skills/`, and the other project-local skill dirs), so it installs for everyone who opens the project. When it is missing, the page says so and offers **Install**.

Both scopes end with **Share skills between AI tools**, the folders each AI tool reads skills from. **Link** points one folder at another so both tools see the same skills, and you add a skill once instead of copying it into every tool. **unlink** turns a folder back into a real directory holding a per-skill symlink for each skill it currently sees, so nothing stops working and each skill's install menu manages it from there. **Add custom path** (`~/.myteam/skills`, `.myteam/skills`) includes a folder OpenKnowledge does not detect on its own. Linking refuses when the two folders hold different versions of the same skill, or when the target holds entries that are not skills; a folder that something outside OpenKnowledge changed is shown as it is on disk, and your next Link / unlink wins.

The **AI tools & CLI** page (User) and **AI tools** page (This project) keep only the *connections*: each editor's MCP entry and the `ok` command. They point at Skills Studio for anything skill-shaped.

A skill can also bundle `references/` and `scripts/` files beside its `SKILL.md`; agents read those through the [`skills` MCP tool](https://openknowledge.ai/docs/reference/mcp).

## Two scopes

- **Project** skills live in the base, versioned and shared with it like any other content.
- **Global** skills live in your home editor dirs (`~/.agents/skills`, `~/.claude/skills`, …) and follow you across every project on your machine. Outside any base, they carry no version history.

The skill that makes an agent behave well the moment you connect it (read and write through the tools, cite sources, keep the link graph healthy) is the project's built-in `open-knowledge` skill. OpenKnowledge ships it read-only: it shows in the dock grouped under `inkeep/open-knowledge-skills`, and **Settings → This project → Skills Studio** installs or removes it.

Moving a skill between scopes re-creates it in the destination scope: history does not transfer, but the locations it occupied are re-projected for you.

## Skills from your other tools

Skills you already keep in `.claude/skills`, `.cursor/skills`, and the like are versioned **in place, automatically** — OpenKnowledge reads and versions them where they live. There is no store to opt into and no `ok skills manage` switch.

The Skills Studio dock lists what OpenKnowledge found across your other tools: the Claude, Codex, Cursor, OpenCode, Copilot, Pi, and Agents skill dirs, plus Claude plugins. They appear as **Detected** rows. Click one to edit it in place; changes save straight to that editor's own copy. From the CLI, `ok skills installed` lists every skill across your agents (read-only) and `ok skills import <source>` brings a new one in.

> **Warn**
>
> Skills are an open standard, and a skill can carry scripts that run on your machine. Treat a third-party skill like installing software. Use trusted sources, and read what it does first.

## Staying up to date

An imported skill records where it came from, so its toolbar can show an **Update from source** action when upstream moves.

Updates from a remote source (skills.sh, GitHub, a git URL) are **not** applied automatically. A skill's instructions run in your agent, so continuing to trust a publisher is an explicit, per-skill opt-in: the **Auto-update** toggle starts off, and until you turn it on you get a diff to review before anything is rewritten. Skills imported from a local path on your own machine stay in sync by default, since the source is already yours.

## Skills we publish

OpenKnowledge's own skills are published at [inkeep/open-knowledge-skills](https://github.com/inkeep/open-knowledge-skills), and they install like any other Claude Code plugin marketplace:

```bash
/plugin marketplace add inkeep/open-knowledge-skills
/plugin install open-knowledge@open-knowledge-skills
```

`open-knowledge` is the platform skill that teaches an agent to read, search, link, and write markdown through the MCP server. The starter packs ship there too, and `ok seed --pack <id>` installs a pack's skills directly.

The repo is grouped by role: `skills/core/` holds the three built-ins, and `skills/starter-packs/<pack>/` holds each pack's skills next to a README explaining the pack. Each pack is also its own plugin, so you can install just the one you want:

```bash
/plugin install knowledge-base@open-knowledge-skills
/plugin install personal-crm@open-knowledge-skills
```

> **Info**
>
> Pack skills were renamed: the generated `open-knowledge-pack-…` names became product names, so `open-knowledge-pack-plain-notes` is now `note-taking` and `open-knowledge-pack-knowledge-base-research` is now `research-with-sources`.
>
> **Skills you already have are not renamed.** Nothing on your disk changes and your `.ok/skills-lock.json` is not rewritten — these are project-level skills that normally live in your git repo, so a silent rename would show up as an unexplained diff for you and everyone who pulls. An existing skill keeps its name, keeps working, and still updates from source. Re-seeding a pack recognizes it and will not install a second copy under the new name. Only newly installed skills use the new names.
>
> Four plugin handles were renamed alongside their packs (`entity-vault` → `personal-crm`, `okf` → `okf-knowledge-base`, `plain-notes` → `note-taking`, `writing-pipeline` → `writing-workflow`). The old handles still resolve for one release and are marked outdated in the marketplace; switch to the new ones. `ok seed --pack <id>` flags are unchanged.

## Up next

- **[Agentic search](https://openknowledge.ai/docs/reference/agentic-search)**: how the agent reads your base.
- **[LLM wiki](https://openknowledge.ai/docs/workflows/karpathy-llm-wiki)**: build a source-grounded knowledge base an agent curates, end to end.

# Timeline and recovery (https://openknowledge.ai/docs/features/timeline-and-recovery)

Review the edit history for the open document, including agent, human, upstream sync, and file system changes, with inline diffs and version restore.

The timeline shows you the history of edits made to the document you have open. From there you can view or revert to older versions of the document.

## What the timeline shows

Open the timeline from the document side panel by clicking the Timeline icon. Each entry shows who made the change, when it happened, and a short summary of the change.

Timeline entries include:

- **Agent and human edits** with contributor attribution and change summaries
- **Upstream sync** entries from Git
- **File system** changes made outside of OpenKnowledge

Project-wide checkpoints saved by agents via the [`checkpoint` MCP tool](https://openknowledge.ai/docs/reference/mcp#tools) do not appear on the timeline; agents find and restore them with the `history` and `restore_version` tools.

The timeline is not the editor's undo history. Each timeline entry is a snapshot of the whole document, while undo steps back through your own recent edits in the pane where you are working. Agent edits, human edits, and file-system changes are batched into an entry once activity settles. Upstream syncs get their own entry when the sync lands. For when source-mode undo starts fresh, see [Editor → Undo in source mode](https://openknowledge.ai/docs/features/editor#undo-in-source-mode).

The timeline is per branch: it shows the branch's own edits, plus history from before the branch diverged from `main`. History is stored in a shadow git repo inside your project, so entries and attribution survive app restarts and agent sessions — see [Attribution and collaboration](https://openknowledge.ai/docs/reference/core-concepts#attribution-and-collaboration).

## Review changes

Click a timeline entry to expand it. OpenKnowledge shows an inline diff so you can compare that version with the current document. If that version has the same body as the current document ([properties](https://openknowledge.ai/docs/features/editor#properties) are not part of the diff), you will see `No changes`.

You can switch between unified and split diff layouts from the toggle at the top of the timeline panel (shown once the timeline has entries).

![An expanded timeline entry showing a split diff next to the document, with removed lines highlighted in red and added lines in green](https://openknowledge.ai/screenshots/timeline-diff-view.png)

## Restore a previous version

Use the restore button Restore on a timeline entry to restore the document to the selected version. Restoring is append-only: OpenKnowledge creates a new change with the old content instead of deleting history.

# Folders and templates (https://openknowledge.ai/docs/advanced/folders-and-templates)

Describe a folder with its own properties, and offer reusable templates that give new documents their starting content and properties.

Folders organize a project the same way they do on your computer: a place to group related docs. OpenKnowledge adds two things on top: a folder can **describe itself** (its own properties, exactly like a document's), and it can offer a set of **templates** for new documents.

Click any folder in the sidebar to open its **overview**: a page listing what's inside, plus cards: **Folder properties**, **Templates available**, and an **Activity** feed of changes to both (hidden while empty).

## Properties, in one minute

Every doc can carry a bit of structured information at the top: a `status`, an `owner`, a `tags` list, a date. OpenKnowledge calls these its **properties**. In the raw markdown file they live in a `---`-fenced block at the very top, which markdown tools call *frontmatter*; the **Properties** panel in the editor edits them as ordinary form fields, so you never touch that block by hand. See [Editor → Properties](https://openknowledge.ai/docs/features/editor#properties) for the basics.

A doc's properties are its own: exactly what's written in that file, nothing injected from elsewhere. The tedious part is setting the same starting properties on every new doc in a folder, which is what **templates** solve (below).

## Folder properties

The **Folder properties** card on a folder's overview page describes the folder *itself*. It works exactly like a document's properties: add any property you like. `title`, `description`, and `tags` are the common ones (they show up in the folder overview and give an AI assistant listing the project a one-line sense of what the folder is for), but the card is open-shape: a `status`, an `owner`, whatever fits.

Folder properties are **self-only**: they describe their own folder and nothing else. They are not inherited by the documents inside the folder, and they do not flow into subfolders. A `posts/` folder described as "Blog posts" stays a description of `posts/`; opening a doc under `posts/` shows only that doc's own properties.

Click **Add a property**, give it a name and a value, and it's saved to the folder. Because folder properties describe the folder and don't flow into its docs, this is *not* the place to give the *documents* in a folder shared starting properties; use a template for that instead.

## Templates

A template is a reusable starting point for a new document: a heading skeleton, a checklist, a layout you fill in every time, optionally with starting properties baked in. Create a template once, and every new document made from it starts with that content. Templates are available both to you (in the New file dialog) and to AI assistants working in your project.

A template is just a markdown file. It lives at `.ok/templates/<name>.md` inside your project, with a little information at the top: its title and description.

### Create a template

There are two places to create a template, depending on how widely you want it to apply.

**For one folder**: open the folder's overview page and find the **Templates available** card, then click **New template**. The template is created in that folder and applies to documents created there and in its subfolders.

**For the whole project**: open Settings (`Cmd/Ctrl-,`) → **This project** → **Templates** → **New template**. The template is created at the project root and applies everywhere.

Either way, the **New template** dialog asks for:

- **Title**: the human-readable name, shown wherever a template is picked. Required.
- **Filename**: derived automatically from the title (`Blog post` → `blog-post.md`). Click **Edit** to set it yourself. The filename is the id agents use.
- **Description**: an optional one-line summary shown under the title.
- **Type** and **Default properties**: the starting properties the new document gets (below).
- **Starter content**: the markdown that becomes the new document's content.

### Use a template

When you create a new file, the **New file** dialog shows a **Start from** picker listing every template that applies to the target folder. Pick one and the new document opens pre-filled with that template's starter content. Pick **Blank note** for an empty document.

### Auto-filled placeholders

A template body may use two placeholders, filled in automatically when a document is created:

- `{{date}}`: today's date, in `YYYY-MM-DD` form.
- `{{user}}`: the display name of the person or agent creating the document.

```md title=".ok/templates/standup.md"
---
template:
  title: Daily standup
  description: Standup notes scaffold
---

# Standup — {{date}}

Recorded by {{user}}

## Yesterday

## Today

## Blockers
```

These two are the only placeholders OpenKnowledge fills in. Any other `{{...}}` is rejected when the template is saved, so a template never quietly grows its own mini-language.

### Give new documents their own properties

The standup template above produces documents with just a body. To give every new doc in a folder the same **starting properties** (a `type`, a `status`, a date, a `tags` array), fill in the **New template** dialog's **Type** field and **Default properties** section (key/value rows); on an existing template, edit the frontmatter block at the top of the template file. The starter content stays pure markdown.

On disk it all lives in one frontmatter block: the template's own identity sits under a reserved `template:` key, and every other top-level key is a property new documents start with:

```md title=".ok/templates/daily.md"
---
template:
  title: Daily entry
  description: Daily journal entry.
type: daily-note
description: Daily journal entry capturing intentions, notes, and reflections.
title: {{date}}
status: draft
tags: [daily]
---

## Morning intentions

(...)
```

The `template:` block is the template's own info (the title and description shown in the picker); OpenKnowledge strips it when a document is created. Every other key — `type`, `description`, `title`, `status`, `tags` above — becomes the new document's own properties, written as real bytes into the file. `description` gives each new document a starting one-line summary; edit it per document. Because these are the new doc's *own* properties, the doc owns them outright: editing one doc never touches another.

(Older templates that used two stacked frontmatter blocks still work — OpenKnowledge reads them and rewrites them to the single-block form the next time they're saved.)

### Edit and delete

In both the folder card and the Settings list, click a template row (or its menu's **Edit**) to open the template as a document tab: the body is the starter content, and the **Properties** panel holds its title, description, and name — change the name to rename the template file. The row's menu also has **Delete**. Editing an **inherited** template edits the file in the ancestor folder that owns it, which affects every folder under that ancestor; the row's **inherited** badge names the owning folder, and the opened tab shows that folder read-only in its Properties.

Deleting a template removes the `.ok/templates/<name>.md` file. Folders and agents that referenced it fall back to an inherited template of the same name, if one exists, or report the template as missing.

## How templates reach subfolders

Templates flow **downhill**: a template on `posts/` applies to `posts/`, `posts/2026/`, `posts/2026/drafts/`, and every folder beneath it. When you create a document, OpenKnowledge starts at the document's own folder and walks up to the top of the project, gathering the templates each folder along the way offers:

- Templates from the document's **own folder** are marked **local**.
- Templates from a **folder above it** are marked **inherited**.

When two folders offer a template with the same filename, the **closest folder wins**. So a template at the project root (`.ok/templates/`) is available everywhere; a template in `marketing/posts/.ok/templates/` is available only under `marketing/posts/`.

Folder *properties* do not behave this way: they describe only their own folder (no inheritance up or down).

## Templates and AI agents

Agents create documents from templates the same way you do. Through MCP, an agent calls:

```text
write({ document: { path: "posts/launch", template: "blog-post" } })
```

OpenKnowledge finds the `blog-post` template the same way the New file dialog does: the agent gets the one that applies to `posts/`. The template's **starter content** becomes the new document; the template's own title and description stay behind as template info and are not copied into the document.

When an agent creates a document without a template in a folder that offers them, the write result lists the folder's available templates (filename and description). This is why a template's **Filename** and **Description** matter: they are what an agent reads when choosing which template fits the document it is about to create.

## Where it's stored

You never have to edit these files by hand (the folder overview page is the front door), but for the curious:

- A folder's own properties live in `<folder>/.ok/frontmatter.yml` (open-shape: any keys; `title` / `description` / `tags` are the conventional ones).
- Templates live in `<folder>/.ok/templates/<name>.md`.

A folder only gets a `.ok/` directory once you give it a description or its first template; folders with nothing special stay plain. See the [Configuration reference](https://openknowledge.ai/docs/reference/configuration#folder-properties) for the file formats, and the [MCP reference](https://openknowledge.ai/docs/reference/mcp) for the `write` and `edit` tools (with `folder` and `template` targets) that set the same things from an AI agent.

## See also

- [Editor](https://openknowledge.ai/docs/features/editor): the Properties panel and the file sidebar.
- [Configuration](https://openknowledge.ai/docs/reference/configuration): the `.ok/` file formats behind these settings.
- [MCP reference](https://openknowledge.ai/docs/reference/mcp): the `write` tool and templates.

# Overview (https://openknowledge.ai/docs/get-started/overview)

Beautiful, AI-native markdown editor for humans and agents. Build knowledge bases, LLM wikis, and agent 2nd brains.

OpenKnowledge is an IDE for co-creating markdown-based knowledge bases and documents with the agents you already use.

Claude, Codex, Cursor, OpenCode, Pi, OpenClaw, Antigravity

Available as a [desktop app](https://openknowledge.ai/download) for macOS, Windows, and Linux — or as a local web app and CLI (any platform, including Intel Macs).

## Three layers

OpenKnowledge is three layers working together: a WYSIWYG editor for you, MCP and skills for agents, and your markdown files underneath.

1. **WYSIWYG Editor** — Your Interface. A beautiful "what you see is what you get" editor for your markdown files. Supports interactive HTML and JS, Mermaid diagrams, LaTeX, Videos, PDFs, and more.
2. **MCP and Skills** — Agent toolkit. Tools for agents to help search, edit, expand, organize, and maintain your knowledge base.
3. **Markdown Content** — Source of truth. Your knowledge base stays as plain markdown or mdx files in your project, version-controlled in git.

## Additional Features

- **[Ask AI](https://openknowledge.ai/docs/features/editor#ask-ai)** experience: chat with your agent **in app** without leaving OpenKnowledge, or hand the work to an external agent app or the built-in **[TUI](https://openknowledge.ai/docs/features/editor#terminal)** in the desktop app.
- Annotate your knowledge base with **[comments](https://openknowledge.ai/docs/features/comments)**, then resolve them with AI.
- **[Auto-sync](https://openknowledge.ai/docs/features/github-sync)** knowledge base changes with your team using Git.
- Explore your knowledge base through **[graph and wiki-link visualizations](https://openknowledge.ai/docs/reference/core-concepts#links-and-backlinks)**.
- [Connect to OpenKnowledge](https://openknowledge.ai/docs/remote-control/connecting-agents) remotely from local agents, remote agents, or other devices.

## Where to start

1. [Try the Quickstart](https://openknowledge.ai/docs/get-started/quickstart) — Install the desktop app and get your first agent-driven edit in under five minutes.
2. [Setting up an editor?](https://openknowledge.ai/docs/integrations/claude-code) — Use with Claude, Cursor, Codex, OpenCode, OpenClaw, Pi, or Antigravity.
3. [Set up an LLM Wiki](https://openknowledge.ai/docs/workflows/karpathy-llm-wiki) — Build a Karpathy-style LLM Wiki

# Quickstart (https://openknowledge.ai/docs/get-started/quickstart)

Set up a knowledge base with OpenKnowledge in less than five minutes.

## Desktop app

### Install the desktop app (macOS, Windows, Linux)

Download the installer for your platform:

- [macOS (Apple Silicon)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-arm64.dmg)
- [Windows (x64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-Setup-x64.exe)
- [Windows (Arm64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-Setup-arm64.exe)
- [Linux .deb (x64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-amd64.deb)
- [Linux .deb (Arm64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-arm64.deb)
- [Linux .rpm (x64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-x86_64.rpm)
- [Linux .rpm (Arm64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-aarch64.rpm)
- [Run in your browser (npm)](https://openknowledge.ai/docs/reference/cli)
- [All builds and checksums](https://openknowledge.ai/download)

> **Verify Git is installed**
>
> Open a terminal and run:
>
> ```bash
> git --version
> ```
>
> OpenKnowledge uses Git for timeline and recovery. If Git isn't installed, you can follow the instructions at [git-scm.com](https://git-scm.com/install/).

### Create a new project

- Open the OpenKnowledge app. On first launch, approve the **Connect your AI tools to OpenKnowledge** dialog — it wires up your AI editors and makes sure the `ok` command is available in your terminal. If you skip it, re-open it later from **File → Set up OpenKnowledge integrations…**.
- You'll then land on the launcher. To follow this guide, choose **Create new project** — or pick a starter pack from the **or use a starter pack** line beneath the cards (for example **Knowledge base**) to scaffold ready-made folders and templates up front and skip the Initialize step below. The count at the end of that line opens the full set of packs.
- Type a name for your project (for example, *Team Wiki*).
- Optionally click **Browse** to change where the project is saved, then click **Create**.

### Initialize a knowledge base

From your newly created project, select **Knowledge base** from the landing page, leave **Project root** selected, and click **Initialize**.

> **Info**
>
> Curious what the app writes to your machine — including the `PATH` changes it manages? See [What OpenKnowledge writes to your system](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

### Open the knowledge base in your AI agent

With no documents open, you will see a screen with an input box and a button labeled **Ask** followed by an agent's name. When an in-app agent is available, that is the default, so the conversation stays inside OpenKnowledge; on a machine where none is set up yet the button falls back to a Terminal CLI, and then to an external app. Type what you want to learn about in the input box and click the button; it hands your project to the agent with your prompt as the starter. Use the chevron beside the button to pick a different agent instead — another in-app agent, a Terminal CLI, or an external app such as Claude, Codex, or Cursor — and the pick is remembered next time. Your agent may take a few seconds to load the project.

Not sure what to write? Paste this into the input box (or use a topic of your choice):

> Create a knowledge base about Large Language Models. Include an overview page and separate pages for three key concepts.

> **Info**
>
> To avoid having to approve all OpenKnowledge tool calls, set your agent to auto-mode, full-access, or an equivalent setting.

> **Info**
>
> If your agent prompts you to approve a new MCP server (Cursor does this), approve `open-knowledge`.

### Watch your agent work

An in-app agent works in OpenKnowledge's Agents panel, which opens beside the editor. If you handed the project to an external app instead — Cursor, Codex, or Claude Desktop — that agent opens the OpenKnowledge editor in an embedded window.

You will see your agent's icon in the top right of the editor window. Click on the icon to see the agent's [activity](https://openknowledge.ai/docs/features/agent-activity) across files in the knowledge base.

If the agent icon is greyed out, your agent may not have started writing yet. Wait a few seconds and check again.

### Explore the knowledge base

The editor's sidebar shows every doc in your knowledge base. Each doc has:

- A WYSIWYG body and a source-mode toggle
- A frontmatter panel for properties

When viewing a document, check out the right side pane for:

- An outline of the document's structure
- A list of incoming and outgoing links to other docs
- A graph view of the document's links in the knowledge base
- A [timeline](https://openknowledge.ai/docs/features/timeline-and-recovery) of recent edits with per-burst diffs and selective rollback

Right-click anywhere in the sidebar (empty space, a folder, or a file) to create, reveal, rename, hide, delete (to your system's trash), and **Open with AI**. The full feature tour is under [Features](https://openknowledge.ai/docs/features/editor).

## Web app

The same editor, served locally and accessible in your browser — for any platform the desktop app doesn't cover (an Intel Mac, a server) or when you just prefer the terminal.

> **Prerequisites — Verify Node and Git are installed**
>
> Open a terminal and run:
>
> ```
> node --version
> git --version
> ```
>
> - **Node.js 24+.** OpenKnowledge requires Node.js 24 or higher. Download from [nodejs.org](https://nodejs.org/en/download/).
> - **`git`.** OpenKnowledge uses Git for timeline and recovery. Install it from [git-scm.com](https://git-scm.com/install/).

### Install the CLI and open the editor

Install the CLI from npm, initialize a project, and start the editor in your browser:

```bash
# Install the CLI globally
npm install -g @inkeep/open-knowledge

# Create (or enter) a project folder, then initialize a knowledge base.
# `ok init` scaffolds .ok/ and wires up Claude Code, Claude Desktop, Cursor, Codex, OpenCode, OpenClaw, Pi, Antigravity, LM Studio, and Hermes.
mkdir my-knowledge-base && cd my-knowledge-base
ok init

# Serve the editor and open it in your browser.
ok start
```

> **Info**
>
> Want to know exactly what these commands write to your machine — in your project, your home directory, and your editors' configs? See [What OpenKnowledge writes to your system](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

### Open the knowledge base in your AI agent

The quickest route is inside the editor: the empty state's **Ask** button (or, with a document open, the bottom **Ask AI** field) hands your prompt to an in-app agent when one is available, so you can chat without leaving OpenKnowledge. To use an external editor instead, `ok init` already registered OpenKnowledge with the AI editors it could configure, so your agent can use the OpenKnowledge tools right away — open your project there, or right-click the project in the sidebar, choose **Open with AI ▸**, and pick one. If a tool's MCP entry didn't appear, see [registration outcomes](https://openknowledge.ai/docs/reference/cli#set-up-a-project).

In the agent's chat window, add the following to get started or use a topic of your choice:

> Create a knowledge base about Large Language Models. Include an overview page and separate pages for three key concepts.

> **Info**
>
> To avoid having to approve all OpenKnowledge tool calls, set your agent to auto-mode, full-access, or an equivalent setting.

> **Info**
>
> If your agent prompts you to approve a new MCP server (Cursor does this), approve `open-knowledge`.

### Watch your agent work

You will see your agent's icon in the top right of the editor window. Click on the icon to see the agent's [activity](https://openknowledge.ai/docs/features/agent-activity) across files in the knowledge base.

If the agent icon is greyed out, your agent may not have started writing yet. Wait a few seconds and check again.

### Explore the knowledge base

The editor's sidebar shows every doc in your knowledge base. Each doc has:

- A WYSIWYG body and a source-mode toggle
- A frontmatter panel for properties

When viewing a document, check out the right side pane for:

- An outline of the document's structure
- A list of incoming and outgoing links to other docs
- A graph view of the document's links in the knowledge base
- A [timeline](https://openknowledge.ai/docs/features/timeline-and-recovery) of recent edits with per-burst diffs and selective rollback

Right-click anywhere in the sidebar (empty space, a folder, or a file) to create, reveal, rename, hide, delete, and **Open with AI**. The full feature tour is under [Features](https://openknowledge.ai/docs/features/editor).

## What's next

- Syncing your knowledge base to GitHub: [Syncing](https://openknowledge.ai/docs/features/github-sync)
- Learn more about the editor: [Editor](https://openknowledge.ai/docs/features/editor)
- Configure OpenKnowledge: [Configuration](https://openknowledge.ai/docs/reference/configuration)

# Antigravity (https://openknowledge.ai/docs/integrations/antigravity)

Use OpenKnowledge with Antigravity.

[Antigravity](https://antigravity.google) is Google's agentic development platform: an IDE and the `agy` terminal agent.

## Install

There are two ways to connect Antigravity, depending on how you run OpenKnowledge:

- **Desktop app** (macOS, Windows, Linux). The first time you open a project, a consent dialog detects Antigravity and configures it for you. To re-trigger the dialog, choose **File → Set up OpenKnowledge integrations…**.
- **Web app / terminal** (any platform, including Intel Macs — see the [web app guide](https://openknowledge.ai/docs/get-started/quickstart#ok-install-web-app)). Run `ok init` in your project: it registers the OpenKnowledge MCP server with Antigravity and the other editors it detects. Every `ok start` repairs the entry if it has drifted (it never adds one you removed).

After `ok init` writes the config, restart Antigravity (or reload MCP servers from the agent
panel) so it picks up the entry. The IDE and the `agy` CLI share one config file, so a single
`ok init` wires up both.

`ok init` writes the OpenKnowledge server into `~/.gemini/config/mcp_config.json` under `mcpServers`, using the same resilient launcher every other editor gets — it finds `ok` whether you installed the desktop app or the npm CLI, so there's no PATH to configure.

> **Info**
>
> Antigravity has **one shared, user-global MCP config** — `~/.gemini/config/mcp_config.json`, read by the IDE, the app, and `agy` alike (the `.gemini` directory is inherited from the Gemini CLI lineage, not `~/.antigravity`). There is no per-project MCP config; per project you can only choose which globally-registered servers an agent may use. So OpenKnowledge registers once, globally, the same way it does for Claude Desktop. Each OK tool call takes a `cwd` argument to target a specific knowledge base, so one registration serves every project you open.

## Verify

Open a project with Antigravity — the IDE or an `agy` session in the project directory — and ask:

> List the first 5 documents you come across in this project.

Antigravity should call the OpenKnowledge `exec` tool and respond with some of your documents.

If the agent doesn't see the tool, reload the MCP servers (the agent panel's **Manage MCP Servers** view, or restart `agy`) and check that OpenKnowledge is listed and enabled. The first tool call can take a moment on a fresh machine — the launcher may be installing the OpenKnowledge CLI via `npx`.

## Use the `agy` CLI

`agy` is Antigravity's terminal agent. Start it in a knowledge base and pass an opening prompt as the positional argument:

```bash
cd /path/to/your/knowledge-base
agy "Summarize the open questions across this project's specs."
```

The session stays open for follow-ups. From the **OpenKnowledge desktop app**, the "Open in Antigravity" action launches `agy` in the docked terminal, pre-filled with a prompt scoped to the doc, folder, or project you're on. The row appears only when `agy` is detected on your `PATH`; its installation directory varies by platform.

## Initialize a knowledge base

`ok init` turns a folder into a knowledge base (and, on the same run, registers the MCP server above):

```bash
cd /path/to/your/knowledge-base && ok init
```

> **Warn**
>
> **`ok init` resolves upward into an existing project.** Run inside a subdirectory of an existing OK project, `ok init` finds the *parent* project and refreshes that one — it does not create a nested project, and it writes no markdown in the subdirectory. To create a genuinely separate knowledge base, run `ok init` in a folder that sits **outside any existing `.ok` project tree**.

## Preview

Write and edit responses include a route-only preview path like `/#/notes/mcp-round-trip` — a route id, **not** a full URL. Call `preview_url` for the openable browser address, and don't screenshot the preview to confirm an edit (the tool response is the confirmation). See [MCP reference → Preview links](https://openknowledge.ai/docs/reference/mcp#preview-links) for the full semantics.

## Agent contract

The **OpenKnowledge skill** — the authoritative agent contract for tool use, grounding, and linking — is project-scoped, written for Claude, Cursor, Codex, and OpenCode. Antigravity reads skills only from its user-global `~/.gemini/skills` hub, with no project skill directory to receive it, so for Antigravity the contract is these two reminders:

- **Markdown is MCP-owned.** Inside an OK project, read and write `.md` / `.mdx` through OK's tools (`exec`, `search`, `write`, `edit`, `links`), not native shell or file tools — native access loses attribution and skips the frontmatter, backlinks, and history OK returns.
- **Discover before concluding the MCP is missing.** Not seeing `exec` in the initial tool list isn't the escape hatch; reload the MCP servers first.

For the complete tool surface, see the [MCP reference](https://openknowledge.ai/docs/reference/mcp).

## Troubleshooting

| Symptom                                                        | Cause                                                                                                 | Fix                                                                                              |
| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `ok init` showed Antigravity as `config root missing; skipped` | `~/.gemini/` doesn't exist yet, so Antigravity wasn't detected                                        | Launch Antigravity or run `agy` once (it creates the directory), then re-run `ok init`           |
| `ok init` reported the config was `left unchanged`             | OpenKnowledge declined to edit `~/.gemini/config/mcp_config.json` for the reason shown in parentheses | Fix what the reason names (invalid JSON, a duplicated server block), then re-run `ok init`       |
| Antigravity doesn't see the `open-knowledge` tools             | MCP servers not reloaded, or the agent's process predates the entry                                   | Reload MCP servers (or restart `agy`), and confirm the server is enabled                         |
| "Open in Antigravity" row is missing in the desktop app        | `agy` isn't on `PATH`                                                                                 | Install the Antigravity CLI and ensure its platform-specific install directory is on your `PATH` |
| `write` / `edit` -> "Hocuspocus server is not running"         | OK server not started                                                                                 | Run `ok start` (or leave the desktop app running) and retry                                      |
| Preview "doesn't open"                                         | used the route-only `/#/…` path in a browser                                                          | Call `preview_url` for the full URL                                                              |

New to OpenKnowledge itself? Start with the [quickstart](https://openknowledge.ai/docs/get-started/quickstart) for `ok init` and `ok start`.

# Claude (https://openknowledge.ai/docs/integrations/claude-code)

Use OpenKnowledge with Claude Code, in the CLI or the Claude Desktop app.

OpenKnowledge works with Claude Code through MCP plus an installed skill that teaches the agent how to use the tools. Both surfaces are supported:

- **Claude Code CLI** — the `claude` command in your terminal.
- **Claude Desktop app** — Claude Code in the **Code** tab.

Claude Desktop's regular chat can use the tools too — see [Claude Desktop chat](https://openknowledge.ai/docs/integrations/claude-code#claude-desktop-chat).

## Install

There are two ways to connect Claude Code, depending on how you run OpenKnowledge:

- **Desktop app** (macOS, Windows, Linux). The first time you open a project, a consent dialog detects Claude Code and configures it for you. To re-trigger the dialog, choose **File → Set up OpenKnowledge integrations…**.
- **Web app / terminal** (any platform, including Intel Macs — see the [web app guide](https://openknowledge.ai/docs/get-started/quickstart#ok-install-web-app)). Run `ok init` in your project: it registers the OpenKnowledge MCP server with Claude Code and the other editors it detects. Every `ok start` repairs the entry if it has drifted (it never adds one you removed).

## Open with AI

From the editor, **Open with AI ▸ Claude** dispatches the current file, folder, or project to Claude Code in one click. Two entry points: the sidebar's right-click menu (file / folder / empty space) and the **File → Open with AI** menu. You can also hand the current doc to Claude from the bottom **Ask AI** composer.

Claude Code receives a short prompt asking it to open the target in OpenKnowledge's web preview, then grounds the rest of the conversation through MCP. See [Editor → Open with AI](https://openknowledge.ai/docs/features/editor#open-with-ai) for the full UX.

## Verify

Open the project in Claude Code and ask:

> List the first 5 documents you come across in this project.

Claude Code should call the OpenKnowledge `exec` tool and respond with some of your documents.

If Claude Code doesn't use the OpenKnowledge `exec` tool, restart it so it picks up the new MCP entry. If it's still missing and `ok init` reported your config was `left unchanged`, OpenKnowledge couldn't edit `~/.claude.json` safely and left it untouched — the summary names the reason in parentheses; fix the config and re-run `ok init`.

## Live editing

When Claude Code writes to a doc, you'll see the change land live in OpenKnowledge. Just ask Claude Code to open it for you.

## Scheduled runs

Claude Code can work your knowledge base on a schedule (a nightly consolidation pass, a morning meeting sync) without OpenKnowledge shipping a scheduler of its own. Three tiers, as of August 2026. Anthropic's scheduler surfaces are moving quickly, so check the linked docs before you rely on a specific limit:

- **[Desktop scheduled tasks](https://code.claude.com/docs/en/desktop-scheduled-tasks).** Claude Desktop's **Routines** page schedules local runs (choose **Local** when creating one) down to the minute. They see your local files and MCP servers, so an OK project works as-is. Tasks fire only while the app is open and your computer is awake.
- **[Cloud routines](https://code.claude.com/docs/en/routines) (`/schedule`).** These run on Anthropic's infrastructure against a fresh clone of a GitHub repo and see only what is committed: no local files, no locally-registered MCP servers. Reaching OK from a routine takes both halves, a committed `.mcp.json` declaring `open-knowledge` and a setup script in the routine's environment that installs the CLI. New projects keep OpenKnowledge's config out of git by default, which leaves `.mcp.json` in `.git/info/exclude` where a clone never sees it. Run `ok config-sharing share` in the project to commit it before relying on this tier. Minimum schedule interval is one hour.
- **OS cron + `claude -p`.** A plain cron job running `claude -p "<prompt>"` in the project loads the committed skill, `.mcp.json`, and CLAUDE.md the same way an interactive session does. That depends on `-p` staying non-bare: [`--bare`](https://code.claude.com/docs/en/headless) skips MCP server discovery, and Anthropic has said it will become the default for `-p` in a future release. Pass `--mcp-config .mcp.json` explicitly if you want the job to survive that change.

See [Ingest meetings](https://openknowledge.ai/docs/workflows/meeting-ingestion#automatically-on-a-schedule) for a worked example.

## Claude Desktop chat

`ok init` and the consent dialog also register the MCP server with Claude Desktop when it's installed — user-level, in `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS or `%APPDATA%\Claude\claude_desktop_config.json` on Windows. Claude Desktop picks up the entry only after you quit and relaunch it.

After relaunching, ask in any chat:

> List the first 5 documents you come across in this project.

Claude Desktop should call the OpenKnowledge `exec` tool and respond with some of your documents.

# Codex (https://openknowledge.ai/docs/integrations/codex)

Use OpenKnowledge with Codex.

OpenKnowledge works with Codex through MCP plus an installed skill.

## Install

There are two ways to connect Codex, depending on how you run OpenKnowledge:

- **Desktop app** (macOS, Windows, Linux). The first time you open a project, a consent dialog detects Codex and configures it for you. To re-trigger the dialog, choose **File → Set up OpenKnowledge integrations…**.
- **Web app / terminal** (any platform, including Intel Macs — see the [web app guide](https://openknowledge.ai/docs/get-started/quickstart#ok-install-web-app)). Run `ok init` in your project: it registers the OpenKnowledge MCP server with Codex and the other editors it detects. Every `ok start` repairs the entry if it has drifted (it never adds one you removed).

## Open with AI

From the editor, **Open with AI ▸ Codex** dispatches the current file, folder, or project to Codex in one click. Entry points include the sidebar's right-click menu (file / folder / empty space), the **File → Open with AI** menu, and the Command Palette. You can also hand the current doc to Codex from the bottom **Ask AI** composer.

Codex receives a short prompt asking it to open the target in OpenKnowledge's web preview. The agent then grounds the rest of the conversation through MCP. See [Editor → Open with AI](https://openknowledge.ai/docs/features/editor#open-with-ai) for the full UX.

## Verify

Open the project in Codex and ask:

> List the first 5 documents you come across in this project.

The agent should call the OpenKnowledge `exec` tool and respond with some of your documents.

If you don't see the tool, restart Codex. If it's still missing and `ok init` reported your config was `left unchanged`, the reason in parentheses says why `~/.codex/config.toml` (or `$CODEX_HOME/config.toml` if set) was left untouched. For config problems, fix the file and re-run `ok init`; for `no format-preserving writer available`, your config isn't the problem — OpenKnowledge can't rewrite the file safely on this platform, so add an `open-knowledge` entry to `mcp_servers` by hand (command: `ok mcp`).

## Scheduled runs

The ChatGPT desktop app runs Codex on a schedule from its **Scheduled** view. Tasks run unattended under your default sandbox, against your local project or an isolated background worktree, and the app must be running for tasks that need local files. See OpenAI's [scheduled tasks](https://learn.chatgpt.com/docs/automations) docs. Codex CLI has no scheduler of its own; use [`codex exec`](https://learn.chatgpt.com/docs/non-interactive-mode) from OS cron instead. Described as of August 2026, while the ChatGPT and Codex surfaces are still changing quickly.

The meeting-sync prompt in [Ingest meetings](https://openknowledge.ai/docs/workflows/meeting-ingestion#automatically-on-a-schedule) works verbatim as a scheduled task.

# Cursor (https://openknowledge.ai/docs/integrations/cursor)

Use OpenKnowledge with Cursor.

OpenKnowledge works with Cursor through MCP plus an installed skill.

## Install

There are two ways to connect Cursor, depending on how you run OpenKnowledge:

- **Desktop app** (macOS, Windows, Linux). The first time you open a project, a consent dialog detects Cursor and configures it for you. To re-trigger the dialog, choose **File → Set up OpenKnowledge integrations…**.
- **Web app / terminal** (any platform, including Intel Macs — see the [web app guide](https://openknowledge.ai/docs/get-started/quickstart#ok-install-web-app)). Run `ok init` in your project: it registers the OpenKnowledge MCP server with Cursor and the other editors it detects. Every `ok start` repairs the entry if it has drifted (it never adds one you removed).

## Approve the server

Cursor prompts you to approve a new MCP server when you next open the project. Approve `open-knowledge`. Until you do, the agent can't see the tools.

## Open with AI

From the editor, **Open with AI ▸ Cursor Desktop** dispatches the current file, folder, or project to Cursor. Entry points: the sidebar's right-click menu (file / folder / empty space), the command palette, and the **File → Open with AI** menu. You can also hand the current doc to Cursor from the bottom **Ask AI** composer.

Cursor uses a two-step handoff: OpenKnowledge spawns `cursor <projectDir>` to open the workspace first, then fires the `cursor://` URL with a short prompt asking Cursor to open the target in OpenKnowledge's web preview. The agent then grounds the rest of the conversation through MCP. See [Editor → Open with AI](https://openknowledge.ai/docs/features/editor#open-with-ai) for the full UX.

Both steps run through the local server's `POST /api/handoff`, the same path in the web app and the desktop app — so the **Open with AI ▸ Cursor Desktop** row works in both.

## Verify

Ask the agent:

> List the first 5 documents you come across in this project.

The agent should call the OpenKnowledge `exec` tool and respond with some of your documents.

If it still doesn't see the tool after approval, restart Cursor so it picks up the new MCP entry. If it's still missing and `ok init` reported your config was `left unchanged`, OpenKnowledge couldn't safely edit `~/.cursor/mcp.json` for the reason shown in parentheses (unreadable, too large, or a duplicate server block) — fix the config and re-run `ok init`.

# GitHub Copilot CLI (https://openknowledge.ai/docs/integrations/github-copilot-cli)

Use OpenKnowledge with GitHub Copilot CLI.

[GitHub Copilot CLI](https://github.com/features/copilot/cli) is a terminal-native AI coding assistant.

## Install

There are two ways to connect GitHub Copilot CLI, depending on how you run OpenKnowledge:

- **Desktop app** (macOS, Windows, Linux). The first time you open a project, a consent dialog detects GitHub Copilot CLI and configures it for you. To re-trigger the dialog, choose **File → Set up OpenKnowledge integrations…**.
- **Web app / terminal** (any platform, including Intel Macs — see the [web app guide](https://openknowledge.ai/docs/get-started/quickstart#ok-install-web-app)). Run `ok init` in your project: it registers the OpenKnowledge MCP server with GitHub Copilot CLI and the other editors it detects. Every `ok start` repairs the entry if it has drifted (it never adds one you removed).

`ok init` writes the MCP server to `~/.copilot/mcp-config.json`, or to `$COPILOT_HOME/mcp-config.json` when `COPILOT_HOME` is set. The configuration is global because Copilot CLI prioritizes global MCP configuration over project configuration.

If the Copilot config directory does not exist yet, launch Copilot CLI once and rerun `ok init` so it can write the MCP configuration.

It also installs the project skill at `.github/skills/open-knowledge/SKILL.md` so Copilot can follow the project's OpenKnowledge workflow.

## Open with AI

In OpenKnowledge's docked terminal, select **GitHub Copilot** from the new-chat picker. It starts Copilot CLI in interactive mode with context for the current project. The option is hidden when the `copilot` executable is not installed.

## Verify

Open the project in GitHub Copilot CLI and ask:

> List the first 5 documents you come across in this project.

GitHub Copilot CLI should call the OpenKnowledge `exec` tool and respond with some of your documents.

If you don't see the tool, restart Copilot CLI. If `ok init` reported that your config was left unchanged, fix the reported config issue and rerun `ok init`. To add it manually, create an `open-knowledge` entry in `mcpServers` that runs `ok mcp`.

# Hermes (https://openknowledge.ai/docs/integrations/hermes)

Use OpenKnowledge with Hermes Agent.

[Hermes Agent](https://hermes-agent.nousresearch.com) is an open-source, self-improving AI agent framework.

## Install

There are two ways to connect Hermes, depending on how you run OpenKnowledge:

- **Desktop app** (macOS, Windows, Linux). The first time you open a project, a consent dialog detects Hermes and configures it for you. To re-trigger the dialog, choose **File → Set up OpenKnowledge integrations…**.
- **Web app / terminal** (any platform, including Intel Macs — see the [web app guide](https://openknowledge.ai/docs/get-started/quickstart#ok-install-web-app)). Run `ok init` in your project: it registers the OpenKnowledge MCP server with Hermes and the other editors it detects. Every `ok start` repairs the entry if it has drifted (it never adds one you removed).

After `ok init` writes the config, restart Hermes (or run `/reload-mcp` in-session) so it picks up the entry. Hermes auto-discovers MCP tools at startup and updates its tool list at runtime.

`ok init` writes the OpenKnowledge server into `~/.hermes/config.yaml` under `mcp_servers`, using the same resilient launcher every other editor gets — it finds `ok` whether you installed the desktop app or the npm CLI, so there's no PATH to configure. Hermes keeps its whole config (models, tool filters, other MCP servers) in that one YAML file, so OK edits only its own `open-knowledge` entry and leaves the rest — your comments included — in place.

> **Info**
>
> Hermes is detected by the presence of `~/.hermes/`. If `ok init` shows Hermes as `config root
>   missing; skipped`, launch Hermes once so it creates its home directory, then re-run `ok init`.

## Initialize a knowledge base

`ok init` turns a folder into a knowledge base (and, on the same run, registers the MCP server above):

```bash
cd /path/to/your/knowledge-base && ok init
```

> **Warn**
>
> **`ok init` resolves upward into an existing project.** Run inside a subdirectory of an existing
> OK project, `ok init` finds the *parent* project and refreshes that one — it does not create a
> nested project, and it writes no markdown in the subdirectory. To create a genuinely separate
> knowledge base, run `ok init` in a folder that sits **outside any existing `.ok` project tree**.

## Verify

Start Hermes in the project and ask it to exercise the server:

> List the first 5 documents you come across in this project.

Hermes should call the OpenKnowledge `exec` tool and respond with some of your documents.

If the agent doesn't see the tool, restart Hermes (or run `/reload-mcp`) — some runtimes lazy-load MCP tools and only surface them after discovery, so absence from the initial list means "not discovered yet," not "not registered."

## Agent contract

The **OpenKnowledge skill** — the authoritative agent contract for tool use, grounding, and linking — is project-scoped, written for Claude, Cursor, Codex, and OpenCode; Hermes has no project skill directory to receive it. So for Hermes agents, the contract is these two reminders:

- **Markdown is MCP-owned.** Inside an OK project, read and write `.md` / `.mdx` through OK's tools (`exec`, `search`, `write`, `edit`, `links`), not native shell or file tools — native access loses attribution and skips the frontmatter, backlinks, and history OK returns.
- **Discover before concluding the MCP is missing.** Not seeing `exec` in the initial tool list isn't the escape hatch; restart Hermes or run `/reload-mcp` first.

For the complete tool surface, see the [MCP reference](https://openknowledge.ai/docs/reference/mcp).

## Troubleshooting

| Symptom                                                   | Cause                                                                                      | Fix                                                                                                                 |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `ok init` showed Hermes as `config root missing; skipped` | `~/.hermes/` doesn't exist yet, so Hermes wasn't detected                                  | Launch Hermes once (it creates its home directory), then re-run `ok init`                                           |
| `ok init` reported the config was `left unchanged`        | OpenKnowledge declined to edit `~/.hermes/config.yaml` for the reason shown in parentheses | Fix what the reason names (invalid YAML, a duplicated `mcp_servers` key, an oversize config), then re-run `ok init` |
| Agent doesn't see the `open-knowledge` tools              | MCP tools lazy-loaded, or Hermes' process predates the entry                               | Restart Hermes or run `/reload-mcp`                                                                                 |
| `write` / `edit` -> "Hocuspocus server is not running"    | OK server not started                                                                      | Run `ok start` (or leave the desktop app running) and retry                                                         |
| Preview "doesn't open"                                    | used the route-only `/#/…` path in a browser                                               | Call `preview_url` for the full URL                                                                                 |

New to OpenKnowledge itself? Start with the [quickstart](https://openknowledge.ai/docs/get-started/quickstart) for `ok init` and `ok start`.

# LM Studio (https://openknowledge.ai/docs/integrations/lm-studio)

Use OpenKnowledge with LM Studio.

[LM Studio](https://lmstudio.ai) is a desktop app for running open models locally on your machine.

## Install

There are two ways to connect LM Studio, depending on how you run OpenKnowledge:

- **Desktop app** (macOS, Windows, Linux). The first time you open a project, a consent dialog detects LM Studio and configures it for you. To re-trigger the dialog, choose **File → Set up OpenKnowledge integrations…**.
- **Web app / terminal** (any platform, including Intel Macs — see the [web app guide](https://openknowledge.ai/docs/get-started/quickstart#ok-install-web-app)). Run `ok init` in your project: it registers the OpenKnowledge MCP server with LM Studio and the other editors it detects. Every `ok start` repairs the entry if it has drifted (it never adds one you removed).

After `ok init` writes the config, open LM Studio's **Program** tab and enable the `open-knowledge` server (LM Studio spawns a process per MCP server on save). The model you chat with must support tool use — see [Pick a tool-capable model](https://openknowledge.ai/docs/integrations/lm-studio#pick-a-tool-capable-model).

`ok init` writes the OpenKnowledge server into LM Studio's `mcp.json`, using the same resilient launcher every other editor gets — it finds `ok` whether you installed the desktop app or the npm CLI, so there's no PATH to configure. LM Studio follows Cursor's `mcp.json` notation, so the entry lands under a top-level `mcpServers` map.

> **Info**
>
> **The config is user-global — one registration serves every project.** LM Studio has no
> project-local MCP config, so OpenKnowledge registers once and each OK tool call takes a `cwd`
> argument to target a specific knowledge base (pass it once and it sticks for that session).

## Pick a tool-capable model

MCP tools only work when the chat model can call functions, and small local models vary a lot in how reliably they do. For the best results, load a model with strong tool-use / function-calling support and enough context to hold OK's tool descriptions. If the model answers in prose instead of calling `exec` / `search` / `write`, it either doesn't support tools or the server isn't enabled — check both before assuming OpenKnowledge is misconfigured.

## Two surfaces: chat and agent mode

LM Studio has a chat and an agent mode, and what OpenKnowledge gives you differs between them.

The MCP server is app-wide, so `ok init` wires up both at once and the tools are there either way. **Agent Skills exist only in agent mode.** In chat you get the tools on their own. In agent mode you also get whatever skills are installed — which by default is the discovery skill, not the read/write contract; see [Agent contract](https://openknowledge.ai/docs/integrations/lm-studio#agent-contract) below for what that means in practice.

If you plan to have a model read and write a knowledge base rather than answer one-off questions, use agent mode.

## Skills

Agent mode reads your skills from `~/.lmstudio/skills`, where OpenKnowledge's user-global skills install like they do for any other tool. They show up under **Bionic Skills** in LM Studio's skills settings, available across all projects.

For a project, LM Studio reads `.agents/skills` in the project's working directory rather than a `.lmstudio/` folder. That folder is shared ground rather than LM Studio's own, so OpenKnowledge offers it as a destination once it can see LM Studio installed, and never picks it for you: choose **`.agents`** when you install a project skill and LM Studio will find it. Nothing lands there until you choose it, and LM Studio does not create the folder itself.

The folder also appears in **Settings → Skills → Folders**, where it can be linked once it exists. Until then it is a destination only — OpenKnowledge will not create it by merging another tool's folder into it.

LM Studio can also read other tools' skill folders (`~/.claude/skills`, `~/.agents/skills`, `~/.cursor/skills` and more), listed under **Use skills found in other apps**. Those are off by default, and turning one on imports everything in that folder, not just OpenKnowledge's skills.

## Verify

Load a tool-capable model, open a knowledge base, and ask:

> List the first 5 documents you come across in this project.

LM Studio should call the OpenKnowledge `exec` tool and respond with some of your documents.

If the model doesn't call the tool, confirm the `open-knowledge` server is toggled on in the **Program** tab and that the loaded model supports tool use.

## Agent contract

The **OpenKnowledge skill** — the authoritative agent contract for tool use, grounding, and linking — is project-scoped. What installs into `~/.lmstudio/skills` by default is the discovery skill; skill authoring is available alongside it. Between them they cover what OpenKnowledge is, how to open and install it, and how to write a skill, and deliberately do not carry the in-project read/write contract. LM Studio can receive that contract, but only through the `.agents` route above, so until you install a project skill there the contract for LM Studio is these two reminders:

- **Markdown is MCP-owned.** Inside an OK project, read and write `.md` / `.mdx` through OK's tools (`exec`, `search`, `write`, `edit`, `links`), not native shell or file tools — native access loses attribution and skips the frontmatter, backlinks, and history OK returns. This matters most in agent mode, where the model can run shell commands in your project folder.
- **Discover before concluding the MCP is missing.** Not seeing `exec` in the initial tool list isn't the escape hatch; reload the MCP servers first.

For the complete tool surface, see the [MCP reference](https://openknowledge.ai/docs/reference/mcp).

## Troubleshooting

| Symptom                                                                | Cause                                                                                                                           | Fix                                                                                                                                                                                                                                                                |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ok init` showed LM Studio as `config root missing; skipped`           | LM Studio's config dir doesn't exist yet, so it wasn't detected                                                                 | Launch LM Studio once (it creates its config dir), then re-run `ok init`                                                                                                                                                                                           |
| `ok init` reported the config was `left unchanged`                     | OpenKnowledge declined to edit `mcp.json` for the reason shown in parentheses                                                   | Fix what the reason names (invalid JSON, a duplicated server block, an oversize config), then re-run `ok init`                                                                                                                                                     |
| Model answers in prose, never calls a tool                             | the loaded model doesn't support tool use, or the server is disabled                                                            | Load a tool-capable model and enable `open-knowledge` in the **Program** tab                                                                                                                                                                                       |
| MCP call fails with `... exceeds the available context size ...` (400) | OpenKnowledge's MCP tool surface is larger than a small local model's context window (e.g. 8K tokens), so the request can't fit | Load the model with the largest context window it supports (or pick a bigger model). If that still isn't enough, [open an issue](https://github.com/inkeep/open-knowledge/issues) or reach out — trimming the tool surface for small local models is on our radar. |
| `write` / `edit` -> "Hocuspocus server is not running"                 | OK server not started                                                                                                           | Run `ok start` (or leave the desktop app running) and retry                                                                                                                                                                                                        |

New to OpenKnowledge itself? Start with the [quickstart](https://openknowledge.ai/docs/get-started/quickstart) for `ok init` and `ok start`.

# OpenClaw (https://openknowledge.ai/docs/integrations/openclaw)

Use OpenKnowledge with OpenClaw.

[OpenClaw](https://openclaw.ai) is an open-source personal AI assistant framework.

**Auto-sync your meetings.** The gateway has a built-in scheduler and a webhook listener, so once OpenKnowledge is registered it can run tasks against your knowledge base unattended. The headline example is keeping meeting notes flowing in on their own. See [Ingest meetings](https://openknowledge.ai/docs/workflows/meeting-ingestion) for the cron recipe and the recorder list. The webhook mechanics, for running an agent the moment a meeting event fires, are documented below.

## Install

There are two ways to connect OpenClaw, depending on how you run OpenKnowledge:

- **Desktop app** (macOS, Windows, Linux). The first time you open a project, a consent dialog detects OpenClaw and configures it for you. To re-trigger the dialog, choose **File → Set up OpenKnowledge integrations…**.
- **Web app / terminal** (any platform, including Intel Macs — see the [web app guide](https://openknowledge.ai/docs/get-started/quickstart#ok-install-web-app)). Run `ok init` in your project: it registers the OpenKnowledge MCP server with OpenClaw and the other editors it detects. Every `ok start` repairs the entry if it has drifted (it never adds one you removed).

After `ok init` writes the config, restart the OpenClaw gateway so running agents pick up the
entry — `openclaw mcp reload` refreshes the current CLI process only. `ok start` isn't needed
here, since OpenClaw manages its own server lifecycle.

`ok init` writes the OpenKnowledge server into `~/.openclaw/openclaw.json` under `mcp.servers`, using the same resilient launcher every other editor gets — it finds `ok` whether you installed the desktop app or the npm CLI, so there's no PATH to configure.

> **Info**
>
> The managed entry doesn't pin a project. Each OK tool call takes a `cwd` argument to target a specific knowledge base — pass it once and it sticks for that session — so one registration serves every project (and git worktree) an agent visits. If you previously hand-wrote an entry with a `cwd` pinned, `ok init` replaces it with the managed launcher; pass `cwd` per tool call instead.

## Verify

Two complementary checks — the probe confirms the gateway can reach the server; the prompt confirms an agent can actually call its tools.

**Server-side** — confirm OpenClaw launched the server and its tools enumerate:

```bash
openclaw mcp doctor --probe         # -> open-knowledge: ok
openclaw mcp probe open-knowledge   # -> open-knowledge: 19 tools
```

Unrelated config-health or plugin-version warnings for *other* servers don't block OpenKnowledge from probing.

**Agent-side** — open the project with an OpenClaw agent and ask:

> List the first 5 documents you come across in this project.

OpenClaw should call the OpenKnowledge `exec` tool and respond with some of your documents.

If the agent doesn't see the tool, run tool discovery — some runtimes lazy-load MCP tools and only surface them after a search, so absence from the initial list means "not discovered yet," not "not registered" — then restart the gateway.

## Initialize a knowledge base

`ok init` turns a folder into a knowledge base (and, on the same run, registers the MCP server above):

```bash
cd /path/to/your/knowledge-base && ok init
```

> **Warn**
>
> **`ok init` resolves upward into an existing project.** Run inside a subdirectory of an existing OK project, `ok init` finds the *parent* project and refreshes that one — it does not create a nested project, and it writes no markdown in the subdirectory. To create a genuinely separate knowledge base, run `ok init` in a folder that sits **outside any existing `.ok` project tree**.

## Smoke test

Point an OpenClaw agent at a fresh knowledge base and have it do the following steps:

```text
Smoke-test the current OpenKnowledge project, one step at a time:

1. Discover your tools — if the open-knowledge tools aren't listed yet, run tool discovery.
2. Read the resolved config and list the project root, to confirm you're pointed at the right project.
3. Write a notes folder, a README, and a note linking to a second doc you'll create next (its broken-link warning is expected).
4. Create that second doc, so the link resolves.
5. Read both docs back — you should get content plus frontmatter, backlinks, and history.
6. Audit dead links (it should be empty now), then search for the two new docs.
7. Return the preview URL for one of the docs.
```

That sequence exercises every layer — discovery, config, read, write, link graph, search, and preview — in one pass.

## Trigger runs from external events

Besides the cron poll (see [Ingest meetings](https://openknowledge.ai/docs/workflows/meeting-ingestion)), the gateway can run an agent the moment something happens elsewhere. Enable the hooks listener in the gateway config:

```json5
{
  hooks: {
    enabled: true,
    token: "shared-secret",
    path: "/hooks",
  },
}
```

The `token` is a dedicated secret, separate from your gateway auth token. Every request must carry it as `Authorization: Bearer <token>` (recommended) or `x-openclaw-token: <token>`. Query-string tokens are rejected.

| Endpoint             | What it does                                                                 |
| -------------------- | ---------------------------------------------------------------------------- |
| `POST /hooks/agent`  | runs an isolated agent turn                                                  |
| `POST /hooks/wake`   | enqueues a system event into the main session                                |
| `POST /hooks/<name>` | resolves through `hooks.mappings`, to reshape a vendor payload into a prompt |

Ingest a meeting the moment its transcript is ready:

```bash
curl -X POST http://127.0.0.1:18789/hooks/agent \
  -H 'Authorization: Bearer SECRET' \
  -H 'Content-Type: application/json' \
  -d '{"message":"A meeting transcript is ready. Pull it from the recorder MCP and write it into the meetings/ folder of this project as meetings/<source>-<source_meeting_id>.","name":"OK meeting ingest"}'
```

Prep a doc the moment a meeting starts:

```bash
curl -X POST http://127.0.0.1:18789/hooks/agent \
  -H 'Authorization: Bearer SECRET' \
  -H 'Content-Type: application/json' \
  -d '{"message":"A meeting just started. Create its doc in the meetings/ folder from the prep template and link each attendee to their people/ dossier.","name":"OK meeting prep"}'
```

Which event you send matters more than the plumbing. [Ingest meetings](https://openknowledge.ai/docs/workflows/meeting-ingestion) covers which recorder emits what, and why a transcript-ready event drives ingestion while `meeting.started` drives prep.

One practical constraint: a vendor cannot POST to `127.0.0.1`. Keep hook endpoints behind loopback, a private network, or a trusted reverse proxy, which means a locally-run gateway needs a tunnel or a relay to receive vendor webhooks. That is why the cron poll is the default and webhooks are the upgrade. Restrict `hooks.allowedAgentIds` to limit which agents an external caller can target, and treat every inbound payload as untrusted.

## Preview

Write and edit responses include a route-only preview path like `/#/notes/mcp-round-trip` — a route id, **not** a full URL. Call `preview_url` for the openable browser address, and don't screenshot the preview to confirm an edit (the tool response is the confirmation). See [MCP reference → Preview links](https://openknowledge.ai/docs/reference/mcp#preview-links) for the full semantics.

## Skill folder

OpenClaw loads skills from `~/.agents/skills`, the shared folder several tools read rather than a `.openclaw/` directory of its own. OpenKnowledge now offers that folder as a skill destination whenever it can see OpenClaw installed, so it appears when you install a skill instead of having to type the path into **Add custom path**. It is also listed in **Settings → Skills → Folders**, though that row stays inert until the folder exists — folder linking merges one folder into another, which is not how it should first be created.

It is offered as a destination only, not as a merge target, until the folder actually exists. Linking another tool's folder into a folder you do not have yet would create it and move your existing skills into it; OpenKnowledge does not do that on your behalf.

## Agent contract

The **OpenKnowledge skill** — the authoritative agent contract for tool use, grounding, and linking — is project-scoped, written for Claude, Cursor, Codex, and OpenCode; OpenClaw has no project skill directory to receive it. The bundles OpenKnowledge installs to `~/.agents/skills/` (which OpenClaw loads) cover discovery and skill authoring, not tool use — so for OpenClaw agents, the contract is these two reminders:

- **Markdown is MCP-owned.** Inside an OK project, read and write `.md` / `.mdx` through OK's tools (`exec`, `search`, `write`, `edit`, `links`), not native shell or file tools — native access loses attribution and skips the frontmatter, backlinks, and history OK returns.
- **Discover before concluding the MCP is missing.** Not seeing `exec` in the initial tool list isn't the escape hatch; run tool discovery first.

For the complete tool surface, see the [MCP reference](https://openknowledge.ai/docs/reference/mcp).

## Troubleshooting

| Symptom                                                     | Cause                                                                                          | Fix                                                                                                            |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `ok init` showed OpenClaw as `config root missing; skipped` | `~/.openclaw/` doesn't exist yet, so OpenClaw wasn't detected                                  | Launch OpenClaw once (it creates its home directory), then re-run `ok init`                                    |
| `ok init` reported the config was `left unchanged`          | OpenKnowledge declined to edit `~/.openclaw/openclaw.json` for the reason shown in parentheses | Fix what the reason names (invalid JSON, a duplicated server block, an oversize config), then re-run `ok init` |
| `openclaw mcp probe` shows 0 tools                          | server couldn't launch, or the folder isn't a KB yet                                           | Confirm `ok` is installed (desktop app or `npm i -g @inkeep/open-knowledge`), and `ok init` the folder         |
| Agent doesn't see the `open-knowledge` tools                | MCP tools lazy-loaded, or the agent's process predates the entry                               | Run tool discovery, then restart the gateway                                                                   |
| `ok init` "refreshed" a parent project, no new KB           | upward project resolution                                                                      | Run `ok init` outside any existing `.ok` tree                                                                  |
| Broken-link warning on a just-written doc                   | forward link to a not-yet-created target                                                       | Create the target, then re-check `links({ kind: "dead" })`                                                     |
| `write` / `edit` -> "Hocuspocus server is not running"      | OK server not started                                                                          | Run `ok start` (or leave the desktop app running) and retry                                                    |
| Preview "doesn't open"                                      | used the route-only `/#/…` path in a browser                                                   | Call `preview_url` for the full URL                                                                            |

New to OpenKnowledge itself? Start with the [quickstart](https://openknowledge.ai/docs/get-started/quickstart) for `ok init` and `ok start`.

# OpenCode (https://openknowledge.ai/docs/integrations/opencode)

Use OpenKnowledge with OpenCode.

[OpenCode](https://opencode.ai) is an open-source AI coding agent.

## Install

There are two ways to connect OpenCode, depending on how you run OpenKnowledge:

- **Desktop app** (macOS, Windows, Linux). The first time you open a project, a consent dialog detects OpenCode and configures it for you. To re-trigger the dialog, choose **File → Set up OpenKnowledge integrations…**.
- **Web app / terminal** (any platform, including Intel Macs — see the [web app guide](https://openknowledge.ai/docs/get-started/quickstart#ok-install-web-app)). Run `ok init` in your project: it registers the OpenKnowledge MCP server with OpenCode and the other editors it detects. Every `ok start` repairs the entry if it has drifted (it never adds one you removed).

OpenCode reads MCP servers from `opencode.json` in the project root, or `~/.config/opencode/opencode.json` for a global install. `ok init` writes the `open-knowledge` server under the top-level `mcp` key in both, using the same resilient launcher every other editor gets. It also installs the OpenKnowledge skill to `.opencode/skills/open-knowledge/`, which OpenCode scans automatically.

## Use a local model

OpenCode is model-agnostic and talks to any OpenAI-compatible endpoint, so you can pair OpenKnowledge with a model running entirely on your machine — no cloud round-trip. Point OpenCode at [Ollama](https://ollama.com), [LM Studio](https://lmstudio.ai), or a self-hosted server in the same `opencode.json` that holds the OpenKnowledge MCP entry:

```jsonc
{
  "$schema": "https://opencode.ai/config.json",
  // Added by `ok init` — connects OpenCode to your knowledge base.
  "mcp": {
    "open-knowledge": { "type": "local", "enabled": true, "command": ["..."] }
  },
  // Your local model, via any OpenAI-compatible endpoint (Ollama shown).
  "provider": {
    "ollama": {
      "npm": "@ai-sdk/openai-compatible",
      "options": { "baseURL": "http://localhost:11434/v1" },
      "models": { "qwen3-coder:30b": {} }
    }
  }
}
```

Pick the model in OpenCode with `/models`. **The model must support tool/function calling** — that's how it drives OpenKnowledge's `exec` / `write` / `edit` tools; a chat-only model will connect but never call them. See OpenCode's [models](https://opencode.ai/docs/models/), [providers](https://opencode.ai/docs/providers/), and [MCP servers](https://opencode.ai/docs/mcp-servers/) docs for the authoritative provider schema.

## Verify

Open the project in OpenCode and ask:

> List the first 5 documents you come across in this project.

OpenCode should call the OpenKnowledge `exec` tool and respond with some of your documents.

If OpenCode doesn't use the OpenKnowledge `exec` tool, restart it so it picks up the new MCP entry, and make sure the model you selected supports tool calling. If it's still missing and `ok init` reported your config was `left unchanged`, OpenKnowledge declined to edit your `opencode.json` for the reason shown in parentheses — an unreadable file, a duplicate `mcp` block, or a config too large to edit safely. Fix that and re-run `ok init`.

# Pi (https://openknowledge.ai/docs/integrations/pi)

Use OpenKnowledge with Pi.

[Pi](https://pi.dev) is an open-source, minimal terminal coding harness. Instead of MCP, OpenKnowledge plugs into Pi through a bridge extension.

## Install

Run `ok init` in your project (or open the project in the OpenKnowledge desktop app — project setup writes the same files). Two things land in the project's `.pi/` directory:

- **`.pi/extensions/open-knowledge.ts`** — the bridge extension. On session start it launches OpenKnowledge's MCP server with the same resilient launcher every other editor uses, then registers each OpenKnowledge tool as a native Pi tool under an `ok_` prefix (`ok_exec`, `ok_search`, `ok_write`, …). The prefix keeps OpenKnowledge's `edit` / `write` from shadowing Pi's built-in file tools.
- **`.pi/skills/open-knowledge/`** — the OpenKnowledge skill, in the [Agent Skills](https://pi.dev/docs/latest) format Pi reads natively from `.pi/skills/`.

These files are managed by OpenKnowledge. Running `ok init` again refreshes them in place, while `ok deinit` removes them.

## Trust the project

Make sure Pi trusts the project folder. Pi does not load project-local extensions or skills from `.pi/` until the project is trusted. When you first run pi in the project, accept the trust prompt. See Pi's [project trust documentation](https://pi.dev/docs/latest/security#project-trust) for more details.

## Verify

Run `pi` in the project, and ask:

> List the first 5 documents you come across in this project.

Pi should call the OpenKnowledge `ok_exec` tool and respond with some of your documents.

If the `ok_` tools are missing: make sure you trusted the folder, then `/reload` (or restart `pi`). The first tool call can take a moment on a fresh machine — the launcher may be installing the OpenKnowledge CLI via `npx`. If Pi shows an "Open Knowledge tools unavailable" notice, install [Node.js 24+](https://nodejs.org) or the OpenKnowledge desktop app and reload.

# From Notion (https://openknowledge.ai/docs/migrate/notion)

How to export a Notion workspace to Markdown and open it in OpenKnowledge, what carries over, and where the two differ.

This guide will cover exporting your Notion workspace to markdown files so that you can access them from OpenKnowledge. OpenKnowledge and Notion differ in a few key ways, so a Notion export needs some cleanup — this guide covers what carries over and what to fix.

## Install OpenKnowledge

### Desktop app (macOS, Windows, Linux)

Download the latest version of the OpenKnowledge desktop app. The button picks your platform automatically; its dropdown carries every other build, and the [downloads page](https://openknowledge.ai/download) lists them all.

- [macOS (Apple Silicon)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-arm64.dmg)
- [Windows (x64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-Setup-x64.exe)
- [Windows (Arm64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-Setup-arm64.exe)
- [Linux .deb (x64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-amd64.deb)
- [Linux .deb (Arm64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-arm64.deb)
- [Linux .rpm (x64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-x86_64.rpm)
- [Linux .rpm (Arm64)](https://github.com/inkeep/open-knowledge/releases/latest/download/OpenKnowledge-aarch64.rpm)
- [Run in your browser (npm)](https://openknowledge.ai/docs/reference/cli)
- [All builds and checksums](https://openknowledge.ai/download)

### Web app (any platform, including Intel Macs)

Install the [ok CLI](https://openknowledge.ai/docs/reference/cli) with npm:

```bash
npm install -g @inkeep/open-knowledge
```

## Export your workspace from Notion

1. In Notion, click on your workspace in the top left and go to **Settings**.
2. Under **General**, scroll down to **Export**.
3. Set **Export format** to **Markdown & CSV** and leave all the default settings.
4. Download and **unzip** the export folder and move it to your desired location.

## Clean up the export with `ok migrate notion`

The [ok CLI](https://openknowledge.ai/docs/reference/cli) ships a cleanup command that resolves format issues with the exported content. By default, the command prints what it would change and writes nothing until you pass `--apply`.

```bash
# Preview what would change (writes nothing)
ok migrate notion /path/to/unzipped-export

# Apply the changes
ok migrate notion /path/to/unzipped-export --apply
```

> **Info**
>
> The desktop app ships the `ok` CLI. The Windows installer and the Linux
> packages put it on your `PATH` on their own. On macOS it installs to
> `$HOME/.ok/bin`, and a pre-checked toggle in the first-launch dialog puts it
> on your `PATH` — if `ok` isn't found, add it from **File → Set up
> OpenKnowledge integrations…**, then open a new terminal window. If it's
> still missing, add `$HOME/.ok/bin` to your `PATH` manually.

Every transform is **idempotent**, so re-running is safe. See the tables below for a full list of differences between the Notion export and what OpenKnowledge supports.

**Options:**

| Flag                              | Effect                                                                                   |
| --------------------------------- | ---------------------------------------------------------------------------------------- |
| `--apply`                         | Write the changes (default is a dry-run preview).                                        |
| `--strip-base64`                  | Delete inline base64 images instead of extracting them to files.                         |
| `--remove-csv`                    | Delete each `_all.csv` once its table page exists (destructive).                         |
| `--only <list>` / `--skip <list>` | Run or skip specific transforms: `links`, `frontmatter`, `callouts`, `images`, `tables`. |
| `--force`                         | Run even if the folder isn't detected as a Notion export.                                |
| `--json`                          | Print a machine-readable report.                                                         |

> **Info**
>
> The command refuses to touch a folder that doesn't look like a Notion export unless you pass `--force`.

## Open the export in OpenKnowledge

### Desktop app

Open OpenKnowledge, choose **Open folder on disk**, select the unzipped export folder, and proceed through the **Setup** screen with defaults selected.

### Web app

`cd` into the unzipped folder and run `ok init && ok start`.

## What exports cleanly

| Notion content                                            | In OpenKnowledge                                                                                                                                                                                                |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Headings, paragraphs, bold / italic, quotes, and dividers | Render natively and are preserved in the exported markdown.                                                                                                                                                     |
| Bulleted, numbered, and to-do lists                       | Render natively, including nesting and `- [ ]` checkboxes.                                                                                                                                                      |
| Code blocks                                               | Render with syntax highlighting.                                                                                                                                                                                |
| Simple (inline) tables                                    | Convert to Markdown pipe tables and render as tables.                                                                                                                                                           |
| Images and file attachments                               | Downloaded into the export folder next to each page and referenced with relative `![alt](path)` links; images render inline. See [Assets and embeds](https://openknowledge.ai/docs/features/assets-and-embeds). |

## Where the two differ

A number of Notion features don't survive the Markdown & CSV export, or land as plain text. Many can be fixed by the [`ok migrate notion <folder-path>`](https://openknowledge.ai/docs/migrate/notion#clean-up-the-export-with-ok-migrate-notion) command.

| Notion feature                                                  | Exported result                                                                                                                            | Fixed by CLI migration command                                                                                                   |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| **Databases** (tables, boards, calendars, galleries, timelines) | Each database exports as a `<name>_all.csv` file and a folder holding one markdown file per row. Full-page databases also get a stub page. | ✅ Creates a Markdown table page for every CSV. The `.csv` files are not deleted unless you pass `--remove-csv`.                  |
| **Database row properties**                                     | Exported as plain `Key: Value` lines under the page title, not YAML frontmatter.                                                           | ✅ Lifted into YAML frontmatter so they show in the [Properties panel](https://openknowledge.ai/docs/features/editor#properties). |
| **Callouts**                                                    | Exported as raw `<aside>` HTML, so they don't pick up OpenKnowledge's callout styling.                                                     | ✅ Converted to native `> [!note]` callouts.                                                                                      |
| **Links between pages**                                         | Link targets are percent-encoded, and once decoded contain spaces.                                                                         | ✅ Decoded and angle-wrapped so they render and resolve.                                                                          |
| **Pasted or inline images**                                     | Some images export inline as un-rendered base64 `data:` URIs instead of files.                                                             | ✅ Extracted to image files (or dropped with `--strip-base64`).                                                                   |
| **Relations, rollups, formulas**                                | Not represented in the Markdown & CSV export.                                                                                              | —                                                                                                                                |
| **Toggles and toggle headings**                                 | Nested content is preserved, but the collapsible toggle wrapper is lost.                                                                   | —                                                                                                                                |
| **Synced blocks**                                               | Written out as ordinary content wherever they appear; the link between copies is gone.                                                     | —                                                                                                                                |
| **Web bookmarks and embeds** (Figma, video, PDF, etc.)          | Converted to plain links. The rich preview card or inline embed is gone. Uploaded files download alongside the page.                       | —                                                                                                                                |
| **Page icons and cover images**                                 | Not exported.                                                                                                                              | —                                                                                                                                |
| **Comments, page history, and backlinks**                       | Not included in the export.                                                                                                                | —                                                                                                                                |

## See also

- [GitHub sync](https://openknowledge.ai/docs/features/github-sync): keep your project synced with your team through GitHub
- [Quickstart](https://openknowledge.ai/docs/get-started/quickstart): the five-minute setup
- [Core concepts](https://openknowledge.ai/docs/reference/core-concepts): links, backlinks, and the file-system-as-database model
- [Assets and embeds](https://openknowledge.ai/docs/features/assets-and-embeds): how images and file references behave

# From Obsidian (https://openknowledge.ai/docs/migrate/obsidian)

How to open an existing Obsidian vault in OpenKnowledge, which markdown syntax carries over, and where the two differ.

OpenKnowledge and Obsidian are both editors over a [folder of plain markdown](https://openknowledge.ai/docs/reference/core-concepts#the-file-system-is-the-database). This means that you can point OpenKnowledge at your existing vault and your notes, links, tags, and frontmatter work out of the box.

> **Info**
>
> OpenKnowledge works **directly on your markdown files** — it never moves, renames, or rewrites them just for opening a vault. It keeps its own state in a `.ok/` directory and ignores Obsidian's `.obsidian/` folder, so your Obsidian setup stays exactly where it is. To back out, `ok deinit` removes everything OpenKnowledge added to the vault and leaves your markdown untouched.

## Open your vault

### Desktop app (macOS, Windows, Linux)

Open OpenKnowledge, choose **Open folder on disk**, and select your existing vault folder, then proceed through the **Setup** screen. OpenKnowledge uses the folder in place — it doesn't copy or move your notes.

### Web app (any platform, including Intel Macs)

Install the [ok CLI](https://openknowledge.ai/docs/reference/cli), `cd` into your vault, and run `ok init && ok start`.

> **Info**
>
> Want to know exactly what these commands write to your machine — in your vault, your home directory, and your editors' configs? See [What OpenKnowledge writes to your system](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

## What carries over

Most of what you write in Obsidian renders and round-trips in OpenKnowledge unchanged. Existing syntax is **preserved byte-for-byte** when a file is saved, so opening a vault is non-destructive.

| Obsidian syntax  | Example                                           | In OpenKnowledge                                                                                                                                                                       |
| ---------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Wiki links       | `[[Page]]`, `[[Page\|alias]]`, `[[Page#Heading]]` | Resolved to the target doc; feed backlinks and the graph. Preserved as written.                                                                                                        |
| Markdown links   | `[text](./page.md)`                               | Fully supported and the [recommended form](https://openknowledge.ai/docs/reference/core-concepts#links-and-backlinks) for new links. Wiki links keep working as a superset.            |
| YAML frontmatter | `---` block at the top                            | Open-shape — any keys. `aliases`, `tags`, `cssclasses`, dates, and lists are preserved. Edit them in the [Properties panel](https://openknowledge.ai/docs/features/editor#properties). |
| Tags             | `#tag`, or `tags:` in frontmatter                 | Both forms are indexed and searchable.                                                                                                                                                 |
| Callouts         | `> [!note]`, `> [!warning]`, foldable `> [!tip]-` | Rendered with icons. 15 types including Obsidian's set (`success`, `question`, `failure`, `bug`, `example`, `quote`, …); aliases like `summary` / `tldr` fold to `abstract`.           |
| Highlights       | `==highlighted==`                                 | Rendered as a highlight mark.                                                                                                                                                          |
| Comments         | `%%hidden note%%`                                 | Shown dimmed in the editor (Obsidian live-preview style), hidden in published output and cross-app paste, kept byte-for-byte in the file. (`<!-- -->` works too.)                      |
| Math             | `$inline$`, `$$block$$`                           | Rendered as LaTeX math.                                                                                                                                                                |
| Mermaid          | ` ```mermaid ` fenced blocks                      | Rendered as diagrams.                                                                                                                                                                  |
| Footnotes        | `[^1]` with `[^1]: …`                             | Supported, including named footnotes.                                                                                                                                                  |
| Asset embeds     | `![[image.png]]`, `![[file.pdf#page=3]]`          | Images and PDFs render inline. See [Assets and embeds](https://openknowledge.ai/docs/features/assets-and-embeds).                                                                      |

## Where the two differ

A handful of Obsidian features are not supported, or behave differently. None of them corrupt your files — the syntax is preserved on disk; it just isn't interpreted the same way.

| Feature                                                                    | What happens in OpenKnowledge                                                                                                                                                                                                                                                 |
| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Note transclusion** — `![[Some Note]]` to embed another note's *content* | Renders as a working link to the note rather than inlining its content — the target is one click away, just not pulled in. Only file assets (images, PDFs) embed inline; documents do not transclude.                                                                         |
| **Block references** — `^block-id` and `[[Page#^block-id]]`                | Not recognized. Heading links (`[[Page#Heading]]`) resolve, but a `#^block-id` fragment won't point at a block.                                                                                                                                                               |
| **The `.obsidian/` folder**                                                | Ignored. OpenKnowledge does not read Obsidian's theme, hotkeys, plugin config, or vault preferences. Configure OpenKnowledge in [`.ok/config.yml`](https://openknowledge.ai/docs/reference/configuration) instead.                                                            |
| **Community plugins**                                                      | Don't run. OpenKnowledge is a separate app, not an Obsidian plugin host. Many features people reach for plugins for in Obsidian — callouts, tags, math, Mermaid, embeds — are built in here, but plugin-specific syntax (for example Dataview queries) is left as plain text. |
| **Canvas (`.canvas`) and Bases (`.base`)**                                 | Open in a read-only text view rather than as interactive canvases or bases. They're still indexed and linkable from your notes.                                                                                                                                               |

> **Info**
>
> OpenKnowledge's defaults match Obsidian's defaults — wiki links and attachments stored alongside your notes — so a standard vault works as-is. There's no migration step today; if you have a vault with heavily non-default settings, [let us know](https://github.com/inkeep/open-knowledge/issues).

## Agent skills, built in

Getting an AI agent to work *well* inside an Obsidian vault usually means adding an agent-skills layer on top — a set of `SKILL.md` instructions that teach the agent your conventions and how to read and write the vault. OpenKnowledge builds that layer in, so there's nothing to bolt on:

- **A project skill is installed for you.** `ok init` (or the desktop setup) drops in a project-local skill that teaches your agent OpenKnowledge's tools and conventions — no hand-written `CLAUDE.md` to maintain.
- **Agents get first-class tools, not just file access.** The [MCP server](https://openknowledge.ai/docs/reference/mcp) gives any MCP-capable agent — Claude Code, Cursor, Codex, OpenCode, OpenClaw — typed `exec` (read) / `write` / `search` / `links` tools over the same files, so edits are consistent, attributed, and keep your link graph healthy.

## See also

- [Quickstart](https://openknowledge.ai/docs/get-started/quickstart): the five-minute setup
- [Core concepts](https://openknowledge.ai/docs/reference/core-concepts): links, backlinks, and the file-system-as-database model
- [Editor](https://openknowledge.ai/docs/features/editor): WYSIWYG, source mode, and the graph, backlinks, and properties panels
- [Timeline and recovery](https://openknowledge.ai/docs/features/timeline-and-recovery): version history and point-in-time rollback
- [Ignore patterns](https://openknowledge.ai/docs/features/ignore-patterns): keep folders out of the editor, search, and agents
- [Assets and embeds](https://openknowledge.ai/docs/features/assets-and-embeds): how images and file references behave
- [GitHub sync](https://openknowledge.ai/docs/features/github-sync): keep your project synced with your team through GitHub

# OKF (https://openknowledge.ai/docs/plugins/okf)

Keep a project portable to Google's Open Knowledge Format with advisory rules, agent-readable schemas, an optional index generator, and a companion skill.

The **OKF** project plugin checks whether a knowledge base stays portable to [Google's Open Knowledge Format](https://github.com/GoogleCloudPlatform/open-knowledge-format/blob/main/SPEC.md) v0.2. Findings are advisory warnings and never block a save or an agent edit.

> **Beta.** Off until a project enables it. The [Open Knowledge Format starter pack](https://openknowledge.ai/docs/workflows/supporting-open-knowledge-format) enables it while creating an OKF-ready bundle.

## Turn on the plugin

Open **Settings → This project → Plugins** and switch on **OKF**. The setting lands in the project's `.ok/config.yml`, so committing it gives every collaborator the same checks. Changes apply immediately, with no restart and no Save button.

The plugin's settings page then holds its controls: a recommended agent skill, optional generated indexes, and one switch per rule.

## Install the agent skill

The plugin recommends **Open Knowledge Format guidance** (`okf-knowledge-base`), a project skill that helps agents choose document types, preserve provenance, and avoid inventing unsupported metadata. Click **Install skill** to add it. The [starter pack](https://openknowledge.ai/docs/workflows/supporting-open-knowledge-format) installs the same skill, so it may already be present. In that case the page shows **Already in project** and leaves the existing copy untouched.

The skill is optional. The plugin's checks are the same with or without it.

## Maintain generated indexes

**Maintain index.md** creates and maintains a navigation file in every folder that contains Markdown, listing that folder's documents by type and linking its subfolders.

Generated indexes are machine-owned: OpenKnowledge rewrites them as documents change, so manual edits are replaced. Turning the setting on shows a disclosure before any files are written. In a Git project it also adds a scoped `.gitattributes` rule (`merge=union`) so concurrent index changes merge cleanly. If another attribute already controls those paths, index maintenance pauses and the plugin page explains what to resolve.

Turning it off stops rebuilds and removes the Git rule, but leaves the generated files in place.

## Tune the rules

Every rule is on while the plugin is enabled unless you switch it off. Findings report under source `okf` with the rule id as the code (`okf/no-wiki-links`). There are no automatic fixes because each finding needs a content decision.

### Rule reference

| Group              | Rule                         | What it checks                                                                                                                                                                                                |
| ------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Document structure | `no-wiki-links`              | Flags `[[wiki-links]]` outside code because other OKF consumers may render them literally                                                                                                                     |
| Document structure | `log-shape`                  | Requires real ISO `YYYY-MM-DD` headings in newest-first order in `log.md`                                                                                                                                     |
| Document structure | `index-shape`                | Requires navigation lists under section headings and links in every `index.md` entry                                                                                                                          |
| Document structure | `reserved-casing`            | Flags reserved `Index.md` or `Log.md` filenames that are not lowercase                                                                                                                                        |
| Frontmatter        | `frontmatter-required`       | Requires a non-empty `type` on every non-reserved document                                                                                                                                                    |
| Frontmatter        | `frontmatter-recommended`    | Validates the shape of optional `title`, `description`, `resource`, and `tags` fields                                                                                                                         |
| Frontmatter        | `frontmatter-provenance`     | Validates optional `sources`, `usage_window`, `generated`, `verified`, `status`, and `stale_after` metadata, including the [timestamp shape](https://openknowledge.ai/docs/plugins/okf#provenance-timestamps) |
| Frontmatter        | `frontmatter-computation`    | Requires `runtime` on documents typed `Attested Computation`                                                                                                                                                  |
| Frontmatter        | `frontmatter-reserved-index` | Disallows frontmatter on `index.md`, except at the bundle root                                                                                                                                                |
| Frontmatter        | `frontmatter-root-index`     | Checks the bundle root's quoted `okf_version` value                                                                                                                                                           |
| Project            | `project-no-mdx`             | Flags `.mdx` files because OKF consumers scan for `.md`                                                                                                                                                       |

Reserved filenames match exactly and case-sensitively: `5-index.md` and `notes-log.md` are ordinary documents that need a `type`, and `reserved-casing` catches `Index.md` and `Log.md`.

### Provenance timestamps

Every timestamp-valued provenance key is an ISO 8601 datetime carrying an explicit UTC offset, not a calendar date: `stale_after: 2026-12-31T00:00:00Z`. That covers `generated.at`, each `verified[].at`, `stale_after`, each `sources[].last_modified`, and both bounds of a `usage_window`, whether the window is written beside `sources` or inside one entry.

A datetime with no offset (`2026-12-31T00:00:00`) is flagged too, so adding a time is not enough on its own.

Other OKF consumers may not agree yet. Upstream revised this part of the spec in place under the same v0.2 label, so a checker pinned to the earlier reading still expects a bare `YYYY-MM-DD` for `stale_after` and will flag a document written to the rule above. The declared `okf_version` is `0.2` under both readings, so it cannot tell them apart. Check the consumer you are targeting before treating either spelling as portable.

Log entry headings are the exception, and they are governed by a different rule: `log.md` uses plain `YYYY-MM-DD`, checked by `log-shape`.

### Where rules run

| Rules                           | Surfaces                                                            |
| ------------------------------- | ------------------------------------------------------------------- |
| Document structure, Frontmatter | Editor (live), Problems panel → **This doc**, `ok lint`, MCP `lint` |
| Project                         | Problems panel → **Project**, `ok audit`, MCP `audit`               |

Project rules need the whole file tree, which is why they run only on project-scope surfaces.

## Generated schema files

While a frontmatter rule is enabled, OpenKnowledge writes its JSON Schema under `.ok/okf/` so agents can read the exact field contract rather than guess:

```text
.ok/okf/required.schema.json
.ok/okf/recommended.schema.json
.ok/okf/provenance.schema.json
.ok/okf/computation.schema.json
.ok/okf/reserved-index.schema.json
.ok/okf/root-index.schema.json
```

The directory mirrors the rule switches: toggling a rule off deletes its schema, and disabling the plugin removes them all. The files are generated and gitignored. The plugin validates from its own compiled copy and never reads them back, so editing one changes nothing. Tune the rule switches instead.

Agent reads advertise the governing schemas through the same channel as [Frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter).

## Configuration

The settings above map to the project config:

```yaml
contentRules:
  okf:
    enabled: true
    generate:
      index: true
    rules:
      no-wiki-links: false
```

Omit a rule to leave it on, or set it to `false` to silence only that rule. Unknown rule ids are ignored for forward compatibility, so check the spelling if a disabled rule keeps reporting. Generated indexes stay off unless `generate.index` is explicitly `true`.

See the [configuration reference](https://openknowledge.ai/docs/reference/configuration) for the surrounding file shape.

## What the plugin writes

| Path                    | When                                                   | Ownership                                                                 |
| ----------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------- |
| `.ok/config.yml`        | You change a plugin setting                            | Project configuration, committed when you want to share it                |
| `.ok/okf/*.schema.json` | The matching frontmatter rule is enabled               | Generated and gitignored. Deleted when the rule or plugin is switched off |
| `**/index.md`           | You opt into generated indexes                         | Generated. Manual edits are replaced                                      |
| `.gitattributes`        | Generated indexes are enabled in a Git project         | OpenKnowledge manages only its scoped rule                                |
| Project skill directory | You install the recommended skill or seed the OKF pack | Editable project skill                                                    |

## See also

- [Supporting the Open Knowledge Format](https://openknowledge.ai/docs/workflows/supporting-open-knowledge-format): start a project with the OKF starter pack
- [Content rules overview](https://openknowledge.ai/docs/advanced/content-rules/overview): where warnings appear and how editor, CLI, and agent surfaces agree
- [Frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter): validate frontmatter against your own JSON Schema files
- [Skills](https://openknowledge.ai/docs/features/skills): project skill installation and editing

# Overview (https://openknowledge.ai/docs/plugins/overview)

Optional capabilities that ship with OpenKnowledge, most of them off until you turn them on. Linters, OKF portability, themes, and presentations.

A plugin is an optional capability that ships with OpenKnowledge. Most stay off until you turn them on (Themes is the exception: it ships enabled). Each one belongs to a scope, and the scope decides who else gets it:

- **Project plugins** are saved to the project's `config.yml`. Commit it and every collaborator gets the same setting.
- **User plugins** are personal to your device. They live in your user config and are never committed.

Turning one on gives it its own page in the settings sidebar, under **Plugins**.

## Turning a plugin on

Open Settings. There is one manage page per scope:

- **This project ▸ Plugins**
- **User ▸ Plugins**

Flip the switch. Open editors react live; there is no restart and no Save button.

## What ships today

| Plugin                                                                                  | Scope   | What it does                                                                                                                     |
| --------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| [markdownlint](https://openknowledge.ai/docs/advanced/content-rules/markdownlint)       | Project | Flags markdown style problems as you write — hard tabs, heading increments, list markers, and the rest of the standard rule set. |
| [Frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter) | Project | Validates document frontmatter against standard JSON Schema files.                                                               |
| [OKF](https://openknowledge.ai/docs/plugins/okf)                                        | Project | Checks portability to Open Knowledge Format v0.2, recommends agent guidance, and can maintain generated indexes. Beta.           |
| [Slidev](https://openknowledge.ai/docs/plugins/slidev)                                  | User    | Presents a document as a slide deck in its own window. Desktop only; needs the Slidev CLI installed separately. Beta.            |
| [Themes](https://openknowledge.ai/docs/plugins/themes)                                  | User    | A personal color-theme picker: one palette per light mode and one per dark, built-in or your own base16 schemes.                 |

markdownlint, Frontmatter schemas, and OKF all use the **content rules** layer. What they have in common — where problems appear, the Problems panel, `ok lint`, agent advisories — is on the [content rules overview](https://openknowledge.ai/docs/advanced/content-rules/overview).

# Slidev (https://openknowledge.ai/docs/plugins/slidev)

Present a markdown document as a slide deck in its own window, rendered by the Slidev CLI.

[Slidev](https://sli.dev/) renders a markdown file as a presentation, with slides separated by `---`. Mark a document, click the toolbar action, and the deck opens in its own OpenKnowledge window.

> **Beta.** Desktop app only. Rendering is Slidev's, so it moves at Slidev's pace.

OpenKnowledge does not bundle or download Slidev. You install it yourself.

## Install Slidev

Install the CLI **and a theme**. Themes ship as separate packages and a deck cannot open without the one it declares; `@slidev/theme-default` covers decks that declare none.

```bash
npm install -g @slidev/cli @slidev/theme-default
```

Or add both packages to the project. A project-local install takes precedence over a global one, which is how you pin a deck to a Slidev version.

Other install routes are in [Slidev's install guide](https://sli.dev/guide/install).

## Turn on the plugin

Open Settings → **User ▸ Plugins** and switch on **Slidev**. It's a user plugin, so the choice stays on your device and is never committed.

The **Slidev** page in the sidebar reports whether OpenKnowledge found Slidev. If it didn't, the page offers the install command to copy and a **Run in terminal** button. Install, click back into OpenKnowledge, and the status updates without a restart.

## Mark a document as a deck

Add `slides: true` to the document's frontmatter:

```md
---
title: Product launch
slides: true
---

# Product launch

---

## Where we are

- Beta since March
- 40 teams onboarded
```

An **Open in Slidev** action appears in the editor toolbar. Activating it starts a Slidev server for that document and opens the deck in its own window.

The action appears only when Slidev resolves. If you expect it and don't see it, check the plugin page.

## Editing while presenting

Edit the document and the open deck updates — Slidev watches the same file OpenKnowledge saves to. Your position in the deck is preserved. Expect a beat between typing and the refresh; OpenKnowledge debounces its writes to disk.

Closing a deck window stops that deck's Slidev process. Quitting OpenKnowledge stops all of them.

## Writing decks

Past the frontmatter flag it's ordinary Slidev:

- [Syntax guide](https://sli.dev/guide/syntax) — separators, per-slide frontmatter, layouts, speaker notes, click animations, code blocks.
- [Themes gallery](https://sli.dev/resources/theme-gallery) — install a theme's package before a deck declares it.
- [Built-in components](https://sli.dev/builtin/components) — the Vue components available inside slides.

Two notes for longer decks:

- **Multi-file decks work.** Slidev's `src:` include pulls a slide's content from another file, so a deck can be one entry file plus a folder of slides.
- **Keep the deck inside the project.** OpenKnowledge only opens documents that resolve inside the open project, including through symlinks.

# Themes (https://openknowledge.ai/docs/plugins/themes)

Recolor the editor with built-in palettes or your own base16 schemes, one palette per light mode and one per dark, saved as portable YAML files.

Themes recolors the whole OpenKnowledge interface with a [base16](https://github.com/tinted-theming/schemes) palette. It's a user plugin: on by default, personal to your device, never committed.

Open **Settings → Plugins → Themes**. The page is a gallery of tiles: **Default**, the built-ins (Dracula, Catppuccin Frappé, Catppuccin Latte, Monokai, Gruvbox, Solarized), and any themes you've created, plus a **Create new theme** tile.

## One palette per mode

Every tile carries a sun and a moon: the sun assigns that palette to light mode, the moon to dark mode. Clicking the tile itself assigns it to whichever mode is currently active. The light/dark switch in **Settings → Preferences** decides which of the two slots is live. On **System**, your OS does, so the palette follows the OS appearance.

A palette forces its own light or dark variant wherever it sits. Put Dracula in the light slot and switching to light mode shows Dracula's dark colors: the slot decides *when* a palette applies, and the palette decides how it looks. **Default** means no palette: the base stylesheet shows through.

Selections are saved to your user config as `appearance.colorThemeLight` / `appearance.colorThemeDark`. See the [configuration reference](https://openknowledge.ai/docs/reference/configuration).

## Create your own

Click **Create new theme**, name it, and the editor opens. Two ways to fill it in:

- **Paste a scheme.** The paste box accepts any [Tinted Theming](https://github.com/tinted-theming/schemes) base16 scheme (hundreds are published), in either the current layout (`palette:` nested) or the original chriskempson one, YAML or JSON.
- **Edit the sixteen slots by hand.** Each `base00`–`base0F` slot names its role. Edits apply live and save automatically.

**Copy as YAML** exports the scheme in the standard layout, so a theme you tuned here is pasteable anywhere else in the base16 ecosystem.

## Theme files

Saved themes are plain Tinted Theming YAML files in `~/.ok/themes/`, one file per theme, filename as identity. Drop a downloaded scheme file there (`.yaml` or `.yml` only) and it appears in the gallery, since the folder is re-scanned when the Themes page opens. A file that doesn't parse is listed with the reason rather than silently dropped, so it can always be fixed.

Deleting a theme from its tile deletes the file, with an undo window before it's gone for good.

## Turn it off

Themes ships enabled. Switch it off in **Settings → User ▸ Plugins**: the palette drops back to the default stylesheet and the page leaves the sidebar. Your selections stay in config, so switching it back on restores them.

# Agentic search (https://openknowledge.ai/docs/reference/agentic-search)

How an agent finds things with no vector database: it searches, greps, and follows backlinks in a loop over live files that come back with their graph context attached, not a vector copy.

OpenKnowledge answers questions across thousands of files with no vector database. Two techniques do the work: retrieval runs as a **loop**, over **virtualized files** that hand the agent a briefing on every read.

## Retrieval is a loop

```html preview
<div style="padding:18px">
  <div id="row" style="display:flex;flex-wrap:wrap;gap:8px;align-items:stretch"></div>
  <div class="cap">Each step is an MCP tool call; the model picks the next one. A wrong hit just loops back. No fixed pipeline.</div>
</div>
<style>
#row .node{flex:1;min-width:112px;border:1px solid var(--border);border-radius:12px;padding:11px 13px;background:var(--card);transition:box-shadow .3s,border-color .3s;cursor:pointer}
#row .node .t{font-weight:600;font-size:13px}
#row .node .s{color:var(--muted-foreground);font-size:11.5px;margin-top:2px}
#row .node.on{border-color:var(--primary);box-shadow:0 0 0 3px var(--accent-soft)}
#row .node.on .t{color:var(--accent-ink)}
#row .arrow{align-self:center;color:var(--muted-foreground)}
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px}
.cap::before{content:"\21BB  ";color:var(--primary);font-weight:700}
@media (prefers-reduced-motion:reduce){#row .node{transition:none}}
</style>
<script>
var steps=[["Ask","your question"],["search / grep / ls","over live files"],["read a file","with graph context"],["decide","enough to answer?"],["answer","with citations"]];
var row=document.getElementById("row"),nodes=[];
steps.forEach(function(s,i){
  if(i){var a=document.createElement("div");a.className="arrow";a.textContent="→";row.appendChild(a);}
  var d=document.createElement("div");d.className="node";
  var t=document.createElement("div");t.className="t";t.textContent=s[0];
  var sub=document.createElement("div");sub.className="s";sub.textContent=s[1];
  d.appendChild(t);d.appendChild(sub);
  d.onclick=function(){pinned=true;set(i);};
  row.appendChild(d);nodes.push(d);
});
var cur=0,pinned=false;
function set(i){cur=i;nodes.forEach(function(n,j){n.classList.toggle("on",j===i);});}
set(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){setInterval(function(){if(!pinned)set((cur+1)%nodes.length);},1400);}
</script>
```

Classic RAG embeds your question once and pastes in the nearest chunks; if they are wrong, so is the answer. Here each step is an MCP tool call and the model picks the next one: search, read, follow a backlink, reformulate. A wrong first hit costs one more step, not the whole answer.

## Every read is a briefing

A `cat` through OpenKnowledge returns more than the bytes on disk: the file, frontmatter and all, plus its place in the graph: the backlinks pointing at it, its outbound links, and its version history.

```html preview
<div style="padding:18px">
  <div class="seg">
    <button data-v="raw">raw disk &middot; cat</button>
    <button data-v="ok" class="on">OpenKnowledge &middot; exec</button>
  </div>
  <pre id="code"></pre>
  <div id="rich"><div class="inner"></div></div>
</div>
<style>
.seg{display:inline-flex;border:1px solid var(--border);border-radius:9px;padding:2px;background:var(--muted);margin-bottom:12px}
.seg button{border:0;background:transparent;color:var(--muted-foreground);font:inherit;font-size:12.5px;padding:6px 12px;border-radius:7px;cursor:pointer}
.seg button.on{background:var(--card);color:var(--foreground);box-shadow:0 1px 2px rgba(0,0,0,.1)}
#code{margin:0;background:var(--muted);border-radius:10px;padding:12px 14px;font:12.5px ui-monospace,Menlo,monospace;color:var(--foreground);white-space:pre-wrap}
.fm{color:var(--muted-foreground)}
.dim{color:var(--muted-foreground)}
#rich{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .45s ease,opacity .45s ease}
#rich.show{grid-template-rows:1fr;opacity:1;margin-top:12px}
#rich .inner{overflow:hidden}
.card{border:1px dashed var(--primary);border-radius:10px;padding:13px}
.hd{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--accent-ink);margin-bottom:10px}
.rowk{display:flex;gap:9px;flex-wrap:wrap;align-items:center;margin-bottom:9px;font-size:12.5px}
.k{width:80px;flex:none;color:var(--muted-foreground);font-family:ui-monospace,monospace}
.chip{border:1px solid var(--border);background:var(--card);border-radius:7px;padding:1px 7px;font-family:ui-monospace,monospace;font-size:11.5px}
.ver{display:flex;flex-direction:column;gap:3px;font-family:ui-monospace,monospace;font-size:11.5px}
.ver b{color:var(--foreground)}
</style>
<script>
var code=document.getElementById("code"),rich=document.getElementById("rich"),btns=document.querySelectorAll(".seg button");
var file='<span class="fm">---\ntitle: Authentication\nstatus: canonical\n---</span>\n# Authentication\n...the file...';
rich.querySelector(".inner").innerHTML='<div class="card"><div class="hd">attached by exec &middot; graph + history</div>'
+'<div class="rowk"><span class="k">graph</span><span class="chip">hub</span> <span class="dim">many docs link here</span></div>'
+'<div class="rowk"><span class="k">backlinks</span><span class="chip">login.md</span><span class="chip">sessions.md</span><span class="chip">sso.md</span><span class="dim">who points here</span></div>'
+'<div class="rowk"><span class="k">links to</span><span class="chip">tokens.md</span><span class="chip">oauth.md</span></div>'
+'<div class="rowk"><span class="k">history</span><span class="ver"><span><b>you</b> &middot; add SSO section</span><span><b>claude-code</b> &middot; tighten token refresh</span></span></div>'
+'</div>';
function set(v){
  code.innerHTML=(v==="ok"?'<span class="dim">exec("cat architecture/auth.md")</span>':'<span class="dim">$ cat architecture/auth.md</span>')+"\n"+file;
  rich.classList.toggle("show",v==="ok");
  btns.forEach(function(b){b.classList.toggle("on",b.dataset.v===v);});
}
btns.forEach(function(b){b.onclick=function(){set(b.dataset.v);};});
set("ok");
</script>
```

The same holds for a whole folder. A raw `ls` is a list of filenames; through OpenKnowledge it comes back as a map, so the agent knows where to look next without opening anything.

```html preview
<div style="padding:18px">
  <div class="seg">
    <button data-v="raw">raw disk &middot; ls</button>
    <button data-v="ok" class="on">OpenKnowledge &middot; exec</button>
  </div>
  <pre id="code"></pre>
  <div id="rich"><div class="inner"></div></div>
</div>
<style>
.seg{display:inline-flex;border:1px solid var(--border);border-radius:9px;padding:2px;background:var(--muted);margin-bottom:12px}
.seg button{border:0;background:transparent;color:var(--muted-foreground);font:inherit;font-size:12.5px;padding:6px 12px;border-radius:7px;cursor:pointer}
.seg button.on{background:var(--card);color:var(--foreground);box-shadow:0 1px 2px rgba(0,0,0,.1)}
#code{margin:0;background:var(--muted);border-radius:10px;padding:12px 14px;font:12.5px ui-monospace,Menlo,monospace;color:var(--foreground);white-space:pre-wrap}
.dim{color:var(--muted-foreground)}
#rich{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .45s ease,opacity .45s ease}
#rich.show{grid-template-rows:1fr;opacity:1;margin-top:12px}
#rich .inner{overflow:hidden}
.card{border:1px dashed var(--primary);border-radius:10px;padding:13px}
.hd{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--accent-ink);margin-bottom:10px}
.rowk{display:flex;gap:9px;flex-wrap:wrap;align-items:baseline;margin-bottom:9px;font-size:12.5px}
.k{width:94px;flex:none;color:var(--foreground);font-family:ui-monospace,monospace}
.chip{border:1px solid var(--border);background:var(--card);border-radius:7px;padding:1px 7px;font-size:11.5px}
</style>
<script>
var code=document.getElementById("code"),rich=document.getElementById("rich"),btns=document.querySelectorAll(".seg button");
rich.querySelector(".inner").innerHTML='<div class="card"><div class="hd">added by OpenKnowledge &middot; beyond the filenames</div>'
+'<div class="rowk"><span class="k">description</span><span>how auth, sessions, and tokens fit together</span></div>'
+'<div class="rowk"><span class="k">tags</span><span class="dim">auth, security</span></div>'
+'<div class="rowk"><span class="k">templates</span><span class="chip">adr</span><span class="dim">Architecture decision record</span></div>'
+'<div class="rowk"><span class="k">contains</span><span class="dim">8 md files &middot; 1 subdir (providers/)</span></div>'
+'<div class="rowk"><span class="k">auth.md</span><span class="chip">Authentication</span><span class="dim">status: canonical &middot; 4 backlinks</span></div>'
+'<div class="rowk"><span class="k">sessions.md</span><span class="chip">Session lifecycle</span><span class="dim">2 backlinks</span></div>'
+'<div class="rowk"><span class="k">most recent</span><span class="chip">auth.md</span></div>'
+'</div>';
var files="auth.md    login.md    oauth.md    sessions.md    sso.md    tokens.md    providers/";
function set(v){
  code.innerHTML=(v==="ok"?'<span class="dim">exec("ls architecture/")</span>':'<span class="dim">$ ls architecture/</span>')+"\n"+files;
  rich.classList.toggle("show",v==="ok");
  btns.forEach(function(b){b.classList.toggle("on",b.dataset.v===v);});
}
btns.forEach(function(b){b.onclick=function(){set(b.dataset.v);};});
set("ok");
</script>
```

And a `grep` returns more than matching lines: each hit carries its file's title, status, and backlink count, so the agent can tell a well-connected hub from a stray mention before opening either.

```html preview
<div style="padding:18px">
  <div class="seg">
    <button data-v="raw">raw disk &middot; grep</button>
    <button data-v="ok" class="on">OpenKnowledge &middot; exec</button>
  </div>
  <pre id="code"></pre>
  <div id="rich"><div class="inner"></div></div>
</div>
<style>
.seg{display:inline-flex;border:1px solid var(--border);border-radius:9px;padding:2px;background:var(--muted);margin-bottom:12px}
.seg button{border:0;background:transparent;color:var(--muted-foreground);font:inherit;font-size:12.5px;padding:6px 12px;border-radius:7px;cursor:pointer}
.seg button.on{background:var(--card);color:var(--foreground);box-shadow:0 1px 2px rgba(0,0,0,.1)}
#code{margin:0;background:var(--muted);border-radius:10px;padding:12px 14px;font:12.5px ui-monospace,Menlo,monospace;color:var(--foreground);white-space:pre-wrap;line-height:1.7}
.dim{color:var(--muted-foreground)}
.m{color:var(--accent-ink);background:var(--accent-soft);border-radius:3px;padding:0 2px;font-weight:600}
#rich{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .45s ease,opacity .45s ease}
#rich.show{grid-template-rows:1fr;opacity:1;margin-top:12px}
#rich .inner{overflow:hidden}
.card{border:1px dashed var(--primary);border-radius:10px;padding:13px}
.hd{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--accent-ink);margin-bottom:10px}
.rowk{display:flex;gap:9px;flex-wrap:wrap;align-items:baseline;margin-bottom:9px;font-size:12.5px}
.k{width:94px;flex:none;color:var(--foreground);font-family:ui-monospace,monospace}
.chip{border:1px solid var(--border);background:var(--card);border-radius:7px;padding:1px 7px;font-size:11.5px}
</style>
<script>
var code=document.getElementById("code"),rich=document.getElementById("rich"),btns=document.querySelectorAll(".seg button");
var hl=function(s){return s.replace(/token refresh/g,'<span class="m">token refresh</span>');};
var lines=hl('architecture/auth.md:42:  the token refresh flow re-issues a short-lived token\narchitecture/sessions.md:88:  a token refresh extends the session silently');
rich.querySelector(".inner").innerHTML='<div class="card"><div class="hd">added by OpenKnowledge &middot; context on each hit</div>'
+'<div class="rowk"><span class="k">auth.md</span><span class="chip">Authentication</span><span class="dim">4 backlinks</span></div>'
+'<div class="rowk"><span class="k">sessions.md</span><span class="chip">Session lifecycle</span><span class="dim">2 backlinks</span></div>'
+'</div>';
function set(v){
  var cmd=v==="ok"?'<span class="dim">exec("grep -rn \'token refresh\' architecture/")</span>':'<span class="dim">$ grep -rn "token refresh" architecture/</span>';
  code.innerHTML=cmd+"\n"+lines;
  rich.classList.toggle("show",v==="ok");
  btns.forEach(function(b){b.classList.toggle("on",b.dataset.v===v);});
}
btns.forEach(function(b){b.onclick=function(){set(b.dataset.v);};});
set("ok");
</script>
```

That folder briefing is why the loop stays short: the backlinks are what it follows, and the purpose plus recency tell it where to look next.

Three read tools ride this layer: ranked **search** (BM25 and recency, the same index as cmd-K), **`exec`** (sandboxed `grep`/`ls`/`cat`/`find` and friends, pipeable, straight off disk; works with the server down, minus backlinks and outbound links), and **`links`** (the link graph: `backlinks`, `forward`, `dead`, `orphans`, `hubs`, `suggest`). `search` takes an `intent` (`omnibar` for fast title/path/folder lookups; the default `full_text` ranks body content too), result `scopes`, and a `limit` (default 20, max 100). Right after the server boots it can answer `ready: false` with empty results while the index builds; retry after a couple of seconds instead of treating that as no matches, and fall back to `exec` grep if it persists.

## Writing talks back too

A `cat >>` or a vim save drops bytes on disk and goes quiet. A `write` or `edit` through OpenKnowledge validates what you wrote and hands back what it found, so mistakes surface at write time instead of rotting in the graph.

```html preview
<div style="padding:18px">
  <div class="seg">
    <button data-v="raw">raw shell &middot; cat &gt;&gt;</button>
    <button data-v="ok" class="on">OpenKnowledge &middot; write</button>
  </div>
  <pre id="code"></pre>
  <div id="rich"><div class="inner"></div></div>
</div>
<style>
.seg{display:inline-flex;border:1px solid var(--border);border-radius:9px;padding:2px;background:var(--muted);margin-bottom:12px}
.seg button{border:0;background:transparent;color:var(--muted-foreground);font:inherit;font-size:12.5px;padding:6px 12px;border-radius:7px;cursor:pointer}
.seg button.on{background:var(--card);color:var(--foreground);box-shadow:0 1px 2px rgba(0,0,0,.1)}
#code{margin:0;background:var(--muted);border-radius:10px;padding:12px 14px;font:12.5px ui-monospace,Menlo,monospace;color:var(--foreground);white-space:pre-wrap}
.dim{color:var(--muted-foreground)}
#rich{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .45s ease,opacity .45s ease}
#rich.show{grid-template-rows:1fr;opacity:1;margin-top:12px}
#rich .inner{overflow:hidden}
.card{border:1px dashed var(--primary);border-radius:10px;padding:13px}
.hd{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--accent-ink);margin-bottom:10px}
.rowk{display:flex;gap:9px;flex-wrap:wrap;align-items:baseline;margin-bottom:9px;font-size:12.5px}
.k{width:74px;flex:none;color:var(--muted-foreground);font-family:ui-monospace,monospace}
.warn{color:var(--chart-5);font-weight:600}
.good{color:var(--chart-2);font-weight:600}
</style>
<script>
var code=document.getElementById("code"),rich=document.getElementById("rich"),btns=document.querySelectorAll(".seg button");
rich.querySelector(".inner").innerHTML='<div class="card"><div class="hd">what the write checked &middot; a raw cat &gt;&gt; cannot</div>'
+'<div class="rowk"><span class="k">links</span><span><span class="good">3 resolve</span> &middot; <span class="warn">1 broken</span>: oauth-v2.md &rarr; not found</span></div>'
+'<div class="rowk"><span class="k">orphan</span><span>nothing links here yet &rarr; link from architecture/INDEX</span></div>'
+'<div class="rowk"><span class="k">renders</span><span>mermaid <span class="good">OK</span></span></div>'
+'<div class="rowk"><span class="k">saved</span><span>versioned &middot; attributed to you &middot; live in the editor</span></div>'
+'</div>';
function set(v){
  code.innerHTML=(v==="ok"?'<span class="dim">write("architecture/auth.md", ...)</span>\nwritten.':'<span class="dim">$ cat &gt;&gt; architecture/auth.md</span>\nbytes appended. silence.');
  rich.classList.toggle("show",v==="ok");
  btns.forEach(function(b){b.classList.toggle("on",b.dataset.v===v);});
}
btns.forEach(function(b){b.onclick=function(){set(b.dataset.v);};});
set("ok");
</script>
```

`brokenLinks` comes back on every write and edit (empty when they all resolve), so the agent never needs a follow-up dead-link check. A brand-new doc nothing points to comes back flagged as an orphan with a hub to link it from. Every write and edit is versioned, attributed, and the preview updates as it lands. Append to a file by hand and the edit still lands: the file watcher folds it into the live doc and versions it, but anonymously, as `file-system`, and nothing talks back. [Skills](https://openknowledge.ai/docs/features/skills) teach the conventions, and the write path enforces the connective tissue.

## The index is authored, not extracted

No vector store means no second copy to rebuild, mis-chunk, or drift. Your links, folders, titles, and folder descriptions *are* the index. Other tools extract structure with an embedding model or an entity-extracting LLM; OpenKnowledge reads the structure you already wrote, so the agent traverses the source of truth. A well-linked base retrieves better because good links shorten the loop. [Folder templates](https://openknowledge.ai/docs/advanced/folders-and-templates) keep that structure consistent as it grows: a folder can carry a template that the write path surfaces at create time, so docs the agent starts from it share the same frontmatter and shape, and the fields search ranks on stay uniform.

## Semantic search (optional)

Off by default. Enable it per project, per machine (each collaborator opts in on their own), and an embeddings signal blends into the ranking; it never replaces lexical search. Vectors sit in a local cache. Turning it on sends your query and matching text to your configured provider. See [Configuration](https://openknowledge.ai/docs/reference/configuration).

# CLI & web app (https://openknowledge.ai/docs/reference/cli)

Install the ok CLI and run the OpenKnowledge web app in your browser — on any platform, including Intel Macs and servers.

For the smoothest experience, use the [desktop app](https://openknowledge.ai/docs/get-started/quickstart) — it ships for **macOS**, **Windows**, and **Linux**. Anywhere else (an **Intel Mac**, a server), or when you'd rather stay in the terminal, install the `ok` CLI from npm and run the **same editor as a local web app** in your browser.

> **Prerequisites — Verify Node and Git are installed**
>
> Open a terminal and run:
>
> ```
> node --version
> git --version
> ```
>
> - **Node.js 24+.** OpenKnowledge requires Node.js 24 or higher. Download from [nodejs.org](https://nodejs.org/en/download/).
> - **`git`.** OpenKnowledge uses Git for timeline and recovery. Install it from [git-scm.com](https://git-scm.com/install/).

## Install

Install the OpenKnowledge CLI globally so you can run `ok` from anywhere:

```bash
npm install -g @inkeep/open-knowledge
```

## Set up a project

Run `ok init` in any folder to turn it into an OpenKnowledge project. It scaffolds a `.ok/` directory and registers the OpenKnowledge MCP server with the AI editors it detects on your machine (Claude Code, Claude Desktop, Cursor, Codex, OpenCode, OpenClaw, Pi, Antigravity, LM Studio, Hermes). It only ever adds its own entry — your other settings, comments, and formatting are left untouched.

```bash
cd my-project
ok init
```

`ok init` asks two questions: where to register the MCP server (user-level, project-level, or both) and whether to share the OpenKnowledge config files with your team (committed alongside your content) or keep them local to this machine.

In a sub-folder of a git repo, `ok init` sets the project up at the git root — one `.ok/` per repo — and the whole repo becomes the content scope. Pass `--content-dir <dir>` to limit content to one folder (`ok init --content-dir .` scopes it to the folder you run in); the choice is saved as `content.dir` in `.ok/config.yml`. For scripts, `--json` prints a structured JSON summary. `ok init` also installs the user-global `open-knowledge-discovery` skill, the same one the desktop's first launch sets up; `--skills discovery,write-skill` adds the authoring skill, and `--no-skills` installs none on that run. `open-knowledge-write-skill` is otherwise one click away in **Settings → Skills Studio**.

> **If an editor shows “left unchanged”**
>
> When an editor's config can't be parsed safely — it's not valid JSON/TOML, has two MCP server blocks, or is unusually large — OpenKnowledge leaves the file **byte-for-byte untouched** and prints `left unchanged (<reason>)` instead of registering. It never rewrites or resets a config it can't fully understand. Fix the underlying config (the editor itself usually reports the parse error) and re-run `ok init`.

Once you have initialized your project, launch the app in your browser:

```bash
ok start
```

One server on one port serves everything — the editor, the API, the MCP endpoint, and real-time collaboration — and the editor opens in your browser automatically (pass `--no-open-browser` to skip that). Running `ok start` again while a server is already up prints the running server's URL and exits.

`ok start` also takes `-p/--port` and `--bind` to pick the address, `--idle-shutdown` (`off` or a duration like `30m`) to control when an unused server exits, `--only server` to run without the editor UI, and `--mode app` to hand off to the desktop app instead of running a server in the terminal (with the desktop app installed).

Idle shutdown is on by default at 30 minutes — a server with no connected clients exits so it doesn't linger. If you keep a session open for a long time without interacting, pass `--idle-shutdown off` (or a longer duration) to keep it running.

For a complete list of every file OpenKnowledge creates or changes — during install, at runtime, and in the desktop app — see [What OpenKnowledge writes to your system](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

## Open a doc, folder, or file

`ok open` takes a doc name, a folder, or a path to a Markdown file.

```bash
ok open <path-to-your-file.md>
```

The output names the absolute project root OpenKnowledge resolved for the argument you gave, so you can tell which project it acted on without running a second command. When the resolved root itself sits inside another project, the output names both, once.

To choose the project yourself, pass `--project <dir>`. It is honored wherever it appears: `--project <dir>` or `--project=<dir>`, before or after the target, and with or without the `.md` extension on the path. If it cannot be honored, the command exits non-zero with a message saying why instead of quietly resolving somewhere else.

If you don't have a project initialized, this still works. The file opens directly in the WYSIWYG/source editor without any of the version history or `open with AI` frills.

## Clone from GitHub

You can pull repositories directly from GitHub and open them with OpenKnowledge:

```bash
ok clone <owner/repo>
```

`ok clone` accepts a full `https://github.com/...` URL or the `owner/repo` shorthand. It clones the repository, initializes OpenKnowledge if it isn't already, and starts the local server — open the printed Editor URL in your browser.

Pin a branch with `-b <branch>`:

```bash
ok clone <owner/repo> -b <branch>
```

If the branch no longer exists, `ok clone` falls back to the repository's default branch.

A **public** repo clones without authentication. For a **private** repo, sign in first:

```bash
ok auth login
```

If you're already logged in with the `gh` CLI, you can skip this step.

`ok auth` also provides `status` (show who's logged in), `repos` (list repositories you can access), `signout` (remove stored credentials), and `pat` (store a Personal Access Token). Every subcommand takes `--host <hostname>` for GitHub Enterprise.

## Sync with GitHub

Sync a project with its remote from the terminal:

| Command   | What it does                         |
| --------- | ------------------------------------ |
| `ok sync` | Commit, pull, and push to the remote |
| `ok pull` | Pull changes from the remote         |
| `ok push` | Push commits to the remote           |

Each command goes through the running server when one is up and falls back to plain git otherwise; pass `--json` for JSONL progress events when scripting. See [GitHub sync](https://openknowledge.ai/docs/features/github-sync) for how syncing works.

## Manage local servers

Each project runs its own local server. These commands let you see and stop them:

| Command     | What it does                                                                                                                                                                                                                                                                                                               |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ok status` | Show whether a server and UI are running for the current project                                                                                                                                                                                                                                                           |
| `ok ps`     | List every running OpenKnowledge server on your machine                                                                                                                                                                                                                                                                    |
| `ok stop`   | Stop the server for the current directory — or pass a port, path, or PID to stop a specific server, or `all` to stop every server. Declines when something is still connected to the target, naming how many clients are attached; pass `--force` to stop it anyway. A server it cannot reach is stopped without the check |
| `ok clean`  | Remove a stale lock file left behind by a crash (never touches live servers)                                                                                                                                                                                                                                               |

## Remote Control

Serve this project's web UI and `/mcp` endpoint to your other devices through any HTTPS tunnel, both on one URL. See the [Remote Control overview](https://openknowledge.ai/docs/remote-control/overview) for the full setup and access-control model. Use [Connect remote agents](https://openknowledge.ai/docs/remote-control/connecting-agents) to point agents at the URL.

| Command                         | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ok start --external-url <url>` | Declare the canonical external origin clients dial (sets `server.externalUrl` for this run) — its host joins the Host/Origin allowlists and issued URLs. External exposure additionally requires consent (`OK_ALLOW_EXTERNAL=1` or `server.allowExternal`). For remote MCP use also set `OK_IDLE_SHUTDOWN=off` — the idle timer only counts editor (WS) connections and would stop the server under a live remote agent. Add `--bind <address>` to serve a non-loopback listen address. |

## More commands

| Command                     | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ok seed`                   | Scaffold a starter pack into the project (`--list-packs` to browse, `--pack <id>` to choose, `--root <dir>` to nest in a subfolder, `--dry-run` to preview)                                                                                                                                                                                                                                                                                                |
| `ok preview`                | Show what content the watcher will track (read-only) — the quick check after a `content.dir` or `.okignore` change                                                                                                                                                                                                                                                                                                                                         |
| `ok migrate notion <dir>`   | Clean up a Notion export in place, dry-run unless `--apply` (see [migrating from Notion](https://openknowledge.ai/docs/migrate/notion))                                                                                                                                                                                                                                                                                                                    |
| `ok config validate`        | Validate the merged config (defaults → user → project)                                                                                                                                                                                                                                                                                                                                                                                                     |
| `ok config migrate`         | Remove keys the schema no longer reads from your config files (`--dry-run` to preview; `--scope` to narrow — defaults to every layer)                                                                                                                                                                                                                                                                                                                      |
| `ok skills installed`       | List every skill installed across your agents (read-only, cross-harness)                                                                                                                                                                                                                                                                                                                                                                                   |
| `ok skills import <source>` | Import a skill as versioned content from skills.sh, a GitHub `owner/repo`, a git URL, or a local path                                                                                                                                                                                                                                                                                                                                                      |
| `ok bug-report`             | Package a support bundle — recent app logs, the project's server logs and lock diagnostics, and system info — as an auto-redacted zip under `~/.ok/bug-reports/`, revealed in your file manager when it's done (`--no-reveal` to skip). Collects the same `standard` set as the desktop app's **Help → Report a bug…**; `ok diagnose bundle` produces the `full` superset (telemetry spans, live server state) into the project's `.ok/local/diagnostics/` |

## Global flags

Every command takes `--cwd <path>` to run from another directory, `--log-level <level>` (`silent`, `error`, `warn`, `info`, `debug`, or `trace`), and `--no-color` to disable color output. `ok start` runs quiet by default; `--log-level debug` turns everything on.

## Commands that run for you

The rest of the CLI exists mostly for tooling and automation. Your AI editor spawns `ok mcp` to talk to the knowledge base; `ok repair-skills` refreshes bundled editor skills automatically during `ok start`; and `ok diagnose` / `ok bug-report` produce support bundles when something goes wrong. Run `ok --help` if you want to see an overview of all commands.

## Remove OpenKnowledge

`ok deinit` reverses `ok init` for one project: it removes the project's `.ok/` directory, its editor MCP entries, git-exclude lines, and shadow repo, leaving your markdown untouched.

`ok uninstall` removes OpenKnowledge from the whole machine — running servers, credentials, the PATH shim, its own editor MCP entries, skill bundles, app data, and `~/.ok`. Your markdown content stays, and your global skills in your editors' skill dirs (`~/.agents/skills`, `~/.claude/skills`, and the like) are never touched. It never deletes the CLI binary itself; it ends by printing the removal command (`npm uninstall -g @inkeep/open-knowledge` for the install above).

Both commands print their plan and ask before acting; `--dry-run` previews without changing anything. Once `ok uninstall` has finished successfully, an interactive run also asks one optional question about why you're leaving — you can skip it, and `--yes`, `--json`, and non-interactive runs never ask. Anything you do answer is [sent to us](https://openknowledge.ai/docs/reference/what-open-knowledge-writes#what-leaves-your-machine).

On the macOS desktop app, **App menu → Uninstall OpenKnowledge…** covers both steps from one flow: it optionally deinitializes your projects, removes the global footprint, shows the same optional "why are you leaving?" screen once removal succeeds, and then guides you through dragging the app to the Trash.

To remove the desktop app on the other platforms: on **Windows**, uninstall OpenKnowledge from **Settings → Apps → Installed apps**; on **Linux**, remove the package with your package manager (`sudo apt remove openknowledge` on Debian/Ubuntu, `sudo dnf remove OpenKnowledge` on Fedora/RHEL). Run `ok uninstall` first if you also want the rest of the footprint cleaned up — it works the same on every platform.

# 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.

# Core Concepts (https://openknowledge.ai/docs/reference/core-concepts)

How OpenKnowledge works: the three-layer model, the file system as the database, links and backlinks, the well-connected knowledge base, and attribution.

This page is the precise reference for the ideas the rest of the docs build on. If you want the persuasive tour instead, start with the [Overview](https://openknowledge.ai/docs/get-started/overview); if you want to start using it, see the [Quickstart](https://openknowledge.ai/docs/get-started/quickstart).

## Three layers

OpenKnowledge has three layers working together:

- a surface you edit
- an engine that keeps it consistent
- the files underneath

```html preview
<div style="padding:18px">
  <div class="tl-surfaces" id="surf"></div>
  <div class="tl-arrows">↓&nbsp;&nbsp;&nbsp;↓&nbsp;&nbsp;&nbsp;↓</div>
  <div class="tl-files" id="files">
    <div class="tl-files-t">your-project/ &middot; *.md</div>
    <div class="tl-files-s">plain markdown, versioned by git</div>
  </div>
  <div class="cap">All three operate on the same files. Nothing locks you out.</div>
</div>
<style>
#surf{display:flex;flex-wrap:wrap;gap:8px}
#surf .s{flex:1;min-width:132px;border:1px solid var(--border);border-radius:12px;padding:11px 13px;background:var(--card);transition:border-color .3s,box-shadow .3s;cursor:pointer}
#surf .s .t{font-weight:600;font-size:13px}
#surf .s .d{color:var(--muted-foreground);font-size:11.5px;margin-top:2px}
#surf .s.on{border-color:var(--primary);box-shadow:0 0 0 3px var(--accent-soft)}
#surf .s.on .t{color:var(--accent-ink)}
.tl-arrows{text-align:center;color:var(--muted-foreground);font-size:15px;margin:9px 0}
.tl-files{border:1px dashed var(--border);border-radius:12px;padding:12px 14px;background:var(--card);text-align:center;transition:border-color .3s,box-shadow .3s}
.tl-files.on{border-color:var(--primary);border-style:solid;box-shadow:0 0 0 3px var(--accent-soft)}
.tl-files-t{font:600 13px ui-monospace,monospace}
.tl-files-s{color:var(--muted-foreground);font-size:11.5px;margin-top:2px}
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px}
@media (prefers-reduced-motion:reduce){#surf .s,.tl-files{transition:none}}
</style>
<script>
var data=[["The editor","WYSIWYG markdown, in the app"],["The knowledge engine","any AI agent, over MCP"],["Any text editor","by hand, whenever"]];
var surf=document.getElementById("surf"),files=document.getElementById("files"),nodes=[];
data.forEach(function(s,i){
  var d=document.createElement("div");d.className="s";
  var t=document.createElement("div");t.className="t";t.textContent=s[0];
  var sub=document.createElement("div");sub.className="d";sub.textContent=s[1];
  d.appendChild(t);d.appendChild(sub);
  d.onclick=function(){pinned=true;set(i);};
  surf.appendChild(d);nodes.push(d);
});
var cur=0,pinned=false;
function set(i){cur=i;nodes.forEach(function(n,j){n.classList.toggle("on",j===i);});files.classList.add("on");setTimeout(function(){files.classList.remove("on");},650);}
set(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){setInterval(function(){if(!pinned)set((cur+1)%nodes.length);},1600);}
</script>
```

- [The editor](https://openknowledge.ai/docs/features/editor): The application you see: a beautiful, themeable markdown editor that renders rich extensions (Mermaid, LaTeX, video and asset embeds, callouts, collapsible sections, interactive HTML) and lets you read and write your knowledge base directly.
- [The knowledge engine](https://openknowledge.ai/docs/reference/mcp): The framework underneath: an MCP server that lets any AI agent read and write your knowledge base while keeping front matter consistent, references intact, and the link graph healthy.
- [The content](https://openknowledge.ai/docs/reference/core-concepts#the-file-system-is-the-database): The files underneath: plain markdown in your own project directory, version-controlled by git. This is the durable layer the other two operate on, described in detail below.

All three layers operate on the **same files**. You can edit through the editor, an agent can edit through the knowledge engine's MCP tools, and you can always drop down to any text editor and change the markdown by hand. Nothing locks you out.

Because the knowledge engine is exposed over [MCP](https://modelcontextprotocol.io), it is **agent-agnostic**. Bring Claude Code, Cursor, Codex, OpenCode, OpenClaw, or any MCP-capable client, and any model you have access to.

## The file system is the database

The third layer is the content itself. OpenKnowledge has **no database dependency**. Your knowledge base is plain markdown files in your own project directory, and the only persistence layer is the file system, version-controlled by git.

This means:

- **No lock-in.** Your knowledge is portable markdown you can read, grep, diff, and commit with ordinary tools.
- **Almost nothing to install.** The recommended path is the desktop app (macOS, Windows, or Linux); there is no separate database or service to run.
- **The engine is a management layer, not a gatekeeper.** It maintains consistency when you go through it, but editing the raw files yourself is always allowed.

The set of files the engine treats as your knowledge base is the configured content directory. See [Configuration](https://openknowledge.ai/docs/reference/configuration) for where that and other settings live.

## Links and backlinks

Internal cross-references are written with **standard markdown links**. The recommended form is **relative** — `[text](./sibling.md)`, `[text](../folder/doc.md)` — which stays portable across GitHub, Obsidian, VS Code, and published sites. A **root-absolute** form (`[text](/folder/doc.md)`, where the leading slash means the content root) is equally valid and convenient for cross-folder links. The two never mix: never glue `./` onto a content-root path, since `./folder/doc.md` written from a doc already inside `folder/` resolves to the doubled, broken `folder/folder/doc.md` — `write`/`edit` flag exactly this in their `brokenLinks` response. Whenever document A links to document B, OpenKnowledge automatically records the inverse on B: a **backlink** from B back to A.

You never write backlinks by hand. They are computed from the links you already write, and together they form the **link graph**: the network of relationships across your knowledge base.

```html preview
<div style="padding:18px">
  <div id="lb"></div>
  <div class="cap">Write the link once; OpenKnowledge records the backlink on the target for you.</div>
</div>
<style>
#lb{display:flex;flex-wrap:wrap;gap:10px;align-items:stretch}
#lb .card{flex:1;min-width:150px;border:1px solid var(--border);border-radius:12px;padding:12px 13px;background:var(--card)}
#lb .card .t{font:600 12.5px ui-monospace,monospace}
#lb .row{font-size:12px;margin-top:8px;color:var(--muted-foreground);opacity:0;transform:translateY(3px);transition:opacity .4s,transform .4s}
#lb .row.on{opacity:1;transform:none}
#lb .row .k{color:var(--accent-ink);font-weight:600}
#lb .mid{align-self:center;color:var(--muted-foreground);font-size:12px;text-align:center;min-width:104px}
#lb .mid .b{margin-top:6px;opacity:0;transform:translateY(3px);transition:opacity .4s,transform .4s}
#lb .mid .b.on{opacity:1;transform:none;color:var(--primary);font-weight:600}
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px}
@media (prefers-reduced-motion:reduce){#lb .row,#lb .mid .b{transition:none}}
</style>
<script>
document.getElementById("lb").innerHTML=
  '<div class="card"><div class="t">login.md</div><div class="row on"><span class="k">link &rarr;</span> [Sessions](./sessions.md)</div></div>'
 +'<div class="mid"><div>you write<br>one link</div><div class="b" id="b">&#x21A9; inverse recorded</div></div>'
 +'<div class="card"><div class="t">sessions.md</div><div class="row" id="bkrow"><span class="k">backlink &larr;</span> login.md</div></div>';
var b=document.getElementById("b"),bk=document.getElementById("bkrow");
function cycle(){b.classList.remove("on");bk.classList.remove("on");setTimeout(function(){b.classList.add("on");},500);setTimeout(function(){bk.classList.add("on");},900);}
cycle();
if(!matchMedia("(prefers-reduced-motion:reduce)").matches)setInterval(cycle,3200);
</script>
```

> **Info**
>
> Backlinks are the payoff of ordinary linking. Every internal link you write earns a backlink on the target for free, so the graph grows as a side effect of normal writing.

## The well-connected knowledge base

"Well-connected" is not a vibe; it has concrete substance:

> **A well-connected knowledge base = backlinks + the link-graph tools (dead / orphans / hubs / suggest) + closed-loop grounding.**

An agent puts all three to work when it retrieves: it searches, greps, and follows backlinks in a loop over your live files, with no vector database. That mechanism is [Agentic search](https://openknowledge.ai/docs/reference/agentic-search).

### Backlinks

The automatic inverse relationships described above. They turn a pile of files into a navigable graph.

### The link-graph tools

The knowledge engine exposes a [`links`](https://openknowledge.ai/docs/reference/mcp) tool whose `kind` selects a view of the graph. Four of these views are how you keep the graph healthy:

| View      | What it surfaces                                                                    |
| --------- | ----------------------------------------------------------------------------------- |
| `dead`    | Links that point at documents that don't exist: broken references to fix or remove. |
| `orphans` | Documents nothing links to: knowledge that's effectively unreachable.               |
| `hubs`    | The most-linked-to documents: the natural centers of gravity in your KB.            |
| `suggest` | Likely-missing links between related documents: connections worth adding.           |

Agents use these to repair and densify the graph as they work, instead of letting it rot.

```html preview
<div style="padding:18px">
  <div id="gt-tabs" class="gt-tabs"></div>
  <div id="gt-nodes" class="gt-nodes"></div>
  <div id="gt-cap" class="cap"></div>
</div>
<style>
.gt-tabs{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px}
.gt-tabs .tab{font:600 11.5px ui-monospace,monospace;border:1px solid var(--border);border-radius:999px;padding:4px 11px;background:var(--card);color:var(--muted-foreground);cursor:pointer;transition:border-color .3s,background .3s,color .3s}
.gt-tabs .tab.on{border-color:var(--primary);background:var(--accent-soft);color:var(--accent-ink)}
.gt-nodes{display:flex;flex-wrap:wrap;gap:8px}
.gt-nodes .n{border:1px solid var(--border);border-radius:10px;padding:8px 11px;background:var(--card);font:12.5px ui-monospace,monospace;color:var(--muted-foreground);transition:border-color .3s,box-shadow .3s,color .3s}
.gt-nodes .n.hot{border-color:var(--primary);box-shadow:0 0 0 3px var(--accent-soft);color:var(--accent-ink)}
.gt-nodes .n .tag{display:block;font:11px system-ui;margin-top:2px;color:var(--primary)}
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px;min-height:1.2em}
@media (prefers-reduced-motion:reduce){.gt-tabs .tab,.gt-nodes .n{transition:none}}
</style>
<script>
var nodes=["README.md","auth.md","tokens.md","draft.md","old-notes.md"];
var views=[
 {k:"dead",hot:{1:"&rarr; missing.md &#x2717;"},cap:"dead: links pointing at documents that don't exist."},
 {k:"orphans",hot:{3:"nothing links here"},cap:"orphans: documents nothing links to."},
 {k:"hubs",hot:{0:"most linked-to"},cap:"hubs: the natural centers of gravity."},
 {k:"suggest",hot:{1:"relates &rarr; tokens.md",2:"relates &rarr; auth.md"},cap:"suggest: likely-missing links worth adding."}
];
var tabsEl=document.getElementById("gt-tabs"),nodesEl=document.getElementById("gt-nodes"),capEl=document.getElementById("gt-cap"),tabs=[];
views.forEach(function(v,i){var t=document.createElement("div");t.className="tab";t.textContent=v.k;t.onclick=function(){pinned=true;set(i);};tabsEl.appendChild(t);tabs.push(t);});
function set(i){var v=views[i];tabs.forEach(function(t,j){t.classList.toggle("on",j===i);});nodesEl.innerHTML=nodes.map(function(n,j){var tag=v.hot[j]?'<span class="tag">'+v.hot[j]+'</span>':'';return '<div class="n'+(v.hot[j]?' hot':'')+'">'+n+tag+'</div>';}).join("");capEl.innerHTML=v.cap;}
var cur=0,pinned=false;set(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches)setInterval(function(){if(!pinned){cur=(cur+1)%views.length;set(cur);}},2400);
</script>
```

### Closed-loop grounding

Every factual claim should trace back to a source **inside** the knowledge base. External material is pulled in and cited locally rather than linked off to the open web, so the knowledge base stays self-contained and auditable. This is the backbone of the source-grounded workflows: see the [LLM wiki workflow](https://openknowledge.ai/docs/workflows/karpathy-llm-wiki) and the [Entity vault (GBrain-compatible) workflow](https://openknowledge.ai/docs/workflows/entity-vault).

> **Info**
>
> OpenKnowledge is unopinionated about which workflow you adopt; these are supported patterns, not requirements. Grounding, backlinks, and the graph tools work the same regardless of how you choose to organize.

## Attribution and collaboration

Every change made through OpenKnowledge is tracked, with **attribution** to whoever made it: a human author or a specific AI agent. The change history is persisted in the file system with no dependency beyond git.

```html preview
<div style="padding:18px">
  <div id="attr"></div>
  <div class="cap">Every edit is attributed — a human or a specific agent — and revertible, with nothing beyond git.</div>
</div>
<style>
#attr{display:flex;flex-direction:column;gap:6px}
#attr .e{display:flex;align-items:center;gap:10px;border:1px solid var(--border);border-radius:10px;padding:9px 12px;background:var(--card);transition:border-color .3s,box-shadow .3s}
#attr .e.on{border-color:var(--primary);box-shadow:0 0 0 3px var(--accent-soft)}
#attr .who{font:600 11px ui-monospace,monospace;border-radius:999px;padding:2px 9px;white-space:nowrap}
#attr .who.h{background:var(--accent-soft);color:var(--accent-ink)}
#attr .who.a{border:1px solid var(--border);color:var(--muted-foreground)}
#attr .sha{font:11.5px ui-monospace,monospace;color:var(--muted-foreground)}
#attr .msg{font-size:12.5px}
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px}
@media (prefers-reduced-motion:reduce){#attr .e{transition:none}}
</style>
<script>
var log=[
 ["a","agent:claude","9f8e7d6","Drafted the overview"],
 ["h","you","a1b2c3d","Fixed the token-refresh race"],
 ["a","agent:cursor","4d5e6f7","Linked auth.md &rarr; tokens.md"],
 ["h","you","b7c8d9e","Promoted to canonical"]
];
document.getElementById("attr").innerHTML=log.map(function(r){return '<div class="e"><span class="who '+r[0]+'">'+r[1]+'</span><span class="sha">'+r[2]+'</span><span class="msg">'+r[3]+'</span></div>';}).join("");
var rows=document.getElementById("attr").querySelectorAll(".e"),cur=0;
function tick(){rows.forEach(function(x,j){x.classList.toggle("on",j===cur);});cur=(cur+1)%rows.length;}
tick();
if(!matchMedia("(prefers-reduced-motion:reduce)").matches)setInterval(tick,1600);
</script>
```

That gives you:

- **A changelog** of every edit across the knowledge base.
- **Point-in-time history.** Revert to any earlier state.
- **Per-author views.** See exactly what one human or one agent changed.

Browse this history and restore earlier versions from the editor's [timeline](https://openknowledge.ai/docs/features/timeline-and-recovery).

Because humans and agents edit the same files through the same tracked layer, collaboration is a first-class property of the system rather than something bolted on.

# MCP (https://openknowledge.ai/docs/reference/mcp)

Tools the OpenKnowledge MCP server exposes to AI agents.

The MCP server gives AI agents structured access to your knowledge base. A few tools answer without a running server: `exec` (fs-direct shell reads straight off the disk — only the backlink and forward-link enrichment needs the server; history comes from the shadow repo on disk), plus `config` and `palette`. `preview_url` resolves from the project's lock files, but treats opening a preview as demand: if no server is running it auto-starts one (under the `OK_MCP_AUTOSTART` gate).

Every other read tool (`search`, `links`, `history`, etc.) and all write tools route through the Hocuspocus server, which OpenKnowledge auto-starts on the first call that needs it, under the same `OK_MCP_AUTOSTART` gate. All tools take a `cwd` (an absolute path inside the target project) that selects which project the call lands on; a globally registered server requires it unless the client advertises exactly one root.

The surface is **21 tools**. The four write verbs — `write`, `edit`, `delete`, `move` — are native CRUD operations polymorphic over a target. `write`, `edit`, and `delete` nest per-target fields inside the address key (`write({ document: { path, content } })`) and take exactly one target per call: `document`, `folder`, `template`, or `skill` — plus `asset` for `write` and `delete`, and a `documents` batch on `write` that writes several docs in order, each entry carrying its own `summary`. `move` takes flat `from`/`to` paths and auto-detects a document, folder, or asset, with nested `template` and `skill` targets for those two.

To point an MCP client at this server, locally or over a tunnel from another machine, see [Connect remote agents](https://openknowledge.ai/docs/remote-control/connecting-agents).

## Tools

| Tool               | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `exec`             | Read-only shell (`cat`, `ls`, `grep`, `find`, `head`, `tail`, `wc`, `sort`, `uniq`, `cut`); reads return frontmatter, backlinks, recent history, and any unresolved [comments](https://openknowledge.ai/docs/features/comments) left on the doc. When the [frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter) plugin is on, doc and folder entries also carry `schemas: …` naming the JSON Schema files that govern them — the contract to read before writing. Fs-direct; works without the server.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `search`           | Ranked workspace search (title boost + body BM25 + recency; same engine the cmd-K palette uses). `intent: "omnibar"` narrows to fast title/path matching (`full_text`, the default, includes the body); `scopes` filters hit kinds; `limit` caps rows (default 20, max 100). When [semantic search](https://openknowledge.ai/docs/reference/configuration#semantic-search) is enabled, an embeddings signal is additionally fused into `full_text` ranking (opt-in, content egress) — pass `semantic: false` to force pure-lexical. Right after boot, an empty result set with `ready: false` means the index is still building — retry after a couple of seconds rather than treating it as no matches.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `links`            | Wiki-link graph. `kind` selects: `backlinks`, `forward`, `dead`, `orphans`, `hubs`, or `suggest`. Accepts an array (e.g. `["dead", "orphans", "hubs"]`) to fetch several views in one call; each view nests under its own key in the response. `dead` is the raw graph view: unlike `audit`, it still lists unresolved links whose source is a skill document                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `lint`             | Report content-rule problems (markdownlint style rules, frontmatter schema validation, document-level OKF checks) for one `document` or a whole-project audit (`path` scopes it); each finding carries `source`/`code`/`message`/`range`/`severity`, audit capped at 10 files × 10 diagnostics and project-wide at 10 warnings with accurate totals; configuration or runtime degradation problems ride a separate `warnings` channel, with `omittedWarningCount` when warnings are dropped. Successful responses include `ran`, the enabled document-lint source families selected for that call (`markdownlint`, `frontmatter`, and/or document-level `okf`). A family absent from `ran` was not checked, and `[]` means no checks were selected at all. Pass `fix: true` with `document` to auto-fix fixable rules in place — the fix lands through the collaborative document (attributed, live preview), not a shell edit; the rest need `edit`/`write`. Example output: [content rules overview](https://openknowledge.ai/docs/advanced/content-rules/overview#ai-agents)                                                                                                                                                                                                                                                                                                                                                                                 |
| `audit`            | Unified read-only validation audit: every content problem — content-rule violations (markdownlint, frontmatter schemas, OKF) **and** broken internal links — in one call, grouped by the file to fix (`path` scopes to a folder or doc). Each finding is tagged with its validator; broken links report under the source doc that contains them, at the offending line, as warnings by default (the project's `validation.links` setting can raise them to errors or hide them). Skill documents are excluded from the broken-link half: a skill's links routinely name files it creates only at runtime (use `links({ kind: "dead" })` to see them; links from other documents to a skill still validate; documents that merely sit under a dot directory, such as `.github/CI_RUNBOOK.md`, keep their findings). Successful responses include `ran`: enabled document-lint families plus `links` unless link validation is off. Document and project-tree OKF checks share the `okf` family, and a family absent from `ran` was not checked. A selected family remains in `ran` if it degrades, with the reason explained in `warnings`; a partial degradation may still have contributed findings. Same 10 × 10 output cap as `lint`'s audit, plus a warning cap set project-wide at 10 warnings, with accurate totals and `omittedWarningCount` when warnings are dropped. No fix shape — lint fixes go through `lint`, link repairs through `edit`/`write` |
| `history`          | Version timeline for a `document`, `folder`, or `skill`. Each entry carries a `version` (commit SHA) you pass to `restore_version`. Filter with `kind` (`checkpoint` / `wip` / `upstream`), `author` / `excludeAuthor`, and `branch`; paginate with `limit` / `offset` (default 50, max 200). `folder: ""` selects the project-root timeline                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `skills`           | Find skills. Pass `query` to search skills.sh and return import-ready rows (`name`, `source`, installs, publisher); omit `name` to list every managed skill across Project + Global (rows carry `installed` + `hosts`); pass `name` to read one (addressed by `name`+`scope`, never by path); pass `name` + `file` to read one bundle file's text — the read path for `references/` and `scripts/` content                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `config`           | Read the effective merged config (`config({ key: "appearance.theme" })` for a sub-tree; omit `key` for the whole config)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `palette`          | Markdown-native authoring forms, themed `html preview` embed starters, and theme tokens. Pass `components` for the canonical components' full JSX prop schemas                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `preview_url`      | Resolve the browser-reachable preview URL. Targets a `document`, `folder`, or `skill` in the current project, or — via `file`, an absolute path — a markdown file outside it (resolved through the session serving that file). Per-response `previewUrl` fields elsewhere are route-only (`/#/<doc>`); call this when you need the full openable URL                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `share_link`       | Build a GitHub-substrate share URL for a doc or folder to send to a teammate (`kind` disambiguates; `path: ""` with `kind: "folder"` shares the content root). Responses carry a `freshness` verdict — `current`, `stale` (unpushed changes), or `absent` (never pushed), omitted when the probe can't run — and prepend a matching warning to relay with the link. Read-only against `.git/`; never publishes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `write`            | Create or overwrite a `document`, `folder`, `template`, `skill`, or `asset`. For a document: `{ path, content }`, or `{ path, template }` to instantiate from a folder template. `position` selects `replace` (full rewrite; the only mode that touches frontmatter), `append`, or `prepend`. `extension` selects `.md` (default) or `.mdx` for a new document; an existing doc keeps its extension (the MCP cannot change it). An `asset` takes exactly one of `content` (base64, for small files) or `source` (a local path the server reads, for large files)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `edit`             | Modify a `document` (body find/replace **or** a frontmatter merge-patch), a `folder` (frontmatter merge-patch), a `template`, or a `skill`. Body edits take `{ path, find, replace, occurrence? }`; `occurrence` selects which match (1 = first)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `delete`           | Delete a `document` (one path or an array), `folder`, `template`, `skill`, or `asset`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `move`             | Move or rename a `document`, `folder`, `asset`, `template`, or `skill`, rewriting every affected link (templates and skills carry no inbound links). A skill also moves between Project and Global scope via `scope` + `toScope`: history does not transfer, and you re-establish its editor locations with `install`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `install`          | Manage **where** a skill is available. A skill is one real folder (its source, which itself loads) plus managed copies/symlinks at other locations — there is no draft state and no "uninstall everywhere". `add` / `remove` change the other locations (editor ids, `agents`, or custom root paths); `mode` flips an editor copy↔symlink; `source` relocates the real folder. To make a skill stop existing, use `delete`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `import`           | Acquire a skill from a full skills.sh skill-page URL (`https://www.skills.sh/<owner>/<repo>/<skill>`, or `https://www.skills.sh/site/<hostname>/<skill>` for a website catalog), `owner/repo[/subpath]`, a git URL, or a local / `file://` path into the vendor-neutral `.agents/skills/<name>/` hub as versioned content — live immediately, with cross-harness projection via `install`. Use `skill` to choose one skill from a multi-skill source or a `skills({ query })` result. Records provenance in `.ok/skills-lock.json`; a name collision lands under `<name>-imported`; scripts are imported as content and never executed                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `checkpoint`       | Save a project-wide version snapshot — a single restore point. Returns its `version`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `restore_version`  | Restore one `document` or `skill` to a historical `version` (the SHA from `history` or `checkpoint`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `conflicts`        | Read GitHub-sync merge conflicts. `kind: "list"` enumerates tracked conflicts; `kind: "content"` returns one file's base / ours / theirs stages                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `resolve_conflict` | Write a chosen resolution (`mine` / `theirs` / `content` / `delete`) and commit                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |

## Output shape mirrors the input

The write verbs nest their result under the same target key you wrote, mirroring the input: `write({ folder })` returns `{ folder: { ok, path } }`, `edit({ document })` returns `{ document: { … } }`, and a batch returns `{ documents: [ … ] }`. The preview envelope (`previewUrl`, `previewUrlSource`, `warning`) stays at the top level on `write` and `edit` responses; read tools (`search`, `links`, `exec`) attach a route-only `previewUrl` to each hit or entry instead. `conflicts` and `links` nest the same way under their `kind`.

## Advisory warnings

`write` and `edit` results may carry a `warnings` array on the document entry (per entry for a batch); `restore_version` carries it at the top level. Entries are discriminated by `kind`. `content-divergence` and `disk-edit-reconciled` are write-integrity signals — the stored doc differs from what the call composed, so re-read it before editing further. `mermaid-parse-error` means the write landed but that fence won't render — fix the fence and re-edit.

## Instantiating from a template

`write` accepts a `template` field on the `document` target — `write({ document: { path, template } })` — that resolves against the cascade of templates available for the target document's parent folder, using the same folder-up walk the [Folders and templates](https://openknowledge.ai/docs/advanced/folders-and-templates#templates) page describes. The template body becomes the new document's content; the template's own info block (its name and description) is stripped, so it doesn't bleed onto the document.

`content` and `template` are mutually exclusive. With `template`, `position` is forced to `replace` (initial create semantics).

## Editing frontmatter

Use `edit({ document: { path, frontmatter } })` for frontmatter edits: it applies a top-level merge patch (set or create a key with a value, delete a key by passing `null`; a nested object replaces that key's whole subtree, and `null` inside a subtree is rejected). A document body find/replace (`edit({ document: { path, find, replace } })`) is body-only and refuses patterns that touch frontmatter; `write` with `position: "replace"` is the only write mode that includes the `---\n…\n---` block (other positions ignore any frontmatter in the payload). Folders and templates take frontmatter the same way: `edit({ folder: { path, frontmatter } })`.

## Edit summaries

The write tools (`write`, `edit`, `move`, `install`, `checkpoint`, `restore_version`) accept an optional `summary` describing the intent of the edit, e.g. `"Fixed token-refresh race"`. Summaries appear on the document's timeline so readers can scan recent agent activity.

Cap is 80 characters. Avoid secrets and PII; summaries persist to git history.

## Conflict-aware writes

Every mutating tool (`write`, `edit`, `delete`, `move`, `restore_version`, and agent undo) refuses to mutate a doc whose GitHub-sync state is `conflict`.

To recover, call `conflicts({ kind: "content", file })` to inspect the merge stages, then `resolve_conflict` with one of:

- `mine`: resolve to your committed version (stage 2 — runs `git checkout --ours <file>` then `git add`).
- `theirs`: resolve to their committed version (stage 3 — runs `git checkout --theirs <file>` then `git add`).
- `content`: write the exact bytes you supply (for example, a merged result you composed by reading both stages).
- `delete`: `git rm` the file (for delete-vs-modify shapes where one stage is missing).

To detect conflict state proactively without waiting for a 409, call `conflicts({ kind: "list" })`; each entry is `{ file, detectedAt, … }`. `conflicts({ kind: "content" })` adds the doc's `lifecycleStatus` alongside the merge stages.

## Preview links

Read and write tools return a `previewUrl` for any doc they touched, but **those are route-only** (`/#/<doc>` with no host:port), meant to identify the doc, not to be opened as-is. Hosts that want to open the editor (Claude Code Desktop's `preview_start({url})`, terminal `open <url>`) call `preview_url` to resolve the full URL once per session.

`preview_url` always carries an `autoOpen` boolean (top-level on its response) reflecting the user's [`appearance.preview.autoOpen`](https://openknowledge.ai/docs/reference/configuration) preference. The write tools (`write`, `edit`) carry the same boolean on the `warning` object, which fires only when no browser is attached to the preview. In the common case (a browser is attached), the write response has no `warning` and no `autoOpen` field. Agents honor `autoOpen` before navigating: `true` (default) follows the host-capability routing in the bundled skill; `false` means the user is managing their own preview window, so the agent surfaces the URL on request but does not open or refresh anything. The value is resolved fresh on every tool call, so a mid-session toggle propagates within 0–1 calls, with no client restart.

# What OpenKnowledge writes to your system (https://openknowledge.ai/docs/reference/what-open-knowledge-writes)

Every file OpenKnowledge creates or changes — when, where, in-project vs. your home directory — plus the opt-outs and what leaves your machine (nothing by default).

OpenKnowledge keeps your knowledge base as plain markdown in your own project directory, but the tooling around it — the CLI, the MCP server, and the desktop app — also writes a handful of supporting files, some of them **outside the project**, in your home directory, your shell startup files, your editors' configs, and the OS credential store.

This page lists every one of them: **when** each write happens, **what** it is, and **where** it is written.

> **The short version**
>
> **Nothing about your content leaves your machine by default.** Diagnostic logs and telemetry stay on your machine. The things that *can* send data off it — semantic search, a diagnostic or bug-report bundle, and GitHub sync — are opt-in or on-demand. Three automatic exceptions, none of which touch your documents: the desktop app checks for updates (sending its app version and channel), on its first launch it opens your browser once to check for a pending share link, and installing a **published skill** reports that install to skills.sh so the skill's public install count is accurate — the skill's name and source repository, once per skill (per project, for a skill installed into a project), and never for a private or local source. All of them are listed under [What leaves your machine](https://openknowledge.ai/docs/reference/what-open-knowledge-writes#what-leaves-your-machine), and the skill report can be turned off.

## How to read this page

Writes fall into two buckets, and each table's **Scope** column tells you which:

- **In-project** — inside your project folder. Either **committed** (shared through git with your team) or **gitignored** (this machine only, under `.ok/local/` or `.git/`).
- **Outside-project** — your **home directory** (`~/.ok/`, `~/Library/...`), your **shell startup files** (`~/.zshrc` and friends), your editors' **user-level configs**, or the OS credential store.

OpenKnowledge installs one of two ways, and the footprint differs a little between them. Pick yours below — each tab lists the complete set of writes for that path. The [opt-outs](https://openknowledge.ai/docs/reference/what-open-knowledge-writes#opt-outs-in-one-place) and [what leaves your machine](https://openknowledge.ai/docs/reference/what-open-knowledge-writes#what-leaves-your-machine) at the end apply to both.

## Desktop app

### Installing and launching the app

How the app lands depends on your platform:

- **macOS** — a signed `.dmg` you drag into `/Applications`. Because a `.dmg` app can't put a CLI on your `PATH` on its own, first launch asks, in one dialog, whether to register the MCP server with your AI editors and whether to add `ok` to your shell's `PATH` (both covered below, both with your consent).
- **Windows** — a per-user installer: the app lands under `%LOCALAPPDATA%\Programs\`, with no admin prompt. The installer itself adds the bundled `ok` CLI to your **user** `PATH` (a registry value under `HKCU\Environment`) and registers the `openknowledge://` URL scheme under `HKCU\Software\Classes` — both removed by its uninstaller. Your shell startup files are never touched.
- **Linux** — a deb or rpm package: the app installs to `/opt/OpenKnowledge/`, and the package's post-install script symlinks `/usr/bin/ok`, `/usr/bin/open-knowledge`, and `/usr/bin/openknowledge` (plus a desktop entry that registers the `openknowledge://` scheme, and an AppArmor profile on Ubuntu 24.04+ so the Chromium sandbox keeps working). Your shell startup files are never touched.

On every platform the app writes app-level state and logs on launch. The table below shows the macOS locations; on Windows the same app-state files live under `%APPDATA%\OpenKnowledge\` and staged updates under `%LOCALAPPDATA%\@inkeepopen-knowledge-desktop-updater\`. On Linux, app state defaults to `~/.config/OpenKnowledge/` and staged updates to `~/.cache/@inkeepopen-knowledge-desktop-updater/` (or the corresponding XDG config and cache directories). The `~/.ok/` paths are the same everywhere.

| Path                                                                         | What it is                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Scope                      |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| `~/Library/Application Support/OpenKnowledge/state.json`                     | Recent projects, window/view state, update channel                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Outside-project (home dir) |
| `~/Library/Application Support/OpenKnowledge/path-install.json`              | Records the `PATH`/shell changes it made and your `PATH`-consent choice, so they can be reverted                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | Outside-project (home dir) |
| `~/.ok/logs/desktop.<date>.log`                                              | Desktop app + renderer log                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Outside-project (home dir) |
| `~/.ok/mcp-status.json`                                                      | Records your first-launch MCP-setup consent choice                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Outside-project (home dir) |
| `~/Library/Caches/@inkeepopen-knowledge-desktop-updater/`                    | Staged auto-update downloads (electron-updater)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Outside-project (home dir) |
| `~/Library/Caches/com.inkeep.open-knowledge.ShipIt/`                         | Install log of the macOS update helper (Squirrel's ShipIt), which swaps the app bundle after the app has quit. The only record of why an update failed to install, so a bug report you send collects it — only this app's, never those of other applications that use the same update mechanism                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Outside-project (home dir) |
| `~/Library/Logs/DiagnosticReports/`                                          | Not written by OpenKnowledge at all — this is macOS's own crash-report directory, shared by every application on the machine. When the OS ends one of this app's processes it records why there, which is sometimes the only account of a session that stopped without a crash dump, so a bug report you send at the **Detailed diagnostics** level collects the reports macOS wrote for this app and its helper processes — only ours, never those of any other application, and never at the standard level. A report also carries machine details macOS puts in every one of them — the account uid, the Mac model, and the name of the process that launched the app — while the identifiers that would link your bundles to each other, the crash-reporter key and the boot session, are replaced before bundling. `ok diagnose bundle` collects them too, on every run: that command produces the Detailed superset by design, and its summary tells you what the search found before it writes the zip | Outside-project (home dir) |
| `~/Library/Application Support/OpenKnowledge/Crashpad/`                      | Native crash minidumps, written by the OS crash handler (Electron's Crashpad) if the app ever crashes. Local-only: the crash reporter runs with `uploadToServer: false`, so no dump is ever uploaded automatically                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Outside-project (home dir) |
| `~/Library/Application Support/OpenKnowledge/bug-report-dirty-shutdown.json` | Dirty-shutdown sentinel — written on **every** launch, refreshed as it runs, and removed on a clean quit. The next launch reads it to tell an app crash (prompts you) from the endings that are not a crash: a reboot, an OS shutdown, or dying asleep (all suppressed and logged instead), and an update install shutting the app down to replace its files (logged either way, and suppressed in almost every case: a shutdown the app had no chance to record, or one it cannot date because the previous session left no heartbeat, can still prompt so you are able to report it)                                                                                                                                                                                                                                                                                                                                                                                                                        | Outside-project (home dir) |
| `~/Library/Application Support/OpenKnowledge/bug-report-crash-acks.json`     | Records which crash-report invitations you already answered or dismissed, so the same crash never re-prompts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | Outside-project (home dir) |

The app registers the `openknowledge://` URL scheme (for deep links) and checks for updates — on launch and periodically while it runs — against the OpenKnowledge update service at `openknowledge.ai/updates`, which redirects to the GitHub release asset and counts the update per version and channel (falling back to GitHub directly if it's unreachable). On macOS and Windows an update on your channel downloads in the background and installs on the next quit; on Linux — where there is no apt/rpm repository — the app downloads the new deb/rpm from the release feed and asks you to authorize the install (a `pkexec` password prompt) each time. You can also check on demand from **Check for updates…** in the app and Help menus. The channel (stable or beta) is fixed by the build you installed. Reinstall a stable build for any supported platform from `openknowledge.ai/download`. The `/download/beta` shortcut serves the latest Apple Silicon macOS beta; Windows and Linux beta installers are available from [GitHub Releases](https://github.com/inkeep/open-knowledge/releases). Override the feed with `OK_UPDATER_FEED_URL`.

On its first launch the app also opens your default browser once to `openknowledge.ai/continue`, to check whether you installed from a share link and redeem it if so; the exchange completes over a short-lived local loopback listener.

### Shell and `PATH`

So `ok` can work in your terminal too, the packaged app manages a `PATH` shim. **This is macOS-only, packaged-build-only, and disabled by `OK_RECLAIM_DISABLE=1`.** It never uses `sudo`, never runs an admin prompt, and never installs a login item. (Windows and Linux don't need it: the Windows installer adds the bundled CLI to your user `PATH` itself, and the Linux packages place `/usr/bin/ok` directly — neither ever edits your shell files.)

The first two rows below live in OpenKnowledge's own `~/.ok/` directory and are maintained automatically. The third — the only write into files OpenKnowledge doesn't own — happens **only with your consent**: the first-launch dialog has an "Add the `ok` command to your terminal" toggle (checked by default), and nothing is written to your shell config if you uncheck it.

| Path                                                                       | What it is                                                                                                          | Scope                          |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| `~/.ok/bin/ok`, `~/.ok/bin/open-knowledge`                                 | Symlinks to the CLI bundled inside the app                                                                          | Outside-project (home dir)     |
| `~/.ok/env.sh`                                                             | A managed shim that prepends `~/.ok/bin` to your `PATH`                                                             | Outside-project (home dir)     |
| `~/.zshrc`, `~/.bash_profile`, `~/.config/fish/conf.d/open-knowledge.fish` | A **fenced managed block** that sources `~/.ok/env.sh` — written **only if you consent** in the first-launch dialog | Outside-project (shell config) |

If you decline, only `ok` typed in an *external* terminal (Terminal, iTerm) is affected — the app's built-in terminal, the MCP server, and the "Open with AI" launches all work without it. You can add it later from **File → Set up OpenKnowledge integrations…**. Machines that already had the block from a version before the consent dialog are treated as consented: the block is left in place and maintained.

The managed block is clearly fenced so you can see and remove it:

```bash
# >>> open-knowledge cli >>>
# ! Contents within this block are managed by OpenKnowledge. Do not edit.
# ! Delete this whole block to opt out — OpenKnowledge will not re-add it.
[ -f "$HOME/.ok/env.sh" ] && . "$HOME/.ok/env.sh"
# <<< open-knowledge cli <<<
```

Delete the block and the app **won't re-add it** — the removal is recorded and respected. Or set `OK_RECLAIM_DISABLE=1` to disable all of this before first launch.

### User-global Agent Skills

OpenKnowledge ships two user-global [Agent Skills](https://openknowledge.ai/docs/features/skills) — `open-knowledge-discovery` (helps your coding agent recognize and route through OpenKnowledge projects) and `open-knowledge-write-skill` (a workflow for authoring new skills). They install into each detected editor's global skill folder (`~/.claude/skills/`, `~/.cursor/skills/`, `~/.codex/skills/`, `~/.opencode/skills/`, `~/.lmstudio/skills/`) plus the shared `~/.agents/skills/` hub.

Only `open-knowledge-discovery` is set up at first launch — it is what lets an agent recognize an OpenKnowledge project at all, so it comes with the MCP wiring. `open-knowledge-write-skill` is an authoring convenience with no bearing on whether your tools work, so setup neither installs it nor records a decision about it; it is offered the first time you open **Settings → Skills Studio**, and remains installable there afterwards.

Both are **consent-gated**, but at different moments. Declining `open-knowledge-discovery` at first launch means it is never installed. `open-knowledge-write-skill` has no first-launch step at all, so nothing is decided about it until you install it from Skills Studio — there is no option to decline it during setup, because setup never asks. Uninstalling either one **removes** its copies. The choice is recorded in `~/.ok/skill-state.yml` and honored by every install path — the desktop app, `ok init`, and `ok start` — so a skill you declined is never re-added, and a skill nobody has been asked about is never installed behind your back. Machines that already had the skills before the consent dialog are treated as consented (left in place).

### Setting up a project

When you initialize or open a project, the app scaffolds the same project files the CLI's `ok init` creates, and registers the `open-knowledge` MCP server with the AI editors it detects. The MCP registration is **surgical** — it adds only its own entry and leaves your other settings, comments, and formatting byte-for-byte intact.

| Path                                                                                                                                                                                                                                                                                                                  | What it is                                                                                                                                                                                                                                                                                                                                                                                                                                | Scope                           |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| `.ok/`, `.ok/config.yml`                                                                                                                                                                                                                                                                                              | Project directory and its [config](https://openknowledge.ai/docs/reference/configuration)                                                                                                                                                                                                                                                                                                                                                 | In-project, committed           |
| `.ok/.gitignore`                                                                                                                                                                                                                                                                                                      | Keeps machine-local runtime state (`local/`, `principal.json`, `server.lock`, …) out of git                                                                                                                                                                                                                                                                                                                                               | In-project, committed           |
| `.okignore`                                                                                                                                                                                                                                                                                                           | Paths excluded from the editor, search, and agents — see [Ignore patterns](https://openknowledge.ai/docs/features/ignore-patterns)                                                                                                                                                                                                                                                                                                        | In-project, committed           |
| `.mcp.json`, `.cursor/mcp.json`, `.codex/config.toml`, `opencode.json`                                                                                                                                                                                                                                                | Project-scoped MCP registration for the four MCP-capable project editors (Claude, Cursor, Codex, OpenCode), written regardless of what's installed so the repo is ready for teammates                                                                                                                                                                                                                                                     | In-project, committed           |
| `.pi/extensions/open-knowledge.ts`                                                                                                                                                                                                                                                                                    | Managed bridge extension connecting [Pi](https://openknowledge.ai/docs/integrations/pi) (which has no MCP support) to the project                                                                                                                                                                                                                                                                                                         | In-project, committed           |
| `.claude/skills/`, `.cursor/skills/`, `.codex/skills/`, `.github/skills/`, `.opencode/skills/`, `.pi/skills/`                                                                                                                                                                                                         | Project-local "rich" OpenKnowledge skill (`SKILL.md`) for Claude, Cursor, Codex, GitHub Copilot, OpenCode, and Pi                                                                                                                                                                                                                                                                                                                         | In-project, committed           |
| `.ok/skills-lock.json`                                                                                                                                                                                                                                                                                                | Provenance for imported skills — source, selected skill, commit/ref when available, content hash, publisher, and import time                                                                                                                                                                                                                                                                                                              | In-project, committed           |
| `~/.claude.json`, `~/Library/Application Support/Claude/claude_desktop_config.json`, `~/.cursor/mcp.json`, `~/.codex/config.toml`, `~/.copilot/mcp-config.json`, `~/.config/opencode/opencode.json`, `~/.openclaw/openclaw.json`, `~/.gemini/config/mcp_config.json`, `~/.lmstudio/mcp.json`, `~/.hermes/config.yaml` | User-level `open-knowledge` MCP registration, one per detected editor, so it's available in every project (`~/.copilot/mcp-config.json` only when `~/.copilot/` exists; `~/.openclaw/openclaw.json` only when `~/.openclaw/` exists; `~/.gemini/config/mcp_config.json` is Antigravity's shared IDE + `agy` config, only when `~/.gemini/` exists; `~/.lmstudio/mcp.json` and `~/.hermes/config.yaml` only when those apps are installed) | Outside-project (editor config) |

The **Scope** column assumes you share this setup with your team. New projects don't: they keep OpenKnowledge's config to yourself, adding every in-project path above to `.git/info/exclude`, so those files are written but stay out of git.

### While a project is open

The app runs a collaboration server for the open project. It writes runtime state, gitignored under `.ok/local/`, plus a shadow git repo that powers the timeline and recovery features:

| Path                                                                                                                                                          | What it is                                                                                                                                                                                                                                                                                                                               | Scope                                   |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `.ok/local/server.lock`                                                                                                                                       | Advertise the running server's URL and capabilities (the `ui` capability marks a UI-serving server)                                                                                                                                                                                                                                      | In-project, gitignored                  |
| `.ok/local/principal.json`                                                                                                                                    | Your local identity (id, email) for edit attribution                                                                                                                                                                                                                                                                                     | In-project, gitignored                  |
| `.ok/local/sync-state.json`, `.ok/local/conflicts.json`                                                                                                       | GitHub-sync engine state                                                                                                                                                                                                                                                                                                                 | In-project, gitignored                  |
| `.ok/local/state.json`, `.ok/local/last-spawn-error.log`                                                                                                      | State-schema manifest and the last detached-spawn error log                                                                                                                                                                                                                                                                              | In-project, gitignored                  |
| `.ok/local/last-server-exit.json`                                                                                                                             | Why the collaboration server the app started last exited — a timestamp, its process id, the exit code, the signal that ended it, and which part of the app was watching. One record, overwritten on each exit; a server the app only attached to, or one you started yourself with `ok start`, leaves none. No document content or paths | In-project, gitignored                  |
| `.ok/local/last-server-crash.json`                                                                                                                            | The collaboration server's own account of a crash it did not survive — a timestamp, the error message and stack, its process id, and how long it had been running. Written by the server itself, so it covers `ok start` too. Holds the most recent crash only                                                                           | In-project, gitignored                  |
| `.ok/local/telemetry/spans-*.jsonl`                                                                                                                           | Local diagnostic spans, rotated at \~50 MB                                                                                                                                                                                                                                                                                               | In-project, gitignored                  |
| `.ok/local/logs/server-*.jsonl`                                                                                                                               | Local server logs, rotated at \~25 MB                                                                                                                                                                                                                                                                                                    | In-project, gitignored                  |
| `.ok/local/cache/<branch>/backlinks.json`                                                                                                                     | Backlink-graph cache                                                                                                                                                                                                                                                                                                                     | In-project, gitignored                  |
| `.ok/local/installed-skills.json`                                                                                                                             | Per-project skill-install markers (which editors each skill is projected into)                                                                                                                                                                                                                                                           | In-project, gitignored                  |
| `.ok/local/terminal/claude-settings-{mcp,tools,mcp-tools}.json`                                                                                               | Windows-only Claude launch settings written when **Open in Claude** requests MCP or tool preapproval; one of three possible files, overwritten rather than appended on each launch that uses that combination. macOS and Linux pass the same settings inline                                                                             | In-project, gitignored                  |
| `.ok/local/comments/`                                                                                                                                         | [Comments](https://openknowledge.ai/docs/features/comments) left on documents, one JSON file each — the note, the passage it quotes, and whether it has been sent                                                                                                                                                                        | In-project, gitignored                  |
| `.ok/worktrees/<branch>/`                                                                                                                                     | Git worktree checkout created when you open a branch in its own window (project switcher); the path is also added to `.git/info/exclude`                                                                                                                                                                                                 | In-project, gitignored                  |
| `.git/ok/` — or `.git/worktrees/<name>/ok/` in a linked worktree, or the enclosing repo's `.git/ok-<slug>/` when your project is a subfolder of a larger repo | Shadow git repo holding per-writer work-in-progress refs; always inside a git admin dir, never in your content tree                                                                                                                                                                                                                      | In-project, gitignored (inside `.git/`) |

Diagnostic logs and telemetry are **on by default but local-only** — credential-bearing attributes are redacted (`[REDACTED]`) before anything is written, the files rotate at the size caps above, and nothing leaves the machine until you explicitly run `ok diagnose bundle`. Turn the local sink off with `telemetry.localSink.enabled: false`; see [Configuration](https://openknowledge.ai/docs/reference/configuration).

On each launch and project open, the app also runs **repair sweeps**: it rewrites *existing* `open-knowledge` MCP entries (in the editor configs above) to the current canonical form, and **seeds** the built-in OpenKnowledge skill bundles (`open-knowledge-discovery` and `open-knowledge-write-skill`, in `~/.agents/skills/` and each detected editor's `~/.claude/skills/`, `~/.cursor/skills/`, `~/.codex/skills/`, `~/.opencode/skills/`, `~/.lmstudio/skills/`) **only when they are missing and you previously installed them** — a bundle you never installed or declined is not seeded, and an existing copy is left untouched (updates to a built-in flow through the skills.sh "update available" path, never a force-refresh). It also removes any stale `open-knowledge-ui` entry left in `.claude/launch.json` by an older OK version. It **never adds** an MCP entry to an editor that doesn't already have one. Same `OK_RECLAIM_DISABLE=1` opt-out.

### Home directory and credentials (`~/.ok/`)

| Path                                                        | What it is                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | Sensitive?       |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| `~/.ok/global.yml`                                          | User-global [config](https://openknowledge.ai/docs/reference/configuration) (applies to every project)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | No               |
| `~/.ok/secrets.yml`                                         | Embeddings provider API key for [semantic search](https://openknowledge.ai/docs/reference/configuration#semantic-search), if you set one. Written `0600`; never in `config.yml`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | **Yes** (`0600`) |
| `~/.ok/auth.yml`                                            | Auth-token fallback, used only when the Keychain is unavailable. Written `0600`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | **Yes** (`0600`) |
| `~/.ok/skill-state.yml`, `~/.ok/skill-install-events.jsonl` | Skill-install bookkeeping                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | No               |
| `~/.agents/skills/<name>/`, `~/.claude/skills/<name>/`, …   | Your global-scope skills, versioned in place in your home editor dirs (kept by `ok uninstall` unless `--purge-content`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | No               |
| `~/.ok/stats.jsonl`                                         | Local "open with AI" handoff stats — local-only, no phone-home                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | No               |
| `~/.ok/themes/`                                             | Your saved color themes from the [Themes plugin](https://openknowledge.ai/docs/plugins/themes), one Tinted Theming base16 YAML file per theme, written when you create or edit a theme                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | No               |
| `~/.ok/mcp-status.json`                                     | First-launch MCP consent record                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | No               |
| `~/.ok/logs/`, `~/.ok/bug-reports/`                         | Desktop and CLI logs, and bug-report bundles from `ok bug-report` and the app's **Help → Report a bug…** dialog (`ok diagnose bundle` writes into the project, under `.ok/local/diagnostics/`). Each report from the dialog also gets a small YAML record holding its send state and a secret-redacted copy of the note you wrote, so report history can still show what a report was about. The zip is deleted once the report is sent; the record is kept, and both go when you delete the report from report history. If that record ever can't be read back, OpenKnowledge leaves it exactly as it is rather than overwriting your note, and writes a second tiny `.sent.yaml` file beside it when a report does send, so history still shows it as sent instead of offering to send it again | No               |
| `~/.ok/bin/`, `~/.ok/env.sh`                                | `PATH` shim (see [Shell and `PATH`](https://openknowledge.ai/docs/reference/what-open-knowledge-writes#shell-and-path) above)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | No               |

**Auth tokens** (for GitHub sync, sharing, and cloning) are stored in your OS credential store under the service name `open-knowledge` — the **Keychain** on macOS, **Credential Manager** on Windows, and the Secret Service keyring (libsecret) on Linux. The `~/.ok/auth.yml` file is only a fallback for headless environments where the credential store isn't reachable; when it becomes available, the token is migrated into it and the file copy is removed.

The **Install for Claude Chat & Cowork** command (command palette or Help menu) builds `~/Downloads/openknowledge.skill` on demand and hands it to Claude Desktop for upload; the build is version-gated via `~/.ok/skill-state.yml`.

## CLI

### Installing the package

Installing `@inkeep/open-knowledge` from npm (or invoking it with `npx`) drops the two CLI binaries plus a bundled native addon. **Nothing is written to your home directory at install time** — no lifecycle scripts run, and no skills are registered until you run `ok init`.

| Path                                    | What it is                                                    | Scope                            |
| --------------------------------------- | ------------------------------------------------------------- | -------------------------------- |
| `ok`, `open-knowledge` (on your `PATH`) | The two CLI binaries (identical entry point)                  | Outside-project (npm global/bin) |
| `<pkg>/dist/native/`                    | Bundled native TOML-editing addon, shipped inside the package | Outside-project (package dir)    |

> **Opt out**
>
> Installing the package writes nothing outside its own directory, so there is nothing to opt out of here. The user-global skills are installed by `ok init`, and `ok init --no-skills` skips installing them for that run.

### `ok init`

`ok init` turns a folder into an OpenKnowledge project. It scaffolds the project's `.ok/` directory, and — unless you pass `--no-mcp` — registers the `open-knowledge` MCP server with the AI editors it detects. The MCP registration is **surgical**: it adds only its own entry and leaves your other settings, comments, and formatting byte-for-byte intact (if a config can't be parsed safely, it's left untouched and reported).

| Path                                                                                                          | What it is                                                                                                                                                                                                     | Scope                 |
| ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| `.ok/`, `.ok/config.yml`                                                                                      | Project directory and its [config](https://openknowledge.ai/docs/reference/configuration)                                                                                                                      | In-project, committed |
| `.ok/.gitignore`                                                                                              | Keeps machine-local runtime state (`local/`, `principal.json`, `server.lock`, …) out of git                                                                                                                    | In-project, committed |
| `.okignore`                                                                                                   | Paths excluded from the editor, search, and agents — see [Ignore patterns](https://openknowledge.ai/docs/features/ignore-patterns)                                                                             | In-project, committed |
| `.gitignore` (project root)                                                                                   | Seeded (with `.DS_Store`) **only** when `ok init` creates a new git repo                                                                                                                                       | In-project, committed |
| `.mcp.json`, `.cursor/mcp.json`, `.codex/config.toml`, `opencode.json`                                        | Project-scoped MCP registration for the four MCP-capable project editors (Claude, Cursor, Codex, OpenCode) regardless of what's installed — prepares the repo for teammates (with `--scope project` or `both`) | In-project, committed |
| `.pi/extensions/open-knowledge.ts`                                                                            | Managed bridge extension connecting [Pi](https://openknowledge.ai/docs/integrations/pi) (which has no MCP support) to the project                                                                              | In-project, committed |
| `.claude/skills/`, `.cursor/skills/`, `.codex/skills/`, `.github/skills/`, `.opencode/skills/`, `.pi/skills/` | Project-local "rich" OpenKnowledge skill (`SKILL.md`) for Claude, Cursor, Codex, GitHub Copilot, OpenCode, and Pi                                                                                              | In-project, committed |
| `.ok/skills-lock.json`                                                                                        | Provenance for imported skills — source, selected skill, commit/ref when available, content hash, publisher, and import time                                                                                   | In-project, committed |

With `--scope user` or `both` (the default), `ok init` also registers the `open-knowledge` MCP server in each detected editor's **user-level** config, so it's available in every project:

| Path                                                              | Editor                                                                            | Scope                           |
| ----------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------- |
| `~/.claude.json`                                                  | Claude Code                                                                       | Outside-project (editor config) |
| `~/Library/Application Support/Claude/claude_desktop_config.json` | Claude Desktop                                                                    | Outside-project (editor config) |
| `~/.cursor/mcp.json`                                              | Cursor                                                                            | Outside-project (editor config) |
| `~/.codex/config.toml`                                            | Codex                                                                             | Outside-project (editor config) |
| `~/.copilot/mcp-config.json`                                      | GitHub Copilot CLI (only when `~/.copilot/` exists; respects `COPILOT_HOME`)      | Outside-project (editor config) |
| `~/.config/opencode/opencode.json`                                | OpenCode                                                                          | Outside-project (editor config) |
| `~/.openclaw/openclaw.json`                                       | OpenClaw (only when `~/.openclaw/` exists)                                        | Outside-project (editor config) |
| `~/.gemini/config/mcp_config.json`                                | Antigravity (only when `~/.gemini/` exists) — shared by the IDE and the `agy` CLI | Outside-project (editor config) |
| `~/.lmstudio/mcp.json`                                            | LM Studio (only when installed; also checks `~/.cache/lm-studio/mcp.json`)        | Outside-project (editor config) |
| `~/.hermes/config.yaml`                                           | Hermes (only when `~/.hermes/` exists)                                            | Outside-project (editor config) |

`ok init` also installs the user-global **discovery skill** — a short instruction file that teaches an agent host how to find and initialize OpenKnowledge — so it is available in every project. It is written **only** into the skills directories of agent hosts already present in your home directory, plus the shared `~/.agents/skills/` hub those hosts read. If none of them is present, nothing is written at all.

| Path                                   | What it is                                                                            | Scope                      |
| -------------------------------------- | ------------------------------------------------------------------------------------- | -------------------------- |
| `~/.agents/skills/open-knowledge-*/`   | Shared hub read natively by Codex, OpenCode, and Cursor                               | Outside-project (home dir) |
| `~/.claude/skills/open-knowledge-*/`   | Written only when `~/.claude/` already exists                                         | Outside-project (home dir) |
| `~/.lmstudio/skills/open-knowledge-*/` | Written only when `~/.lmstudio/` already exists (LM Studio reads these in agent mode) | Outside-project (home dir) |
| `~/.cursor/skills/open-knowledge-*/`   | Written only when `~/.cursor/` already exists                                         | Outside-project (home dir) |
| `~/.codex/skills/open-knowledge-*/`    | Written only when `~/.codex/` already exists                                          | Outside-project (home dir) |
| `~/.opencode/skills/open-knowledge-*/` | Written only when `~/.opencode/` already exists                                       | Outside-project (home dir) |
| `~/.ok/skill-state.yml`                | Records which skill version is installed, so the step is skipped next time            | Outside-project (home dir) |
| `~/.ok/skill-install-events.jsonl`     | Append-only local log of skill-install attempts                                       | Outside-project (home dir) |

The install is **non-fatal** (`ok init` still exits 0 if it fails), **version-gated** (a no-op when the current version is already installed), and needs **no network access**. Skip it for one run with `ok init --no-skills`, or choose individual bundles with `--skills <ids>`. Skipping records nothing and removes nothing, so skills already on the machine are left alone. An installed bundle is remembered in `~/.ok/skill-state.yml` and honored by every later install path; to turn the built-in skills off for good, decline at the first-launch prompt or uninstall from **Settings → Skills Studio**, either of which records a durable machine-wide choice. `ok repair-skills` re-runs it.

Pi is the exception: it has no user-level MCP config (it has no MCP support at all), so its integration is only ever the project-scoped bridge extension above.

On Windows, Claude Desktop and OpenCode configs live under `%APPDATA%\Claude\` and `%APPDATA%\opencode\`, and the MCP entry is a PowerShell launcher.

`ok init` also installs `open-knowledge-discovery` (the same skill first launch sets up — pass `--skills discovery,write-skill` to add the authoring skill too), honoring any prior consent choice, and, for a project it is setting up for the first time, adds the OpenKnowledge paths to `.git/info/exclude` so `.ok/` stays off your remote. Pass `--shared` to commit it instead. Re-running `ok init` on a project you already set up keeps that project's current choice. Switch modes later with `ok config-sharing share` / `unshare` (`unshare` refuses if an OK config file is already tracked); `ok config-sharing status` prints the current mode and the excluded paths.

> **Controlling it**
>
> - `--no-mcp` — scaffold `.ok/` only; register nothing with any editor.
> - `--scope user | project | both` — where the MCP entry is written. In an interactive terminal, `ok init` prompts for this; scripted/CI runs default to `both`, so pass the flag to constrain them.
> - `--no-skills` — install no user-global skills on this run; `--skills discovery,write-skill` — install only the named bundles. **Default installs `discovery` only**, matching what the desktop's first launch sets up; `write-skill` is opt-in here and in **Settings → Skills Studio**. Installing is recorded and honored by the desktop app too; skipping is not recorded, so it never affects another project.
> - `--shared` (vs `--local-only`) — commit `.ok/` rather than keeping it out of git.

### `ok start`

`ok start` runs the collaboration server. Most of what it writes stays **inside your project**, gitignored under `.ok/local/`, plus a shadow git repo for the timeline and recovery features:

| Path                                                                                                                                                          | What it is                                                                                                                                                                            | Scope                                   |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `.ok/local/server.lock`                                                                                                                                       | Advertise the running server's URL and capabilities (the `ui` capability marks a UI-serving server)                                                                                   | In-project, gitignored                  |
| `.ok/local/principal.json`                                                                                                                                    | Your local identity (id, email) for edit attribution                                                                                                                                  | In-project, gitignored                  |
| `.ok/local/sync-state.json`, `.ok/local/conflicts.json`                                                                                                       | GitHub-sync engine state                                                                                                                                                              | In-project, gitignored                  |
| `.ok/local/state.json`, `.ok/local/last-spawn-error.log`                                                                                                      | State-schema manifest and the last detached-spawn error log                                                                                                                           | In-project, gitignored                  |
| `.ok/local/last-server-crash.json`                                                                                                                            | The server's own account of a crash it did not survive — a timestamp, the error message and stack, its process id, and how long it had been running. Holds the most recent crash only | In-project, gitignored                  |
| `.ok/local/telemetry/spans-*.jsonl`                                                                                                                           | Local diagnostic spans, rotated at \~50 MB                                                                                                                                            | In-project, gitignored                  |
| `.ok/local/logs/server-*.jsonl`                                                                                                                               | Local server logs, rotated at \~25 MB                                                                                                                                                 | In-project, gitignored                  |
| `.ok/local/cache/<branch>/backlinks.json`                                                                                                                     | Backlink-graph cache                                                                                                                                                                  | In-project, gitignored                  |
| `.ok/local/installed-skills.json`                                                                                                                             | Per-project skill-install markers (which editors each skill is projected into)                                                                                                        | In-project, gitignored                  |
| `.ok/local/comments/`                                                                                                                                         | [Comments](https://openknowledge.ai/docs/features/comments) left on documents, one JSON file each — the note, the passage it quotes, and whether it has been sent                     | In-project, gitignored                  |
| `.git/ok/` — or `.git/worktrees/<name>/ok/` in a linked worktree, or the enclosing repo's `.git/ok-<slug>/` when your project is a subfolder of a larger repo | Shadow git repo holding per-writer work-in-progress refs (powers the timeline); always inside a git admin dir, never in your content tree                                             | In-project, gitignored (inside `.git/`) |

Diagnostic logs and telemetry are **on by default but local-only** — credential-bearing attributes are redacted (`[REDACTED]`) before anything is written, the files rotate at the size caps above, and nothing leaves the machine until you explicitly run `ok diagnose bundle`. Turn the local sink off with `telemetry.localSink.enabled: false`; see [Configuration](https://openknowledge.ai/docs/reference/configuration).

On each boot, `ok start` also runs **repair sweeps**: it re-checks a few things that drift as OpenKnowledge updates and rewrites any that have fallen out of the current canonical form — a no-op when nothing has changed, and it **never adds** an entry that isn't already there.

| What it re-checks                                                                      | Where                                                                                                                                                                                                     | Scope                                                |
| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| Existing `open-knowledge` MCP entries                                                  | The same editor configs `ok init` writes — **both** user-level (`~/.claude.json`, `~/.cursor/mcp.json`, …) and project-level                                                                              | Outside-project (editor config) **and** in-project   |
| OpenKnowledge skill bundles (`open-knowledge-discovery`, `open-knowledge-write-skill`) | Project (`.claude/skills/…`) and user-global — `~/.agents/skills/` plus each detected editor's `~/.claude/skills/`, `~/.cursor/skills/`, `~/.codex/skills/`, `~/.opencode/skills/`, `~/.lmstudio/skills/` | In-project **and** outside-project (home dir)        |
| Stale `open-knowledge-ui` entry in `.claude/launch.json`                               | Project                                                                                                                                                                                                   | Removed if present — OK no longer writes launch.json |

Disable all three with `OK_RECLAIM_DISABLE=1`.

### `ok mcp`

`ok mcp` is the stdio bridge your editor spawns to talk to the server. It **writes nothing to disk** — it routes tool calls to a running `ok start` backend (starting one on the first tool call that needs it — reads included).

### Home directory and credentials (`~/.ok/`)

| Path                                                        | What it is                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | Sensitive?       |
| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| `~/.ok/global.yml`                                          | User-global [config](https://openknowledge.ai/docs/reference/configuration) (applies to every project)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | No               |
| `~/.ok/secrets.yml`                                         | Embeddings provider API key for [semantic search](https://openknowledge.ai/docs/reference/configuration#semantic-search), if you set one. Written `0600`; never in `config.yml`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | **Yes** (`0600`) |
| `~/.ok/auth.yml`                                            | Auth-token fallback, used when no OS credential store is reachable. Written `0600`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | **Yes** (`0600`) |
| `~/.ok/skill-state.yml`, `~/.ok/skill-install-events.jsonl` | Skill-install bookkeeping                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | No               |
| `~/.agents/skills/<name>/`, `~/.claude/skills/<name>/`, …   | Your global-scope skills, versioned in place in your home editor dirs (kept by `ok uninstall` unless `--purge-content`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | No               |
| `~/.ok/stats.jsonl`                                         | Local "open with AI" handoff stats — local-only, no phone-home                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | No               |
| `~/.ok/themes/`                                             | Your saved color themes from the [Themes plugin](https://openknowledge.ai/docs/plugins/themes), one Tinted Theming base16 YAML file per theme, written when you create or edit a theme                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | No               |
| `~/.ok/logs/`                                               | CLI logs (`cli.<date>.log`), size- and age-capped                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | No               |
| `~/.ok/bug-reports/`                                        | Bug-report bundles from `ok bug-report` — the desktop app's **Help → Report a bug…** dialog writes here too (`ok diagnose bundle` writes into the project, under `.ok/local/diagnostics/`). A report filed from the dialog also leaves a small YAML record beside its bundle, holding the report's send state and a secret-redacted copy of the note you wrote so report history can name it. That record outlives the bundle: the zip is deleted once the report is sent, and the record is kept until you delete the report from report history. If the record ever can't be read back, OpenKnowledge leaves it exactly as it is rather than overwriting your note, and writes a second tiny `.sent.yaml` file beside it when a report does send. That marker holds only the send time and the report's reference, never your note, and it is removed with the rest of the report | No               |

**Auth tokens** (for GitHub sync, sharing, and cloning) are stored in your operating system's secure credential store when one is available — the macOS **Keychain** (service `open-knowledge`), or the platform equivalent (Windows Credential Manager, Linux Secret Service) — falling back to a `0600` `~/.ok/auth.yml` file on headless systems (a Linux server, CI) where none is reachable.

## Opt-outs in one place

| To skip…                                                       | Do this                                                                                                  |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| The `postinstall` skill install (CLI)                          | `npm install --ignore-scripts`                                                                           |
| The shell-config `PATH` block (macOS app)                      | Uncheck "Add the `ok` command to your terminal" in the first-launch dialog                               |
| All editor MCP registration in `ok init`                       | `ok init --no-mcp`                                                                                       |
| User-level (vs project) MCP writes                             | `ok init --scope project`                                                                                |
| Committing `.ok/` to git                                       | Off by default for a new project; `ok init --shared` commits it instead                                  |
| The repair sweeps and the desktop app's shell / `PATH` changes | `OK_RECLAIM_DISABLE=1` (also delete the managed block from your shell config to remove the `PATH` entry) |
| Local diagnostic logs and telemetry                            | `telemetry.localSink.enabled: false` in config                                                           |
| Auto-starting the MCP server                                   | `OK_MCP_AUTOSTART=0`                                                                                     |

To review the footprint after the fact, `ok diagnose` reports what's on disk (and the desktop `path-install.json` records exactly which shell/`PATH` changes were made). To reverse it, `ok deinit` removes OpenKnowledge from one project and `ok uninstall` removes it from the whole machine — both keep your markdown content, and your global skills in your editors' skill dirs (`~/.agents/skills`, `~/.claude/skills`, and the like) are never touched, and both take `--dry-run` to preview. See the [CLI reference](https://openknowledge.ai/docs/reference/cli) for the full semantics.

In the desktop app you don't need the CLI for finer control, and the split follows what each thing is. **Settings → AI tools & CLI** (under User) lists the *connections* OpenKnowledge manages — each editor's `open-knowledge` MCP entry and the `ok` PATH command — with a checkbox reflecting live installed state; toggling a row installs or removes just that component. **Settings → This project → AI tools** does the same for the open project's project-scoped MCP configs. *Skills* live on their own page at both scopes: **Settings → Skills Studio** carries the skills OpenKnowledge ships (user scope) or the project's own skill (project scope), each with an explicit Install/Uninstall button behind a confirmation naming every path it touches, plus the folders your AI tools read skills from. Removal keeps the same guest discipline as install: only entries OpenKnowledge recognizably wrote are removed, and anything you customized is left intact.

## What leaves your machine

None of your content, by default. Everything above is written to your own disk. The only ways data leaves your machine are these — all opt-in or on-demand, except three automatic actions that carry no document content (the desktop update check, a one-time share-link check on first launch, and the skill-install report):

| What                       | When                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Where it goes                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Diagnostic bundle          | Only when you run `ok diagnose bundle` (you can inspect it first). On macOS it also carries the crash reports the OS recorded for this app and its helper processes (listed above), which are machine-wide rather than project-scoped; only this app's are collected, never another application's, and the summary printed before the zip is written says what that search found even when the answer is nothing                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | Wherever you send it                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| In-app bug report          | Only when you click **Send** in the desktop app's **Help → Report a bug…** dialog, or the `Cmd+Shift+D` / `Ctrl+Shift+D` shortcut that opens the same dialog — it builds a secret-redacted zip you can reveal and inspect first, and nothing is sent without that explicit click. The zip carries a picture of the app window unless you uncheck **Screenshot** in that dialog, and the preview there is the picture that would be sent. A report reached through a surface you opened only to get to the reporter — the command palette, the Help popover, or the Windows and Linux menu bar — waits for that surface to close first, and carries no marker, because the row it would point at has already gone. Every other report you start yourself photographs the screen as it is, so an open menu or another person's document visible at the time is in the picture, and carries a marker drawn where your pointer was, since a screenshot never includes the cursor. No marker is drawn either if the pointer has not moved since the window loaded, or has since left the window. The invitation that appears on its own after a crash is the exception to all of this: it carries no picture at all. After a crash, the same dialog adds an **off-by-default** "Include crash dump" checkbox: a crash dump is a memory snapshot that can contain document content and can't be redacted, and it is only ever attached when you check that box. On macOS the zip also carries the update helper's install log (listed above), which is machine-wide rather than project-scoped; the zip's own README names it and says where it came from. At the **Detailed diagnostics** level — pre-checked on the invitation that follows a crash — it additionally carries the crash reports macOS itself recorded for this app and its helpers (also listed above), which are machine-wide for the same reason and named in the same README; only this app's are collected, never another application's, and the zip always says what that search found even when the answer is nothing. The zip also carries a short send history of the bug reports previously generated on this machine — when each was generated and whether sending it succeeded — which is what makes a report that failed to send diagnosable at all. That history is machine-wide too, so it names the other projects you have filed reports from; it carries no document content, and the README names it the same way | By default, Send makes no network request — it opens a prefilled email draft to [support@inkeep.com](mailto:support@inkeep.com) that you send yourself, naming the zip to attach. Only when an operator has configured a bug-report intake endpoint ([`OK_BUG_REPORT_INTAKE_URL`](https://openknowledge.ai/docs/reference/configuration#environment-variables)) does Send upload the zip there, falling back to the same email draft if the upload fails |
| Uninstall feedback         | Only after an uninstall has already succeeded, and only if you pick a reason, type a note, or give an email address on the optional "Before you go" screen (desktop app) or prompt (`ok uninstall`). Skipping it, `--yes`, `--json`, and any non-interactive run send nothing                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `openknowledge.ai/api/feedback` — the reason you picked, your note, your email address if you gave one, plus the app version and platform. It files one ticket for the team; nothing from your notes is included                                                                                                                                                                                                                                         |
| Semantic search embeddings | Only when you enable [semantic search](https://openknowledge.ai/docs/reference/configuration#semantic-search) **and** set a key — off by default                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | Your configured embeddings provider (OpenAI by default)                                                                                                                                                                                                                                                                                                                                                                                                  |
| GitHub sync / share        | When you sync, clone, publish, or share                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | GitHub                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| Update check               | Automatically — on launch and periodically while the desktop app runs                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | OpenKnowledge update service (`openknowledge.ai/updates`), which redirects to GitHub; sends the app version and channel                                                                                                                                                                                                                                                                                                                                  |
| First-launch share check   | Once, automatically, on the desktop app's first launch                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | `openknowledge.ai/continue` — redeems a share link if you installed from one                                                                                                                                                                                                                                                                                                                                                                             |
| Skill-install report       | Automatically, the first time a published skill is actually installed — OpenKnowledge's own built-in and starter-pack skills, and skills you install from a skills.sh listing. Counted once per skill for machine-wide installs (the built-in bundles), and once per skill **per project** for skills installed into a project, since each project gets its own copy in its own editor dirs. Never re-sent on a re-run, a reinstall, an app launch, or reopening a project — an app launch that installs nothing sends nothing. A skill you install by typing a **third-party** repository yourself is **not** reported, and neither is a private, local, or internal-hostname source. A skill bundle you switched off is never installed and so is never reported                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `add-skill.vercel.sh` — the skill's name, its source repository, and which agent tools it was installed for. No file contents. This is what gives a published skill an accurate install count. Turn it off in **Settings → Preferences**, by setting `telemetry.skillInstallReports.enabled: false`, or with `DO_NOT_TRACK=1` / `DISABLE_TELEMETRY=1`                                                                                                    |
| npm/`npx` fetch            | During install (CLI)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | The npm registry                                                                                                                                                                                                                                                                                                                                                                                                                                         |

Local diagnostic logs and telemetry are scrubbed of credential-bearing attributes and never sent anywhere on their own.

## See also

- [Configuration](https://openknowledge.ai/docs/reference/configuration): Every config key and environment variable, including the telemetry and semantic-search settings referenced here.
- [CLI & web app](https://openknowledge.ai/docs/reference/cli): Install the CLI, run `ok init` / `ok start`, and the commands behind these writes.
- [Ignore patterns](https://openknowledge.ai/docs/features/ignore-patterns): Control which files the editor, search, and agents can see.
- [Core Concepts](https://openknowledge.ai/docs/reference/core-concepts): Why the file system is the database, and how attribution works.

# Authentication (https://openknowledge.ai/docs/remote-control/authentication)

Control who can reach your knowledge base.

## How access works

Add access control in front of the server. The browser and MCP clients use different authentication methods:

- **Browser users** sign in with Google or your SSO. The browser sends a session cookie with editor and live collaboration requests.
- **AI agents** connect to `/mcp` without a browser. Use a token or an MCP-aware OAuth proxy. OAuth proxies can authenticate agents through [dynamic client registration](https://datatracker.ietf.org/doc/html/rfc7591) or [client ID metadata documents](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/). See [MCP-native edges](https://openknowledge.ai/docs/remote-control/authentication#mcp-native-edges).

For a public deployment, protect the editor with a browser login. Protect `/mcp` with a token or MCP-compatible OAuth.

Add access control to the tunnel, private network, or proxy in front of the server. Use a tunnel or private network with the [CLI method](https://openknowledge.ai/docs/remote-control/methods/cli). Use a proxy with the [Docker method](https://openknowledge.ai/docs/remote-control/methods/docker). A container can also run behind a protected tunnel.

## Tunnels and private networks

A tunnel or private network can restrict access before requests reach the server.

### Tailscale

`tailscale serve` limits access to devices on your tailnet. It protects both the editor and `/mcp` without a separate login or token. Use [tailnet ACLs](https://tailscale.com/kb/1018/acls) to restrict access further.

`tailscale serve` cannot reach mobile or cloud agents such as Claude on iOS or Cursor cloud because they connect from the provider's network. To support those clients, use `tailscale funnel` with access control such as [Pomerium](https://openknowledge.ai/docs/remote-control/authentication#example-pomerium).

### ngrok

ngrok can apply separate access rules by path. Use OAuth for the editor and Basic authentication for `/mcp`.

```yaml title="policy.yaml"
on_http_request:
  - expressions: ["req.url.path.startsWith('/mcp')"]
    actions:
      - type: basic-auth
        config: { credentials: ["agent:CHANGE_ME"] }
  - expressions: ["!req.url.path.startsWith('/mcp')"]
    actions:
      - type: oauth
        config: { provider: google }
```

Run the tunnel with `--traffic-policy-file policy.yaml`. Browsers get the Google login. An agent connects by sending the Basic credential as a header. See [Connect remote agents](https://openknowledge.ai/docs/remote-control/connecting-agents). Replace `CHANGE_ME` with a strong secret from `openssl rand -hex 24`. For SSO, ngrok's `--oidc` supports Okta, Azure AD, and Google Workspace.

### Cloudflare Tunnel

Pair it with [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/policies/access/). Use a login policy on the hostname for browsers and [service tokens](https://developers.cloudflare.com/cloudflare-one/identity/service-tokens/) for agents. Service tokens do not expire every hour. [Managed OAuth](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/managed-oauth/) can also provide an OAuth popup for agents. See [MCP-native edges](https://openknowledge.ai/docs/remote-control/authentication#mcp-native-edges). Managed OAuth requires your domain to use Cloudflare.

## In front of a container

Hosting platforms provide a public domain without access control. Add an authentication proxy in front of the container. Use one of these methods for agents:

- A **static bearer token** sent by every agent as a header. The recipe below uses one shared token.
- An **MCP OAuth popup**, where the agent logs in with nothing to copy, and the only path that reaches mobile and cloud connectors (Claude iOS, Cursor cloud). The [Pomerium recipe](https://openknowledge.ai/docs/remote-control/authentication#example-pomerium) below sets it up.

### Example: Caddy + oauth2-proxy

Two small proxy services sit in front of the server. **Caddy** receives public traffic. It sends `/mcp` directly to the server behind a shared bearer token. It sends everything else through **[oauth2-proxy](https://oauth2-proxy.github.io/oauth2-proxy/)**, which handles Google login for the browser. Agents do not use the browser login. Browsers do not see the agent token. Because `/mcp` bypasses oauth2-proxy, its streaming response is not interrupted by the login proxy. None of the three services needs a database.

The hostnames below are Railway's private network (`*.railway.internal`, IPv6-only). On another Docker host, swap them for your compose service names.

**Caddy** receives public traffic. Include its configuration in the image because managed platforms may not support mounting a file:

```dockerfile title="Dockerfile (caddy)"
FROM caddy:2
COPY Caddyfile /etc/caddy/Caddyfile
```

```text title="Caddyfile"
:{$PORT} {
	# Agents use a shared bearer token.
	@mcp path /mcp /mcp/*
	handle @mcp {
		route {
			@bad not header Authorization "Bearer {env.MCP_TOKEN}"
			respond @bad "Unauthorized" 401
			reverse_proxy ok.railway.internal:8080 {
				header_up Host {host}
				header_up X-Forwarded-Proto https
			}
		}
	}
	# Everyone else: through the Google login.
	handle {
		reverse_proxy oauth2-proxy.railway.internal:4180 {
			header_up Host {host}
			header_up X-Forwarded-Proto https
		}
	}
}
```

Set `MCP_TOKEN` on the Caddy service to a value from `openssl rand -hex 24`. Agents send this value when they connect.

**oauth2-proxy** runs the browser login. Use the `quay.io/oauth2-proxy/oauth2-proxy:v7` image with your own Google OAuth client (a **Web application** client whose redirect URI is `https://<your-domain>/oauth2/callback`):

```bash title="oauth2-proxy service environment"
OAUTH2_PROXY_PROVIDER=google
OAUTH2_PROXY_CLIENT_ID=<your-google-client-id>
OAUTH2_PROXY_CLIENT_SECRET=<your-google-client-secret>
OAUTH2_PROXY_REDIRECT_URL=https://<your-domain>/oauth2/callback
OAUTH2_PROXY_EMAIL_DOMAINS=example.com
OAUTH2_PROXY_COOKIE_SECRET=<openssl rand -hex 16>
OAUTH2_PROXY_UPSTREAMS=http://ok.railway.internal:8080/
OAUTH2_PROXY_HTTP_ADDRESS=[::]:4180
OAUTH2_PROXY_WHITELIST_DOMAINS=<your-domain>
OAUTH2_PROXY_REVERSE_PROXY=true
OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
OAUTH2_PROXY_FLUSH_INTERVAL=100ms
```

**The knowledge-base service** takes the usual two declarations plus the IPv6 bind, and no public domain of its own (Caddy is the only way in):

```bash title="knowledge-base service environment"
OK_ALLOW_EXTERNAL=1
OK_EXTERNAL_URL=https://<your-domain>
OK_BIND=::
```

An agent then connects with the token and no browser step:

```bash
--header "Authorization: Bearer <MCP_TOKEN>"
```

Four settings decide whether this boots the first time:

- **`OAUTH2_PROXY_WHITELIST_DOMAINS` must list your public domain.** Behind Caddy, oauth2-proxy builds its post-login redirect from the forwarded host and refuses any host not on this list, so without it the login silently loops back to itself.
- **`OAUTH2_PROXY_COOKIE_SECRET` must decode to 16, 24, or 32 bytes.** Use `openssl rand -hex 16` to create a valid 32-character value. Do not use `openssl rand -base64 32` because it creates 44 characters and oauth2-proxy will not start.
- **Caddy must send `X-Forwarded-Proto: https`** using the two `header_up` lines above. TLS ends at the platform edge, so Caddy uses plain HTTP between the edge and the server. Without this header, the server gives the editor a `ws://` connection. The browser blocks it as mixed content on an `https` page and the editor does not load. oauth2-proxy forwards the header automatically. Set `OAUTH2_PROXY_REVERSE_PROXY=true` so oauth2-proxy trusts forwarded headers for client IP addresses and post-login redirects. Set `FLUSH_INTERVAL=100ms` to follow the [never-buffer rule](https://openknowledge.ai/docs/remote-control/methods/docker#rules-for-anything-in-front-of-the-server).
- **Bind to IPv6 where the platform's private network requires it.** Railway's is IPv6-only, so `OK_BIND=::` and `HTTP_ADDRESS=[::]:4180`, and Caddy dials the `*.railway.internal` names.

### Example: Pomerium

Use this setup when mobile or cloud agents need their own login. Examples include Claude on iOS and Cursor cloud. Each agent gets its own OAuth token, so there is no shared secret or tailnet membership.

This setup has two layers. A public access method such as ngrok, Cloudflare, or the Caddy example above provides the **public endpoint**. [Pomerium](https://www.pomerium.com/) adds **per-user OAuth**. It provides OAuth for `/mcp` and a cookie login for the editor from one configuration.

Claude on iOS and Cursor's cloud agents connect from the provider's cloud, not from your device. The endpoint must be publicly reachable. A phone cannot use the `mcp-remote` fallback and is not on your tailnet. `tailscale serve` cannot reach it. You can use the public `tailscale funnel` with Pomerium instead.

```text
Internet  (Claude / Cursor cloud dial in from here)
   |  https://<host>/ (editor)   +   https://<host>/mcp (agents)
   v  Tailscale funnel: public TLS on your *.ts.net name
   v  Pomerium: MCP OAuth on /mcp, cookie session for the browser editor
   v  OpenKnowledge: loopback, told its public externalUrl
```

**OpenKnowledge** runs on loopback, told its public origin. Pomerium is the only way in, so the server keeps no exposure of its own:

```bash
OK_ALLOW_EXTERNAL=1 ok start -p 24550 --no-open-browser --idle-shutdown off \
  --external-url https://<host>
```

The "external access enabled, no server-side auth" banner is expected here: Pomerium is the auth, and the server stays on loopback.

**Pomerium** runs as a container with a persistent databroker, so sessions and tokens survive a restart. It needs your own Google OAuth client (a **Web application** client whose redirect URI is `https://<host>:8443/oauth2/callback`), plus two secrets and a signing key you generate locally:

```bash
# Run this to write .env. The $(...) expressions execute now.
cat > .env <<EOF
SHARED_SECRET=$(openssl rand -base64 32)
COOKIE_SECRET=$(openssl rand -base64 32)
SIGNING_KEY=$(openssl ecparam -genkey -name prime256v1 -noout | base64 | tr -d '\n')
IDP_PROVIDER=google
IDP_CLIENT_ID=<your-google-client-id>
IDP_CLIENT_SECRET=<your-google-client-secret>
EOF
```

Then edit `.env` and fill in `IDP_CLIENT_ID` and `IDP_CLIENT_SECRET` from your Google client.

```yaml title="config.yaml"
address: ":8080"
autocert: false

# Persistent databroker. The default `memory` store loses every session and
# token on restart, which surfaces as reconnect prompts and an OAuth login loop.
databroker_storage_type: file
databroker_storage_connection_string: file:///data/databroker

# Claude uses client ID metadata documents (CIMD). Cursor uses
# dynamic client registration (DCR). Enable both.
runtime_flags:
  mcp_dynamic_client_registration: true
mcp_allowed_client_id_domains:
  - 'claude.ai'
  - 'claude.com'
  - 'anthropic.com'
  - '*.anthropic.com'

authenticate_service_url: https://<host>:8443

routes:
  # Agents: the MCP endpoint, path-scoped to /mcp.
  - from: https://<host>
    to: http://host.docker.internal:24550
    name: OpenKnowledge MCP
    prefix: /mcp
    preserve_host_header: true
    mcp:
      server:
        path: /mcp
    policy: { allow: { and: [ { email: { is: you@example.com } } ] } }

  # People: the editor and everything else, behind the Google cookie login.
  - from: https://<host>
    to: http://host.docker.internal:24550
    name: OpenKnowledge UI
    preserve_host_header: true
    allow_websockets: true
    policy: { allow: { and: [ { email: { is: you@example.com } } ] } }
```

```yaml title="docker-compose.yaml"
services:
  pomerium:
    image: pomerium/pomerium:main
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./config.yaml:/pomerium/config.yaml:ro
      - pomerium-data:/data
    ports:
      - "127.0.0.1:8085:8080"
    extra_hosts:
      - "host.docker.internal:host-gateway"
volumes:
  pomerium-data:
```

Start it with `docker compose up -d`. The `pomerium/pomerium:main` tag is required because Pomerium's MCP support is not yet in a stable release.

On Linux without Docker Desktop, `host.docker.internal` may not reach a server bound to loopback. `host-gateway` resolves to the Docker bridge IP instead of `127.0.0.1`. Run Pomerium with `--network host`, or bind the server to the bridge gateway and restrict it with a firewall rule.

**Tailscale funnel** makes it public on your `*.ts.net` name (MagicDNS, HTTPS, and Funnel must be enabled for the node):

```bash
tailscale funnel --bg --https=443  https+insecure://127.0.0.1:8085
tailscale funnel --bg --https=8443 https+insecure://127.0.0.1:8085
```

Then point a custom MCP connector in Claude (iOS or Desktop) or Cursor at `https://<host>/mcp` and complete the Google login. [Connect remote agents](https://openknowledge.ai/docs/remote-control/connecting-agents) has the per-client steps.

The settings that are easy to miss:

- **Two routes, not one.** A single `mcp: server` route answers the browser with "This is an MCP route." Split `/mcp` (agents) from the catch-all (editor).
- **`allow_websockets: true`** on the editor route, or the editor loads but never syncs (the `/collab` socket is blocked).
- **Set `preserve_host_header: true`.** This sends the public host to the server as its `externalUrl`. Without it, every request returns `403`.
- **Enable both DCR and CIMD.** `mcp_dynamic_client_registration: true` supports Cursor. `mcp_allowed_client_id_domains` supports Claude. Pomerium fetches Claude's `client_id` URL and checks it against this list.
- **A persistent databroker** (above), or a restart drops every token.

### MCP-native edges

Alternatives to the Pomerium recipe above, as pointers rather than full recipes:

- **[obot mcp-oauth-proxy](https://github.com/obot-platform/mcp-oauth-proxy)**: an MCP-aware OAuth proxy that implements dynamic client registration, the flavor today's clients (Cursor among them) speak.
- **[Cloudflare Managed OAuth](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/managed-oauth/)** provides the MCP OAuth popup at Cloudflare's edge. It works with the [Access](https://openknowledge.ai/docs/remote-control/authentication#tunnels-and-private-networks) login for the UI and requires your domain to use Cloudflare.

## Troubleshooting

Common symptoms and their fixes:

- **The editor loads but never syncs, and the browser console shows a `ws://` "Mixed Content" error.** The proxy terminates TLS but reports the request to the server as plain HTTP, so the server hands the editor an insecure `ws://` socket. Make the proxy forward `X-Forwarded-Proto: https` (Caddy: `header_up X-Forwarded-Proto https`).
- **Every request returns `403` with a problem-JSON `"title"` of `Host header not allowed.`.** The request `Host` does not match `OK_EXTERNAL_URL`. Configure the proxy to preserve the client's `Host`. For Caddy, use `header_up Host {host}`. Set `OK_EXTERNAL_URL` to the public origin. See the full [proxy rules](https://openknowledge.ai/docs/remote-control/methods/docker#rules-for-anything-in-front-of-the-server).
- **Every request returns `403` with a problem-JSON `"title"` of `Proxied request refused: ...`.** The proxy stamps forwarding headers (`X-Forwarded-For`, `Forwarded`, `X-Forwarded-Proto`, ...) but the server has not consented to external exposure. Set BOTH `OK_EXTERNAL_URL` to the public origin and `OK_ALLOW_EXTERNAL=1` — tolerance for forwarding headers requires the pair, and `OK_EXTERNAL_URL` alone is not enough. `OK_ALLOW_EXTERNAL` consents to exposing a server with no authentication of its own, so only set it behind an authenticating edge.
- **The browser login returns to the login page.** oauth2-proxy is rejecting the post-login redirect because the domain is not allowed. Add the public domain to `OAUTH2_PROXY_WHITELIST_DOMAINS`.
- **An agent gets an HTML login page back from `/mcp`.** The `/mcp` path is behind the browser login instead of a token. Route `/mcp` around the login proxy and gate it with a token, or if you use an MCP-OAuth edge, confirm the client supports it (see [Connect remote agents](https://openknowledge.ai/docs/remote-control/connecting-agents)).
- **`/mcp` hangs or times out.** A proxy is buffering the streamed response. Turn off buffering for `/mcp`. In nginx, set `proxy_buffering` to `off`. For oauth2-proxy, set `FLUSH_INTERVAL=100ms`. Caddy streams by default.

Once auth is in place, [Connect remote agents](https://openknowledge.ai/docs/remote-control/connecting-agents) covers how each client presents its credential.

# Connect remote agents (https://openknowledge.ai/docs/remote-control/connecting-agents)

Connect agents running on another machine or in the cloud to your OpenKnowledge project.

Connect agents on another computer or in a cloud service to the OpenKnowledge project on your server. They connect through the project's remote MCP endpoint and do not need a local copy of the knowledge base.

> **Info**
>
> If the agent runs on the same machine as your OpenKnowledge project, follow the [Quickstart](https://openknowledge.ai/docs/get-started/quickstart). `ok init` registers supported local agents for you. This page is for agents connecting over the network.

## Before you start

Set up a running server with [remote control](https://openknowledge.ai/docs/remote-control/overview). Every remote agent connects to the same address, `https://<your-external-url>/mcp`, whether the server runs on your laptop, a VPS, or in a container.

OpenKnowledge does not authenticate callers. The tunnel, private network, or proxy in front of the server controls access. Configure it first. Then give the agent any required network access or credentials. See [Authentication](https://openknowledge.ai/docs/remote-control/authentication).

## Connect agents on another machine

Run this on the machine where the agents are installed. [`add-mcp`](https://github.com/neon-solutions/add-mcp), a community tool, detects supported agents and writes each one's MCP config for you (Claude Code, Cursor, Codex, VS Code, Windsurf, Goose, Zed, and more):

```bash
npx add-mcp https://<your-external-url>/mcp --name ok-remote
```

If your `/mcp` endpoint is behind a token at the edge (a **bearer token** or **Basic Auth** from your [Authentication](https://openknowledge.ai/docs/remote-control/authentication) setup), pass it as a header so the agent connects with no browser step:

```bash
npx add-mcp https://<your-external-url>/mcp --name ok-remote \
  --header "Authorization: Bearer <your-token>"
```

For Basic Auth, the header is `Authorization: Basic $(printf 'user:pass' | base64)` instead.

A headless agent cannot complete a standard browser login. Protect `/mcp` with a token, a [path-specific access policy](https://openknowledge.ai/docs/remote-control/authentication#tunnels-and-private-networks), or Tailscale.

The exception is an [MCP-native edge](https://openknowledge.ai/docs/remote-control/authentication#mcp-native-edges), such as Pomerium. It lets the agent sign in through its own browser popup with no token to set. Pomerium serves CIMD by default for Claude. Enable `mcp_dynamic_client_registration` to add DCR support for Cursor and `mcp-remote`. The [Pomerium recipe](https://openknowledge.ai/docs/remote-control/authentication#example-pomerium) turns both on.

The per-client setups below use a plain `/mcp` URL. If yours is behind a token, add the same `Authorization` header shown above (details in [Authentication](https://openknowledge.ai/docs/remote-control/authentication)).

## Claude Code

```bash
claude mcp add --transport http ok-remote https://<your-external-url>/mcp
```

## Cursor

```json title="~/.cursor/mcp.json"
{
  "mcpServers": {
    "ok-remote": { "url": "https://<your-external-url>/mcp" }
  }
}
```

## Codex

Recent Codex versions take the URL directly:

```toml title="~/.codex/config.toml"
[mcp_servers.ok-remote]
url = "https://<your-external-url>/mcp"
```

## Claude Desktop

Claude Desktop only takes command-type servers, so it reaches the server through the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge, a local process that forwards to your `/mcp` URL.

Its config file is `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS and `%APPDATA%\Claude\claude_desktop_config.json` on Windows. In Claude Desktop open **Settings → Developer → Edit Config**, which creates the file and reveals it. Then add:

```json title="claude_desktop_config.json"
{
  "mcpServers": {
    "ok-remote": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://<your-external-url>/mcp"]
    }
  }
}
```

If your `/mcp` is behind a token, add it to the `args` array: `"--header", "Authorization: Bearer <your-token>"`.

## Agent harnesses (OpenClaw, Hermes, etc.)

Add the `/mcp` URL to your harness's MCP server config, the same way as above. Its agents can then read and write your knowledge base.

## Cloud-hosted agents and connectors (Claude.ai, ChatGPT, etc.)

Cloud-hosted agents and connectors reach the MCP endpoint from the provider's cloud. The URL must be publicly reachable over HTTPS. The Claude and ChatGPT connectors require an MCP-aware OAuth service in front of `/mcp`. The [Pomerium recipe](https://openknowledge.ai/docs/remote-control/authentication#example-pomerium) has been verified with the Claude connector on iOS and Desktop and with Cursor's cloud agents. Support varies by provider.

Connectors that accept a bearer token or API key can use a token enforced at the edge. This includes Microsoft Copilot Studio and Perplexity. Protect `/mcp` with a static bearer token, then add that token to the connector's settings.

Claude Desktop is not a cloud connector. It uses the `mcp-remote` bridge covered above.

# Overview (https://openknowledge.ai/docs/remote-control/overview)

Set up remote access to your OpenKnowledge server.

By default, OpenKnowledge is only available on the machine where it runs. Remote access lets your other devices, teammates, and agents connect to one shared project without installing OpenKnowledge on every machine.

The setup has four parts: run the server, make it reachable, add access control, then connect through the browser or MCP.

## Run the server

There are three ways to run an OpenKnowledge server:

- [**Desktop**](https://openknowledge.ai/docs/get-started/quickstart#install-the-desktop-app-macos-windows-linux)
- [**CLI**](https://openknowledge.ai/docs/remote-control/methods/cli)
- [**Docker**](https://openknowledge.ai/docs/remote-control/methods/docker)

## Make the server reachable

Choose the instructions that match where your server runs:

| Your setup                      | What to follow                                                                                                                                                                                                                                               |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Desktop or CLI on your computer | [Connect the computer through a private network or tunnel](https://openknowledge.ai/docs/remote-control/methods/cli#your-laptop-behind-a-tunnel). If Desktop already runs the project, skip the CLI installation step. The networking settings are the same. |
| CLI on an always-on machine     | [Set up a server you manage](https://openknowledge.ai/docs/remote-control/methods/cli#a-server-you-manage).                                                                                                                                                  |
| Docker                          | [Deploy the container](https://openknowledge.ai/docs/remote-control/methods/docker).                                                                                                                                                                         |

Each method provides an HTTPS URL and the required configuration. See the [configuration reference](https://openknowledge.ai/docs/reference/configuration) for all available settings.

## Add access control

> **Warn**
>
> **Never expose OpenKnowledge directly to the public internet.** Use a private network, or put authentication in front of the server before opening or sharing a public URL.

OpenKnowledge does not have built-in user accounts yet. Everyone who can reach the server has full read and write access as the same owner.

A private network such as Tailscale can be the access-control layer. A public URL needs an authentication edge or proxy in front of it. Follow [Authentication](https://openknowledge.ai/docs/remote-control/authentication) before continuing.

## Try it out

Once the server is reachable and protected, the same URL provides both connection points.

Open the editor in a browser:

```text
https://<your-external-url>/
```

Connect agents to the MCP endpoint:

```text
https://<your-external-url>/mcp
```

Open the browser URL first to confirm the editor loads. Then follow [Connect remote agents](https://openknowledge.ai/docs/remote-control/connecting-agents) for Claude, Cursor, Codex, and other MCP clients. Give teammates the browser URL plus any network access or credential required by your access-control setup.

## Turn off remote access

Remove `allowExternal: true` from `.ok/local/config.yml`, or remove `OK_ALLOW_EXTERNAL=1`, then restart OpenKnowledge. The Docker setup on this site uses `OK_BIND=0.0.0.0`, so the container will refuse to start without external-access consent. Stop the tunnel or disable the public domain to close network access. If a public URL was exposed, regenerate it or rotate the domain at your tunnel or hosting provider.

# Codebase wiki (https://openknowledge.ai/docs/workflows/codebase-wiki)

An AI reads your code and writes a plain-English guide to it: how it's built, how the pieces fit, with diagrams and links back to the real files. The guide lives right in your repo, stays private, and helps the next person (or AI) get up to speed fast. Built on the Codebase wiki starter pack.

A **codebase wiki** is an agent-authored map of a codebase — architecture, modules, key flows, and domain concepts — written as navigable, diagram-rich markdown that lives *in the repo*. Think DeepWiki, but the pages are files you own: version-controlled, diffable, private by default, human and agent co-editable, and doubling as durable grounding context for every future agent session.

```html preview
<div style="padding:18px">
  <div id="loop" style="display:flex;flex-wrap:wrap;gap:10px;align-items:stretch"></div>
  <div class="cap">The agent reads source with native tools, then authors the wiki with OpenKnowledge — diagrams, cross-links, and source references, one section at a time.</div>
</div>
<style>
#loop .card{flex:1;min-width:150px;border:1px solid var(--border);border-radius:12px;padding:12px 14px;background:var(--card);transition:box-shadow .3s,border-color .3s;cursor:pointer}
#loop .card .t{font-weight:600;font-size:13px}
#loop .card .s{color:var(--muted-foreground);font-size:11.5px;margin-top:3px;line-height:1.35}
#loop .card.on{border-color:var(--primary);box-shadow:0 0 0 3px var(--accent-soft)}
#loop .card.on .t{color:var(--accent-ink)}
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px}
.cap::before{content:"\21BB  ";color:var(--primary);font-weight:700}
@media (prefers-reduced-motion:reduce){#loop .card{transition:none}}
</style>
<script>
var steps=[["1 · Survey","agent reads the repo: entry points, packages, dependency shape"],["2 · Author","writes architecture / modules / flows / concepts as linked pages + mermaid diagrams"],["3 · Ground","every claim references a real file it read — no invented paths"],["4 · Refresh","on the next run, diffs the commit range and updates only what changed"]];
var loop=document.getElementById("loop"),cards=[];
steps.forEach(function(s,i){
  var d=document.createElement("div");d.className="card";
  var t=document.createElement("div");t.className="t";t.textContent=s[0];
  var sub=document.createElement("div");sub.className="s";sub.textContent=s[1];
  d.appendChild(t);d.appendChild(sub);
  d.onclick=function(){pinned=true;set(i);};
  loop.appendChild(d);cards.push(d);
});
var cur=0,pinned=false;
function set(i){cur=i;cards.forEach(function(n,j){n.classList.toggle("on",j===i);});}
set(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){setInterval(function(){if(!pinned)set((cur+1)%cards.length);},1600);}
</script>
```

There's no separate Q\&A product bolted on. Q\&A *is* your OpenKnowledge-grounded agent plus [search](https://openknowledge.ai/docs/reference/agentic-search): once the wiki exists, any agent session can read it back, follow its backlinks, and answer "where do I change X?" from pages that cite real source files.

New joiners get a navigable map before they touch anything, ramping in an afternoon instead of reverse-engineering over months. Maintainers generate the architecture once and refresh it as code moves, so they stop re-explaining it to every new contributor. Agent-assisted developers keep the wiki as grounding context, so future sessions read it first instead of rediscovering the same structure. And for OSS, it doubles as a public architecture guide with GitHub-linked sources and no secrets — contributors navigate the project without you.

## The shape

```
wiki/
  OVERVIEW.md     hub: what it is, a big-picture architecture diagram, a nav map to every section
  log.md          append-only generation / refresh audit trail
  architecture/   system boundaries, layers, subsystems, cross-cutting concerns + diagrams
  modules/        one page per package / module: purpose, entry points, key files, deps
  flows/          key end-to-end flows as sequence / flow diagrams + narrative
  concepts/       glossary: atomic pages for domain terms and core abstractions
  guides/         task-oriented "how / where do I change X" walkthroughs
```

Every section links to the others: a flow page links every module it crosses; a module page links the concepts it implements. That dense cross-linking is what makes the wiki navigable — each concept page becomes a hub for everywhere it appears.

## Two knobs: audience and depth

You don't configure the wiki in a settings file — you say what you want in plain language, and the choice is recorded in `OVERVIEW.md` frontmatter so refreshes stay consistent.

| Knob         | Values                           | Default    | What it changes                                                                                                                                                       |
| ------------ | -------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **audience** | `internal`, `public`             | `internal` | `public` means polished prose, no secrets or internal infra or ticket numbers, and GitHub-URL source references when the repo has a remote (relative paths otherwise) |
| **depth**    | `tour`, `standard`, `exhaustive` | `standard` | Scales coverage from OVERVIEW + architecture + top flows, up through per-package module pages, concepts, and task guides                                              |

So "build the wiki, public and exhaustive" and "just give me a quick internal tour" produce very different artifacts from the same pack.

## The scenario

You've inherited a 200-file TypeScript service and have to add a feature this week. Instead of grepping blind:

> Build a codebase wiki for this repo. Internal audience, standard depth.

The agent surveys the repo, confirms its proposed page list with you, writes `OVERVIEW.md` with a top-level architecture diagram, then fills `architecture/`, `modules/`, `flows/`, and `concepts/` — architecture and flow pages carrying mermaid diagrams, every page linking to the specific files it read. An hour later you open `flows/request-lifecycle.md`, see the sequence diagram, follow the link into `modules/auth.md`, and know exactly where your feature slots in. Next week, after you've merged, `refresh` updates only the pages your diff touched.

## What's in your project after seeding

Pick **Codebase wiki** in the starter-pack picker, or run:

```bash
ok seed --pack codebase-wiki
```

The pack scaffolds the `wiki/` tree above with a stubbed `OVERVIEW.md` (empty `source_commit`), a starter template per section (`architecture-page`, `module-page`, `flow-page`, `concept-page`, `guide-page`), and `wiki/log.md`. Each folder's `.ok/frontmatter.yml` teaches the agent that section's job, so guidance lives next to the action rather than inside every page.

> **Seeding also installs a skill**
>
> `ok seed --pack codebase-wiki` installs the **Codebase wiki** project skill into your agent editors (Claude Code, Cursor, Codex, OpenCode). It's the "how to work here" guidance behind the generate / refresh procedure, the audience and depth knobs, and the source-reference discipline — read automatically, and editable like any other doc. It lands as a real `SKILL.md` committed to your repo — one source folder in your project's skills directory (`.agents/skills/` when you have one, otherwise your editor's, e.g. `.claude/skills/`), copied into the skills directory of each editor already set up for the project. The copies refresh from the source until you hand-edit one. See [Skills](https://openknowledge.ai/docs/features/skills) and [what OpenKnowledge writes to disk](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

## Generate, then refresh

Don't ask the agent to free-hand it. The pack drives generation through a phased, guided workflow that auto-detects which mode you're in:

> Generate the codebase wiki.

To update it later, once it exists:

> Refresh the codebase wiki.

Either prompt works — the pack auto-detects which mode to run from `source_commit` in `OVERVIEW.md`:

- **Generate** (a stubbed `OVERVIEW.md`): survey → overview → architecture → modules → flows → concepts → a final link-graph audit that catches orphans and dead links. It stops twice for your confirmation — first the knobs and coverage, then the proposed page list — before writing anything.
- **Refresh** (a stamped `OVERVIEW.md`): diff the recorded `source_commit` against current `HEAD`, update only the affected pages, and re-stamp.

`OVERVIEW.md` carries `source_commit` — the git commit the wiki was last built against. It's the freshness anchor: refresh diffs from it, so the wiki tracks the code instead of drifting into fiction.

> **Info**
>
> **Two toolsets, on purpose.** The agent reads *source code* with native file tools (it's not markdown, so OpenKnowledge doesn't index it) and authors the *wiki* with OpenKnowledge — so every page lands as a real CRDT document with attribution and history. Source references are grounded: the agent links only files it actually read, never invented paths.

## Cadence

| When                      | Do                                                                                                                        |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| First arrival at a repo   | Generate at `standard` depth; skim `OVERVIEW.md` and the architecture pages                                               |
| Onboarding someone        | Point them at the wiki; regenerate at `exhaustive` if they need sub-module pages, task guides, and per-flow failure modes |
| After a significant merge | Refresh — the agent updates only the pages the diff touched and re-stamps `source_commit`                                 |
| Going public              | Regenerate with `audience: public` for GitHub-linked, secret-free prose                                                   |

## Further reading

- **[Software lifecycle workflow](https://openknowledge.ai/docs/workflows/software-lifecycle).** The proposals → decisions → specs the wiki's code implements.
- **[LLM wiki workflow](https://openknowledge.ai/docs/workflows/karpathy-llm-wiki).** Same source-grounded discipline, aimed at external sources instead of your own code.
- **[Agentic search](https://openknowledge.ai/docs/reference/agentic-search).** How an agent retrieves across the wiki — search, grep, and backlinks over live files, no vector database.
- **[Agent activity](https://openknowledge.ai/docs/features/agent-activity).** Every wiki page write, attributed and diffable.

# Entity vault (GBrain-compatible) workflow (https://openknowledge.ai/docs/workflows/entity-vault)

A personal memory for the people, companies, and meetings in your world. Each one gets its own page that an AI keeps up to date as you learn more, and you can correct anything by hand. Works with Garry Tan's gbrain if you use it, but doesn't need it.

An **Entity vault (GBrain-compatible Markdown)** is a Markdown brain organized around typed dossiers: people, companies, meetings, concepts, originals, and media. Each person, company, and concept dossier has two zones:

1. **Compiled truth** — the current synthesis, rewritten as evidence changes.
2. **Timeline** — append-only evidence bullets, dated and attributable.

Meetings, originals, and media stay raw records.

OpenKnowledge scaffolds and edits that Markdown. Garry Tan's [`gbrain`](https://github.com/garrytan/gbrain), if you install it, can import/sync the same vault and add its DB-backed retrieval, graph extraction, embedding, and automation. OK does **not** replace `gbrain`; it gives GBrain-style Markdown brains a human cockpit for review, correction, attribution, and Git-visible edits.

```html preview
<div style="padding:18px">
  <div id="dv" style="max-width:430px;margin:0 auto;border:1px solid var(--border);border-radius:12px;overflow:hidden;background:var(--card)">
    <div style="padding:10px 14px;border-bottom:1px solid var(--border);font-weight:600;font-size:13px">Jane Founder <span style="color:var(--muted-foreground);font-weight:400">· person</span></div>
    <div style="padding:11px 14px">
      <div style="font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted-foreground)">Compiled truth</div>
      <div id="dv-truth" style="font-size:12.5px;margin-top:4px;line-height:1.45"></div>
    </div>
    <div style="padding:11px 14px;border-top:1px dashed var(--border)">
      <div style="font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted-foreground)">Timeline (append-only)</div>
      <div id="dv-tl" style="margin-top:6px;display:flex;flex-direction:column;gap:5px"></div>
    </div>
  </div>
  <div class="cap">Two zones per person, company, or concept dossier: a compiled truth the agent rewrites as evidence changes, and a timeline it only ever appends to.</div>
</div>
<style>
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px;text-align:center}
.cap::before{content:"\1F9E0  ";font-weight:700}
#dv-tl .b{font-size:11.5px;color:var(--muted-foreground);border-left:2px solid var(--accent-soft);padding-left:8px;animation:dvin .4s ease}
@keyframes dvin{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
</style>
<script>
var truths=["Co-founder and CEO of Jane Co.","Co-founder and CEO of Jane Co. Ex-profiler; sharp on cost-per-token economics.","Co-founder and CEO of Jane Co. Ex-profiler; sharp on cost-per-token economics. Go-to-market still forming."];
var bullets=["2026-05-12 · coffee — described the observability wedge","2026-05-13 · agent enrichment — prior OSS profiler work","2026-05-20 · intro from Alex — GTM still developing"];
var truth=document.getElementById("dv-truth"),tl=document.getElementById("dv-tl");
function render(i){truth.textContent=truths[i];tl.innerHTML="";for(var j=0;j<=i;j++){var b=document.createElement("div");b.className="b";b.textContent=bullets[j];tl.appendChild(b);}}
var cur=0;render(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){setInterval(function(){cur=(cur+1)%3;render(cur);},1800);}
</script>
```

Founders and investors get a second brain over **people, companies, meetings, and concepts** — a network that's queryable, not just a pile of source documents. Agents maintain the dossiers while you keep final editorial control, so wrong claims get corrected by hand rather than trusted blindly. If you already run `gbrain`, this is a visual editor layer over the Markdown it indexes; if you don't yet, the GBrain-compatible Markdown shape is portable, so adopting that engine later is never a migration.

## The division of labor

| Layer            | OpenKnowledge                                                           | Garry Tan's `gbrain`                                                  |
| ---------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- |
| Markdown files   | Creates, edits, reviews, templates, folder guidance                     | Imports/syncs as source material                                      |
| Human correction | WYSIWYG/source editor, activity attribution, version checkpoints        | Sees corrections after import/sync                                    |
| Agent writes     | OK MCP tools (`write`, `edit`, `links`, `checkpoint`, `search`, `exec`) | GBrain MCP/skills if you choose to run them separately                |
| Search/retrieval | OK project search and graph tooling                                     | PGLite/Postgres, embeddings, hybrid retrieval, graph/index automation |
| Interop contract | Plain Markdown + Git                                                    | `gbrain import` / `gbrain sync --repo`                                |

## What the pack creates

Seeding needs an initialized project — run [`ok init`](https://openknowledge.ai/docs/get-started/quickstart) first. Pick **Personal CRM** in the starter-pack picker, or run:

```bash
ok seed --pack entity-vault
```

By default the pack suggests a `vault/` subfolder and creates:

```txt
your-project/
└── vault/
    ├── USER.md
    ├── SOUL.md
    ├── ACCESS_POLICY.md
    ├── HEARTBEAT.md
    ├── log.md
    ├── people/
    ├── companies/
    ├── meetings/
    ├── concepts/
    ├── originals/
    └── media/
```

Each folder includes an `.ok/frontmatter.yml` description that agents see during file listings/searches, plus templates under `.ok/templates/`.

> **Seeding also installs a skill**
>
> `ok seed --pack entity-vault` installs the **Entity vault** project skill into your agent editors (Claude Code, Cursor, Codex, OpenCode). It's the "how to work here" guidance behind the dossier conventions — compiled-truth vs. append-only timeline, path-qualified links, the correction loop — read automatically, and editable like any other doc. It lands as a single `SKILL.md` in your repo, in your project's skills directory (`.agents/skills/` when you have one, otherwise your editor's), copied into the skills directory of each editor already set up for the project. The copies refresh from the source until you hand-edit one. See [Skills](https://openknowledge.ai/docs/features/skills) and [what OpenKnowledge writes to disk](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

## Markdown shape

A person dossier generated from the pack starts in a GBrain-compatible shape:

```markdown
---
type: person
title: Jane Founder
created: 2026-05-12
author: mike
tags: [person, founder]
---

## Compiled truth

Co-founder and CEO of [[companies/jane-co|Jane Co]]. Met through
[[people/alex-seed-investor|Alex Seed Investor]]. Strong on cost-per-token
economics; go-to-market is still developing.

--- timeline ---

## Timeline

- **2026-05-12** | [[meetings/2026-05-12-jane-founder-coffee|coffee meeting]] | @mike — Jane described Jane Co's agent-runtime observability wedge. Confidence: direct note.
- **2026-05-13** | agent enrichment | @agent — Public GitHub profile confirms prior OSS profiler work. Confidence: external profile.
```

Compatibility details:

- Use document frontmatter `title:` and `type:`.
- Prefer path-qualified wikilinks where identity matters: `[[people/jane-founder|Jane Founder]]`, `[[companies/jane-co|Jane Co]]`.
- Keep the compiled-truth section rewritable.
- Separate compiled truth from the timeline with a `--- timeline ---` sentinel line.
- Keep timeline entries append-only and dated: `- **YYYY-MM-DD** | source | @author — event. Confidence: ...`.

## Get meetings in from a recorder

OpenKnowledge does not record meetings. Bring any recorder that speaks MCP, and each meeting lands in `meetings/` as markdown, ready for the loop below. Ask for them on demand, or have them sync automatically.

See [Ingest meetings](https://openknowledge.ai/docs/workflows/meeting-ingestion) for the recorder list, the `source` and `source_meeting_id` addressing rule, and the scheduled and event-driven options.

## Worked loop: meeting → dossiers → human correction

1. Create `meetings/2026-05-12-jane-founder-coffee.md` from the meeting template.
2. Write raw notes with path-qualified links:

```markdown
---
type: meeting
title: Jane Founder coffee
date: 2026-05-12
attendees: [Jane Founder]
tags: [meeting, ai-infra]
---

## Notes

Jane runs [[companies/jane-co|Jane Co]], a stealth AI infra company focused on
[[concepts/agent-runtime-observability|agent-runtime observability]].
Introduced by [[people/alex-seed-investor|Alex Seed Investor]].

Quote: "the agent runtime is the new kernel."
```

3. Ask your MCP-capable agent:

```txt
From meetings/2026-05-12-jane-founder-coffee.md, create or update the
referenced person, company, and concept dossiers using the Entity vault (GBrain-compatible Markdown) templates. Append dated timeline bullets. Do not rewrite existing timeline entries.
```

4. Review the agent edits in OK. If the agent inferred something wrong, correct it in the editor.
5. Commit the Markdown changes.

The core value is the correction loop: the durable memory is not hidden in a model context window or database row. It is a file you can inspect, edit, diff, and roll back.

## Interop with Garry Tan's `gbrain`

If you also run `gbrain`, point it at the same Markdown vault after OK has written the files:

```bash
gbrain import ~/your-ok-vault --no-embed
gbrain embed --stale
gbrain sync --repo ~/your-ok-vault
```

Recommended operating model:

- Use `gbrain import ... --no-embed` for the first bulk load when you want to avoid embedding during the scan.
- Run `gbrain embed --stale` after import or after any no-embed sync.
- Commit OK changes, then run `gbrain sync --repo ~/your-ok-vault` for incremental refresh.
- Keep OK as the place where humans inspect/correct the Markdown.
- Keep `gbrain` as the engine that indexes, searches, extracts graph/timeline data, and runs its own automation.

No file collision is required: OK writes Markdown; `gbrain` reads/imports/syncs that Markdown into its own configured storage. If you wire separate GBrain skills or cron jobs, treat that as a separate integration choice rather than something the Entity vault (GBrain-compatible Markdown) pack does by itself.

## Power-user demo path

For a GBrain-literate reviewer, the demo target is not "OK replaces GBrain." It is:

1. Open a GBrain-style Markdown vault in OK.
2. Let an agent write/update a dossier through OK MCP.
3. Watch the edit land live with attribution.
4. Correct a wrong claim by hand in OK.
5. Commit the changes.
6. Run `gbrain sync --repo <vault>`.
7. Query/search in `gbrain` and see the corrected fact.

Aha moment: **GBrain makes agent memory useful; OK makes the Markdown memory inspectable, correctable, and collaborative.**

## Cadence

| Cadence             | What                                                                                           |
| ------------------- | ---------------------------------------------------------------------------------------------- |
| After each meeting  | Drop raw notes into `meetings/<date>-<slug>.md`; link mentioned people/companies/concepts.     |
| End of day          | Ask an agent to create/update dossiers and append timeline bullets from new meetings.          |
| Weekly              | Audit stale dossiers, empty timelines, and compiled truth that conflicts with recent evidence. |
| Monthly             | Run OK's dead-link audit; triage new entities vs typos vs intentional placeholders.            |
| When using `gbrain` | Commit OK edits, then run `gbrain sync --repo <vault>` and `gbrain embed --stale` as needed.   |

## Naming note

The starter-pack picker presents the pack as **Personal CRM**; its CLI pack id is `entity-vault`. "Entity vault" names the portable Markdown workflow OK scaffolds. "GBrain-compatible" describes the interop contract with Garry Tan's `gbrain` without claiming ownership of his project or implying OK is a replacement engine.

## Further reading

- **[Garry Tan's gbrain](https://github.com/garrytan/gbrain).** Optional engine/index/automation layer for the same Markdown vault.
- **[Karpathy LLM wiki workflow](https://openknowledge.ai/docs/workflows/karpathy-llm-wiki).** Source-grounded counterpart to the entity-vault posture.
- **[Agent activity](https://openknowledge.ai/docs/features/agent-activity).** How OK attributes human and agent edits.
- **[Claude Code](https://openknowledge.ai/docs/integrations/claude-code)**, **[Cursor](https://openknowledge.ai/docs/integrations/cursor)**, **[Codex](https://openknowledge.ai/docs/integrations/codex).** MCP-capable agent hosts.

# LLM wiki (https://openknowledge.ai/docs/workflows/karpathy-llm-wiki)

Point an AI at a folder of your own notes and let it keep them organized, linked, and easy to search. The more you add, the tidier it gets instead of messier. Built on the Knowledge base starter pack.

An LLM wiki is a knowledge base of your own markdown files that an agent reads, writes, and keeps organized. You supply the raw material. The agent files it, links it, and cites it. It gets more organized the more you use it.

```html preview
<div style="padding:18px">
  <div id="row" style="display:flex;flex-wrap:wrap;gap:8px;align-items:stretch"></div>
  <div class="cap">Each file is a live CRDT (Yjs) document, so you and the agent can edit the same one and it converges.</div>
</div>
<style>
#row .node{flex:1;min-width:120px;border:1px solid var(--border);border-radius:12px;padding:11px 13px;background:var(--card);transition:box-shadow .3s,border-color .3s;cursor:pointer}
#row .node .t{font-weight:600;font-size:13px}
#row .node .s{color:var(--muted-foreground);font-size:11.5px;margin-top:2px}
#row .node.on{border-color:var(--primary);box-shadow:0 0 0 3px var(--accent-soft)}
#row .node.on .t{color:var(--accent-ink)}
#row .arrow{align-self:center;color:var(--muted-foreground)}
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px}
.cap::before{content:"\21BB  ";color:var(--primary);font-weight:700}
@media (prefers-reduced-motion:reduce){#row .node{transition:none}}
</style>
<script>
var steps=[["You","drop a source"],["Agent","files &middot; links &middot; cites"],["Your wiki","markdown + backlinks"],["You ask","agent reads it back"]];
var row=document.getElementById("row"),nodes=[];
steps.forEach(function(s,i){
  if(i){var a=document.createElement("div");a.className="arrow";a.textContent="→";row.appendChild(a);}
  var d=document.createElement("div");d.className="node";
  var t=document.createElement("div");t.className="t";t.textContent=s[0];
  var sub=document.createElement("div");sub.className="s";sub.innerHTML=s[1];
  d.appendChild(t);d.appendChild(sub);
  d.onclick=function(){pinned=true;set(i);};
  row.appendChild(d);nodes.push(d);
});
var cur=0,pinned=false;
function set(i){cur=i;nodes.forEach(function(n,j){n.classList.toggle("on",j===i);});}
set(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){setInterval(function(){if(!pinned)set((cur+1)%nodes.length);},1500);}
</script>
```

The difference from a folder of notes or a RAG app is who maintains it, and how it is stored:

|                 | Note pile / RAG app                 | An LLM wiki                      |
| --------------- | ----------------------------------- | -------------------------------- |
| Who organizes   | you, eventually, never              | the agent, as you go             |
| What's stored   | raw clips, or an opaque vector copy | authored markdown with structure |
| Over time       | it decays                           | it compounds                     |
| Can you read it | the notes yes, the index no         | all of it, it's just files       |

The structure you and the agent author (folders, titles, backlinks, a one-line purpose per folder) *is* the index. No second copy, nothing to keep in sync. The agent retrieves across it with [agentic search](https://openknowledge.ai/docs/reference/agentic-search) — searching, grepping, and following backlinks over your live files, no vector database — and you teach it conventions with [skills](https://openknowledge.ai/docs/features/skills) authored in the same editor as your docs.

The rest of this guide builds one end to end. The pattern is Andrej Karpathy's, from [his April 2026 gist on LLM-curated wikis](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f). OpenKnowledge ships the **Knowledge base** starter pack as a direct implementation, with one extension Karpathy doesn't formalize: **the wiki layer is split** into `research/` (provisional work stored as `status: draft`) and `articles/` (canonical knowledge stored as `status: stable`), with the `consolidate` procedure as the explicit promotion step, so premature canonicalization becomes a thing you opt into rather than something that drifts in.

The point of this guide is to show how **OpenKnowledge's features compose** to make the pattern feel native: the starter-pack picker scaffolds the layout in one click; per-folder templates + agent-readable folder frontmatter teach the LLM the conventions (Karpathy's centralized `CLAUDE.md` / `AGENTS.md` schema, distributed per-folder); the platform skill's `ingest` procedure plus the pack's `research` and `consolidate` skills cover the sources → provisional → canonical pipeline; the WYSIWYG editor + CRDT keep editing frictionless; the activity panel attributes every write; the `links` tool keeps the source graph clean. Every step uses three or four of these together. That's the product.

If you're new to LLM-curated PKM, the draw is an agent that remembers your context without the vault turning into a junk drawer of unprocessed clips — notes that compound instead of rotting. If you're already practicing Karpathy's pattern in Obsidian, plain folders, or Claude projects, the question is what OpenKnowledge does differently and what carries over.

Either way, by the end of this guide you'll have a working source-grounded knowledge base, a routine for adding sources, and a daily-driver agent setup that interrogates the vault on your behalf.

## Before you begin

You need:

- **The OpenKnowledge desktop app.** The desktop app (macOS, Windows, Linux) is the canonical surface: WYSIWYG editor, file sidebar, agent activity panel, version timeline, and the starter-pack picker all live there. Install it from the [Quickstart](https://openknowledge.ai/docs/get-started/quickstart).
- **An MCP-capable agent assistant.** [Claude Code](https://openknowledge.ai/docs/integrations/claude-code), [Cursor](https://openknowledge.ai/docs/integrations/cursor), [Codex](https://openknowledge.ai/docs/integrations/codex), [OpenCode](https://openknowledge.ai/docs/integrations/opencode), or [OpenClaw](https://openknowledge.ai/docs/integrations/openclaw). The OK desktop app's first-launch flow detects them and wires them up.
- **A read of the source pattern.** Andrej Karpathy's [LLM wiki gist](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) is the canonical description of what we're implementing here. It's a 10-minute read and worth doing first; this guide builds on it directly.

If you're brand new to OK, run the [Quickstart](https://openknowledge.ai/docs/get-started/quickstart) (≤5 minutes to first agent-driven edit) and come back here.

## The scenario

You're evaluating a new agent framework for an upcoming architecture decision. You've collected five sources:

- The framework's GitHub README
- A 12-page architecture overview from the docs site
- A skeptical Hacker News thread
- A paper on the underlying coordination model
- A Twitter thread from a maintainer responding to the HN thread

By the end of the afternoon you can ask your agent "what does the framework do when two sub-agents race on the same write?" and get an answer that cites the specific paragraph in the architecture doc, contextualized with the maintainer's clarification on Twitter, without re-reading any of the five sources yourself.

That's the payoff. Below is how you get there in OpenKnowledge.

## What's in your vault after one afternoon

```
your-project/
├── external-sources/
│   ├── framework-readme.md
│   ├── framework-architecture-overview.md
│   ├── hn-skeptical-thread.md
│   ├── coordination-model-paper.md
│   └── maintainer-twitter-thread.md
├── research/
│   └── agent-framework-evaluation.md
├── articles/                  (empty; nothing canonical yet)
└── log.md                     (append-only audit trail)
```

- **`external-sources/`.** Five `.md` files, each carrying the verbatim source text plus YAML frontmatter with `type: source`, the original URL, fetch date, and author metadata. *Immutable* after capture. The agent reads these but never edits them. (This is Karpathy's raw-sources layer; his gist leaves the folder name open.)
- **`research/`.** One provisional article synthesizing the five sources into an evaluation. Every claim cites a specific path in `external-sources/`. Status: `draft` (you can change it).
- **`articles/`.** Still empty. You consolidate to here only when you've actually decided (e.g., "yes we're adopting this framework"). Premature consolidation is how wikis go stale. (Karpathy's gist puts both provisional and canonical pages in a single wiki layer; OK splits them so the promotion step is explicit.)
- **`log.md`.** One append-only file at project root recording each ingest, research pass, and consolidation. The audit trail. (Direct from Karpathy's pattern.)

## The workflow behind it

Karpathy's pattern, summarized from the [gist](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f):

| Element         | What it is                                                                                                                      | Karpathy's filenames                                                 |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| **Raw sources** | Immutable collection of the actual documents (articles, papers, repo READMEs, transcripts). The LLM reads but does not modify.  | left open (`raw/assets/` appears only as an example attachment path) |
| **The wiki**    | LLM-generated markdown files: summaries, entity pages, concept pages, cross-references. Single layer; the LLM owns it entirely. | left open                                                            |
| **The log**     | Append-only record of ingests, queries, lint passes: *"what happened and when."*                                                | `log.md`                                                             |
| **The index**   | Content-oriented catalog listing every page with link, summary, optional metadata.                                              | `index.md`                                                           |
| **The schema**  | Config document telling the LLM how the wiki is structured.                                                                     | `CLAUDE.md` (Claude Code) or `AGENTS.md` (other hosts)               |

Two operations turn the layers into a living artifact:

- **Ingest.** When a new source arrives, the LLM reads it, extracts the key information, integrates findings into existing wiki pages, updates the index, and appends to the log. *"A single source might touch 10-15 wiki pages."*
- **Query.** When you ask a question, the LLM searches relevant pages and synthesizes an answer. *"Good answers can be filed back into the wiki as new pages. A comparison you asked for, an analysis, a connection you discovered: these are valuable and shouldn't disappear into chat history."* This is the part most people miss.

Plus periodic **lint** for contradictions, stale claims, orphan pages, missing concepts, dangling cross-references, and data gaps.

Karpathy's framing of why this matters:

> the wiki is a persistent, compounding artifact. The cross-references are already there.

## How OpenKnowledge implements (and extends) this

The **Knowledge base** pack scaffolds Karpathy's pattern with one significant addition: **the wiki layer is split** into `research/` (provisional) and `articles/` (canonical), with `consolidate` as the explicit promotion ritual.

| Karpathy                                       | OpenKnowledge                                                                                                                                                                                                                                                                                                                   |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Raw sources (verbatim, immutable)              | `external-sources/` + `clip` template + the `ingest` procedure                                                                                                                                                                                                                                                                  |
| The wiki (single LLM-owned layer)              | **Split**: `research/` (provisional, `status: draft`, sources cited) + `articles/` (canonical, `status: stable`, supersedes chain); `consolidate` promotes                                                                                                                                                                      |
| `log.md` (append-only record)                  | `log.md` at project root, written by the pack                                                                                                                                                                                                                                                                                   |
| `index.md` (static content catalog)            | Provided dynamically via `exec` (`ls`) + the file sidebar. The agent reads folder frontmatter + per-doc metadata on every list call, so a static index isn't required. You can still hand-write `index.md` if you prefer.                                                                                                       |
| `CLAUDE.md` / `AGENTS.md` (centralized schema) | `<folder>/.ok/frontmatter.yml` + per-folder templates; schema lives next to the action                                                                                                                                                                                                                                          |
| LLM agent                                      | [Claude Code](https://openknowledge.ai/docs/integrations/claude-code), [Cursor](https://openknowledge.ai/docs/integrations/cursor), [Codex](https://openknowledge.ai/docs/integrations/codex), [OpenCode](https://openknowledge.ai/docs/integrations/opencode), [OpenClaw](https://openknowledge.ai/docs/integrations/openclaw) |

When your agent lists `external-sources/`, it reads the folder description telling it to ingest verbatim and *not* analyze in those files. That's the schema layer doing its job, without you hand-writing a `CLAUDE.md` from scratch.

The templates use [OKF-compatible metadata](https://openknowledge.ai/docs/plugins/okf): `draft` and `stable` are the stored statuses, while provisional and canonical remain the workflow concepts.

## Step-by-step: getting to the scenario above

### 1. Open the desktop app and pick a project folder

Launch the **OpenKnowledge** desktop app. Either drag-and-drop a folder, click **Open folder on disk**, or click **Create new project** to start a new vault. The first-launch consent dialog scaffolds `.ok/` and offers to wire up any MCP-capable editors it detects (Claude Code, Cursor, Codex, OpenCode, OpenClaw).

Don't have the desktop app yet? See the [Quickstart](https://openknowledge.ai/docs/get-started/quickstart).

### 2. Initialize the Knowledge base starter pack

In the editor, on a fresh project, click the **Knowledge base** card in the empty state, leave **Project root** selected (or choose **In a subfolder** to nest the layers under something like `brain/`), and click **Initialize**. On a project with existing documents, the same flow starts from the **Add a starter pack** button.

You'll get `external-sources/`, `research/`, `articles/`, the three matching templates, and `log.md` at the chosen root. The folders carry descriptions written *for the agent*; see them on the folder's overview page (click the folder in the sidebar), and in any `exec` directory listing from an agent.

The starter-pack picker is idempotent (safe to re-run on the same project).

> **Seeding also installs skills**
>
> `ok seed --pack knowledge-base` (or the desktop starter-pack picker) installs three project skills: `knowledge-base`, `research-with-sources`, and `consolidate-notes`. They carry the layer discipline and provisional-to-canonical promotion described below; the platform skill carries the `ingest` procedure. Each lands as a real `SKILL.md` in your agent editors' skills dirs (`.claude/skills`, `.cursor/skills`, …), versioned with your repo — one canonical copy mirrored into each editor already set up for the project. See [Skills](https://openknowledge.ai/docs/features/skills) and [what OpenKnowledge writes to disk](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

### 3. Confirm your agent host is wired

If you accepted the desktop app's first-launch offer to wire up your agent assistant, you're done. If not, in your agent assistant, ask:

> list the OpenKnowledge tools available

You should see the standard reads/writes (`exec`, `search`, `write`, `edit`, `links`, etc.). The `ingest` / `research` / `consolidate` procedures aren't tools: `ingest` ships in the platform skill, while `research` and `consolidate` ship in the pack's member skills.

### 4. Ingest the five sources

For each source, paste the URL into your agent and say:

> ingest this: [https://github.com/example/framework](https://github.com/example/framework)

The agent fetches the URL, follows the platform skill's `ingest` procedure, and writes the verbatim content to `external-sources/framework-readme.md` with `type: source` frontmatter capturing the URL, fetch date, and any author metadata it can extract. **Never copy-paste raw URLs into chat as "sources" without ingesting.** The knowledge base is closed-loop. Every claim must cite a local doc.

Five ingests, five files. After the fifth, ask:

> what's in external-sources/, summary of each

Your agent uses `exec("ls external-sources/")` to read frontmatter and per-file summaries.

### 5. Synthesize into a research log

> research the agent-framework question; synthesize the five sources in external-sources/

That kicks off the pack skill's step-by-step research procedure: it confirms scoping, lists what's covered by the existing sources, identifies gaps (maybe one source you haven't ingested yet), and produces a `research/agent-framework-evaluation.md` with `type: research-note`, `status: draft`, and `sources:` entries shaped as `- resource: ../external-sources/<file>.md`.

Every claim in the log cites a specific source path. You can now ask:

> what does the framework do when two sub-agents race on the same write?

The agent reads `research/agent-framework-evaluation.md` first, follows the citation chain into `external-sources/`, and returns a synthesized answer with traceable evidence.

## What's inside `.ok/` after seeding

The pack doesn't just create three folders. It scaffolds the **agent-readable schema layer** that makes the LLM behave per Karpathy's conventions without you hand-writing a `CLAUDE.md`.

Each folder gets a `.ok/frontmatter.yml` (the agent reads it on every `exec` directory listing) plus a `.ok/templates/<name>.md` (the agent picks it on every `write({ document: { template } })` call). `external-sources/.ok/frontmatter.yml`, for example, carries this description:

> Raw sources saved verbatim — the fetched text of URLs, extracted PDFs, and copied files, each with the original URL and access date in frontmatter. Produced by `ingest`. Immutable after capture; no analysis here (that goes in `research/`).

Result: the agent learns each layer's discipline without a separate prompt or skills file. `research/` and `articles/` carry analogous descriptions. This is Karpathy's centralized `CLAUDE.md` schema, distributed per-folder, so schema lives closest to the agent action. Customize via `mcp__open-knowledge__edit` (with a `folder` target).

## The consolidate step, deferred

You don't consolidate yet in our scenario; the team hasn't decided. Maybe in two weeks you adopt the framework. Then:

> consolidate the agent-framework research into a canonical article

The `consolidate` procedure starts with a STOP gate asking whether the decision is actually made. If yes, it writes `articles/agent-framework-evaluation.md` with `type: article`, `status: stable`, and a `supersedes:` chain pointing back to the research log. That `research/` doc isn't deleted; it becomes `status: deprecated` and points forward with `superseded_by`, so the evidence chain stays intact.

**What just composed.** Five sources became one canonical article via: the **Knowledge base starter pack** (three folders, three templates, agent-readable folder frontmatter, one click), the platform skill's **`ingest`** procedure and the pack's **`research` / `consolidate`** skills (mapping 1:1 to Karpathy's layers), the **agent activity panel** (every fetch + write attributed), the **wikilink graph + frontmatter `sources:` arrays** (citation chain audit-able doc by doc), and the **WYSIWYG editor** (review and refine without leaving the same surface). The same vault, the same product, the whole stack working as one thing.

## The promotion rhythm

When to use each tool, and when *not* to:

| Trigger                                         | Tool                                      | Why                                                                                                      |
| ----------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Source arrives (URL, PDF, transcript)           | `ingest`                                  | Preserve verbatim before analyzing                                                                       |
| You searched the web yourself to ground a claim | `ingest`                                  | Closed-loop: the KB doesn't cite the live web, only local docs                                           |
| You're synthesizing 2+ sources into an answer   | `research`                                | Provisional article, citations required, `status: draft`                                                 |
| Team decided; this is the canonical position    | `consolidate`                             | Writes `status: stable` with supersedes chain                                                            |
| You just want to write a note                   | `write`                                   | Not every doc needs the pipeline (scratch notes, project pages, runbooks)                                |
| You ran a one-off query and the answer's useful | Save the chat to a new doc in `research/` | Per Karpathy, good answers get filed back into the wiki as new pages; don't let them die in chat history |

**Anti-pattern: consolidating too early.** If you `consolidate` before the team has actually decided, you'll be rewriting canonical articles every week. `status: draft` exists for a reason. Keep things in `research/` until decisions are real.

**Anti-pattern: ingesting your own thoughts.** `ingest` is for external sources, preserved verbatim. Your reflections, hypotheses, and reactions go in `research/` (provisional analysis) or a separate `notes/` folder. Don't pollute the immutable layer.

## Cadence

| When                  | Do                                                                                   | Composes                                                                                            |
| --------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| **As sources arrive** | `ingest` (30 sec per source)                                                         | pack-skill procedure + `external-sources/.ok/` frontmatter + `clip` template + agent activity panel |
| **Weekly**            | A `research` pass to synthesize recent ingests; ask the agent to flag contradictions | `research` procedure + folder frontmatter discipline + wikilink graph                               |
| **Per-decision**      | `consolidate` to canonical                                                           | `consolidate` procedure + supersedes chain + `articles/` template                                   |
| **Monthly**           | *"find stale claims in `articles/`, orphans, missing cross-refs"*                    | `links` (orphans, dead links, backlinks)                                                            |

The compounding move: end each ingest session with one synthesis query. The answer becomes a `research/` doc. The wiki grows.

## Tips

- **Folder descriptions are agent-readable.** The pack writes `<folder>/.ok/frontmatter.yml` so the agent learns layer discipline on every `exec` directory listing, with no separate `CLAUDE.md`.
- **`log.md` doubles as a journal.** Every `ingest` / `research` / `consolidate` lands there with timestamps. Underrated.
- **`New from template`** in the sidebar gives you the right frontmatter shape instantly. Don't hand-author.

## Looking for entity tracking instead?

This guide is the **source-grounded** posture: bring sources, the LLM curates the wiki. If what you actually want is to track *people, companies, and meetings* (who's in your network, what was said, what changes over time), the [Entity vault (GBrain-compatible) workflow guide](https://openknowledge.ai/docs/workflows/entity-vault) is the better starting point. Different pattern, same OpenKnowledge editor.

## Further reading

### Source pattern

- **[Karpathy's LLM wiki gist](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f).** The original three-layer pattern, in his words. Read this before or alongside this guide.

### OpenKnowledge internals that support this workflow

- **[Agent activity](https://openknowledge.ai/docs/features/agent-activity).** How every agent edit lands in the shadow repo with attribution.
- **[Claude Code](https://openknowledge.ai/docs/integrations/claude-code)**, **[Cursor](https://openknowledge.ai/docs/integrations/cursor)**, **[Codex](https://openknowledge.ai/docs/integrations/codex)**, **[OpenCode](https://openknowledge.ai/docs/integrations/opencode)**, **[OpenClaw](https://openknowledge.ai/docs/integrations/openclaw).** Agent-assistant integrations.
- **[`STARTER_PACKS` registry](https://github.com/inkeep/open-knowledge/blob/main/packages/server/src/seed/starter.ts).** Canonical source for the Knowledge base pack content.

### Adjacent OK workflow

- **[Entity vault (GBrain-compatible) workflow in OpenKnowledge](https://openknowledge.ai/docs/workflows/entity-vault).** Entity-vault counterpart, if the source-grounded posture isn't your fit.

# Ingest meetings (https://openknowledge.ai/docs/workflows/meeting-ingestion)

Get meeting transcripts into your knowledge base as markdown, where they are searchable, linkable, and answerable. OpenKnowledge does not record meetings. You bring a recorder, OK holds the result.

OpenKnowledge does not capture audio. It is the knowledge layer: the moment a meeting lands as a markdown doc it is searchable, linkable, and answerable like everything else in your knowledge base. Capture is bring-your-own, so any recorder that speaks MCP can feed OK.

Meetings live in a `meetings/` folder. Any project can have one. The [Entity vault](https://openknowledge.ai/docs/workflows/entity-vault) pack sets one up already and feeds each meeting into per-person dossiers.

There are two ways to get meetings in:

- **On demand.** You ask, the agent writes. Works in any editor, no extra infrastructure.
- **Automatically.** A schedule or a webhook asks for you, so meetings appear on their own. Schedules run from your agent's own scheduler (Claude Code, Codex) or the [OpenClaw](https://openknowledge.ai/docs/integrations/openclaw) gateway; webhooks route through the gateway.

Both write the same doc, into the same folder, using the same addressing rule below.

## Address each meeting by its source id

Give every ingested meeting two frontmatter keys, and name its doc after them:

```yaml
---
title: Roadmap sync
date: 2026-07-08
attendees: []
source: granola
source_meeting_id: abc123
---
```

The doc lives at `meetings/<source>-<source_meeting_id>`, so the example above is `meetings/granola-abc123`.

That pair is the dedup key. Because the path is derived from the recorder's own stable id, re-syncing a meeting rewrites the same doc in place instead of creating a second copy. This is what makes an unattended sync safe to run every thirty minutes.

Keep the transcript verbatim. Notes and summaries are yours to edit; the transcript is the record.

## On demand

1. Add your recorder's MCP server to your **editor's** config, next to `open-knowledge`. OK has no MCP client of its own, so this is editor config, not OK config.
2. Ask your agent to write recent meetings into `meetings/`.

The agent pulls the transcript, notes, attendees, and date from the recorder and writes one doc per meeting through OK's `write` tool. Indexing, backlinks, and the live preview all happen on write. Nothing new to install in OK.

## Which recorders work

The axis that matters most is bot versus no-bot. A bot recorder joins the call as a visible participant. Granola captures locally, so nothing joins.

| Recorder   | Capture       | MCP                     | Webhook                                 |
| ---------- | ------------- | ----------------------- | --------------------------------------- |
| Fireflies  | bot           | official, write-capable | `Transcription completed` (HMAC-signed) |
| Circleback | bot           | official                | via Zapier                              |
| tl;dv      | bot           | official (self-host)    | yes                                     |
| Fathom     | bot           | official                | via integrations                        |
| Fellow     | bot           | official                | via Zapier                              |
| Granola    | local, no bot | official, read-only     | none native, bridges via Zapier         |

Otter, Avoma, Teams (MS Graph), and Google Meet expose an API but no official MCP yet, so they need more wiring. Verify a recorder's MCP endpoint is live before relying on it. A few of these are asserted on vendor launch pages rather than deep developer docs.

## Automatically, on a schedule

Schedule the on-demand flow in whatever scheduler your agent already has. This prompt works verbatim in any of them:

```text
Pull meetings from the recorder MCP since the last run. Write each new one into the meetings/ folder of this project, addressed as meetings/<source>-<source_meeting_id> so re-runs update in place. Do not duplicate meetings already present.
```

- **Claude Code.** Three tiers, from a local desktop task to a cloud routine that runs with your machine off. Each reaches a different set of files and MCP servers: [Scheduled runs](https://openknowledge.ai/docs/integrations/claude-code#scheduled-runs).
- **Codex.** The ChatGPT desktop app schedules it; Codex CLI runs it from OS cron: [Scheduled runs](https://openknowledge.ai/docs/integrations/codex#scheduled-runs).
- **[OpenClaw](https://openknowledge.ai/docs/integrations/openclaw).** The gateway has a built-in scheduler. Register both MCP servers with it, `open-knowledge` via `ok init` and your recorder's MCP per the vendor's install guide, then create one cron job that wakes an agent with the prompt above:

```bash
openclaw cron create "*/30 * * * *" \
  "Pull meetings from the recorder MCP since the last run. Write each new one into the meetings/ folder of this project, addressed as meetings/<source>-<source_meeting_id> so re-runs update in place. Do not duplicate meetings already present." \
  --name "OK meeting sync" \
  --session isolated \
  --no-deliver
```

Polling works for every recorder, including Granola, which has no native webhook. Schedules fire only while their host is running: the OpenClaw gateway, or the Claude / ChatGPT desktop app. Claude cloud routines are the exception, running on Anthropic's infrastructure with your machine off.

## Automatically, on an event

If you want lower latency than a poll, the gateway can run the same agent the moment something happens. The wiring (hooks config, endpoints, auth) lives in [trigger runs from external events](https://openknowledge.ai/docs/integrations/openclaw#trigger-runs-from-external-events).

Choosing which event to send matters more than the plumbing, because the two moments are different jobs:

| Event                                                                  | Fires when                                                    | Use it for                                                        |
| ---------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------- |
| `Transcription completed` (Fireflies), note added (Granola via Zapier) | the transcript is ready, a few minutes after the meeting ends | **Ingestion.** There is nothing to write before this.             |
| `meeting.started` (Zoom)                                               | the moment the meeting begins                                 | **Prep.** Create the doc and link attendees before anyone speaks. |

Google Calendar push notifications only signal that the calendar changed and carry no event data, so they need a follow-up API call before they are useful here.

One practical constraint: a vendor cannot POST to `127.0.0.1`. A locally-run gateway needs a tunnel or a relay to receive vendor webhooks, which is why the cron poll is the default and webhooks are the upgrade.

# Plain notes (https://openknowledge.ai/docs/workflows/plain-notes)

The simplest setup: one folder for notes and one for a daily journal. Just write, link the things worth linking, and a connected web of notes builds itself. Built on the Plain notes starter pack.

**Plain notes** is the "I just want to write" layout. Two folders, no structure imposed: a flat `notes/` for topics and a `daily/` journal. You write; you link the things worth linking; and a navigable graph emerges on its own. It's the pack to reach for when the other packs feel like too much scaffolding for what you're actually doing.

```html preview
<div style="padding:16px">
  <div id="pn" style="display:flex;gap:18px;flex-wrap:wrap;justify-content:center">
    <svg id="pn-graph" viewBox="0 0 200 160" style="width:200px;height:160px" role="img" aria-label="Notes linking into a small graph"></svg>
    <div id="pn-days" style="display:flex;flex-direction:column;gap:6px;justify-content:center"></div>
  </div>
  <div class="cap">Left: mention something in a note and it becomes a link — the graph builds itself. Right: each day's entry links to yesterday's, so the journal is also a chain.</div>
</div>
<style>
.cap{margin-top:10px;color:var(--muted-foreground);font-size:12.5px;text-align:center}
.cap::before{content:"\1F517  ";font-weight:700}
#pn-graph line{stroke:var(--border);stroke-width:1.5}
#pn-graph line.lit{stroke:var(--primary)}
#pn-graph circle{fill:var(--card);stroke:var(--border);stroke-width:1.5;transition:stroke .3s}
#pn-graph circle.on{stroke:var(--primary)}
.day{border:1px solid var(--border);border-radius:9px;padding:6px 11px;font-size:12px;font-weight:600;color:var(--muted-foreground);background:var(--card);transition:border-color .3s,color .3s}
.day.on{border-color:var(--primary);color:var(--accent-ink)}
@media (prefers-reduced-motion:reduce){#pn-graph circle,#pn-graph line,.day{transition:none}}
</style>
<script>
var P=[[100,30],[45,100],[155,95],[100,140]],GE=[[0,1],[0,2],[1,3],[2,3]];
var g=document.getElementById("pn-graph"),NS="http://www.w3.org/2000/svg",gl=[],gc=[];
GE.forEach(function(e){var l=document.createElementNS(NS,"line");l.setAttribute("x1",P[e[0]][0]);l.setAttribute("y1",P[e[0]][1]);l.setAttribute("x2",P[e[1]][0]);l.setAttribute("y2",P[e[1]][1]);g.appendChild(l);gl.push({el:l,a:e[0],b:e[1]});});
P.forEach(function(p,i){var c=document.createElementNS(NS,"circle");c.setAttribute("cx",p[0]);c.setAttribute("cy",p[1]);c.setAttribute("r",8);g.appendChild(c);gc.push(c);});
var days=["Mon","Tue","Wed","Thu"],box=document.getElementById("pn-days"),de=[];
days.forEach(function(d){var el=document.createElement("div");el.className="day";el.textContent=d+" — daily entry";box.appendChild(el);de.push(el);});
var cur=0;
function set(i){gc.forEach(function(c,j){c.classList.toggle("on",j===i%gc.length);});gl.forEach(function(o){o.el.classList.toggle("lit",o.a===i%gc.length||o.b===i%gc.length);});de.forEach(function(el,j){el.classList.toggle("on",j===i%de.length);});}
set(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){setInterval(function(){cur=(cur+1)%4;set(cur);},1400);}
</script>
```

There's no pipeline and no lifecycle here. The only discipline is the linking habit — and OpenKnowledge's graph, backlinks, and search do the rest.

Note-takers get a plain notes app backed by markdown they own, with an agent keeping everything linked and nothing locked in a proprietary format. Journalers get each daily entry chained to the last, with mood and gratitude fields, so the journal is also a navigable graph to look back across. And if you're not sure which pack to pick, this is the lightest possible starting point — begin now and promote notes into a more structured layout later.

## The folders

```
notes/    one file per topic, flat
daily/    one journal entry per day (YYYY-MM-DD.md)
```

The pack ships just two templates — `note` and `daily`. The daily entry has a light shape (morning intentions, capture through the day, evening reflection) plus optional `mood`, `top3`, and `gratitude` frontmatter fields you fill when journaling, so you can look back across days later.

## The one habit: link liberally

The entire value of this pack is the graph that emerges from links. So when a note or entry mentions something worth its own page, link it — and if that page doesn't exist yet, stub it. The agent does this for you:

> Turn today's notes into a daily entry. Link anything worth its own page and stub the ones that don't exist yet. Link this entry to yesterday's.

On the first entry of a day the agent links back to yesterday's entry and pre-fills the date, so your linear journal is also a navigable graph you can walk in either direction.

## What's in your project after seeding

Seeding needs an initialized project — run [`ok init`](https://openknowledge.ai/docs/get-started/quickstart) first. Pick **Plain notes** in the starter-pack picker, or run:

```bash
ok seed --pack plain-notes
```

The pack creates `notes/` and `daily/` at the project root (no subfolder), each with its `.ok/frontmatter.yml` guidance and template. That's the whole footprint — minimal on purpose.

> **Seeding also installs a skill**
>
> `ok seed --pack plain-notes` installs the **Plain notes** project skill into your agent editors (Claude Code, Cursor, Codex, OpenCode). It's the "how to work here" guidance that gives the agent the linking habit and the daily-chain behavior — read automatically, and editable like any other doc. It lands as a real `SKILL.md` committed to your repo in your project's skills directory (`.agents/skills/` when you have one, otherwise your editor's), copied into the skills directory of each editor already set up for the project. The copies refresh from the source until you hand-edit one. See [Skills](https://openknowledge.ai/docs/features/skills) and [what OpenKnowledge writes to disk](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

## Cadence

| When                         | Do                                                                              |
| ---------------------------- | ------------------------------------------------------------------------------- |
| Any time                     | Drop a note in `notes/`; link the nouns worth their own page                    |
| Each morning or evening      | A `daily/` entry; let the agent chain it to yesterday and fill mood / gratitude |
| Weekly                       | Ask the agent to link loosely-related notes so the graph tightens               |
| When a topic outgrows a note | Promote it — or switch to a more structured pack for that area                  |

## Further reading

- **[LLM wiki workflow](https://openknowledge.ai/docs/workflows/karpathy-llm-wiki).** When your notes become source-grounded research, this is the pack to graduate to.
- **[Writing pipeline workflow](https://openknowledge.ai/docs/workflows/writing-pipeline).** When your notes grow into essays or newsletters, this pack adds ideas → drafts → published stage gates.
- **[Core Concepts](https://openknowledge.ai/docs/reference/core-concepts).** Links, backlinks, and the well-connected graph that make the emergent structure visible.
- **[Skills](https://openknowledge.ai/docs/features/skills).** What the installed pack skill is, and how to edit it.
- **[Claude Code](https://openknowledge.ai/docs/integrations/claude-code)**, **[Cursor](https://openknowledge.ai/docs/integrations/cursor)**, **[Codex](https://openknowledge.ai/docs/integrations/codex)**, **[OpenCode](https://openknowledge.ai/docs/integrations/opencode).** MCP-capable agent hosts.

# Software lifecycle (https://openknowledge.ai/docs/workflows/software-lifecycle)

One home for the docs an engineering team writes around its code: the pitch for a change, the decision that settles it, the plan to build it, and the write-up when something breaks. Each links to the next, and an AI keeps the connections current. Built on the Software lifecycle starter pack.

A **software lifecycle** knowledge base is where an engineering team's thinking lives before, during, and after the code: the proposal that argued for a change, the decision that settled it, the spec that built it, and the postmortem written when it broke. Those are the usual names for each stage, and if they're new to you the page explains them as it goes. One folder per stage, one flow connecting them, and an AI that keeps the links between them honest.

```html preview
<div style="padding:18px">
  <div id="flow" style="display:flex;flex-wrap:wrap;gap:8px;align-items:stretch"></div>
  <div class="cap">Each stage is a folder of markdown. The arrows are links the agent maintains, so a spec always points back to the proposal it implements.</div>
</div>
<style>
#flow .node{flex:1;min-width:118px;border:1px solid var(--border);border-radius:12px;padding:11px 13px;background:var(--card);transition:box-shadow .3s,border-color .3s;cursor:pointer}
#flow .node .t{font-weight:600;font-size:13px}
#flow .node .s{color:var(--muted-foreground);font-size:11.5px;margin-top:2px}
#flow .node.on{border-color:var(--primary);box-shadow:0 0 0 3px var(--accent-soft)}
#flow .node.on .t{color:var(--accent-ink)}
#flow .arrow{align-self:center;color:var(--muted-foreground)}
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px}
.cap::before{content:"\2192  ";color:var(--primary);font-weight:700}
@media (prefers-reduced-motion:reduce){#flow .node{transition:none}}
</style>
<script>
var steps=[["proposals/","argue for a change"],["decisions/","freeze the ADR"],["specs/","implement it"],["postmortems/","when it breaks"]];
var flow=document.getElementById("flow"),nodes=[];
steps.forEach(function(s,i){
  if(i){var a=document.createElement("div");a.className="arrow";a.textContent="→";flow.appendChild(a);}
  var d=document.createElement("div");d.className="node";
  var t=document.createElement("div");t.className="t";t.textContent=s[0];
  var sub=document.createElement("div");sub.className="s";sub.textContent=s[1];
  d.appendChild(t);d.appendChild(sub);
  d.onclick=function(){pinned=true;set(i);};
  flow.appendChild(d);nodes.push(d);
});
var cur=0,pinned=false;
function set(i){cur=i;nodes.forEach(function(n,j){n.classList.toggle("on",j===i);});}
set(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){setInterval(function(){if(!pinned)set((cur+1)%nodes.length);},1500);}
</script>
```

The stages are the standard artifacts an engineering org already produces — RFCs, ADRs, specs, blameless postmortems, runbooks — but here they live as linked markdown an agent reads and writes alongside you, not scattered across Google Docs, Notion pages, and Slack threads that are hard to trace six months later.

Engineering leads get every proposal, decision, and spec in one linked graph, with the agent surfacing what supersedes what, so design docs stop rotting across scattered tools. OSS maintainers give contributors the *why* behind the code — in accepted proposals and frozen ADRs — instead of leaving them to do archaeology through closed PRs. On-call engineers get postmortems that link to each other and stub the follow-up guides automatically, so recurring incidents become visible instead of quietly repeating.

## The flow

```
proposals/    in-flight, RFC-shape design proposals   (draft → fcp → accepted/rejected)
   ↓ accepted
decisions/    frozen ADRs — the record of what was decided
   ↓ derived
specs/        implementation specs for accepted proposals
   ↓ when things break
postmortems/  blameless incident write-ups
guides/       how-to / onboarding / runbooks — referenced throughout
```

The pack ships a template for each: `proposal`, `decision`, `spec` / `spec-plan` / `spec-tasks` (the [GitHub spec-kit](https://github.com/github/spec-kit) three-file shape), `postmortem`, and `guide` / `onboarding-guide` / `runbook`. Templates carry only structure; what each section is for is taught to the agent by the folder itself, so document bodies stay clean.

## The scenario

Your team is deciding whether to move the job queue off Postgres onto a dedicated broker. Over two weeks:

1. An engineer writes `proposals/0007-dedicated-job-broker.md` — motivation, design, drawbacks, alternatives, unresolved questions. Status `draft`.
2. After review it reaches `fcp` (final comment period), then `accepted`. The agent graduates it into `decisions/0007-adopt-nats-for-jobs.md`, a frozen ADR that links back to the proposal.
3. Implementation gets a `specs/007-job-broker/` folder — `spec.md`, `plan.md`, `tasks.md` — referencing the parent proposal.
4. Three weeks post-ship the broker drops messages under load. `postmortems/2026-07-14-job-broker-message-loss.md` captures the timeline and root cause, and the agent surfaces a `Related:` link to an older queue postmortem that shares the subsystem.

Nothing here is new to your team. What's new: it's all one linked graph an agent can read back to you, and the links stay correct because the agent maintains them.

## What's in your project after seeding

Pick **Software lifecycle** in the starter-pack picker, or run:

```bash
ok seed --pack software-lifecycle
```

By default the pack scaffolds everything at the project root:

```
your-project/
├── proposals/       0001-feature-name.md …
├── decisions/       NNNN-title.md (ADRs)
├── specs/           NNN-name/{spec,plan,tasks}.md
├── postmortems/     YYYY-MM-DD-name.md
└── guides/          how-to / onboarding / runbooks
```

Each folder carries an `.ok/frontmatter.yml` description the agent reads on every directory listing, plus its templates under `.ok/templates/`. That description is where the workflow lives — so the agent knows an accepted proposal graduates to `decisions/`, that ADRs freeze once accepted, and that a new decision superseding an old one links back via `Supersedes:`.

> **Seeding also installs a skill**
>
> `ok seed --pack software-lifecycle` installs the **Software lifecycle** project skill into your agent editors (Claude Code, Cursor, Codex, OpenCode). It's the "how to work here" guidance behind the behaviors below — the status flows, the `Supersedes:` scanning, the postmortem follow-ups — read automatically, and editable like any other doc. It lands as a real `SKILL.md` in your repo, in your project's skills directory (`.agents/skills/` when you have one, otherwise your editor's), copied into the skills directory of each editor already set up for the project. The copies refresh from the source until you hand-edit one. See [Skills](https://openknowledge.ai/docs/features/skills) and [what OpenKnowledge writes to disk](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

## What the agent does for you

The pack teaches the agent behaviors you'd otherwise have to remember. *They're encoded in the pack skill and run **when you prompt the agent** — not background automations that fire on a timer.*

| When                                 | The agent                                                                                        | Why it matters                                  |
| ------------------------------------ | ------------------------------------------------------------------------------------------------ | ----------------------------------------------- |
| A new decision lands                 | Scans existing ADRs touching the same subsystem, surfaces `Supersedes:` candidates before commit | The decision log stays a true chain, not a pile |
| A proposal sits at `draft` > 14 days | Surfaces it to advance, park, or close                                                           | Stale proposals don't silently die              |
| A postmortem is published            | Scans action items for guide-shaped follow-ups and stubs a guide pre-filled with the symptom     | The fix becomes documented, not just discussed  |
| A new postmortem                     | Adds a `Related:` block linking prior postmortems in the same subsystem                          | Recurring incidents become visible              |

## Step by step

### 1. Seed the pack

In an initialized project (run `ok init` first — see the [quickstart](https://openknowledge.ai/docs/get-started/quickstart)), open the starter-pack picker and choose **Software lifecycle** (or run `ok seed --pack software-lifecycle`). To nest everything under a subfolder — `project-docs/` or wherever your team's docs live — select **In a subfolder** in the picker or pass `--root project-docs`.

### 2. Write the first proposal

In your agent, from a rough idea:

> Draft a proposal in proposals/ for moving our job queue off Postgres onto a dedicated broker. Use the proposal template: motivation, design, drawbacks, alternatives, unresolved questions. Status: draft.

Review it in the editor. The frontmatter tracks `status`; advance it to `fcp` then `accepted` as review progresses.

### 3. Graduate it to a decision

> This proposal is accepted. Create the ADR in decisions/, link it back to the proposal, and flag any existing decisions it supersedes.

The agent writes the frozen record and — per the pack behavior — checks the subsystem for supersede candidates.

### 4. Derive the spec

> Create an implementation spec for the accepted broker decision under specs/, using the spec / spec-plan / spec-tasks templates. Reference the parent proposal.

### 5. Write postmortems as incidents happen

> Write a blameless postmortem for the job-broker message loss on 2026-07-14: summary, timeline, root cause, what went well, action items. Link related prior postmortems.

## Cadence

| When               | Do                                                                                          |
| ------------------ | ------------------------------------------------------------------------------------------- |
| Per design change  | A proposal in `proposals/`; advance its status as review moves                              |
| On acceptance      | Graduate to a `decisions/` ADR with a `Supersedes:` chain where relevant                    |
| Per implementation | A `specs/NNN-name/` folder derived from the decision                                        |
| Per incident       | A blameless postmortem; let the agent link related ones and stub follow-up guides           |
| Monthly            | Ask the agent to surface stale `draft` proposals and guides past their `last_verified` date |

## Further reading

- **[LLM wiki workflow](https://openknowledge.ai/docs/workflows/karpathy-llm-wiki).** The source-grounded counterpart, if you're curating external sources rather than a doc lifecycle.
- **[Codebase wiki workflow](https://openknowledge.ai/docs/workflows/codebase-wiki).** An agent-authored wiki of the code the specs implement.
- **[Agent activity](https://openknowledge.ai/docs/features/agent-activity).** How every proposal, decision, and spec edit lands with attribution.
- **[Claude Code](https://openknowledge.ai/docs/integrations/claude-code)**, **[Cursor](https://openknowledge.ai/docs/integrations/cursor)**, **[Codex](https://openknowledge.ai/docs/integrations/codex).** MCP-capable agent hosts.

# Supporting the Open Knowledge Format (https://openknowledge.ai/docs/workflows/supporting-open-knowledge-format)

Start with a small OKF v0.2 knowledge base, keep it portable as it grows, and give agents the format guidance they need.

The **Open Knowledge Format (OKF)** is a portable convention for agent-readable knowledge: Markdown files, YAML frontmatter, and standard links. Use this workflow when the knowledge base may move between tools, teams, or organizations and you want that portability built in from the first file.

OpenKnowledge's **Open Knowledge Format** starter pack creates the minimum useful OKF v0.2 bundle. It also enables the [OKF plugin](https://openknowledge.ai/docs/plugins/okf), which warns when later edits would be misread by another OKF consumer.

> **Info**
>
> **OKF here means Google's Open Knowledge *Format*.** The format keeps its conformance floor deliberately small: every non-reserved document needs parseable YAML frontmatter with a non-empty `type`. Read the [OKF v0.2 specification](https://github.com/GoogleCloudPlatform/open-knowledge-format/blob/main/SPEC.md).

## Start with the OKF pack

Seeding needs an initialized project. Run [`ok init`](https://openknowledge.ai/docs/get-started/quickstart), then pick **Open Knowledge Format** in the starter-pack picker, or run:

```bash
ok seed --pack okf
```

The pack adds:

| Path                                | What it provides                                                                           |
| ----------------------------------- | ------------------------------------------------------------------------------------------ |
| `index.md`                          | The bundle root, declaring `okf_version: "0.2"` and linking to Getting started             |
| `concepts/getting-started.md`       | A typed guide to the bundle's purpose and conventions                                      |
| `concepts/.ok/templates/concept.md` | A starting shape for new concepts, including `type: Concept` and `status: draft`           |
| Project configuration               | The OKF plugin enabled for this project                                                    |
| `okf-knowledge-base`                | Project skill that teaches agents the format's types, provenance, links, indexes, and logs |

Existing files with the same paths are left alone. Re-seeding fills only what is missing.

## Keep the bundle portable

The starter gives the project a conformant starting point. Three habits keep it portable:

1. Give every non-reserved `.md` document parseable frontmatter with a non-empty `type`.
2. Keep reserved `index.md` and `log.md` filenames lowercase. An index normally has no frontmatter; only the bundle-root index may declare `okf_version`.
3. Use standard Markdown links. OpenKnowledge understands `[[wiki-links]]`, but another OKF consumer may not.

Types are an open vocabulary. Choose a useful, stable name for the concept instead of forcing every document into a fixed taxonomy.

## Use the plugin for ongoing feedback

The starter pack shapes the initial files; the **OKF project plugin** watches what happens afterward. Its warnings are advisory and never block a save or an agent edit.

The plugin:

- checks document structure, frontmatter, reserved files, links, and `.mdx` use;
- advertises generated OKF schemas to agents so they can read the contract before writing;
- offers the companion OKF agent skill when it is not already in the project;
- can optionally maintain an `index.md` in every Markdown folder.

Open **Settings → Plugins → OKF** to tune its rules or generated indexes. See the [OKF plugin](https://openknowledge.ai/docs/plugins/okf) page for the controls and complete rule reference.

You can also enable the plugin in an existing project without seeding the starter pack. It reports portability gaps but does not rewrite your documents. Index generation is a separate opt-in because those files are machine-owned.

## Work with agents

The project skill gives an agent the judgment the deterministic rules cannot: choosing useful types, recording only real provenance, and preserving trust and lifecycle metadata without inventing it.

Try:

> Create a concept for our deployment policy. Follow the OKF guidance, use standard Markdown links, and record only provenance supported by the project.

The plugin checks the resulting shape. The skill guides the content decision.

## Further reading

- **[OKF plugin](https://openknowledge.ai/docs/plugins/okf).** Enable the plugin, install its recommended skill, tune rules, and opt into generated indexes.
- **[Content rules overview](https://openknowledge.ai/docs/advanced/content-rules/overview).** Where warnings appear in the editor, CLI, and agent tools.
- **[Open Knowledge Format v0.2 specification](https://github.com/GoogleCloudPlatform/open-knowledge-format/blob/main/SPEC.md).** The portable format OpenKnowledge implements.

# Worldbuilding (https://openknowledge.ai/docs/workflows/worldbuilding)

A living encyclopedia for a story you're writing: characters, places, factions, and lore, each on its own page that links to the others. Mention someone new and the AI adds a page for them; contradict your own canon and it flags it. Built on the Worldbuilding starter pack.

A **worldbuilding** knowledge base is a living encyclopedia of your story world. Characters, settings, themes, factions, and lore — each its own page, all linking to each other. The graph *is* the product: the value isn't any single entry, it's the web connecting them, and an agent that grows and guards that web as you write.

```html preview
<div style="padding:16px">
  <svg id="web" viewBox="0 0 460 220" style="width:100%;max-width:520px;display:block;margin:0 auto" role="img" aria-label="A graph of story entities linking to each other"></svg>
  <div class="cap">Mention a name that isn't captured yet and the agent stubs its page and backlinks it. Every entity becomes a hub for everywhere it appears.</div>
</div>
<style>
.cap{margin-top:10px;color:var(--muted-foreground);font-size:12.5px;text-align:center}
.cap::before{content:"\29C9  ";color:var(--primary);font-weight:700}
#web line{stroke:var(--border);stroke-width:1.5}
#web line.lit{stroke:var(--primary)}
#web .n circle{fill:var(--card);stroke:var(--border);stroke-width:1.5;transition:stroke .3s,filter .3s}
#web .n.on circle{stroke:var(--primary);filter:drop-shadow(0 0 5px var(--accent-soft))}
#web .n text{fill:var(--muted-foreground);font-size:10px;text-anchor:middle;font-weight:600}
#web .n.on text{fill:var(--accent-ink)}
@media (prefers-reduced-motion:reduce){#web .n circle,#web line{transition:none}}
</style>
<script>
var N=[["Kael",120,50],["Iron Pact",300,45],["Duskfen",70,150],["The Sundering",250,160],["Betrayal",380,120]];
var E=[[0,1],[0,2],[1,3],[2,3],[0,4],[3,4]];
var svg=document.getElementById("web"),NS="http://www.w3.org/2000/svg",lines=[],nodes=[];
E.forEach(function(e){var l=document.createElementNS(NS,"line");l.setAttribute("x1",N[e[0]][1]);l.setAttribute("y1",N[e[0]][2]);l.setAttribute("x2",N[e[1]][1]);l.setAttribute("y2",N[e[1]][2]);svg.appendChild(l);lines.push({el:l,a:e[0],b:e[1]});});
N.forEach(function(n,i){var g=document.createElementNS(NS,"g");g.setAttribute("class","n");var c=document.createElementNS(NS,"circle");c.setAttribute("cx",n[1]);c.setAttribute("cy",n[2]);c.setAttribute("r",7);var t=document.createElementNS(NS,"text");t.setAttribute("x",n[1]);t.setAttribute("y",n[2]-12);t.textContent=n[0];g.appendChild(c);g.appendChild(t);g.onclick=function(){pinned=true;set(i);};svg.appendChild(g);nodes.push(g);});
var cur=0,pinned=false;
function set(i){cur=i;nodes.forEach(function(g,j){g.classList.toggle("on",j===i);});lines.forEach(function(o){o.el.classList.toggle("lit",o.a===i||o.b===i);});}
set(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){setInterval(function(){if(!pinned)set((cur+1)%nodes.length);},1500);}
</script>
```

Unlike the other packs, the flow here isn't a pipeline — it's a network that thickens over time. Your job is to write scenes and notes; the agent's job is to keep the encyclopedia consistent with them.

Novelists and screenwriters lean on it to stop losing track of their own canon as the story grows. Game masters keep a navigable world bible — who controls which region, which faction hates which. Narrative-design teams use it to hold a shared setting together as more people contribute.

## The folders

| Folder        | Holds                                                                          | The story question it answers |
| ------------- | ------------------------------------------------------------------------------ | ----------------------------- |
| `characters/` | One page per character (PC + NPC); type, status, faction, first appearance     | *Who*                         |
| `settings/`   | Locations, regions, world-rules; region, controlling faction, danger level     | *Where*                       |
| `themes/`     | Recurring narrative concerns (love, betrayal, identity), each with its tension | *Why*                         |
| `factions/`   | Political, social, criminal, magical, religious groups                         | *Who's aligned with whom*     |
| `lore/`       | History, mythology, cosmology, magic systems                                   | *What's true about the world* |

The pack ships templates for the shapes that recur — `faction`, `political-faction`, `religion`, `lore`, `magic-system`, `historical-event`, and the core character / setting / theme pages.

## The two behaviors that make it worth it

This is where the agent earns its keep:

- **Auto-stub on mention.** When a chapter, session log, or existing entry names someone or somewhere not yet captured, the agent stubs a page in the right folder and backlinks it to where it came up. Your canon grows as you write, not in a separate cataloguing session.
- **Flag contradictions.** When a character's `faction` contradicts their actions in a scene, or a setting gets described two ways, the agent *surfaces* the conflict rather than silently "fixing" it — because in fiction a contradiction is often a plot point, not a bug. You decide whether it's a mistake or a mystery.

## The scenario

You write a chapter where Kael, a member of the Iron Pact, quietly warns the people of Duskfen before a raid. You drop the scene in and ask:

> Update the world encyclopedia from this chapter. Stub any characters, settings, or factions I mention that don't have pages yet, and link them. Flag anything that contradicts existing canon.

The agent stubs `characters/kael.md` linked to `factions/iron-pact.md` and `settings/duskfen.md` — then notes that Kael warning Duskfen contradicts the Iron Pact's stated hostility toward it. Is Kael a traitor, a double agent, or did you misremember the Pact's alignment? That flag is the agent doing worldbuilding *with* you.

## What's in your project after seeding

In an initialized project (`ok init`), pick **Worldbuilding** in the starter-pack picker, or run:

```bash
ok seed --pack worldbuilding
```

Pass `--root world` (or pick **In a subfolder** in the picker) to nest the pack under `world/`:

```
your-project/
└── world/
    ├── characters/
    ├── settings/
    ├── themes/
    ├── factions/
    └── lore/
```

Each folder's `.ok/frontmatter.yml` teaches the agent that entity type and what belongs there, so the guidance never clutters the entries themselves.

> **Seeding also installs a skill**
>
> `ok seed --pack worldbuilding` installs the **Worldbuilding** project skill into your agent editors (Claude Code, Cursor, Codex, OpenCode). It's the "how to work here" guidance behind the auto-stub and contradiction-flagging behaviors — read automatically, and editable like any other doc. It lands as one real `SKILL.md` in your project's skills directory (`.agents/skills/` when you have one, otherwise your editor's), committed to your repo and copied into the skills directory of each editor already set up for the project. The copies refresh from the source until you hand-edit one. See [Skills](https://openknowledge.ai/docs/features/skills) and [what OpenKnowledge writes to disk](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

## Cadence

| When                             | Do                                                                                               |
| -------------------------------- | ------------------------------------------------------------------------------------------------ |
| After writing a scene or session | Ask the agent to update the encyclopedia — stub new entities, link mentions, flag contradictions |
| Periodically                     | Thread the graph: ask it to link characters ↔ factions ↔ settings ↔ lore so each entry is a hub  |
| Before a big reveal              | Ask it to surface every page that touches the character or lore involved, so continuity holds    |
| Monthly                          | Ask the agent to audit dead links and stubbed-but-never-filled entities                          |

## Further reading

- **[LLM wiki workflow](https://openknowledge.ai/docs/workflows/karpathy-llm-wiki).** The same wiki-graph discipline, aimed at real-world sources instead of a fictional world.
- **[Entity vault workflow](https://openknowledge.ai/docs/workflows/entity-vault).** The non-fiction sibling — track real people, companies, and meetings the same way you track characters and factions.
- **[Core Concepts](https://openknowledge.ai/docs/reference/core-concepts).** Links and backlinks — the connected graph you navigate the world through.
- **[Claude Code](https://openknowledge.ai/docs/integrations/claude-code)**, **[Cursor](https://openknowledge.ai/docs/integrations/cursor)**, **[Codex](https://openknowledge.ai/docs/integrations/codex)**, **[OpenCode](https://openknowledge.ai/docs/integrations/opencode).** MCP-capable agent hosts.

# Writing pipeline (https://openknowledge.ai/docs/workflows/writing-pipeline)

Three folders that move a piece of writing from idea to published: catch ideas before they slip away, draft with an AI while every revision is saved automatically, and leave finished pieces untouched. Built on the Writing pipeline starter pack.

A **writing pipeline** is the shortest path from a premise to a published piece: three folders, one direction of travel. Ideas you capture before they fade, drafts you actually work on, and published work you leave alone. No named-revision folders, no ceremony — the CRDT history covers your revisions, and an agent nudges pieces along so nothing stalls.

```html preview
<div style="padding:18px">
  <div id="pipe" style="display:flex;flex-wrap:wrap;gap:8px;align-items:stretch"></div>
  <div class="cap">One direction of travel. A premise gets promoted to a draft when you commit to it; a draft gets published when it ships — and then it's immutable.</div>
</div>
<style>
#pipe .node{flex:1;min-width:130px;border:1px solid var(--border);border-radius:12px;padding:12px 14px;background:var(--card);transition:box-shadow .3s,border-color .3s;cursor:pointer}
#pipe .node .t{font-weight:600;font-size:13px}
#pipe .node .s{color:var(--muted-foreground);font-size:11.5px;margin-top:3px;line-height:1.35}
#pipe .node.on{border-color:var(--primary);box-shadow:0 0 0 3px var(--accent-soft)}
#pipe .node.on .t{color:var(--accent-ink)}
#pipe .arrow{align-self:center;color:var(--muted-foreground);font-size:12px;text-align:center;min-width:64px}
.cap{margin-top:12px;color:var(--muted-foreground);font-size:12.5px}
.cap::before{content:"\2192  ";color:var(--primary);font-weight:700}
@media (prefers-reduced-motion:reduce){#pipe .node{transition:none}}
</style>
<script>
var steps=[["ideas/","one-line premises, captured before they fade"],["drafts/","active prose; CRDT history covers revisions"],["published/","shipped work — treat as immutable"]];
var edges=["commit to it","ship it"];
var pipe=document.getElementById("pipe"),nodes=[];
steps.forEach(function(s,i){
  if(i){var a=document.createElement("div");a.className="arrow";a.innerHTML="→<br><span style='font-size:10px'>"+edges[i-1]+"</span>";pipe.appendChild(a);}
  var d=document.createElement("div");d.className="node";
  var t=document.createElement("div");t.className="t";t.textContent=s[0];
  var sub=document.createElement("div");sub.className="s";sub.textContent=s[1];
  d.appendChild(t);d.appendChild(sub);
  d.onclick=function(){pinned=true;set(i);};
  pipe.appendChild(d);nodes.push(d);
});
var cur=0,pinned=false;
function set(i){cur=i;nodes.forEach(function(n,j){n.classList.toggle("on",j===i);});}
set(0);
if(!matchMedia("(prefers-reduced-motion:reduce)").matches){setInterval(function(){if(!pinned)set((cur+1)%nodes.length);},1600);}
</script>
```

The point is to keep the friction where it belongs. Capturing an idea should cost one line. Drafting should feel like writing, not file management. And publishing should be a deliberate step that freezes the piece — to revise later, you copy it back to a new draft rather than editing history.

Essayists and newsletter writers get a frictionless place to capture premises before good ideas escape. Drafting with an AI collaborator happens on the actual piece, every revision in the timeline, instead of scattered across chat windows. And shipped work stays separate from work in progress — a clean record of published pieces with canonical URLs and channels.

## The flow

```
ideas/      one-line premises, headlines, fragments — kept short on purpose
   ↓ commit to writing it
drafts/     active prose; frontmatter tracks status, word count, parent idea
   ↓ ship
published/  shipped work; carries published_at, canonical_url, channel — immutable
```

The pack ships an `idea`, `draft`, and `published` template. Structure only — the meaning of each stage lives in the folder guidance the agent reads, so your prose stays uncluttered.

## The scenario

A thought hits you on a walk: *"the best abstraction is the one you didn't write."* You drop it into `ideas/` as a single line. Two weeks later you commit to it:

> Promote the "best abstraction is the one you didn't write" idea into a draft. Pull the premise into the draft template and set status: drafting.

You write it with your agent over a few sessions — every revision captured in the [timeline](https://openknowledge.ai/docs/features/timeline-and-recovery), no manual versioning. When it's ready you publish, and the piece moves to `published/` with its canonical URL filled in. The draft's full history stays intact; the published copy is frozen.

## What's in your project after seeding

Pick **Writing pipeline** in the starter-pack picker, or run:

```bash
ok seed --pack writing-pipeline --root writing
```

The picker pre-fills the `writing/` subfolder; `--root writing` does the same from the CLI (omit it to scaffold at the project root):

```
your-project/
└── writing/
    ├── ideas/       one file per premise, flat
    ├── drafts/      active prose; status: drafting | review
    └── published/   shipped, immutable
```

Each folder carries its `.ok/frontmatter.yml` guidance and templates. If a draft needs research notes, the agent creates `drafts/<slug>/research/` on demand rather than cluttering the top level.

> **Seeding also installs a skill**
>
> `ok seed --pack writing-pipeline` installs the **Writing pipeline** project skill into your agent editors (Claude Code, Cursor, Codex, OpenCode). It's the "how to work here" guidance behind the behaviors below — the stage promotions, the idle-draft nudges, the publish-time URL fill — read automatically, and editable like any other doc. It lands as a single `SKILL.md` in your project's skills directory (`.agents/skills/` when you have one, otherwise your editor's), committed to your repo and copied into the skills directory of each editor already set up for the project. The copies refresh from the source until you hand-edit one. See [Skills](https://openknowledge.ai/docs/features/skills) and [what OpenKnowledge writes to disk](https://openknowledge.ai/docs/reference/what-open-knowledge-writes).

## What the agent does for you

*These behaviors are encoded in the pack skill and run **when you prompt the agent** — they are not background automations that fire on a timer.*

| When you ask                | The agent                                                                               |
| --------------------------- | --------------------------------------------------------------------------------------- |
| An idea sits idle > 30 days | Surfaces it to park or promote — so `ideas/` stays a live shortlist, not a graveyard    |
| A draft sits idle > 14 days | Nudges it; for drafts in `review`, suggests publication targets based on `target_form`  |
| A draft needs sources       | Creates `drafts/<slug>/research/` on demand instead of a top-level folder               |
| You publish                 | Auto-fills `canonical_url` when you paste a Substack / Ghost / Mirror URL into the file |

## Cadence

| When                        | Do                                                                                      |
| --------------------------- | --------------------------------------------------------------------------------------- |
| Whenever a premise strikes  | One line into `ideas/`. That's the whole capture step                                   |
| When you commit to a piece  | Promote the idea into `drafts/`; write with your agent, revisions tracked automatically |
| On ship                     | Move to `published/`; let the agent fill `canonical_url`                                |
| To revise a published piece | Copy it back to a new draft — never edit `published/` in place                          |
| Monthly                     | Ask the agent to surface stale ideas and stalled drafts                                 |

## Further reading

- **[LLM wiki workflow](https://openknowledge.ai/docs/workflows/karpathy-llm-wiki).** If your writing is source-grounded research rather than original prose, the source-curation posture fits better.
- **[Editor](https://openknowledge.ai/docs/features/editor).** The WYSIWYG surface you'll actually draft in.
- **[Timeline and recovery](https://openknowledge.ai/docs/features/timeline-and-recovery).** Where your draft revisions live — the reason there are no named-revision folders.
- **[Claude Code](https://openknowledge.ai/docs/integrations/claude-code)**, **[Cursor](https://openknowledge.ai/docs/integrations/cursor)**, **[Codex](https://openknowledge.ai/docs/integrations/codex)**, **[OpenCode](https://openknowledge.ai/docs/integrations/opencode).** MCP-capable agent hosts.

# Frontmatter schemas (https://openknowledge.ai/docs/advanced/content-rules/frontmatter)

Validate document frontmatter against standard JSON Schema files — scoped to folders with globs, with schema-driven selects in the property panel and a no-code schema editor.

Frontmatter schemas is a content-rules linter that validates each document's YAML frontmatter against [JSON Schema](https://json-schema.org/) files. The schema files are plain, portable JSON Schema — nothing OK-specific in them — so the same files work with any external tool that understands the standard.

This page covers the schema mappings, the schema files, the no-code editors, and what agents see. For the linter-agnostic parts — where problems appear, the Problems panel, `ok lint`, and agent advisories — see the [content rules overview](https://openknowledge.ai/docs/advanced/content-rules/overview).

## Schema mappings

Which docs validate against which schema lives in the project's `config.yml` (shared via git):

```yaml
contentRules:
  frontmatter:
    enabled: true
    schemas:
      - appliesTo: "docs/**"
        file: ".ok/schemas/doc.schema.json"
      - appliesTo:
          - "specs/**"
          - "!**/index"
        file: ".ok/schemas/spec.schema.json"
```

- **`file`** is a project-root-relative path to a JSON Schema file. Schemas can live anywhere in the project; `.ok/schemas/` is the default home the settings editor creates them in.
- **`appliesTo`** is a glob or list of globs matched against content-relative, extension-less doc paths (`docs/guide`, not `docs/guide.md`). A leading `!` excludes, and a doc is in scope when it matches a positive glob and no negated one. A list with no positive glob gains an implicit `**`, so `["!drafts/**"]` means every doc except drafts; an absent or empty `appliesTo` means **every doc**.
- **Bad globs surface on the config channel, never per-doc.** One that can't compile matches nothing and is reported as a configuration problem — it never silently widens a mapping. One that compiles but can't match a normalized doc path gets an advisory instead: a trailing slash (`docs/`), a leading slash (`/docs/**`), or a file extension (`docs/**/*.md`), none of which appear in the paths being matched.
- **Every matching mapping validates.** Two schemas both in scope for a doc act as a conjunction — the frontmatter must satisfy both. One file mapped from several entries still validates once.
- Each mapping has its own **`enabled`** toggle, so you can park a mapping without deleting it. Absent means enabled — set `enabled: false` to park one.

## Schema files

A schema file that is missing, malformed, or refused by the validator is a **configuration problem** — it shows at the top of the Problems panel's project scope, in `ok lint` warnings, and on the MCP `lint` tool, never as a per-doc diagnostic.

A doc with no frontmatter block (or unparsable YAML) validates as an empty object — so `required` fields still report on brand-new docs. The reverse also works: a schema with `"maxProperties": 0` enforces that docs carry no frontmatter at all.

### Dialects

Four dialects are supported, each validated against its own rules:

| `$schema`                                      | Dialect  |
| ---------------------------------------------- | -------- |
| `http://json-schema.org/draft-06/schema#`      | draft-06 |
| `http://json-schema.org/draft-07/schema#`      | draft-07 |
| `https://json-schema.org/draft/2019-09/schema` | 2019-09  |
| `https://json-schema.org/draft/2020-12/schema` | 2020-12  |

Each is accepted in `http` or `https` form, with or without the trailing `#`. An **absent `$schema` is treated as draft-07**, and that is also what **New schema** scaffolds — the widest-compatibility choice, and the friendly subset the Fields editor writes is spelled identically in every dialect. A file declaring anything else (draft-04 and older) is skipped with a configuration problem.

Three things worth knowing:

- **`format` always asserts.** From 2019-09 on, the spec demotes `format` to an annotation that validators may ignore. OK asserts it on every dialect instead, so raising a schema's `$schema` never silently switches off format checks it already had. This is deliberately stricter than the spec.
- **The Fields editor doesn't model `prefixItems`.** 2020-12 spells tuple arrays as `prefixItems`, which the no-code editor leaves alone — it appears in the "advanced rules" note alongside `allOf` and friends, and survives every edit verbatim. Uniform arrays (`items`) are modeled as usual.
- **draft-06 is validated by the draft-07 engine.** draft-07 only added keywords, so every draft-06 construct behaves identically — but the reverse also holds: keywords draft-07 introduced (`if`/`then`/`else`, `readOnly`, `writeOnly`) are honored in a schema that declares draft-06, where a strict draft-06 validator would ignore them. Harmless unless you rely on another tool ignoring them.

## What a finding looks like

Take the schema above governing `docs/**`, and a doc that misses one required field and misspells an enum value:

```markdown
---
status: shipped
---

# Guide
```

`ok lint` (and the Problems panel) reports both violations, each carrying source `frontmatter` and the violated JSON Schema keyword as its code:

```text
docs/guide.md
  1:1     warning  Frontmatter property "owner" is required  frontmatter/required
  2:1     warning  Frontmatter property "status" must be one of: draft, review, published (got "shipped")  frontmatter/enum

2 problems (0 errors, 2 warnings) across 1 file.
Checks run: frontmatter.
```

As a diagnostic in `ok lint --json` or the MCP `lint` tool's structured content (0-based, end-exclusive ranges — the text report is 1-based):

```json
{
  "range": { "start": { "line": 1, "character": 0 }, "end": { "line": 1, "character": 15 } },
  "severity": "warning",
  "source": "frontmatter",
  "code": "enum",
  "message": "Frontmatter property \"status\" must be one of: draft, review, published (got \"shipped\")"
}
```

**Anchoring.** A violation on a key underlines that key's line (the `enum` finding above sits on line 2, where `status:` is written); a missing `required` field anchors to the opening `---` fence; a doc with no fence anchors to the top. And a broken schema file is never a per-doc finding — it rides the configuration channel instead, as a `!` line in `ok lint`:

```text
! frontmatter schema .ok/schemas/missing.schema.json: cannot read (ENOENT: no such file or directory, …)
```

Frontmatter findings are always **warnings** — there's no severity promotion yet, so `ok lint --errors-only` won't gate on them — and none carry `fixes` (choosing the right value is a human decision), so `ok lint --fix` and the MCP `fix: true` leave them in place, still reported.

## The settings editor

**Settings ▸ Plugins ▸ Frontmatter schemas** (once the plugin is enabled) lists the project's schema files: search, and **New schema** to scaffold a fresh file in `.ok/schemas/`. Each entry shows a plain-language summary of its scope ("everything under docs/ — except any doc named index") with a live match count ("Matches 12 of 40 docs right now"), a **Pick folders** button, an editor for the `appliesTo` globs, an enable toggle, and delete for schema files OK manages. **Pick folders** scopes a schema to folders without hand-writing glob syntax: checking a folder authors its recursive `folder/**` pattern into the glob editor, and unchecking removes it. The live count is the immediate tell when a hand-typed pattern misses (a bare folder name like `blog` matches only a doc literally named `blog`, so it reads "Matches 0 of N docs right now" until it becomes `blog/**`).

The toggle is the only control over whether a schema validates. Turning it off keeps the schema's `appliesTo` globs, so turning it back on restores them — the toggle never discards the mapping. To drop a schema from `config.yml` entirely, edit `config.yml` directly. (Deleting a schema file is the one destructive action here: it removes the file and, with it, its `config.yml` mapping.)

## The schema editor

Opening a schema file in the editor (any `*.schema.json`, or a `.json` under `.ok/schemas/`) gets a **Source / Fields** toggle, like the markdownlint config's rule browser. **Source** shows the raw JSON; **Fields** is a no-code editor: each frontmatter field as a row with its type (string, number, boolean, enum, array, object), required toggle, description, allowed values, and pattern — recursing into object fields and array-of-object elements for nested frontmatter.

Edits are **non-destructive**: the editor merges only the field you changed, and keywords it doesn't model (`allOf`, `if`/`then`, `x-` extensions, …) survive verbatim — with a note when the schema carries root-level advanced rules the Fields view doesn't show. Your hand-written schema stays yours.

## The property panel

The document panel's property editor reads the same schemas through the same `appliesTo` matching as the linter, so the two can never disagree. A field constrained by `enum` renders as a select instead of free text; an array field with `items.enum` renders as a multi-select. When several schemas govern a doc, the offered values are the intersection — and if no value could satisfy them all, the field falls back to free text while the linter reports the conflict.

Adding a property is schema-aware too. The **Add properties** button carries a badge counting the schema-required properties the document is missing, and clicking it stages one pre-named row per missing property — the widget type taken from the schema, the cursor in the first value. Nothing is written until you fill a row in and add it, so a half-finished batch never leaves an empty property behind or clears a `required` warning with a blank value. The name field on any add-row also offers the fields the governing schemas declare — filtered as you type, each showing its type and description and marking the required ones; picking one fills in the name and type together. It stays free text, so a property no schema declares is still yours to add, and enum-constrained fields offer their vocabulary while being added, not only once they exist.

## AI agents

Beyond the shared `lint` tool and write advisories (see the [overview](https://openknowledge.ai/docs/advanced/content-rules/overview#ai-agents)), reads advertise the contract up front: `exec` listings and reads carry a `schemas:` entry naming the schema files that govern each doc — and each folder, so an agent learns the expected shape **before** writing the first doc there. An `exec("ls docs/")` comes back with:

```text
- **docs/** (directory) — schemas: .ok/schemas/doc.schema.json — 2 md files — most recent: guide.md (docs/guide.md, 2026-07-21)
- **docs/guide.md** (docs/guide.md) — status: shipped — schemas: .ok/schemas/doc.schema.json — backlinks: 0
```

The server resolves the globs; agents never evaluate `appliesTo` themselves. And when a write violates a governing schema, the write still lands and the response carries the violations as `lint-violation` advisories (1-based `line`/`column`), so the agent can correct its own frontmatter in a follow-up edit:

```json
"warnings": [
  {
    "kind": "lint-violation",
    "source": "frontmatter",
    "code": "required",
    "message": "Frontmatter property \"owner\" is required",
    "severity": "warning",
    "line": 1,
    "column": 1
  }
]
```

## See also

- [Content rules overview](https://openknowledge.ai/docs/advanced/content-rules/overview): where problems show up, the Problems panel, `ok lint`, and agents
- [markdownlint](https://openknowledge.ai/docs/advanced/content-rules/markdownlint): the sibling linter for markdown style
- [Configuration reference](https://openknowledge.ai/docs/reference/configuration): `config.yml` and what's shared via git
- [JSON Schema specification links](https://json-schema.org/specification-links): the supported dialects

# markdownlint (https://openknowledge.ai/docs/advanced/content-rules/markdownlint)

The markdownlint linter for content rules — VS Code-parity defaults, your project's native `.markdownlint.*` config, and a no-code browser for all 53 rules.

markdownlint is the content-rules linter for markdown style — the standard [markdownlint](https://github.com/DavidAnson/markdownlint) engine, the same one behind `markdownlint-cli2` and the popular editor extensions.

This page covers its defaults, how it reads your project's config, and the built-in rule browser. For the linter-agnostic parts — where problems appear, the Problems panel, `ok lint`, and agent advisories — see the [content rules overview](https://openknowledge.ai/docs/advanced/content-rules/overview). For validating frontmatter rather than markdown style, see [Frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter).

## Default rules

The rules aren't stored in OK config — they live in your project's own native markdownlint file (see [below](https://openknowledge.ai/docs/advanced/content-rules/markdownlint#your-markdownlint-file)). With **no file**, OK uses the same default as the [VS Code markdownlint extension](https://github.com/DavidAnson/vscode-markdownlint): every rule on, except `MD013` (line-length). Nothing else is pre-disabled — so, exactly as in VS Code:

- `MD033` flags inline HTML, including MDX/JSX components like `<Callout>`,
- `MD041` flags docs that don't open with a top-level heading,
- `MD025` flags a frontmatter `title` beside an H1.

Turn off whichever don't fit your project — the first change [creates the file for you](https://openknowledge.ai/docs/advanced/content-rules/markdownlint#the-rule-browser).

## Your `.markdownlint.*` file

The rules live in your project's own native markdownlint file — `.markdownlint.jsonc`, `.markdownlint.json`, `.markdownlint.yaml`, or `.markdownlint.yml` (`.markdownlintrc` is recognized too; the first that exists wins). It's a plain, portable markdownlint config: the same file `markdownlint-cli2` and editor extensions read, with nothing OK-specific in it.

- **A file governs wholesale.** When a native file exists it's honored exactly as `markdownlint-cli2` would honor it — OK layers nothing underneath.
- **Per-folder cascade.** The nearest `.markdownlint.*` file on the walk from a doc's folder up to the project root governs that doc, matching `markdownlint-cli2` conventions. Subfolders inherit shared config explicitly via `extends` with a relative path (npm-package `extends` isn't resolved).
- **Aliases and severities.** Rule aliases are valid keys (`line-length` ≡ `MD013`, matched case-insensitively), and `"error"` / `"warning"` severity strings are honored — either as the rule's value (`"MD010": "error"`) or as a `severity` key in its options object. Everything else reports as a warning.
- **Executable configs.** A `.markdownlint.cjs` / `.mjs` is detected but **not executed**: you get a loud configuration warning, linting falls back to OK's defaults, and OK will never rewrite the file. Convert it to JSON, JSONC, or YAML to have it govern.

## The rule browser

**Settings ▸ Plugins ▸ markdownlint** (markdownlint appears there once the plugin is enabled) lists the full catalog — all 53 rules, generated from the installed engine's own config schema, so it always matches what actually runs. Search by id, alias, or name; browse by category (Headings, Lists, Whitespace, Code, Links & images, Style); or check **Only modified** to see just the rules your config changes. Expanding a rule reveals a link to its upstream documentation and typed editors for each of its options.

The same browser also opens **directly on the config file**. Open your project's root `.markdownlint.json` or `.markdownlint.jsonc` in the editor (reveal hidden files to find it) and use the **Source / Rules** toggle: **Source**, the default, shows the raw file read-only; **Rules** is the same no-code browser. Your choice is remembered separately from the document editor's own Visual/Markdown mode.

That toggle appears for JSON and JSONC configs only — a `.markdownlint.yaml`, `.markdownlint.yml`, or `.markdownlintrc` opens in the read-only file preview instead. And because rule edits target the project's governing root config, **Rules** is enabled only for that file; open a nested JSON config and **Rules** is disabled with a tooltip while **Source** still shows it.

Every edit writes back to your native `.markdownlint.*` file, and a rule you configured under an alias stays under that alias. JSON and JSONC files get minimal text edits, so comments and formatting survive; a YAML config is re-serialized, which drops its comments. Severity strings are shown as a read-only badge. If the project has no file yet, the first change creates a `.markdownlint.json` seeded with the defaults — from then on that file is the whole story for OK and every other markdownlint tool.

## What a finding looks like

A markdownlint finding carries the rule id as its `code` under source `markdownlint` — rendered as `markdownlint/MD010` in reports — with the upstream engine's message. In `ok lint` text output:

```text
docs/guide.md
  7:5     warning  Hard tabs: Column: 5  markdownlint/MD010
```

And as a diagnostic in `ok lint --json` (or the MCP `lint` tool's structured content), where an auto-fixable rule also carries its `fixes` — the exact text edits `--fix` or the editor's **Fix** action would apply:

```json
{
  "range": { "start": { "line": 6, "character": 4 }, "end": { "line": 6, "character": 5 } },
  "severity": "warning",
  "source": "markdownlint",
  "code": "MD010",
  "message": "Hard tabs: Column: 5",
  "fixes": [
    { "range": { "start": { "line": 6, "character": 4 }, "end": { "line": 6, "character": 5 } }, "newText": " " }
  ]
}
```

JSON ranges are 0-based and end-exclusive; the text report is 1-based. A rule that can't be auto-fixed simply has no `fixes` key. The shared report shapes — file grouping, counts, configuration warnings — are on the [overview](https://openknowledge.ai/docs/advanced/content-rules/overview#what-it-returns).

## Auto-fix and severities

- **Auto-fix covers only some rules.** markdownlint can mechanically fix problems like hard tabs, trailing whitespace, list-marker style, and blank-line spacing — but not ones that need a human decision, such as inline HTML (`MD033`) or a missing top-level heading (`MD041`). In source mode the **Fix** action appears only on a problem markdownlint can fix; the Problems panel offers **Fix all** to apply every fixable problem in the current scope at once; and `ok lint --fix` does the same across the whole project. Whatever's left is still reported, untouched — no affordance resolves the rules that need a human decision.
- **Severities.** A rule reports as a warning unless your config promotes it to `"error"`. Set `"error"` as the rule's value or via a `severity` key, then gate CI on just those rules with `ok lint --errors-only`.

## See also

- [Content rules overview](https://openknowledge.ai/docs/advanced/content-rules/overview): where problems show up, the Problems panel, `ok lint`, and agents
- [Frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter): the sibling linter for frontmatter validation
- [Configuration reference](https://openknowledge.ai/docs/reference/configuration): `config.yml` and what's shared via git
- [markdownlint rules](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md): the upstream rule reference

# Overview (https://openknowledge.ai/docs/advanced/content-rules/overview)

A non-blocking linting layer over your project's markdown — problems as you write, a Problems panel, `ok lint` for CI, and advisories for AI agents. Enable it per project.

Content rules are a lightweight linting layer over your project's markdown. **Enable a linter for a project** and it checks your markdown as you write. Findings are **non-blocking**: they surface as warnings, never stop a save, and never gate an agent's edit.

Three linters ship today. [markdownlint](https://openknowledge.ai/docs/advanced/content-rules/markdownlint) is the standard engine for markdown style — hard tabs, heading increments, list markers, and the rest. [Frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter) validates each doc's frontmatter against standard JSON Schema files. [OKF](https://openknowledge.ai/docs/plugins/okf) checks portability to the Open Knowledge Format. The system is pluggable and more linters are planned.

This page covers what's the same whatever linter runs; each linter's page covers its own rules and configuration.

## Where problems show up

- **Source mode** — wavy underlines on the offending range, markers in the lint gutter, hover tooltips, and (for auto-fixable rules) an inline **Fix** action.
- **WYSIWYG** — the block a problem falls in is marked, so issues stay visible without raw markdown lines to underline.
- **The Problems panel** — a tab in the document panel on the right, with a live count badge and two scopes:
  - **This doc** — live diagnostics for the open document, in both WYSIWYG and source mode. Click a problem to jump to it: source mode lands on the exact line and column, WYSIWYG scrolls to the block it falls in.
  - **Project** — an on-demand audit of every in-scope document. It runs when you first open the scope and on the refresh button — never in the background. Results group per file with error/warning totals; configuration problems (a malformed config file, a broken `extends`) surface at the top. Clicking a problem opens the offending doc at that position.
- **The file sidebar** — a file with problems has its row tinted (red for errors, amber for warnings) and carries a count badge. The badge is a control: click it, or tab to it and press Enter or Space, and the file opens with the Problems panel showing that file's problems in **This doc** scope. Keyboard activation moves focus into the panel; clicking preserves pointer focus. Set `validation.fileTreeIndicators: false` in the project's `config.yml` to turn the tint and the badge off (see the [configuration reference](https://openknowledge.ai/docs/reference/configuration)).

Skill documents are excluded from link checking: files in a skills folder (`.claude/skills`, `.agents/skills`, `.github/skills`, and the like) and the skill documents you open from the Skills panel. A skill's links routinely name files it creates only when it runs, so they report as broken by construction. What goes quiet is the Problems panel in both scopes, and with it the squiggles and gutter marks the panel drives. What stays is the link styling the editor resolves on its own as you type, so a wiki link to a file the skill has not created yet still renders as unresolved, and the Links panel still lists it under Missing. In short: the skill's Problems tab reads clean while the link itself may still look broken in the text. `links({ kind: "dead" })` and `GET /api/dead-links` list them too.

The exclusion is scoped to the skills folder itself, not to the dot-directory containing it, so ordinary documents that happen to live under a dot directory (`.github/CI_RUNBOOK.md`, a `.changeset` entry) keep their link findings. So do folder templates under `.ok/templates`, which you author like any document. A skills folder you added yourself at a visible path, such as `team/skills`, is ordinary content and is still checked. Links from your other documents to a skill still validate normally.

In both scopes, each row tags the validator that produced it in uppercase — for example `MARKDOWNLINT`, `FRONTMATTER`, or `LINKS` — next to the rule code, and a finding that repeats collapses into one row with an instance count you can expand to reach the individual lines.

The panel is also available in [single-file sessions](https://openknowledge.ai/docs/features/editor#open-a-single-file) (`ok <file>`).

## Enabling a linter

Open **Settings ▸ This project ▸ Plugins** and turn on a linter; open editors react live. Each linter has its own toggle, off until you enable it. The choice is saved to the project's `config.yml` (see the [configuration reference](https://openknowledge.ai/docs/reference/configuration)), so committing it shares the setting with every collaborator — the whole-project equivalent of a committed lint config.

## From the command line

`ok lint` runs content rules headlessly, with the same config resolution as the editor:

```bash
ok lint                 # audit the whole project
ok lint guides/         # scope to a folder
ok lint guides/intro.md # or a single file
ok lint --fix           # apply auto-fixes in place
ok lint --json          # structured JSON output
ok lint --errors-only   # exit non-zero only on error-severity problems
```

The exit code is non-zero when any problem is found. Findings are warnings unless your `.markdownlint.*` promotes a rule to `"error"`, so `--errors-only` gates CI on just the rules you chose to enforce. Only markdownlint rules can be promoted — [frontmatter](https://openknowledge.ai/docs/advanced/content-rules/frontmatter) findings are always warnings, so `--errors-only` never gates on them.

### What it returns

The text report lists each finding as `line:column` (1-based), severity, message, and a composed `source/code` id naming the linter and the violated rule:

```text
docs/guide.md
  7:5     warning  Hard tabs: Column: 5  markdownlint/MD010
  1:1     warning  Frontmatter property "owner" is required  frontmatter/required
  2:1     warning  Frontmatter property "status" must be one of: draft, review, published (got "shipped")  frontmatter/enum

3 problems (0 errors, 3 warnings) across 1 file.
Checks run: markdownlint, frontmatter.
```

Configuration problems (a malformed config file, a broken schema) print as `!` lines after the findings — they describe your setup, not a document. `--json` emits the same data as a machine-readable object:

```json
{
  "contentDir": "/path/to/project",
  "files": [
    {
      "file": "docs/guide.md",
      "fixed": false,
      "diagnostics": [
        {
          "range": { "start": { "line": 6, "character": 4 }, "end": { "line": 6, "character": 5 } },
          "severity": "warning",
          "source": "markdownlint",
          "code": "MD010",
          "message": "Hard tabs: Column: 5",
          "fixes": [
            { "range": { "start": { "line": 6, "character": 4 }, "end": { "line": 6, "character": 5 } }, "newText": " " }
          ]
        },
        {
          "range": { "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 3 } },
          "severity": "warning",
          "source": "frontmatter",
          "code": "required",
          "message": "Frontmatter property \"owner\" is required"
        },
        {
          "range": { "start": { "line": 1, "character": 0 }, "end": { "line": 1, "character": 15 } },
          "severity": "warning",
          "source": "frontmatter",
          "code": "enum",
          "message": "Frontmatter property \"status\" must be one of: draft, review, published (got \"shipped\")"
        }
      ]
    }
  ],
  "warnings": [],
  "fileCount": 1,
  "errorCount": 0,
  "warningCount": 3,
  "fixedCount": 0,
  "ran": ["markdownlint", "frontmatter"]
}
```

Conventions to know:

- JSON ranges are **0-based and end-exclusive** (LSP-aligned); the text report displays 1-based positions.
- `fixes` appears only on auto-fixable findings — its presence is how tooling knows `--fix` would resolve the problem.
- `fixed` is `true` on each file `--fix` rewrote, and the top-level `fixedCount` counts **files**, not problems.
- The top-level `warnings` array carries configuration or runtime degradation problems.
- The top-level `ran` array names the enabled source families selected for the run. A family absent from `ran` was not checked. An explicit `[]` means no checks were selected at all. It does not mean the document was checked and found clean.

With `--fix`, fixable findings are applied in place, fixed files are marked `(fixed)`, and the report lists what remains:

```text
docs/guide.md (fixed)
  1:1     warning  Frontmatter property "owner" is required  frontmatter/required
  2:1     warning  Frontmatter property "status" must be one of: draft, review, published (got "shipped")  frontmatter/enum

2 problems (0 errors, 2 warnings) across 1 file.
Fixed 1 file.
Checks run: markdownlint, frontmatter.
```

Every linter ships off, so a project that has not enabled one closes its report with `No checks ran.` in place of the `Checks run:` line. The `✓ No problems` above it then means only that nothing was checked. Turn a linter on under **Settings ▸ This project ▸ Plugins**.

`ok audit` widens the same report to the full validation plane — content-rule problems **and** broken internal links, each finding tagged with its source:

```bash
ok audit                 # audit the whole project (lint + links)
ok audit guides/         # scope to a folder or a single file
ok audit --json          # the full structured diagnostic plane
ok audit --errors-only   # exit non-zero only on error-severity problems
```

Unlike `ok lint`, `ok audit` needs the project's server running (`ok start` or OK Desktop) — the links validator reads the live backlink index. There's no `--fix` because the audit is read-only (lint fixes go through `ok lint --fix`, link repairs are content edits).

`--json` returns the same per-file grouping as `ok lint --json`, with two differences: the audit never writes, so there's no `contentDir`, `fixed`, or `fixedCount`; and a `links` finding carries `linkTarget` — the unresolved target verbatim, so tooling never parses it back out of the message:

```json
{
  "files": [
    {
      "file": "docs/guide.md",
      "diagnostics": [
        {
          "range": { "start": { "line": 11, "character": 0 }, "end": { "line": 11, "character": 0 } },
          "severity": "warning",
          "source": "links",
          "code": "dead-link",
          "message": "Link target \"guides/setup\" does not resolve to an existing document.",
          "linkTarget": "guides/setup"
        }
      ]
    }
  ],
  "warnings": [],
  "fileCount": 1,
  "errorCount": 0,
  "warningCount": 1,
  "ran": ["markdownlint", "frontmatter", "links"]
}
```

The audit's top-level `ran` array reports the selected source families: enabled document linters plus `links` unless link validation is `off`. OKF's document and project-tree checks share the public `okf` family, so `ran` reports selection, not the number of internal validators or a pass verdict.

Broken links are warnings by default. The project's `validation.links` setting — **Settings ▸ This project ▸ Content rules** — decides both whether they appear and at what severity: `warning` (the default), `error` to gate CI on them with `--errors-only`, or `off` to drop them from the plane entirely. Content-rule findings keep their own severities, so `--errors-only` covers both planes at once. Skill documents are the one exception: their link findings are always excluded, whatever this setting says, because a skill's links name files it creates only at runtime.

## AI agents

Agents get the same signal you see, across three surfaces. See the [MCP reference](https://openknowledge.ai/docs/reference/mcp) for the full tool list.

### The `lint` tool

Lints a single document, or audits the project when `document` is omitted (`path` scopes it to a folder or file). `fix: true` — which requires `document` — auto-fixes fixable rules in place, attributed and live in the preview, the same result as the editor's **Fix** action.

A single-document call returns a readable summary. Configuration problems appear under a `Lint incomplete` warning block, and the closing hint tells the agent whether `fix: true` would help:

```text
docs/guide.md: 2 warnings
  ⚠ line 1 frontmatter/required: Frontmatter property "owner" is required
  ⚠ line 2 frontmatter/enum: Frontmatter property "status" must be one of: draft, review, published (got "shipped")
Lint incomplete — 1 warning (findings may be partial):
  ⚠ frontmatter schema .ok/schemas/missing.schema.json: cannot read (ENOENT: no such file or directory, …)
None are auto-fixable — these need content edits via `edit`/`write`.
Checks run: frontmatter.
```

With no linter enabled the report closes with `No checks ran.` instead, the same string `ok lint` prints.

The structured content is close to `ok lint --json`, with three differences:

|                     | `ok lint --json` | MCP `lint`                                                                   |
| ------------------- | ---------------- | ---------------------------------------------------------------------------- |
| Project path        | `contentDir`     | `cwd`                                                                        |
| `fixedCount` counts | files rewritten  | **problems** resolved                                                        |
| Cap fields          | —                | `omittedFileCount`, per-file `omittedDiagnosticCount`, `omittedWarningCount` |

Everything else matches: `files[].diagnostics` with 0-based `range`, `severity`, `source`, `code`, `message`, plus `errorCount`, `warningCount`, `fileCount` on an audit, configuration or degradation `warnings`, and `ran` for the selected source families. Audit output is capped project-wide at 10 warnings, plus 10 files × 10 diagnostics per file — the text channel marks the remainder with "… and N more", the structured channel with the three cap fields above. Counts always reflect the full scan, and re-running with a narrower `path` recovers the detail. With `fix: true` the summary reports what was applied and what remains: `Fixed 1 problem in docs/guide.md.` followed by the unfixable findings.

### The `audit` tool

The agent-side `ok audit` — content-rule problems and broken links in one read-only call, grouped by file, same 10 × 10 + 10-warning cap with `omittedWarningCount` when warnings are dropped, no fix shape. Its text and structured results both report `ran`. If a selected validator degrades, its family remains in `ran` and the `warnings` channel explains why; a partial degradation may still have contributed findings.

### Write responses

Every write response carries validation findings for the document it touched, on two channels. Both nest under `document` in the structured content, and both are advisory: a finding never blocks the write.

```json
"document": {
  "brokenLinks": [
    { "href": "./guides/setup", "resolvedTo": "guides/setup", "reason": "no-such-doc" }
  ],
  "warnings": [
    {
      "kind": "lint-violation",
      "source": "frontmatter",
      "code": "enum",
      "message": "Frontmatter property \"status\" must be one of: draft, review, published (got \"shipped\")",
      "severity": "warning",
      "line": 2,
      "column": 1
    }
  ]
}
```

**`warnings`** carries up to 10 findings across the whole validation plane — lint violations and broken links alike, honoring the project's `validation.links` setting. Positions are 1-based (`line`/`column`), ready to echo back to a human, and a `links` finding adds `linkTarget`. The field is present only when the write produced findings.

**`brokenLinks`** is the dedicated link channel, and unlike `warnings` it is **always present** — an empty array is the positive "every outbound link resolves" confirmation, which saves a separate `links({ kind: "dead" })` round-trip. Each entry names the href exactly as authored, so an agent can grep for it. `reason` is `no-such-doc` (resolved to a docName that doesn't exist), `no-such-file` (a linked asset or source file missing from disk), or `unresolvable` (an empty href, or a relative path escaping the content root); `resolvedTo` is `null` for `unresolvable`.

## See also

- [markdownlint](https://openknowledge.ai/docs/advanced/content-rules/markdownlint): the rules, your `.markdownlint.*` config, and the rule browser
- [Frontmatter schemas](https://openknowledge.ai/docs/advanced/content-rules/frontmatter): JSON Schema validation of document frontmatter, and the schema editor
- [Editor](https://openknowledge.ai/docs/features/editor): source mode, WYSIWYG, and the document panel
- [Configuration reference](https://openknowledge.ai/docs/reference/configuration): `config.yml` and what's shared via git
- [MCP reference](https://openknowledge.ai/docs/reference/mcp): the tools agents use to read and write your project

# CLI (https://openknowledge.ai/docs/remote-control/methods/cli)

Run OpenKnowledge with the CLI on your computer, a VM, or a server.

Install OpenKnowledge from npm, then run the server yourself:

```bash
npm install -g @inkeep/open-knowledge
```

Run OpenKnowledge on a laptop or an always-on server. Use a private network, tunnel, or reverse proxy to make it reachable. See the [Remote Control overview](https://openknowledge.ai/docs/remote-control/overview) to choose an access method.

## Your laptop, behind a tunnel

A tunnel gives the OpenKnowledge server on your laptop an HTTPS URL. The server is available only while the laptop is awake. For continuous access, use [an always-on server](https://openknowledge.ai/docs/remote-control/methods/cli#a-server-you-manage) or the [Docker method](https://openknowledge.ai/docs/remote-control/methods/docker).

### 1. Pin a port and open a tunnel

A local start normally picks a free port dynamically, but a tunnel forwards to one fixed port, so pin it first. Add a `server:` block to the `.ok/config.yml` that `ok init` created (the file is all commented defaults until you add keys):

```yaml title=".ok/config.yml"
server:
  port: 8080
```

Any HTTPS tunnel works. It forwards a public URL to that local port. Pick one:

> **Info**
>
> Install the tunnel client on the machine before continuing.

> **Warn**
>
> Tailscale Serve stays inside your tailnet. Before starting ngrok or Cloudflare Tunnel, [set up authentication](https://openknowledge.ai/docs/remote-control/authentication) at that edge. Do not open an unprotected public URL.

### Tailscale

Private, only devices on your tailnet can reach it. Requires [HTTPS enabled in your tailnet](https://tailscale.com/kb/1153/enabling-https).

```bash
tailscale serve --bg 8080
# → https://<machine>.<tailnet>.ts.net
```

If `serve` says Tailscale is stopped, run `tailscale up` first (on a Linux server you may also need `sudo systemctl start tailscaled`).

### ngrok

Public URL. Add access control at the edge. See [Authentication](https://openknowledge.ai/docs/remote-control/authentication).

```bash
ngrok http 8080
# → https://<something>.ngrok.app
```

### Cloudflare Tunnel

A quick public URL on a `*.trycloudflare.com` address, fresh each run. For a stable URL on your own domain, and to pair with [Cloudflare Access](https://openknowledge.ai/docs/remote-control/authentication), set up a [named tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) instead.

```bash
cloudflared tunnel --url http://localhost:8080
```

Each one gives you an HTTPS URL that clients can reach. Tailscale keeps it private. ngrok and Cloudflare need access control at the edge. That URL is your `server.externalUrl`.

### 2. Configure and start

Declare the public origin in the project config, next to the port:

```yaml title=".ok/config.yml"
server:
  port: 8080
  externalUrl: https://<your-tunnel-url>
```

> **Warn**
>
> **`server.externalUrl` must match the public address.** Use the full tunnel URL with `https://` and no trailing path. A mismatch returns `403`.

Then consent on this machine, in the gitignored per-machine config:

```yaml title=".ok/local/config.yml"
server:
  allowExternal: true
  idleShutdown: "off"
```

`idleShutdown: "off"` keeps the server from stopping after 30 idle minutes. The idle timer only counts editor connections, not agent calls, so without it a server busy with a remote agent looks idle and shuts down mid-session.

Now start it from the project directory:

```bash
ok start
```

The server prints a warning banner naming the public origin on every exposed start. For a one-off session, the environment does the same job without touching config files:

```bash
OK_ALLOW_EXTERNAL=1 OK_IDLE_SHUTDOWN=off ok start --external-url https://<your-tunnel-url> --port 8080
```

If the URL is public, [add access control](https://openknowledge.ai/docs/remote-control/authentication) before opening it. Tailscale Serve already limits access to your tailnet. Once the route is protected, [try it out](https://openknowledge.ai/docs/remote-control/overview#try-it-out).

## A server you manage

Use a VPS, Mac mini, or another always-on computer. This keeps the knowledge base available continuously.

### Set up the server

Create an Ubuntu 24.04 VPS with Hetzner, DigitalOcean, Lightsail, or another provider. Allocate 1 GB of RAM. A small knowledge base can run with 512 MB. Add an SSH key during setup, then connect with `ssh root@<ip>`. Run the following commands in that shell.

Install Node 24 and OpenKnowledge:

```bash
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo bash - && sudo apt-get install -y nodejs git
npm install -g @inkeep/open-knowledge
```

Create the project (`ok init` sets up git for you):

```bash
mkdir ~/knowledge && cd ~/knowledge && ok init
```

To start from existing notes, `git clone` them as the project directory instead of `mkdir`, then run `ok init` inside.

### Configure network access

Choose one of these access methods:

- **A tunnel.** Follow the [laptop tunnel steps](https://openknowledge.ai/docs/remote-control/methods/cli#1-pin-a-port-and-open-a-tunnel) on the server. With Tailscale, add the server to your tailnet. Only devices on the tailnet can connect.
- **Your own domain with a reverse proxy.** Point DNS at the server and let [Caddy](https://caddyserver.com/) terminate TLS (it provisions certificates automatically):

  ```text title="/etc/caddy/Caddyfile"
  notes.example.com {
      reverse_proxy 127.0.0.1:8080
  }
  ```

  ```yaml title=".ok/config.yml"
  server:
    port: 8080
    externalUrl: https://notes.example.com
  ```

  Caddy automatically handles the [proxy rules](https://openknowledge.ai/docs/remote-control/methods/docker#rules-for-anything-in-front-of-the-server), including Host preservation, `X-Forwarded-Proto`, and no buffering on `/mcp`. Do not add an `encode` directive on `/mcp`. Configure these rules yourself when using another proxy such as nginx. Without them, requests may return `403` and the editor's WebSocket may fail.

For either option, allow external access in `.ok/local/config.yml` by setting `allowExternal: true` and `idleShutdown: "off"`. Use the same [configuration and start steps](https://openknowledge.ai/docs/remote-control/methods/cli#2-configure-and-start) as the laptop setup.

### Run it as a service

So it survives logout and reboots:

```ini title="/etc/systemd/system/openknowledge.service"
[Unit]
Description=OpenKnowledge server
After=network-online.target

[Service]
User=root
WorkingDirectory=/root/knowledge
ExecStart=/usr/bin/env ok start
Restart=on-failure

[Install]
WantedBy=multi-user.target
```

This matches the `ssh root@<ip>` VPS setup above. The project is at `/root/knowledge`, and the `server.*` settings are in the config file, so the unit needs no flags. If you use a different account, update `User` and `WorkingDirectory` to match that account and project path. For a more secure setup, create a dedicated unprivileged account with `sudo useradd --system --create-home openknowledge`. Then update `User` and `WorkingDirectory` to match.

Register the service so it starts on every boot, and launch it now:

```bash
sudo systemctl enable --now openknowledge
```

> **macOS and Windows do not use systemd.** Run `ok start` in a terminal and disable sleep. For automatic restarts, use launchd on macOS, or Task Scheduler or a Windows service on Windows.

If the URL is public, [add access control](https://openknowledge.ai/docs/remote-control/authentication) before opening it. Once the route is protected, [try it out](https://openknowledge.ai/docs/remote-control/overview#try-it-out).

## Back up to a git remote

Optional, but recommended for a server. Your knowledge base is a plain git repository, and OpenKnowledge has a built-in sync engine: point it at a GitHub remote and it commits and pushes as agents write, on its own.

Create an empty **private** GitHub repo, then on the machine:

```bash
gh auth login              # authorize this machine (or add a deploy key with write access)
cd ~/knowledge
git remote add origin https://github.com/you/knowledge.git
git branch -M main && git add -A && git commit -m "knowledge base" && git push -u origin main
```

Turn on auto-sync:

```bash
printf '\nautoSync:\n  mode: full\n' >> .ok/local/config.yml
sudo systemctl restart openknowledge
```

From now on every edit is committed and pushed automatically. The commit author comes from the machine's git identity (`git config user.name` / `user.email`). A fresh server has none set, so commits fall back to a service identity, **OpenKnowledge**. Signing in with `gh` handles pushing, not authorship. Set a git identity if you'd rather commits carry your name. See [GitHub sync](https://openknowledge.ai/docs/features/github-sync) for sync modes, conflict handling, and authentication.

The git remote is also a way to edit locally: `git clone` it on your laptop and open the clone in OK Desktop. That gives you a separate local copy with the full editor. It syncs with the server through git (each side pushes and pulls), not live, so changes cross over on the next sync rather than instantly.

# Docker (https://openknowledge.ai/docs/remote-control/methods/docker)

Build the OpenKnowledge container image and run it on Railway, Fly.io, or any Docker host.

For an always-on knowledge base without managing a box, run the server as a container on a hosting platform (Railway, Fly.io, or your own Docker host). The platform provides the domain and TLS. A volume holds the project. The `OK_*` environment variables carry the same declarations as every other path. Start with the [Remote Control overview](https://openknowledge.ai/docs/remote-control/overview) if you have not chosen an access path yet.

## What any host must provide

The recipes on this page are examples, not requirements. The server runs anywhere that provides:

- **Run one always-on instance.** The collaboration server supports one server process per project. Keep the replica count at one and do not scale to zero between requests.
- **A persistent volume with a real filesystem**, mounted at `/data`. The project is a live git repository the server watches and writes. Container-layer storage is lost on recreate, and network filesystems built for object storage fit it poorly.
- **Use HTTP ingress that supports WebSockets and unbuffered streaming responses.** Live editing uses a WebSocket. `/mcp` uses server-sent events. See the [proxy rules](https://openknowledge.ai/docs/remote-control/methods/docker#rules-for-anything-in-front-of-the-server).
- **Use TLS at the edge and preserve `X-Forwarded-Proto: https`.** Hosting platforms usually handle this automatically. If you add your own plain HTTP proxy, set the header yourself.
- **The two remote-access declarations from the [configuration reference](https://openknowledge.ai/docs/reference/configuration)**: `OK_EXTERNAL_URL` names the public origin, and `OK_ALLOW_EXTERNAL=1` consents to exposure.

Anything that checks those boxes, whether a hosting platform, an orchestrator, or a box under your desk, will run the image.

## Build the image

There is no official OpenKnowledge image on a registry yet, so build your own from two small files:

```dockerfile title="Dockerfile"
FROM node:24-slim

# git is a hard boot requirement: the server runs a git preflight at boot
# and the version-history subsystems shell out to the binary.
RUN apt-get update \
 && apt-get install -y --no-install-recommends git ca-certificates \
 && rm -rf /var/lib/apt/lists/*

RUN npm install -g @inkeep/open-knowledge

# PORT is the platform-injection contract: Railway/Fly/Cloud Run override
# it at run time. 8080 is only the local default.
# OK_BIND=0.0.0.0 makes the listener reachable from the container network.
# Consent (OK_ALLOW_EXTERNAL=1) is deliberately NOT baked: a run without it
# refuses to boot and names the fix. That refusal is the secure default.
ENV PORT=8080 \
    OK_BIND=0.0.0.0

# No `VOLUME /data`: managed builders (Railway, Fly) reject the
# instruction, and for plain `docker run` it only creates anonymous
# volumes. Persistence is the operator's job: mount a volume at /data,
# or data lives in the container layer and is lost on recreate.
WORKDIR /data
EXPOSE 8080

COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
```

The entrypoint is the container's boot script. It runs every time the container starts. `ok start` needs an initialized project, but no one is inside the container to run `ok init`. The script handles this automatically. The first boot initializes the project on the empty volume. Later boots find `.ok/` and start the server directly. Official database images use the same pattern for empty data directories.

The `--no-mcp --no-skills` flags only skip local agent configuration and skill files that the container does not need. The server still provides its `/mcp` endpoint.

```sh title="entrypoint.sh"
#!/bin/sh
# First-boot init: scaffold the project exactly once, on an empty volume.
# An initialized or restored volume starts the server.
# stdin is closed so `ok init` takes its non-TTY defaults and can never
# hang on a prompt.
set -eu
if [ ! -d /data/.ok ]
then
  echo "[entrypoint] /data is not initialized. Running ok init --no-mcp --no-skills"
  ok init --no-mcp --no-skills < /dev/null
fi
exec ok start
```

Test it locally:

```bash
docker build -t open-knowledge:local .
docker run --rm -p 8080:8080 -e OK_ALLOW_EXTERNAL=1 -v ok-data:/data open-knowledge:local
# → http://localhost:8080
```

## The environment surface

| Variable            | Container default | Meaning                                                                                                                                                    |
| ------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PORT`              | `8080`            | Listen port. Railway, Fly, and Cloud Run set `PORT` automatically. Do not set it yourself on those platforms.                                              |
| `OK_BIND`           | `0.0.0.0`         | Bind address list. On an IPv6-only network, use `::` instead. It also serves IPv4. Do not combine `0.0.0.0 ::` on one port because the listeners conflict. |
| `OK_ALLOW_EXTERNAL` | unset             | Run-time exposure consent. Without it the interlock refuses to boot.                                                                                       |
| `OK_EXTERNAL_URL`   | unset             | The public origin the deployment is reached at. Required behind any real domain, or hostname requests are rejected with `403`.                             |
| `OK_IDLE_SHUTDOWN`  | unset             | Idle shutdown defaults to `off` on a non-loopback bind. Set a duration such as `30m` to turn it back on.                                                   |

Health endpoints for platform checks: `GET /healthz` (liveness) and `GET /readyz` (readiness).

> **Run exactly one replica per volume**
>
> The collaboration server is single-writer: one server process per project volume. Keep the service at **1 replica**, because scaling to 2+ silently runs two writers against the same data, and inside a container nothing enforces the exclusion. Multiple *projects* are fine as multiple services, each with its own container, volume, and domain.

## Deploy on Railway

Railway builds the Dockerfile, sets `PORT`, enables TLS, and provides a domain. The full setup takes one deploy. Run these commands from the directory that contains `Dockerfile` and `entrypoint.sh`:

```bash
railway login
railway init --name my-kb                                        # create the project
railway add --service my-kb --variables "OK_ALLOW_EXTERNAL=1"    # create the service + consent
railway volume --service <service-id> add --mount-path /data     # persistent volume (see note below)
railway domain --service my-kb --port 8080                       # generate the domain (works pre-deploy)
railway variables --service my-kb --set 'OK_EXTERNAL_URL=https://${{RAILWAY_PUBLIC_DOMAIN}}'
railway up --service my-kb --detach                              # one deploy with externalUrl set on first boot
railway logs --service my-kb                                     # watch the first boot
```

`${{RAILWAY_PUBLIC_DOMAIN}}` is a Railway reference variable. It resolves to the generated domain during deployment, so `OK_EXTERNAL_URL` is correct on the first boot. The entrypoint initializes the empty volume and starts the server. The log shows an exposure banner with your domain. Set the platform health check to `/readyz`.

A few Railway-specific caveats:

- **Do not set `PORT`.** Railway sets it automatically.
- **Leave replicas at 1** (see the single-writer callout above).
- **`railway volume` wants the service ID, not the name.** Every other command above accepts the service name, but on current CLIs `volume` can crash on a name. Get the ID from `railway status --json`.
- **Subcommand flags drift across Railway CLI versions.** If a command errors, check `railway <cmd> --help` for where the `--service` flag goes.
- **Setting a variable and immediately running `railway redeploy` can race.** The redeploy can snapshot the environment before the variable applies. Use `railway up`, or redeploy after the variable shows in `railway variables`.

## Other platforms

The same image runs anywhere Docker is available. On Fly.io, mount a volume at `/data`, set the same two `OK_*` variables with the `.fly.dev` domain, and point a health check at `/readyz`.

With Docker Compose, you can add a `cloudflared` sidecar as the only ingress. This works on a hosting platform or your own VPS. You do not need a separate public domain. Set `OK_EXTERNAL_URL` to the tunnel hostname.

AWS, Google Cloud, and Azure can run the image on a virtual machine with Docker. You can also use the [CLI method](https://openknowledge.ai/docs/remote-control/methods/cli#a-server-you-manage) on a virtual machine. Serverless container services must provide persistent storage and support one always-on replica. Vercel and Netlify cannot host OpenKnowledge because they run request-based functions instead of one long-lived stateful process.

## Upgrading

The image installs the current version of `@inkeep/open-knowledge` when you build it. To upgrade, rebuild the image and deploy it to the same volume. The entrypoint finds the existing project and starts the server. Your content, history, and settings remain on the volume. On Railway, run `railway up` again from the Dockerfile directory.

Two practices make this predictable:

- **Pin the version in the Dockerfile.** Use `RUN npm install -g @inkeep/open-knowledge@<version>` and update the version when you upgrade. An unpinned install can reuse Docker's cached build layer and leave you on an older version. Changing a pinned version clears that cache and gives you an exact version to rebuild later.
- **Back up the volume before upgrading.** Use the hosting platform's volume backup or create a tar archive of `/data`. Restoring the backup to a new volume preserves the initialized project.

## Rules for anything in front of the server

Four rules apply to any proxy you place between clients and the container (an auth proxy, nginx, a CDN layer):

- **The server must consent before any proxy fronts it.** A request carrying a forwarding header (`X-Forwarded-For`, `Forwarded`, `X-Forwarded-Proto`, ...) is refused with `403 Proxied request refused` unless BOTH `OK_EXTERNAL_URL` and `OK_ALLOW_EXTERNAL=1` are set. Every proxy stamps at least one of these headers, so this pair is a prerequisite for the rules below.

- **Preserve the original `Host` header.** The server compares each request's `Host` with `OK_EXTERNAL_URL`. Many reverse proxies replace `Host` with the upstream address, such as `ok:8080`. The server rejects these requests with `403`. Forward the client's host instead. In Caddy, use `header_up Host {host}`. In nginx, set the `Host` header to `$host` with `proxy_set_header`. Hosting platform edges usually preserve it automatically. A proxy you add may not.

- **Never buffer `/mcp`.** It is a streaming endpoint that uses server-sent events. A proxy that buffers or compresses the response causes agent connections to time out. In nginx, set `proxy_buffering` to `off` for this path. Caddy streams by default. Hosting platform edges pass server-sent events through. Apply this rule to any proxy you add.

- **A TLS proxy that uses plain HTTP to reach the server must set `X-Forwarded-Proto: https`.** The server uses this header to create secure WebSocket URLs with `wss://`. If the proxy reports `http`, the editor tries to open an insecure `ws://` connection from an `https://` page. Browsers block the connection and the editor does not load. Hosting platform edges usually set the header correctly. An authentication proxy between the edge and the server may not.

## Next steps

The platform domain has no login of its own, so anyone who finds the URL has full read and write access. [Add authentication](https://openknowledge.ai/docs/remote-control/authentication) before opening the public domain. Once the route is protected, [try it out](https://openknowledge.ai/docs/remote-control/overview#try-it-out), then [Connect remote agents](https://openknowledge.ai/docs/remote-control/connecting-agents).

# Accordion (https://openknowledge.ai/docs/reference/components/accordion)

Standalone expand/collapse via native HTML5 \<details>/\<summary>. Group siblings with the `name` prop for exclusive-accordion UX

## Example

> **Show me the details**
>
> Click to expand
>
> Native `<details>` under the hood — same substrate as the app render.
> Pass a shared `name` to sibling accordions and the browser will keep
> only one open at a time.

```mdx
<Accordion title="Title">
  Body content revealed when the accordion is expanded.
</Accordion>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | `string` | yes |  | Heading shown inside the \<summary> |
| `defaultOpen` | `boolean` | no | `false` | When true, the block renders expanded on initial load |
| `icon` | `string` | no |  | Custom lucide icon override (e.g. \`lucide:Rocket\`) |
| `description` | `string` | no |  | Optional subtitle rendered below the title inside \<summary> |
| `id` | `string` | no |  | HTML id attribute for deep-linking (e.g. \`#advanced-options\`) |
| `name` | `string` | no |  | HTML5 \<details name=> group — siblings with the same name are mutually exclusive |

*Also matches:* `accordion`, `expandable`, `details`, `disclosure`, `collapse`, `fold`

## Author it

Type `/accordion` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Audio (https://openknowledge.ai/docs/reference/components/audio)

HTML5 audio player with native controls

## Example

```md
![[podcast-episode.mp3]]
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `src` | `string` | yes |  | Audio source URL |
| `controls` | `boolean` | no | `true` | Show native HTML5 audio controls (defaults to true) |
| `autoplay` | `boolean` | no |  | Begin playback as soon as possible (usually requires muted) |
| `title` | `string` | no |  | Native HTML title attribute (tooltip) |
| `muted` | `boolean` | no |  | Mute audio on load |
| `loop` | `boolean` | no |  | Restart from the beginning when playback ends |
| `preload` | `'none' \| 'metadata' \| 'auto'` | no |  | Hint for how much of the audio to preload |

*Also matches:* `audio`, `sound`, `music`, `mp3`, `podcast`, `player`

## Author it

Type `/audio` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Callout (https://openknowledge.ai/docs/reference/components/callout)

Alert / admonition with 15 type variants — 5 GFM (note, tip, important, warning, caution) plus 10 Obsidian-parity (abstract, info, todo, success, question, failure, danger, bug, example, quote)

## Example

> **Ship a good default**
>
> Pick the type that matches the intent — `tip` for advice, `warning` for things that can bite, `note` for
> background context. The icon and accent color track the type automatically.

```md
> [!TIP]
> Pick the type that matches the intent — `tip` for advice, `warning`
> for things that can bite, `note` for background context.
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `type` | `'note' \| 'tip' \| 'important' \| 'warning' \| 'caution' \| 'abstract' \| 'info' \| 'todo' \| 'success' \| 'question' \| 'failure' \| 'danger' \| 'bug' \| 'example' \| 'quote'` | no | `note` | Callout variant |
| `title` | `string` | no |  | Optional heading shown above the body |
| `icon` | `string` | no |  | Custom lucide icon override (e.g. \`lucide:Lightbulb\`) |
| `color` | `string` | no |  | Optional accent color override (hex — e.g. \`#F05032\`) |
| `collapsible` | `boolean` | no | `false` | Render as a foldable \`\<details>\` (Obsidian \`\[!TYPE]+/-\`) |
| `defaultOpen` | `boolean` | no | `true` | When collapsible, start in the open state |
| `children` | `ReactNode` | yes |  | Callout content |

*Also matches:* `note`, `tip`, `important`, `warning`, `caution`, `abstract`, `info`, `todo`, `success`, `question`, `failure`, `danger`, `bug`, `example`, `quote`, `alert`, `admonition`, `callout`

## Author it

Type `/note` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Embed (https://openknowledge.ai/docs/reference/components/embed)

Inline web embed (iframe) — drop a URL, get a resizable preview pane. For YouTube / Vimeo / Loom prefer `<video src="…">` (player props, click-facade); `<Embed>` auto-rewrites watch URLs as a fallback

## Example

```mdx
<Embed src="" title="Title" />
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `src` | `string` | yes |  | Embedded page URL (must start with http\:// or https\://) |
| `title` | `string` | no |  | Iframe title (accessible label for screen readers) |
| `width` | `string` | no |  | Embed width as a CSS length (e.g. "100%", "640px") |
| `height` | `string` | no |  | Embed height as a CSS length (e.g. "26rem", "480px") |

*Also matches:* `embed`, `iframe`, `website`, `page`, `inline`, `frame`, `preview`

## Author it

Type `/embed` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Excalidraw (https://openknowledge.ai/docs/reference/components/excalidraw)

Embedded snapshot of an `.excalidraw` board, linking to its canvas editor

## Example

```mdx
<Excalidraw src="diagrams/board.excalidraw" title="Title" />
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `src` | `string` | yes |  | Path to the \`.excalidraw\` board this embed renders |
| `title` | `string` | no |  | Accessible label for the embedded board |

*Also matches:* `excalidraw`, `board`, `whiteboard`, `sketch`, `drawing`, `canvas`

## Author it

Type `/excalidraw` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# File (https://openknowledge.ai/docs/reference/components/file)

Downloadable file attachment — inline row with name + size + download link

## Example

```md
![[quarterly-report.pdf]]
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `src` | `string` | yes |  | File URL |

*Also matches:* `file`, `attachment`, `download`, `document`, `zip`, `docx`, `doc`

## Author it

Type `/file` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Align block (https://openknowledge.ai/docs/reference/components/html-align-block)

GitHub-style `<div align>` wrapper. Children render as normal blocks with the alignment applied; the `<div align>` syntax is preserved on round-trip

## Example

```mdx
<HtmlAlignBlock>
  Content goes here.
</HtmlAlignBlock>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `align` | `'center' \| 'left' \| 'right' \| 'justify'` | no | `center` | Block alignment from the \`\<div align>\` attribute. |

*Also matches:* `align`, `center`, `centered`, `div`

## Author it

Type `/align` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Image (https://openknowledge.ai/docs/reference/components/img)

Image with click-to-zoom and HTML-native attributes

## Example

```md
![A short description](./path/to/image.png)
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `src` | `string` | yes |  | Image source URL |
| `alt` | `string` | yes |  | Alt text |
| `width` | `number` | no |  | Image width |
| `height` | `number` | no |  | Image height |
| `srcset` | `string` | no |  | Responsive image candidate set (e.g. "x.png 1x, y.png 2x") |
| `sizes` | `string` | no |  | Responsive image sizes hint paired with srcset |
| `loading` | `'eager' \| 'lazy'` | no | `lazy` | Native img loading strategy (defaults to lazy) |
| `title` | `string` | no |  | Native HTML title attribute (tooltip) |
| `decoding` | `'sync' \| 'async' \| 'auto'` | no | `auto` | Hint for how the browser should decode the image |
| `fetchpriority` | `'high' \| 'low' \| 'auto'` | no | `auto` | Resource fetch priority hint |
| `crossorigin` | `'anonymous' \| 'use-credentials'` | no |  | CORS mode for the image fetch |
| `referrerpolicy` | `string` | no |  | Referrer policy for the image fetch (HTML referrerpolicy values) |

*Also matches:* `image`, `zoom`, `picture`, `photo`

## Author it

Type `/image` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Overview (https://openknowledge.ai/docs/reference/components)

The rich content blocks the OpenKnowledge editor understands — callouts, media, math, mermaid diagrams, accordions, tabs, and transclusion.

OpenKnowledge documents are plain markdown, plus a small set of rich content blocks the editor understands out of the box — callouts, media, math, mermaid diagrams, accordions, tabs, and transclusion. Type `/` in the editor to insert one from the slash menu, or write it directly in source mode.

Every block on this page uses the same tag syntax in the raw markdown file:

```mdx
<Callout type="tip" title="Nice">
  Prose or nested markdown lives in the body.
</Callout>
```

## Content

- [Callout](https://openknowledge.ai/docs/reference/components/callout) — alerts and admonitions with 15 type variants
- [Accordion](https://openknowledge.ai/docs/reference/components/accordion) — collapsible sections
- [Tabs](https://openknowledge.ai/docs/reference/components/tabs) — grouped panel switcher (with `<Tab>` children)
- [Math](https://openknowledge.ai/docs/reference/components/math) — KaTeX-rendered inline or block equations
- [MermaidFence](https://openknowledge.ai/docs/reference/components/mermaid-fence) — diagrams from a ` ```mermaid ` fence

## Media

- [img](https://openknowledge.ai/docs/reference/components/img) — image with click-to-zoom
- [video](https://openknowledge.ai/docs/reference/components/video) — video player
- [audio](https://openknowledge.ai/docs/reference/components/audio) — audio player
- [Pdf](https://openknowledge.ai/docs/reference/components/pdf) — embedded multi-page PDF viewer
- [File](https://openknowledge.ai/docs/reference/components/file) — inline download row for arbitrary attachments
- [Embed](https://openknowledge.ai/docs/reference/components/embed) — sandboxed iframe for external URLs
- [Excalidraw](https://openknowledge.ai/docs/reference/components/excalidraw) — live snapshot of an `.excalidraw` board, with a full-screen viewer and a jump to its canvas editor

## Transclusion

- [Mirror](https://openknowledge.ai/docs/reference/components/mirror) — read-only view of a `<MirrorSource>` block from another doc

# Math (https://openknowledge.ai/docs/reference/components/math)

Block math equation rendered with KaTeX from a LaTeX source string

## Example

```md
$$
E = mc^2
$$
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `formula` | `string` | yes |  | LaTeX math source (rendered with KaTeX in the browser) |
| `id` | `string` | no |  | HTML id attribute for deep-linking (e.g. \`#eq-pythagoras\`) |
| `language` | `string` | no |  | Forward-compat hint for the math source language (default \`latex\`). Reserved for future MathJax / Typst / AsciiMath substrates. |

*Also matches:* `math`, `latex`, `equation`, `formula`, `katex`, `tex`

## Author it

Type `/math` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Mermaid (https://openknowledge.ai/docs/reference/components/mermaid-fence)

Diagram rendered from Mermaid source (flowchart, sequence, class, state, ER, gantt, pie). Authored exclusively as ` ```mermaid ` fenced code

## Example

```mermaid
graph LR
  Author((Author)) --> Editor[OK Editor]
  Editor -- CRDT --> Server[(Hocuspocus)]
  Server --> Agent{{AI Agent}}
  Agent --> Editor
```

````text
```mermaid
graph LR
    Author((Author)) --> Editor[OK Editor]
    Editor -- CRDT --> Server[(Hocuspocus)]
    Server --> Agent{{AI Agent}}
    Agent --> Editor
```
````

## Props

*No public props.*

*Also matches:* `mermaid`, `diagram`, `flowchart`, `graph`, `sequence`, `sequencediagram`, `class`, `state`, `er`, `erdiagram`, `gantt`, `pie`, `chart`

## Author it

Type `/mermaid` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Mirror (https://openknowledge.ai/docs/reference/components/mirror)

Render a read-only copy of a `<MirrorSource>` block from another doc. Use to keep the same content in sync across multiple docs without copy-paste — edits land at the source, every Mirror reflects the change

## Example

This body renders live from wherever the `<MirrorSource id="overview-diagram">`
with a matching id lives — edits there ripple here without a copy step.

````text
```mdx
<Mirror src="specs/architecture" anchor="overview-diagram" />
```
````

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `src` | `string` | yes |  | Path of the source doc, extension-less (e.g. \`api-spec\`). |
| `anchor` | `string` | yes |  | Id of the \`\<MirrorSource>\` block within the source doc. |

## Also: `<MirrorSource>`

Mark a block as the source of truth for content that appears in multiple docs. Wrap any block content; `<Mirror src="…" anchor="<id>">` references render this verbatim read-only at every call-site. Edit here, propagate everywhere.

The canonical content this block owns. Every `<Mirror>` that references
this id anywhere in the project reads from here.

```mdx
<MirrorSource id="demo-1">
  Authoritative content lives inside this block — edits here propagate to every `<Mirror>` that references this id.
</MirrorSource>
```

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes |  | Stable id agents and authors use to reference this block from \`\<Mirror>\` elsewhere. |
| `children` | `ReactNode` | yes |  | Block content this MirrorSource owns — paragraphs, callouts, code, nested JSX, anything. |

*Also matches:* `mirror`, `sync`, `synced`, `transclude`, `embed`, `reference`, `shared block`

## Author it

Type `/mirror` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# PDF (https://openknowledge.ai/docs/reference/components/pdf)

Embedded PDF viewer (`#page=N` to open at page N, `#height=N` for viewer height)

## Example

```mdx
<Pdf src="" title="Title" />
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `src` | `string` | yes |  | PDF source URL |
| `title` | `string` | no |  | Accessible label for the embedded PDF viewer |
| `anchor` | `string` | no |  | PDF viewer parameters as a single URL-fragment string (e.g. \`page=3\&height=600\`) |

*Also matches:* `pdf`, `document`, `embed`, `pdfjs`

## Author it

Type `/pdf` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Tabs (https://openknowledge.ai/docs/reference/components/tabs)

Horizontal tab strip + active panel below. Each `<Tab>` child is one panel; clickable pills at the top switch the active one. Active selection is ephemeral (resets on reload)

## Example

### Install

Run `npm install @inkeep/open-knowledge` to add the CLI to your project.

### Configure

Point `.ok/config.yml` at your content directory. Frontmatter, ignore
patterns, and folder defaults all live in this one file.

### Serve

`ok start` boots the collaboration server and opens the editor.

```mdx
<Tabs>
  <Tab label="One">Body of the first tab panel.</Tab>
  <Tab label="Two">Body of the second tab panel.</Tab>
</Tabs>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | no |  | HTML id attribute for deep-linking (e.g. \`#install-tabs\`) |

## Also: `<Tab>`

A single tab panel inside a `<Tabs>` container — carries the strip label and the block-content body.

### A single Tab

Each `<Tab>` is one panel of a `<Tabs>`
group. The `label` becomes the pill at the top; the body
renders when the pill is active.

### Second panel

Switch between panels without losing scroll — the parent tracks which
one is active client-side.

```mdx
<Tab label="Tab">
  Panel content — must be nested inside a `<Tabs>` parent.
</Tab>
```

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `label` | `string` | yes | `Tab` | Tab strip label — shown in the clickable pill at the top |
| `id` | `string` | no |  | HTML id attribute for deep-linking (e.g. \`#tab-npm\`) |

*Also matches:* `tabs`, `tabbed`, `panels`, `tabgroup`, `switcher`

## Author it

Type `/tabs` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Toggle (https://openknowledge.ai/docs/reference/components/toggle)

Standalone expand/collapse block (Notion-style). Same render + props as `<Accordion>` — pick whichever vocabulary matches the vault

## Example

> **Show me the details**
>
> Click to expand
>
> Native `<details>` under the hood — same substrate as the app render.
> Pass a shared `name` to sibling toggles and the browser will keep
> only one open at a time.

```mdx
<Toggle title="Title">
  Body content revealed when the toggle is expanded.
</Toggle>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | `string` | yes |  | Heading shown inside the \<summary> |
| `defaultOpen` | `boolean` | no | `false` | When true, the block renders expanded on initial load |
| `icon` | `string` | no |  | Custom lucide icon override (e.g. \`lucide:Rocket\`) |
| `description` | `string` | no |  | Optional subtitle rendered below the title inside \<summary> |
| `id` | `string` | no |  | HTML id attribute for deep-linking (e.g. \`#advanced-options\`) |
| `name` | `string` | no |  | HTML5 \<details name=> group — siblings with the same name are mutually exclusive |

*Also matches:* `toggle`, `collapsible`, `expand`, `details`, `disclosure`, `fold`

## Author it

Type `/toggle` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.

# Video (https://openknowledge.ai/docs/reference/components/video)

HTML5 video player with native controls

## Example

```md
![[demo-clip.mp4]]
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `src` | `string` | yes |  | Video source URL |
| `controls` | `boolean` | no | `true` | Show native HTML5 video controls (defaults to true) |
| `autoplay` | `boolean` | no |  | Begin playback as soon as possible (usually requires muted) |
| `poster` | `string` | no |  | Poster image URL shown before playback |
| `width` | `number` | no |  | Video width |
| `height` | `number` | no |  | Video height |
| `title` | `string` | no |  | Native HTML title attribute (tooltip) |
| `muted` | `boolean` | no |  | Mute audio on load |
| `loop` | `boolean` | no |  | Restart from the beginning when playback ends |
| `playsinline` | `boolean` | no |  | Play inline on iOS rather than entering fullscreen |
| `preload` | `'none' \| 'metadata' \| 'auto'` | no |  | Hint for how much of the video to preload |

*Also matches:* `video`, `media`, `player`, `mp4`, `webm`, `movie`

## Author it

Type `/video` in the editor to insert it from the slash menu, or write the tag directly in source mode. The Properties panel on the right of the editor exposes every prop above as a form field once the block is selected.