---
title: KMS (the Vault) — notes, editor, search and sharing (part 5)
---

# KMS (the Vault) — notes, editor, search and sharing (part 5)

## What it is

This is part 5 of the [KMS](kms.md) page. It covers the parts of the vault that reach outside it: the AI summaries and tools, asking a question based on a note, the ways to open the vault faster or in its own window, linking straight to a note, and publishing one for someone else to read.

## Where to find it

From the **KMS** panel and from **Settings**. Publishing produces a public web address you can share; deep links are URLs you can keep or send.

## How it behaves

### Phase 6 — AI summaries + read-only agent tools

Phase 6 wired two independent slices on top of the Phase 1-5 foundation.

**Per-note AI summaries.** Each note can carry a `<!-- kms:summary v=1 hash=… -->…<!-- /kms:summary -->` HTML-comment marker block at the top of its body containing an AI-written paraphrase (≤300 chars). Omniscio tracks drift via a canonical SHA-256 hash of the body (with the marker stripped, whitespace normalized, image URLs token-replaced) and flips the summary to `stale` on content change, or `user_edited` (locked) on summary text change. An in-editor **SummaryStatusChip** in the status bar surfaces six states (Up to date / Stale / Locked / Generating / Budget reached / hidden when none); single-click on stale regenerates, right-click opens the full menu. An opt-in background worker (off by default — Settings → Features: `nothariAutoRegenEnabled` + scan interval + per-note cooldown) regenerates stale-and-unlocked summaries; concurrency 2 / 50-per-tick are fixed. Spend goes through `api_cost_log` via `trackApiCost(accountId, 'kms-summary', model, in, out)` with a per-account daily $ cap (`nothariSummaryCostCapUsd`, default $2, local-midnight reset). Four IPC operations: `kms:generate-summary` (with `force` to bypass the lock), `kms:get-summary-status` (the only `cost-capped` reporter), `kms:clear-summary`, `kms:set-summary-user-edited`. Bulk-seed worker for first-time vault-wide generation has its own cap (`nothariBulkSeedCostCapUsd`, default $5). Full mechanics: [kms-summaries.md](kms-summaries.md).

**Read-only agent tools (MCP).** When `nothariAgentToolsEnabled` is on (gated by the master toggle), every Claude Code session Omniscio spawns gets a `.mcp.json` entry pointing at a local Node subprocess that exposes five tools: `kms_search`, `kms_read`, `kms_list_tags`, `kms_list_recent`, `kms_get_backlinks`. The agent can query your vault during its turn — local SQLite only, zero API spend, hidden notes filtered out, payload-capped at 100 KB per call with cursor pagination, cross-vault scope. Write/delete/rename counterparts were planned (6h/6i/6j) but cut from scope — destructive operations remain available as bash file-system operations against the `.md` files directly. Full mechanics: [kms-agent-tools.md](kms-agent-tools.md).

The image analyzer (Phase 5f), summary service (6b), bulk-seed (6d), and auto-regen worker (6e P2) all share a common cost-tracking pattern via `trackApiCost()` and the per-account daily ledger in `src/main/services/kms/cost-cap-ledger.ts` (Phase 6c). Each path has its own configurable cap so a runaway summary worker can't exhaust the image-analysis budget and vice-versa.

### Ask your vault (one-click note-seeded chat)

From an open note you can hand the whole vault to Claude in one click. The KMS editor header carries an **Ask AI** button (a ✨ Sparkles trigger) that drops a small menu with three actions — **Summarize this note**, **Find related notes**, and **Ask a question** — and the same three live as command-palette rows (`ai.summarize-note`, `ai.find-related`, `ai.ask-about-vault`). Picking any of them spawns a Claude session scoped to the vault (the `__nothari__` project), flips the KMS main pane to its **Sessions** view (`kmsViewMode: 'sessions'`), and streams the answer there. Because that session already carries the vault primer plus the eight read-only `kms_*` retrieval tools (the same tools the "Agent sessions" tab and the MCP surface above expose), the answer reads and cites your real notes rather than guessing.

