# FactMem

**Category:** 🗄️ Databases  
**Repository:** https://github.com/gordonkjlee/factmem  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/factmem

## Description
A local memory engine. You own the SQLite file. Not Mem0's hosted OpenMemory MCP.

## Claude Desktop Quick Installation
Heuristic fallback — verify the package name and runner against the repository README before running it. Uses `npx` (confidence: low):

```json
"mcpServers": {
  "factmem": {
    "command": "npx",
    "args": ["-y","factmem"]
  }
}
```

## Documentation & README

# Facthouse

<img src="https://raw.githubusercontent.com/gordonkjlee/factmem/HEAD/brand/mark.png" width="128" align="right" alt="Facthouse">

Facthouse is a local memory engine for AI tools. Most “memory” products index chat logs. Facthouse takes agent activity - messages, tool use, and other MCP traffic - and applies neuroscience-inspired consolidation so it moves through **Data** (what happened in the session) → **Information** (extracted facts) → **Knowledge** (integrated beliefs on an entity graph). During this process, Facthouse links entities, drops duplicates, reconciles conflicts, and supersedes what is out of date. Vector embeddings add optional semantic search on top of that graph. The store is a SQLite file on your disk.

[![npm](https://img.shields.io/npm/v/@facthouse/mcp.svg)](https://www.npmjs.com/package/@facthouse/mcp)
[![CI](https://github.com/gordonkjlee/facthouse/actions/workflows/ci.yml/badge.svg)](https://github.com/gordonkjlee/facthouse/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/github/license/gordonkjlee/facthouse)](LICENSE)

<a id="quick-start"></a>

## Quick Start

Needs Node 22.5 or 24+.

<!-- x-release-please-start-version -->
```bash
npm install -g @facthouse/mcp@0.30.0
facthouse init
```
<!-- x-release-please-end -->

If npm install -g fails because a command named mcp already exists, remove that leftover command and retry.

`facthouse init --web` is the same setup as a browser form — it prints a 127.0.0.1 URL and does not open a browser.

Press Enter to accept each default (copy = Claude Code or Cursor session logs on disk; type record if the assistant should save facts). If you picked copy, init asks whether to copy existing logs, then whether to extract and integrate. Init prints an MCP snippet — paste it into the client and restart.

In the client, state something durable in ordinary conversation — there is no remember command.

Ask it back in the next session, or `facthouse search`. That is the store.

Copy from Claude Code or Cursor logs, or record from any MCP client: [How conversations get in](#how-conversations-get-in). Replay: [facthouse.dev/demo.html](https://facthouse.dev/demo.html). CLI: [below](#cli).

## What you get

- **Local SQLite.** Optional Postgres. Isolation is the directory, not a column.
- **Entity graph.** People, organisations, projects, places, products — extracted, typed and linked.
- **Hybrid search.** BM25 + structured domain + entity-graph paths, merged via Reciprocal Rank Fusion. An embedding provider adds meaning as a fourth list; off by default.
- **In-session memory.** `get_session_context` is the same briefing as `memory://briefing`. Tools-only clients should call it at session start.
- **Immutable history.** Facts are never deleted, only superseded.

## How it works

One SQLite database. Three tables in it, not three databases: **Data** (what happened in the session) → **Information** (extracted facts) → **Knowledge** (integrated beliefs).

- **D** (`session_events`) — what was said (copied transcripts, or what the assistant records)
- **I** (`session_facts`) — what was just extracted, or `capture_fact`
- **K** (`facts`) — integrated knowledge

FTS5 (words) and optional embeddings (meaning) are indexes of **K**. They are not a second store. Semantic search is off unless you turn it on: `search "shellfish"` finds a shellfish fact, `search "food"` does not, until you choose an embedding model — a model is an opinion about what “similar” means.

Two speeds. **Extract** turns new transcript lines into self-contained facts. **Integrate** fits them into what the store already knows: domains, entities, duplicates, contradictions, the graph. `consolidate` runs copy, extract, and integrate together — in the server at session start and at compaction, or by hand from the CLI. Extract is capped at 50 lines per run, so a first backfill is never spent on the lot; each automatic run extracts facts from the oldest 50 lines. The MCP server copies the raw log on a call; it does not extract then. Consolidation does not invent a sentence nobody said.

Storage needs Node. Intelligence needs a language model. By default that is the [Claude Code CLI](https://github.com/anthropics/claude-code) on your existing subscription. Without it, consolidation falls back to a built-in heuristic that **does not extract facts from transcripts**. `capture_fact` still stores facts, with no entities and no domain routing.

## How conversations get in

Two ways. Pick one per store.

| | Copy from transcripts | The assistant records |
|---|---|---|
| Who | Claude Code or Cursor (session logs on disk, under the client home) | Any MCP client (Grok, Desktop, …) |
| How | Name a source; Facthouse copies new lines from those logs into the store | Empty `sources`; the assistant calls `capture_fact` |
| First run | TTY walk-through, pick **copy**, set cwd; init asks whether to copy existing logs, then whether to extract and integrate | TTY walk-through, pick **record** |

On a copy store, capture_fact is a correction for every MCP client, not only the one that writes JSONL. Grok has no transcript adapter — do not put Claude Code on copy and Grok on the same store expecting Grok to record.

```bash
facthouse init
```

Pick copy, set cwd. Init asks whether to copy existing logs, then whether to extract and integrate (Enter = all copied lines). Decline extract to do that later with `facthouse consolidate` (`--all` takes the whole backlog). After that, the server copies new lines when it handles a call.

Compact (optional): `facthouse notify compaction` — not a turn-end Stop hook.

## MCP

Works with any MCP-compatible tool. Default store: `~/.facthouse`. A different path is `"env": { "FACTHOUSE_DATA": "/absolute/path" }` on the MCP snippet. JSON accepts forward slashes on Windows.

Cursor consumes tools but not resources until a later adapter exists — `search_knowledge` and `get_entity` still work there; call `get_session_context` at session start.

Resources are context the client loads **automatically** — no tool call. Tools only help if the assistant remembers to reach for them; resources are simply present.

- `memory://briefing` — Everything worth knowing right now: profile, what was learned in the last consolidation, open threads, and recent knowledge. Markdown, kept to roughly a screenful.
- `memory://profile` — Core identity facts, most important first.

Both are read-only views over the same database the tools query. Clients that never load resources (Cursor, Windsurf, Grok) get the same briefing by calling `get_session_context` at the start of a conversation. No second profile schema.

### Tools

**Session**

- `log_event` — Log conversation events (messages, artifacts).
- `get_events` — Retrieve events from current or previous session.
- `get_session_context` — Working briefing (the same markdown as `memory://briefing`) plus facts captured in this session. Call at the start of every conversation if the client does not load resources.

**Reading**

- `get_entity` — Everything known about any named subject — person, organisation, project, place, product — and how it connects. When several rows share the name under different types, facts from all of them come back. Hyphens, underscores, and stray punctuation count as the same letters only when that does not join two names already stored as separate rows. If there is no entity by that name, facts that mention the wording still come back rather than an empty miss.
- `get_context` — Everything relevant to a topic (search + entity traversal)
- `search_knowledge` — Hybrid search across integrated knowledge

**Writing**

- `capture_fact` — Store a fact. On a copy store this is a correction for something extraction missed; on a store with empty `sources` it is how facts get in. The description the assistant sees is generated from that same rule.
- `consolidate` — Integrate pending facts into long-term knowledge. Extracts entities, resolves duplicates, detects contradictions, builds the knowledge graph.
- Inference tools — Opt-in, off by default (`inferences.enabled` in config.json). A hypothesis cites existing fact ids and stays pending until confirmed. Those tools are not registered until you turn the gate on. Consolidate never invents a sentence nobody said.

**Meta**

- `get_schemas` — Available domains and structure
- `get_stats` — Fact count, entity count, domain distribution, extract backlog, intelligence spend

## CLI

The MCP JSON starts the server via npx and does not need a global install. npm install -g puts facthouse on PATH for init, settings, stats, and inspect. The same CLI without PATH is npx -y -p "@facthouse/mcp" -- facthouse — pin the version; quote the package so PowerShell does not splat. -p and -- stop an older global binary winning. npx -y @facthouse/mcp with no -p / facthouse is the server; do not run it as a shell command for init, settings, or stats. The MCP paste starts the server. It does not put facthouse on PATH. To inspect the file from a terminal, see CLI below.

These CLI commands work in bash, zsh, and PowerShell. Quote @facthouse/mcp in PowerShell. Git Bash /c/... paths are not PowerShell; use C:/... and pass --data instead of cd or export. In Git Bash, quote a backslash path or write C:/... — unquoted \ is an escape. ~/ is expanded on every platform. WSL uses /mnt/c/.... FACTHOUSE_DATA on an MCP snippet applies only to that server process. A terminal facthouse command needs --data, or FACTHOUSE_DATA in the environment that shell inherits. Hooks do not see mcp.json env.

<!-- x-release-please-start-version -->
```bash
npm install -g @facthouse/mcp@0.30.0
facthouse init --yes
```

```bash
npx -y -p "@facthouse/mcp@0.30.0" -- facthouse init --yes
npx -y -p "@facthouse/mcp@0.30.0" -- facthouse settings --json
npx -y -p "@facthouse/mcp@0.30.0" -- facthouse stats
npx -y -p "@facthouse/mcp@0.30.0" -- facthouse inspect
```
<!-- x-release-please-end -->

| Job | Use |
|-----|-----|
| MCP server (what the client starts) | The JSON snippet: `npx` with args `-y` and a **pinned** `@facthouse/mcp@…`. No global install. |
| `facthouse` on PATH | `npm install -g @facthouse/mcp@…` (same pin). Update it when you bump the snippet. |
| Change extra knobs later | `facthouse settings` (or `settings --data <dir>`). Does not reset the file. |
| One CLI command, no PATH | `npx -y -p "@facthouse/mcp@…" -- facthouse …` |

#### `facthouse init [dir]`

The walk-through is how a human first-run writes `config.json`. Skip it and the server still creates the directory on first MCP boot.

On a terminal, init asks data directory, **copy transcripts vs assistant records** (default copy), semantic search, and More settings. `--yes` never prompts and leaves `sources` empty. `--web` prints a `127.0.0.1` URL and does not open a browser; `--yes` refuses `--web`. On a terminal, `--force` still asks those questions, then replaces the whole file; `--yes --force` is the silent reset. `--force` does not merge with the previous file.

```bash
facthouse init --yes
```

```bash
facthouse init --yes ~/my-memory
```

```bash
facthouse init --yes --force
```

The generated `config.json` is where you change consolidation behaviour — most notably `intelligence.provider` (`cli` by default; `heuristic` for a zero-dependency regex fallback, or `FACTHOUSE_PROVIDER=heuristic` at runtime). Init does not ask that field.

#### `facthouse settings`

Change extra knobs on an existing `config.json` (CLI model, timeout, optional local extract). Does not reset the rest of the file. Refuses if there is no `config.json` (this command does not create a store). `--json` / not a terminal prints the current knobs and does not write. `--web` is the same knobs on a local page (print URL, no auto-open).

```bash
facthouse settings
```

```bash
facthouse settings --data ~/my-memory
```

#### `facthouse record`

Inserts events directly into the database (no running server needed). Supported for demos and for stores that have no named source. Not the Claude Code or Cursor default — that is `sources` plus `facthouse consolidate`.

```bash
# From a hook (reads JSON payload from stdin):
echo '{"hook_event_name":"UserPromptSubmit","prompt":"hello"}' | facthouse record --role user

# With explicit content:
facthouse record --role user --event-type message --content "hello world"

# Options:
#   --role          user | assistant | system | tool (default: user)
#   --event-type    message | tool_call | tool_result | artifact (default: message)
#   --content-type  text | json | image | audio | binary (default: text)
#   --content       Event content (or pipe via stdin)
#   --speaker       Named participant when the transcript has one
#   --session-id    Target session (default: most recent)
#   --data          Data directory (default: ~/.facthouse or FACTHOUSE_DATA)
```

#### `facthouse consolidate`

Copy new lines from `config.sources`, extract candidate facts from them, and integrate the pending facts into knowledge. The one command that spends model calls:

```bash
facthouse consolidate
facthouse consolidate --copy            # copy only; spends nothing
facthouse consolidate --integrate       # pending facts to knowledge; no extract pass
facthouse consolidate --all             # extract the whole backlog now
facthouse consolidate --limit 200       # extract the oldest 200

# Steps — named steps run, in order; none named means all three:
#   -c, --copy       copy new transcript lines into the store
#   -e, --extract    turn new lines into candidate facts (the model call)
#   -i, --integrate  classify, link, dedupe, supersede, embed
# Extract is capped at 50 lines per run so a first backfill is never spent on
# the lot; the run says how many remain. --all lifts the cap, --limit N sets it.
#   --json           print the result object instead of the summary
#   --data           Data directory (default: ~/.facthouse or FACTHOUSE_DATA)
```

Honours the configured provider (by default `claude -p`). Empty `sources` makes the copy step a no-op. Set `cwd` on the source unless you intend to copy every project group. Do not also run `record` hooks on a store with named sources.

#### `facthouse notify <moment>`

Tell the running MCP server that a moment happened. The server decides what to run and does it in the background, so a hook returns at once:

```bash
facthouse notify compaction   # the client window is about to collapse: copy, extract, integrate now
facthouse notify threshold    # events arrived: extract if the threshold is due

# Options:
#   --data     Data directory (default: ~/.facthouse or FACTHOUSE_DATA)
```

No server listening is not an error: the command says so and exits 0, and the next session start covers it. This is what the PreCompact hook calls.

#### `facthouse search <query>`

```bash
facthouse search "coffee"
facthouse search "coffee" --domain preferences
facthouse search "coffee" --json

# Options:
#   --domain   Prioritise a domain. Biases ranking; does not filter
#   --limit    Maximum results (default: 20)
#   --json     Emit the raw search payload
#   --data     Data directory (default: ~/.facthouse or FACTHOUSE_DATA)
```

`--domain` **biases ranking rather than filtering.** A hard filter would hide a fact filed under a near-synonym.

#### `facthouse stats`

```bash
facthouse stats
facthouse stats --json
```

Facts are immutable — superseded facts are kept — so the current count and the total legitimately differ once anything has been superseded. `--json` includes the answering binary's package version. Intelligence spend is calls, tokens, and elapsed time for extract / classify / entities / reconcile / supersede / summarise, with provider and model per stage. Embeddings are not that number.

#### `facthouse inspect`

Sample D, I, K, entities, and the graph. Writes a local HTML file under the data directory (not the cwd). Prints the path. Does not open a browser. The file is a memory export — treat it like `stats --json`. The same page also shows intelligence spend (Graph / Spend).

```bash
facthouse inspect
facthouse inspect --graph
facthouse inspect --layer k
facthouse inspect --json
facthouse inspect --entity Helios --limit 20 --output ~/inspect.html
```

`--layer health|d|i|k|entities|graph|all` prints terminal tables (newest-first, capped). `--graph` (the default when no `--layer` / `--json`) writes `inspect.html`. `--limit` is 10 for tables and 50 for the canvas. `--all` draws every node — a hairball, explicit. Search and type filter in the page can still reach a node that was outside the cap.

## Advanced

### Another store

The store is this directory. Clients share it by using the same path. A second store is a second directory, not a second install. The default MCP server name is `facthouse`. Splitting is not a filter on which client wrote the row. Work and personal is one reason to split, not a required setup.

A non-default data directory prints a distinct MCP server name so two stores can share one `mcp.json`. Init against each extra directory prints that snippet. Example:

<!-- x-release-please-start-version -->
```json
{
  "mcpServers": {
    "facthouse-personal": {
      "command": "npx",
      "args": ["-y", "@facthouse/mcp@0.30.0"],
      "env": { "FACTHOUSE_DATA": "C:\\Users\\alex\\.facthouse-personal" }
    },
    "facthouse-work": {
      "command": "npx",
      "args": ["-y", "@facthouse/mcp@0.30.0"],
      "env": { "FACTHOUSE_DATA": "C:\\Users\\alex\\.facthouse-work" }
    }
  }
}
```
<!-- x-release-please-end -->

Point each store's `sources.cwd` (or hook `--data`) at that store only. Two directories do not isolate anything if both copy the same home.

### Postgres (optional)

SQLite is the default and needs no extra software. To use Postgres instead, set `storage.provider` to `"postgres"` in that store's `config.json`, or `FACTHOUSE_STORAGE=postgres` on the MCP entry, and set `FACTHOUSE_POSTGRES_URL` to a `postgres://` (or `postgresql://`) URL. The password belongs in the environment, not in `config.json`. If the URL is missing or the server cannot be reached, Facthouse stops; it does not create a SQLite file.

The data directory is still the memory: `config.json` and the scheduler socket live there. Tables live at the URL. Two memories need two directories and two databases.

Init does not ask which engine to use. `facthouse init --yes` still writes sqlite.

Example — placeholders only; do not put a real password in a committed file:

<!-- x-release-please-start-version -->
```json
{
  "mcpServers": {
    "facthouse": {
      "command": "npx",
      "args": ["-y", "@facthouse/mcp@0.30.0"],
      "env": {
        "FACTHOUSE_DATA": "C:\\Users\\alex\\.facthouse-work",
        "FACTHOUSE_STORAGE": "postgres",
        "FACTHOUSE_POSTGRES_URL": "postgres://USER:PASSWORD@localhost:5432/facthouse"
      }
    }
  }
}
```
<!-- x-release-please-end -->

### Copy versus record

Choose one mechanism per store.

**Recommended — copy.** Name a `claude-code` or `cursor` source (set `cwd`) and run `facthouse consolidate` from the CLI first. The MCP server also copies at session start and when it handles a call. Grok and Codex are later adapters. Unknown `kind` values are rejected.

```json
{
  "sources": [
    {
      "kind": "claude-code",
      "home": "~/.claude",
      "cwd": "C:\\dev\\app"
    }
  ]
}
```

`home` is the client config dir (`~/.claude` or `~/.cursor` — path examples, not extra discovery). Cursor is `"kind": "cursor"` and `home/projects/*/agent-transcripts/**/*.jsonl` only — not Composer SQLite. Cursor encodes `C:\\dev\\app` as `c-dev-app` (Claude Code uses `C--dev-app`). A first backfill of more than 50 lines takes several runs, or one `facthouse consolidate --all`.

**Alternative — record, no sources.** Leave `sources` empty. Pipe a client hook payload into `facthouse record` if you have one. MCP `log_event` / `capture_fact` keep working.

Do not install record hooks on this store — both write the same rows. Facthouse does not detect or rewrite existing hook configs.

### MCP-only record mode

To skip the wizard (record only — no transcript copy), paste this. The server creates `~/.facthouse` on first boot; you are not asked those questions.

<!-- x-release-please-start-version -->
```json
{
  "mcpServers": {
    "facthouse": {
      "command": "npx",
      "args": ["-y", "@facthouse/mcp@0.30.0"]
    }
  }
}
```
<!-- x-release-please-end -->

### Hooks (after the first consolidate)

`mcp.json` `env` is **not** visible to hooks. Pass the same `--data` (or set `FACTHOUSE_DATA` in the environment the client itself inherits). The command must invoke the CLI (`facthouse`), never the server binary. `npx -y @facthouse/mcp` with no `-p` / `facthouse` starts the MCP **server** and hangs a hook. Pin the package version, quote it if the hook runs PowerShell, and put `--` before `facthouse` so a globally installed older binary on PATH cannot win.

<details>
<summary>PreCompact hook JSON</summary>

<!-- x-release-please-start-version -->
```json
{
  "hooks": {
    "PreCompact": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "npx -y -p @facthouse/mcp@0.30.0 -- facthouse notify compaction --data /absolute/path/to/the-same-store"
          }
        ]
      }
    ]
  }
}
```
<!-- x-release-please-end -->

</details>

PreCompact `notify compaction` asks the running server to consolidate: copy the newest JSONL lines, extract, integrate. The hook returns at once; the server does the work. We do not install a turn-end Stop hook. On Windows the `--data` path is the same absolute directory you put in `FACTHOUSE_DATA` (for example `C:\\Users\\alex\\AppData\\Local\\Temp\\facthouse-try`).

Frequent incremental copying interleaves conversations on the global sequence: a long chat kept open is sliced between other chats. Extract progress is per conversation, so a timeout in one chat does not discard another. Shrinking `extraction.batch_size` means more extract calls (more chances of a timeout), not a store-wide hold-all. `facthouse stats` reports unextracted events against that extract watermark.

If the MCP server does not start, or lists no tools, check the package version the client actually spawned. A global `facthouse` on PATH can be years behind the pin in this README. Diagnose with `facthouse stats --data <dir>` (the CLI prints whether the scheduler is listening) and by inspecting `serverInfo.version` from `initialize` plus `tools/list` over stdio. `0.2.x` answers `initialize` then throws on `tools/list`.

### Embeddings, model, timeout, bitemporal

Set `embedding.provider` in `config.json` to `"ollama"` (local, no API key) or `"voyage"` (hosted), run `facthouse consolidate`, and `search "food"` starts returning the allergy. Facts are embedded when they are consolidated. Voyage applies a **3 requests/minute** rate limit until a payment method is on the account.

Meaning-search is an exact scan of stored vectors when the set is small. When that set is large (default 32 MiB of the current model), an HNSW index of those vectors is used instead: in-process on SQLite, or a Postgres `vector` sidecar when the extension is enabled. Small stores stay exact. A missing engine keeps exact search and prints a warning; Facthouse does not install a native addon. `embedding.ann` is `null` (auto), `false` (never), or `true` (force when the engine allows). This does not turn embeddings on.

`intelligence.cli.model` and `intelligence.cli.timeout_ms` are extra knobs. First-run More settings (Y) can write them; later, `facthouse settings`. Init does not ask `intelligence.provider`; `FACTHOUSE_PROVIDER=heuristic` is the kill-switch. The heuristic fallback **does not extract facts from transcripts**.

Unnamed user-channel speech is attributed to the store's owner; a display name still does not create a person. Extra backing (assent, a tool observation, a different speaker restating) is recorded, not scored, unless the store sets `interlocutor` ranking weights in `config.json`. The engine ships none. Weight keys match the speaker string as stored, so two people with the same name share a key.

Set `temporal.mode` to `bitemporal` to record when the system retracted a belief, so search can answer what the store believed at an instant.

### Intelligence spend

`facthouse stats` and `get_stats` report billed consolidation calls: tokens, elapsed time, and the provider plus model on each stage (extract, classify, entities, reconcile, supersede, summarise). A run that did not report tokens omits those fields rather than showing zero. Embeddings are a different API and are not this number.

Optional `intelligence.token_budget` caps billed extract per provider on rolling windows. Unset is unlimited. Over the cap, consolidate skips extract, holds the watermark, and does not fall back to the heuristic. Stats and inspect Spend show used and remaining on each cap, and when oldest usage in that window ages out (`resets`).

```json
"intelligence": {
  "token_budget": {
    "cli": { "week": "10M" }
  }
}
```

`hour`, `day`, `week`, and `month` are rolling. Omit a scale to leave it unlimited. Remaining room is on `facthouse stats`, `get_stats`, and inspect Spend. Set the cap in this store's `config.json` — there is no budget command.

Optional local intelligence is a different switch from embeddings. Add `intelligence.http` on an OpenAI-compatible host. The protocol is `POST /v1/chat/completions`; only the port changes:

| Host | Typical URL |
|------|-------------|
| Ollama | `http://localhost:11434/v1` (the default if you omit the URL) |
| LM Studio | `http://localhost:1234/v1` |
| vLLM | `http://localhost:8000/v1` |
| llama.cpp | `http://localhost:8080/v1` |

The model string is whatever that host lists. `GET {base_url}/models` prints the names. `nomic-embed-text` is embed-only and will not extract. If the host is up and serves exactly one chat model, Facthouse uses it for this run and tells you to pin `intelligence.http.model`. If several chat models are listed, set that field; extract will not guess.

Extract and summarise then use that host; reconcile and supersede stay on the CLI unless you list `intelligence.stages`. Each stage can set on-fail to cli, http, or none (see the JSON below). HTTP extract defaults to retrying on the CLI (counts against the CLI token budget). Contradiction defaults to none — no provider switch. none holds the extract watermark — it does not fall through to the heuristic. First-run More settings (Y, after the recommended path) can set the host, model, and extract on-fail. Later, `facthouse settings` merges those knobs into an existing file without resetting it. `facthouse inspect` Spend shows the same knobs and copies JSON; it does not save `config.json`.

The live script `npm run test:http-intelligence` has passed on `qwen2.5vl:7b`.

```json
"intelligence": {
  "http": {
    "base_url": "http://localhost:11434/v1",
    "model": "qwen2.5vl:7b"
  },
  "stages": {
    "extract": { "provider": "http", "on_fail": "cli" },
    "summarise": { "provider": "http", "on_fail": "cli" },
    "reconcile": { "provider": "cli", "on_fail": "none" },
    "supersede": { "provider": "cli", "on_fail": "none" }
  }
}
```

### CLI demo (no transcript source)

Throwaway store, not the capture path for a real Claude Code or Cursor home. These three lines are typed in.

<!-- x-release-please-start-version -->
```bash
export FACTHOUSE_DATA=/tmp/facthouse-demo
om() { npx -y -p "@facthouse/mcp@0.30.0" -- facthouse "$@"; }

om init --yes

om record --role user --content "I prefer dark mode in every editor, and I never want telemetry enabled."
om record --role user --content "I am allergic to shellfish, so avoid seafood restaurants when booking anything."
om record --role user --content "My colleague Robin at Acme is leading the Atlas migration project this quarter."

om consolidate
om search "Atlas"
om stats
```

```powershell
$env:FACTHOUSE_DATA = Join-Path $env:TEMP "facthouse-demo"
function om { npx -y -p "@facthouse/mcp@0.30.0" -- facthouse @args }
om init --yes
om record --role user --content "I prefer dark mode in every editor, and I never want telemetry enabled."
om record --role user --content "I am allergic to shellfish, so avoid seafood restaurants when booking anything."
om record --role user --content "My colleague Robin at Acme is leading the Atlas migration project this quarter."
om consolidate
om search "Atlas"
om stats
```
<!-- x-release-please-end -->

`allergies` is not a domain Facthouse ships. The engine has no built-in vocabulary — it read the conversation and decided that fact needed a home. A domain **biases ranking rather than filtering**. Clean up: `rm -rf /tmp/facthouse-demo` (Git Bash / macOS / Linux) or `Remove-Item -Recurse -Force $env:TEMP\facthouse-demo` (PowerShell).

## Integration

Facthouse's tool descriptions tell assistants when to search and when a correction is worth staging. They are not how Claude Code conversations enter the store — that is copy from a named source.

### Without configuration

Claude Code or Cursor: name a `sources` entry (set `cwd`) and run `facthouse consolidate` from the CLI first. MCP session start also copies. `capture_fact` is there if the assistant needs to correct or add something copy-plus-extraction will not produce.

Clients with no copy adapter still rely on `log_event` / `capture_fact` until their adapter exists.

### Hook points

| Hook point | When | What to call | Why |
|---|---|---|---|
| Session start | Conversation begins | `memory://profile` (automatic), `search_knowledge` | The assistant knows who you are from message one |
| Correction | A durable fact is missing from the store | `capture_fact` | Optional; Claude Code conversations are already in `session_events` via copy |
| Pre-response search | Before generating a reply | `search_knowledge`, `get_context` | Responses informed by stored knowledge |
| Pre-compaction | Before context window compression | `facthouse notify compaction` | The server copies new lines, extracts, integrates |
| Natural breakpoints | Topic change, task completion | `consolidate` (optional) | Keeps the knowledge graph current |

**On pre-compaction:** `facthouse notify compaction` asks the server to consolidate. It is not a `record` hook.

### Claude Code

Create `.claude/rules/facthouse.md` in your project (or `~/.claude/rules/facthouse.md` globally):

```markdown
# Facthouse

- Conversations are copied from the named Claude Code source (first backfill: `facthouse consolidate` on the CLI)
- Do not install record hooks on this store
- Identity context loads automatically from the `memory://profile` resource — no tool call needed
- Before answering questions this store might already know, call `search_knowledge`
- Call `capture_fact` only to correct or add something that is not in the transcript
- When the conversation is getting long, call `consolidate` (or rely on PreCompact `facthouse notify compaction`)
- At natural breakpoints (topic change, task completion), call `consolidate` to keep the knowledge graph current
```

To allow Facthouse tools without per-call approval prompts, add to the `permissions.allow` array in `.claude/settings.json`:

```json
{
  "permissions": {
    "allow": [
      "mcp__facthouse__*"
    ]
  }
}
```

### Cursor / Windsurf

Add to `.cursorrules` (Cursor) or `.windsurfrules` (Windsurf) in your project root:

```
When the facthouse MCP server is available:
- Before answering questions this store might already know, call search_knowledge
- To find out everything known about a particular person, project, or thing, call get_entity
- Call capture_fact only to correct or add something copy or extraction missed
- When context is getting long, call consolidate to process pending facts before they are lost
```

Cursor and Windsurf consume tools but not resources, so `memory://profile` will not load on its own there. Cursor conversations themselves are copied with `kind: "cursor"` (JSONL under `~/.cursor/projects/`, not the SQLite composer store).

### Claude Desktop / other MCP clients

No copy adapter yet. Tool descriptions handle search and optional `capture_fact`; conversations are not tailed until a later adapter exists.

## Reclaiming space

Facthouse logs raw conversation and tool output to `session_events`. On a store wired into an agentic client this becomes almost all of the database. A store measured in daily use held 47,000 events and 493 MB against 21 integrated facts.

`facthouse stats` reports the raw layer alongside the knowledge, including how much is reclaimable. To reclaim it:

```bash
facthouse prune                    # report only — nothing is deleted
facthouse prune --apply --vacuum   # delete, then rebuild the file
```

Set `retention.disk_budget` in `config.json` to a size such as `"2GB"` to cap `memory.db`. Unset is unlimited; init does not write a cap. When a cap is set and the file is full, unreachable raw events are pruned automatically so new logs can reuse that space; if nothing unused remains, more raw events are refused. Facts are never deleted to meet the number. Compacting (`--vacuum`) is still a human step — it copies the whole file so the operating system sees the smaller size.

If most of that volume is tool output you judge to be noise, `extraction.event_types` and `extraction.roles` restrict what is examined, and `extraction.min_content_length` skips trivial events. Measure before you do. Volume and value are not the same axis.

**The rule is reachability, not age.** An event is removed only when all three hold:

1. Extraction has already read it. Anything ahead of the consolidation watermark is still input.
2. No fact's provenance cites it.
3. It has fallen outside its own session's most recent `extraction.working_memory_size` events — a spare so consolidation can still glance at recent raw notes. That window is evidence of the current topic, not a pronoun dictionary.

No fact, entity, embedding or search result is affected. Deleting rows does not shrink the file on its own — that is `--vacuum`. Without a cap, nothing prunes automatically.

## Development

```bash
git clone https://github.com/gordonkjlee/facthouse
cd facthouse
npm install
npm run build
npm test
```

`npm test` always runs hermetic pipelines (fixture JSONL → copy → extract →
search) with a recording extractor, and skips live evals that need a real
model:

- Semantic recall needs Ollama with `nomic-embed-text`. Start it, then
  `npm run test:semantic`.
- The live first-fact eval needs the `claude` CLI. Run `npm run test:first-fact`.
- The live coding-store eval (warehouse-shaped Cursor transcripts) also
  needs the `claude` CLI. Run `npm run test:coding-store`.
- Local HTTP extract needs a chat model on an OpenAI-compatible host and
  `FACTHOUSE_HTTP_MODEL` (verified on `qwen2.5vl:7b`). Run
  `npm run test:http-intelligence`.

Each of those scripts fails rather than skips when its dependency is missing,
so a green run means the claim was actually verified rather than quietly
stepped over.

## Contribute

Issues and pull requests are welcome. Open an issue first if the change is more than a typo.

- Questions: [GitHub Discussions](https://github.com/gordonkjlee/facthouse/discussions)
- How to build and test: [CONTRIBUTING.md](https://github.com/gordonkjlee/factmem/blob/HEAD/CONTRIBUTING.md)

## License

MIT

