# Knowledge base and search

> How notes become knowledge base pages, what the filer and nightly tidy do, and how agents search by keyword and by meaning.

Every workspace has a knowledge base: Markdown pages in your git folder, kept up to date from what agents learn, and searched before they answer about past work. This page explains how it fills itself, how to turn on search by meaning, and how to look after it.

## The knowledge loop

![When a conversation goes quiet, the agent writes a note with wheems note, and it waits in knowledge/inbox/. The filer files it by topic, Wheems checks the filer's work in code, and the result goes into knowledge/: pages/ (one topic per page), brief.md (what every agent loads) and notes/ (the note, kept as a source). The change is committed to git as "Wheems".](https://wheems.dev/docs/diagrams/knowledge-loop.svg)

1. **Agents write notes**, never pages.
2. **The filer**, a built-in agent you never talk to, turns each note into page changes.
3. **Wheems checks the filer's work in code**, then writes and commits it.
4. **Agents search** the pages, notes and inbox with `wheems kb search`.
5. **Overnight, the tidy** reports contradictions and stale pages, and refreshes the brief.

## About you and the brief

Two pages are loaded into every agent's instructions when it starts:

| Page | Where | Who changes it |
| --- | --- | --- |
| **About you** | `~/wheems/about-you.md`, shared by every workspace | You. The filer can suggest changes, which you approve in `wheems review`. |
| **The brief** | `workspaces/<id>/knowledge/brief.md` | The filer keeps it current: what's in progress, recent decisions, what's waiting on you. |

Only the first 4,000 characters of `about-you.md` are loaded. The filer keeps the brief under 4,000 characters.

## Notes

Agents write notes with `wheems note`, which reads Markdown:

```bash title="Terminal (in an agent's session)"
wheems note - <<'EOF'
# Picked the boat name

Halcyon, over Kestrel, because it's calmer.
EOF
```

- Wheems adds what agents can't be trusted with: the workspace, agent, engine, conversation and time. It saves the note to `knowledge/inbox/`, named by time, agent and title.
- `wheems note` works only inside a conversation Wheems started. Anywhere else it fails with `wheems note works inside a conversation started by Wheems (WHEEMS_CONVERSATION is not set).`
- A note is at most 32 KB.
- A note that looks like it holds a password, token or key is refused: `The note looks like it holds a password, token or key. Notes are kept in git: write it again without it.`

A saved note looks like this:

```markdown title="knowledge/inbox/2026-11-11-213709-chief-of-staff-picked-the-boat-name.md"
---
workspace: personal
agent: chief-of-staff
engine: claude-code
conversation: "personal:slack:D0123ABCD:1762896000.000100"
origin: person
written: 2026-11-11T21:37:09.000Z
---

# Picked the boat name

Halcyon, over Kestrel, because it's calmer.
```

### Notes when a conversation goes quiet

When a conversation has been quiet for 30 minutes after new work, and before an idle session is stopped to make room, Wheems asks the agent for a note, as a turn of its own. That turn's answer is never posted to Slack.

Change the wait, or turn it off, in `workspace.yaml`:

```yaml title="~/wheems/workspaces/personal/workspace.yaml"
knowledge:
  note_after_idle: 30 # minutes; 0 turns it off
```

## Filing

When a note lands, the service runs **the filer**. It runs in Herdr core like any agent, so you can watch it with `hc attach`, and handles one note at a time per workspace. It works on a copy of the knowledge base in `.wheems/filing/<workspace>/`: it reads the note, updates or creates pages, and keeps the brief current.

Then Wheems checks its work in code. A change is applied only if:

- each page has a name of lowercase words and dashes, and keeps at least three quarters of its text;
- every changed page lists the note among its `sources`, and keeps the sources it had;
- no page is deleted;
- the brief stays under 4,000 characters;
- nothing looks like a credential.

Anything else the filer wrote is ignored. Changes that pass are written, the note moves to `notes/<year>/<month>/`, and only those paths are committed, as "Wheems".

A page looks like this:

```markdown title="knowledge/pages/boat-name.md"
---
title: Boat name
sources:
  - 2026-11-11-213709-chief-of-staff-picked-the-boat-name.md
updated: 2026-11-11
---

The boat is called Halcyon, chosen over Kestrel because it's calmer.
```

**Your edits are safe.** If a page or the brief changed while the filer worked, such as an edit by hand, nothing is applied and the note is filed again from the new version.

**When filing fails.** A note the filer can't file is tried three times, then waits until the service restarts. A note that looks like it holds a credential is moved to `.wheems/quarantine/` and never filed or committed.

### Which model files

The filer runs on Claude Sonnet when the chief of staff is on Claude Code, and otherwise on the chief of staff's engine and model. Change it per workspace:

```yaml title="~/wheems/workspaces/acme/workspace.yaml"
knowledge:
  filer:
    engine: claude-code
    model: claude-sonnet-5-5
    account: claude-subscription
```

## The nightly tidy

Once a night the filer reads the whole knowledge base and writes two reports for you:

- `knowledge/reports/contradictions.md`: places where pages disagree with each other or with the brief, and which looks current;
- `knowledge/reports/stale.md`: pages that look out of date, such as a plan for a date that has passed.

It also refreshes the brief. It changes no page. Nights with nothing new are skipped, so it costs nothing then. A service that was stopped overnight tidies when it starts.

```yaml title="~/wheems/workspaces/acme/workspace.yaml"
knowledge:
  tidy_at: '03:00' # local time; false turns it off
```

## Search

Agents search before answering about past work. You can too:

```bash title="Terminal"
wheems kb search what did we decide about the boat name --workspace personal
```

```text title="Output"
Boat name
  /Users/you/wheems/workspaces/personal/knowledge/pages/boat-name.md (page, updated 2026-11-11)
  The boat is called Halcyon, chosen over Kestrel because it's calmer.
```

When nothing matches:

```text title="Output"
Nothing in the knowledge base matches.
```

It searches one workspace: its pages, filed notes and inbox. Inside an agent's session, that's the agent's own workspace. From your terminal, give `--workspace` unless you have only one. Add `--limit <n>` for more than five results, or `--json` for scripts.

### How results are ranked

- **By keyword**, always, with SQLite's full-text search, built into Node.js. Common words such as "what" and "the" are left out, and any remaining word can match.
- **By meaning** too, once you turn it on (below).
- The two rankings are combined by reciprocal rank fusion, and each file is shown by its best passage.
- A match by meaning alone must score at least the model's floor (0.5 for `nomic-embed-text`) and be within 0.1 of the best match. So a question the knowledge base can't answer gets "Nothing in the knowledge base matches." rather than unrelated pages.
- Notes from automated jobs, and pages built only from them, count half as much as your own work.

Files are split into passages of up to 1,200 characters, starting afresh at each heading. Links, files over 1 MB, and anything that looks like a credential are left out of the index.

### Turning on search by meaning

Search by meaning finds "the vessel is Halcyon" when you ask "what is the boat called". It uses [Ollama](https://ollama.com) with `nomic-embed-text`, running on your Mac for free. Nothing you index is sent anywhere.

1. Install Ollama and start it.
2. Run `wheems setup` and choose **Knowledge**, then **By meaning, with Ollama**. Setup uses the embedding model Ollama already has, or offers to run `ollama pull nomic-embed-text`.
3. Restart the service so it builds the index with the model: `wheems service restart`.

Setup writes this to `install.yaml`:

```yaml title="~/wheems/install.yaml"
knowledge:
  embeddings:
    driver: ollama
    model: nomic-embed-text
```

Add `url` if Ollama isn't at `http://127.0.0.1:11434`. To go back to keywords only, choose **Keyword search only** in setup, or remove `embeddings`.

### The index

The index is running state in `.wheems/index/`, never committed and rebuildable at any time. The service brings it up to date when it starts, after each note is filed, and every ten minutes for edits by hand. Only files that changed are read again.

```bash title="Terminal"
wheems kb reindex                      # every workspace
wheems kb reindex --workspace acme     # one
```

```text title="Output"
acme: indexed 12 files with nomic-embed-text
```

`reindex` builds a fresh index and swaps it in when it's done, so search keeps working meanwhile. When the service is running, it does the rebuild.

- Changing the embedding model rebuilds the index, because vectors from different models can't be compared. Until it finishes, search uses keywords only.
- If Ollama isn't running, search falls back to keywords and says so. `wheems doctor` warns: `Ollama isn't answering at http://127.0.0.1:11434, so search is keyword-only for now`.

## Editing the knowledge base by hand

Pages are Markdown files in git: edit them in any editor. The filer never overwrites an edit, and the service re-indexes within ten minutes. To undo a change the filer made, use git in `~/wheems`.

> **An instruction, not a lock:** Agents are told not to edit the knowledge base or `about-you.md` themselves, but nothing stops them yet: an agent runs in its workspace folder and could edit files there. Such an edit is treated like one by hand. Permission rules that enforce this arrive in milestone 6.