The chat is **not** a separate inline panel — it reuses the exact session the KMS "Agent sessions" tab already spawns (the main-pane `SessionPanel`), so there is no second chat/streaming stack to maintain; the launcher only makes that session **launchable in one click and optionally pre-seeded** with a prompt about the active note. The seed is a short instruction that names the note by **title + vault-relative path** and tells the agent to read it with its tools — the note **body is never embedded in the prompt** (a 167 KB note would blow the token budget). **Summarize this note** and **Find related notes** need an open note (they seed from it) and disable when none is open; **Ask a question** only needs a vault. Everything is gated on a configured vault root — with no vault set, the header trigger and the note-seeded palette rows render disabled and a click surfaces a humanized toast instead of a spawn. Each chat is a normal user-initiated session tracked by Omniscio's usual session cost path — there is no separate per-feature cost cap (summaries keep their own daily cap; this does not).

**How it's wired (repo detail).** The launcher seam is [useAskVault.ts](/src/renderer/src/features/kms/hooks/useAskVault.ts) — `useAskVault()` returns `{ askVault, canAskVault }`; `askVault()` resolves the `__nothari__` project id at call time, flips `setKmsViewMode('sessions')`, and calls `launchSession(projectId, …, 'kms-session-host', seedPrompt?)` — the **exact** path [KmsSessionsList.tsx](/src/renderer/src/features/kms/sessions/KmsSessionsList.tsx) uses, with the mandatory `'kms-session-host'` source, plus the pure seed builders `buildSummarizeSeed` / `buildRelatedSeed`. The header dropdown is [AskAiMenu.tsx](/src/renderer/src/features/kms/toolbar/AskAiMenu.tsx) (a `ToolbarMenu`, editor view only), rendered from [KmsView.tsx](/src/renderer/src/features/kms/KmsView.tsx), which also threads `askVault` / `canAskVault` onto the `CommandContext`. The three previously-disabled `ai.*` rows in [commands/registry.ts](/src/renderer/src/features/kms/commands/registry.ts) are now live; `ai.continue-writing` stays a deliberately disabled stub (inline in-editor generation is out of scope for this wave). Full invariants — seed-never-embeds-body, the reused session seam, the vault gate, never-throws-to-caller, and the summary auto-regen ignition that shipped alongside — live in the `KMS Ask-Your-Vault contract`.

### Keep KMS loaded for instant switching (keep-warm)

By default (desktop), once you open KMS its editor stays **loaded in the background** when you switch to a chat session or another project, so switching back into KMS is an instant display flip instead of a cold rebuild of the TipTap editor. It uses the same "permanent display-toggled layer" technique Omniscio already uses for the AutoHotkey editor and plugin webviews.

- **Setting:** Settings → Performance → **"Keep KMS loaded for instant switching"** (`kmsKeepWarmEnabled`, default **on** / opt-out). Turn it off to load KMS fresh each time (the older behavior).
- **Cost:** one editor held in memory, and only **after** you've opened KMS at least once — nothing is held until then.
- **Desktop only.** Phones mount a single panel, so there is nothing to keep warm; the setting has no effect there.
- **Env kill switch:** `AMC_DISABLE_KMS_KEEP_WARM=1` force-disables it regardless of the setting.
- The KMS editor is mounted in exactly one place at a time — the warm layer when keep-warm is on, or the dashboard overlay when it is off, never both. It hides automatically when KMS yields to a session chat (Sessions view + a session) or when a file/diff/inbox view takes the main pane.

Contract + invariants: `kms-keep-warm-contract.md`. For an _always-open second window_ instead, see **Standalone KMS window** below.

### Standalone KMS window

A separate OS window that hosts the full KMS experience — file tree + editor **and a real session host** — so the user can work KMS (notes _and_ AI sessions) on a second monitor while Omniscio runs in the main window. Both surfaces mount the same `KmsView`/stores over the same vault and stay in sync in real time. **Ctrl+T in the pop-out launches a KMS session and opens its chat right there** — see "Session host" below.

**Spec:** [/docs/superpowers/specs/2026-05-30-kms-standalone-window-design.md](../superpowers/specs/2026-05-30-kms-standalone-window-design.md)

### Window chrome — the note tabs live in the title strip

The window is frameless (`titleBarStyle: 'hidden'`, shared with the Scratchpad window). Instead of the Scratchpad's label-only `StandaloneWindowTitleBar`, the KMS window renders its own [KmsWindowTitleBar](/src/renderer/src/features/kms/KmsWindowTitleBar.tsx) that **hosts the note tabs (`TabBar`) directly in that slim top strip** with custom HTML caption buttons (`WindowControls`) at the trailing edge — so the pop-out reclaims the row the in-pane tabs bar used to spend, putting the editor ~one bar higher. The strip stays the window's drag region (only its tab controls and caption buttons opt out via `.no-drag` / a scoped `.kms-window-titlebar … { -webkit-app-region: no-drag }` rule in `globals.css`, so the window still moves while tabs stay clickable / drag-to-reorder), keeps the shared 40px height, and reserves space so tabs never tuck under the buttons (trailing on Windows, leading inset on macOS). `KmsView` renders its in-pane `TabBar` only when **not** `isStandaloneWindow`, so the tabs never double up; the in-app dashboard panel is unchanged. Contract: `scratchpad-window-contract.md` (`frameless-chrome-shared-with-the-kms-window`) and `window-controls-contract.md`.

### Three open affordances (all call `kmsWindow.open()`)

All three call the same show-or-focus entrypoint — opening the window when closed, or bringing it to the front when already open. The window is never toggled-to-hide.

1. **Pop-out button in the KMS toolbar** — an `ExternalLink` icon in the toolbar's View group, tooltip "Open KMS in a separate window".
2. **Global hotkey** — default `Ctrl+Alt+N` (`CommandOrControl+Alt+N`), system-wide OS-level shortcut. Customizable and toggleable under Settings → Keyboard Shortcuts ("KMS window" row).
3. **Tray menu** — "Open KMS" item in the Omniscio system-tray menu.

### Session host (Ctrl+T + chat)

The pop-out is a full session host, not only a notes surface. It boots the session store (`useSessionSync` + a KMS-project fetch, mirroring the detached single-session window), so the Sessions tab in its sub-sidebar goes live, and it renders the session **chat** (the pooled `SessionPanel`) in place of the editor when you are on the Sessions view with a session active — the shared `pickKmsMainPane` decides. Closing the chat drops back to the note editor in place (a multi-session host never self-closes on chat-close).

**Ctrl+T** launches a new KMS session and opens its chat in the window. Chromium swallows Ctrl+T before the DOM sees it, so the main process intercepts it in [kms-window.ts](/src/main/services/kms-window.ts) via `before-input-event` and delivers a **targeted** `KMS_WINDOW_NEW_SESSION` `webContents.send` to this window ONLY (never `emitPush`/broadcast) — so a Ctrl+T in the main window can never spawn in the pop-out and vice-versa. `KmsWindowApp` handles it by launching into the KMS project with the `kms-session-host` source (the same path the sidebar "+ New" uses) and activating the new row.

### Live sync

The standalone window forwards to the renderer exactly the channels in `KMS_WINDOW_ALLOWLIST` (defined in [/src/main/services/detached-push-filter.ts](/src/main/services/detached-push-filter.ts)):

```
KMS_NOTE_CHANGED, KMS_ACTIVE_VAULT_CHANGED, KMS_BOOKMARKS_CHANGED, KMS_SUMMARY_UPDATED, KMS_IMAGES_CHANGED, SETTINGS_CHANGED, CUSTOM_THEMES_CHANGED, KMS_OPEN_NOTE
```

(`CUSTOM_THEMES_CHANGED` was added so the KMS's own custom-theme override picks up an imported / deleted custom theme live in the pop-out window — see "Custom theme options". `KMS_OPEN_NOTE` is the quick-find → pop-out signal: when the Find Note search picks a note and this window is open, the handler foregrounds it and pushes the note here to open via the shared `requestOpenNote` sink — see "Find Note quick-switcher".)

The window is registered under `role: 'kms'` **with its KMS project id**. On top of `KMS_WINDOW_ALLOWLIST`, the fan-out also forwards the global chrome channels (`DETACHED_GLOBAL_ALLOWLIST` — theme / network / focus / app-update, the same set the detached single-session window gets, since the pop-out now hosts a real `SessionPanel`) and **this project's** session-scoped channels — matched by the pre-resolved `sessionProjectId`, so the live session list + open chat stay current. That last group is **project-scoped on purpose:** a non-KMS session's high-frequency `SESSION_OUTPUT` never reaches the pop-out, so a large concurrent-session fleet doesn't tax the second renderer. Everything else is dropped. Drift is prevented by [/tests/unit/lint/kms-window-push-listener-coverage.test.ts](/tests/unit/lint/kms-window-push-listener-coverage.test.ts) — that lint fails if a new KMS push listener appears in the renderer without a matching entry in `KMS_WINDOW_ALLOWLIST` (the targeted `KMS_WINDOW_NEW_SESSION` is exempt — a point-to-point send, not a broadcast).

### Key implementation files

| Concern                                               | File                                                                                                                     |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Window manager (singleton, lazy-created)              | [/src/main/services/kms-window.ts](/src/main/services/kms-window.ts)                                                     |
| IPC handler (`KMS_WINDOW_OPEN` / `'kms:window-open'`) | [/src/main/ipc/kms-window-handlers.ts](/src/main/ipc/kms-window-handlers.ts)                                             |
| Push allowlist (`KMS_WINDOW_ALLOWLIST`)               | [/src/main/services/detached-push-filter.ts](/src/main/services/detached-push-filter.ts)                                 |
| Renderer HTML entry                                   | [/src/renderer/kms-window.html](/src/renderer/kms-window.html)                                                           |
| Renderer TS entry                                     | [/src/renderer/src/kms-window.tsx](/src/renderer/src/kms-window.tsx)                                                     |
| Root component (`KmsWindowApp`)                       | [/src/renderer/src/features/kms/KmsWindowApp.tsx](/src/renderer/src/features/kms/KmsWindowApp.tsx)                       |
| Title strip (frameless; hosts the note tabs)          | [/src/renderer/src/features/kms/KmsWindowTitleBar.tsx](/src/renderer/src/features/kms/KmsWindowTitleBar.tsx)             |
| Vite rollup input (`kmsWindow`)                       | `electron.vite.renderer-shared.ts`                                               |
| Allowlist drift guard                                 | [/tests/unit/lint/kms-window-push-listener-coverage.test.ts](/tests/unit/lint/kms-window-push-listener-coverage.test.ts) |

### Position memory

Window bounds (x/y/width/height) are persisted in the FK-free `detached_window_positions` table under the sentinel key `'__kms_window__'` — the same table used by the detached-session window. No migration was needed because `session_id` is a plain `TEXT PRIMARY KEY` and the sentinel key `'__kms_window__'` is a valid value. Defaults: 1100×760, centered on the primary display. Bounds are saved on move/resize (500 ms debounce) and synchronously on close.

### Window chrome

Frameless like the main window: `titleBarStyle: 'hidden'` + a slim draggable title strip with custom HTML caption buttons (`WindowControls`), with the native "View/Window" menu removed (`removeMenu()`). The window `backgroundColor` is the dark-first `#09090b` (matches `kms-window.html`). Chrome is shared with the scratchpad window via `standaloneWindowChromeOptions()` / `applyStandaloneWindowChrome()` — see `scratchpad-window-contract.md` `frameless-chrome-shared-with-the-kms-window` and `window-controls-contract.md`.

### Deep linking to a note

A specific note can be opened from **outside** Omniscio (an email, another note, a dashboard, a script) via the `omniscio://` protocol:

```
omniscio://note/<vault-relative-path>[?vault=<vaultId>]
```

- The path is the note's vault-relative path, percent-encoded. `omniscio://note/Architecture.md`, `omniscio://note/One-on-Ones/Brett.md`, and `omniscio://note/Column%20Layout%20Demo.md` all work; sub-folders may be passed as real `/` separators OR a single `%2F`-encoded segment (both rebuild to the same path). `new URL` collapses any `.`/`..` traversal before the parser runs, so a link can never escape the vault root.
- `?vault=<vaultId>` is optional — it targets a specific vault; omit it to use the active / only vault.
- The link **navigates** (activates the KMS panel + dashboard view) and opens the note as a tab. It is **non-destructive** — no session spawn, no message send — so, unlike `new-session` / `open-superprompt`, it is **NOT** confirmation-gated (it matches the trust level of `navigate-session` / `activate-project`).
- A **cold** link (KMS panel not yet mounted, no vault loaded) still lands: `requestOpenNote` stashes the path in the store's `pendingOpenNotePath`, and the tail of `loadTree` opens it once `activeVaultId` + the note list have hydrated (epoch-guarded so a racing vault switch can't open into a stale vault).
- **KMS disabled** → the KMS virtual project is absent from the project list and the router surfaces a "KMS is not enabled" toast. A path that no longer matches an **indexed** note (renamed / deleted since the link was made) surfaces a "couldn't find note" toast instead of opening a blank tab.

A dev / sandbox instance running alongside production uses the per-instance scheme `agentmc-<id>://note/...` (see `getInstanceDeepLinkPrefix()`); production uses bare `omniscio://`.

**Wiring** (main → renderer): the OS hands the URL to [/src/main/app/single-instance.ts](/src/main/app/single-instance.ts) → `handleDeepLinkArgs` → `parseDeepLinkUrl` ([/src/main/services/deep-link.ts](/src/main/services/deep-link.ts), returns `{ action: 'open-note', relativePath, vaultId? }`). `open-note` is not the paid `new-session` path, so single-instance forwards it verbatim over the `DEEP_LINK_ACTION` push. The renderer's [/src/renderer/src/lib/deep-link-router.ts](/src/renderer/src/lib/deep-link-router.ts) (`case 'open-note'`) activates the KMS virtual project (`KMS_PROJECT_ID`) and calls `openKmsNote`, wired in [/src/renderer/src/hooks/useDeepLinkListener.ts](/src/renderer/src/hooks/useDeepLinkListener.ts) to the store's `requestOpenNote` / `consumePendingOpenNote` ([/src/renderer/src/features/kms/kms-store.ts](/src/renderer/src/features/kms/kms-store.ts)). Tests: [/tests/unit/deep-link-open-note.test.ts](/tests/unit/deep-link-open-note.test.ts) (parser), [/tests/unit/lib/deep-link-router-open-note.test.ts](/tests/unit/lib/deep-link-router-open-note.test.ts) (router), [/tests/unit/features/kms/request-open-note.test.ts](/tests/unit/features/kms/request-open-note.test.ts) (store cold/warm/switch/not-found).

> **Get the link from the UI — right-click → Copy link.** A KMS page's deep link is now obtainable without hand-forming it: right-click any page — in the **file tree**, on its **editor tab**, or inside the **open page** (the editor body) — and choose **Copy link** to copy `omniscio://note/<relativePath>?vault=<vaultId>` to the clipboard (a `Link to "<page>" copied` toast confirms; an honest error toast fires if both clipboard paths fail). The tree row is **file-only** (a folder is not a page); the tab + editor rows (from the shared `buildPageShareActions` factory, [page-share-actions.tsx](/src/renderer/src/features/kms/page-share-actions.tsx)) act on the open note. The link is built by `buildAgentmcNoteUrl(relativePath, vaultId?)` ([agent-markdown-path-utils.ts](/src/renderer/src/components/ui/agent-markdown-path-utils.ts)) — the build-inverse of the `parseDeepLinkUrl` reader above, so it round-trips exactly (a page name with sub-folders / spaces / `#` / `&` / unicode survives) — and copied through the resilient `copyKmsNoteLink` chokepoint ([copy-kms-note-link.ts](/src/renderer/src/lib/copy-kms-note-link.ts)), mirroring the session "Copy link". Works in the in-app panel AND the pop-out KMS window. Invariants (one format, the `?vault=` param name, file-only, honest toast) are pinned by [kms-note-link-contract.md](/.claude/memory/contracts/kms-note-link-contract.md); tests: [build-agentmc-note-url.test.ts](/tests/unit/lib/build-agentmc-note-url.test.ts) (build↔parse round-trip), [copy-kms-note-link.test.ts](/tests/unit/lib/copy-kms-note-link.test.ts), [kms-copy-link.spec.ts](/tests/e2e/ui/kms-copy-link.spec.ts).

### Publish a page to a public web link

Beyond the app-only deep link above, a KMS page can be **published** to a public web page anyone can open in a browser — on a phone, with no Omniscio install. Right-click a page — on its **editor tab**, inside the **open page**, or in the **file tree** — and choose **Publish page…**.

- **What it produces.** A standalone, mobile-responsive web page hosted on **Omniscio Shares** (`https://shares.omniscio.com/s/<token>`). It renders the note's text and formatting, and its **local vault images are inlined** (base64) so they load anywhere without the desktop app's `nothari-asset://` scheme. The public URL is copied to your clipboard (a toast confirms).
- **Confirm + sign-in gated.** Publishing always pops a confirmation first (the page becomes PUBLIC — anyone with the link can view it) and requires a signed-in Omniscio cloud account (a toast points you to Settings when signed out). There is no accidental one-click publish.
- **Managed in the Shares panel.** A published page is a normal Share artifact — it appears in the Shares panel where you can unpublish / revoke it, exactly like any other share.
- **Re-publishing updates the SAME link in place.** Edit a page and publish it again and its existing public link updates in place (same URL, new content) instead of piling up a new share each time. The page is associated to its share via `shared_links.vault_page_id`; the Shares relay's `mint-upload` op takes an owner-checked `existingToken` to re-upload to the same token (`serveShare` reads fresh, so the same URL serves the new content). Requires the Shares relay Cloud Function to be deployed; until then it degrades gracefully to a fresh link (the client detects the relay didn't honor the token and mints a new one).
- **Publish from desktop OR mobile.** The desktop right-click menus AND the phone/web KMS action sheet's **Publish page…** row both work; the desktop main process always does the render + image-inlining (a paired phone just triggers it over the WS bridge — `KMS_PAGE_PUBLISH` is baselined, not blocked), so the page renders identically either way.

**Security.** The publish path reuses the audited Share pipeline end-to-end: images are inlined ONLY through the guarded `serveVaultAsset` resolver (realpath-under-vault containment — a `../` traversal can never leak an off-vault file into the public page), remote `http(s)` images are left as URLs and **never fetched server-side** (no SSRF), and the markdown is rendered through the audited `renderMarkdown` sanitizer (marked + `sanitize-html`) so a `<script>` / `onerror` payload in the note never reaches the artifact.

**Wiring** (repo detail). The renderer's `usePublishPage` hook (`src/renderer/src/features/kms/hooks/usePublishPage.ts`) gates on `useShareSignedIn`, confirms via `useConfirmDialog`, then invokes `KMS_PAGE_PUBLISH` ([kms-handlers.ts](/src/main/ipc/kms-handlers.ts)). Main renders the page via `renderVaultPageHtml` ([publish-page.ts](/src/main/services/kms/publish-page.ts) — strip frontmatter → inline local images → `renderMarkdown` → standalone HTML shell) and publishes it through the shared Share Artifacts service (`pastedContent` HTML) so one Firebase publisher + dedup cache back both this and the renderer's `SHARE_PUBLISH_ARTIFACT`. The Copy link + Publish rows on the tab and editor-body menus come from the shared `buildPageShareActions` factory; the file tree adds Publish alongside its existing Copy link. Tests: [publish-page.test.ts](/tests/unit/services/kms/publish-page.test.ts) (inline security + render/sanitize), [page-share-actions.test.tsx](/tests/unit/features/kms/page-share-actions.test.tsx).

## Related

The vault itself is the [KMS](kms.md) page, and the internals — the IPC surface, how it is built, and what it promises about your data — are [part 6](kms-part-6.md). The note summaries are covered in more depth on [KMS summaries](kms-summaries.md), and the agent tools on [KMS agent tools](kms-agent-tools.md).
