Skip to main content
Glama
pcamarajr

personal-knowledge

by pcamarajr
README.md
# personal-knowledge — local memory MCP server

> **Status: personal project, not production-ready.** This is shared for
> reference and informational purposes only. It's a local-first experiment I
> use and validate on my own machine — expect rough edges, breaking changes,
> and no support or stability guarantees. Use at your own risk.

A portable, AI-agnostic second brain. Stores typed memories (self / directive /
state / knowledge / episode / inquiry) with semantic recall and self-learning
re-ranking, exposed to any MCP-capable AI as a local server.

Built on [AgentDB](https://github.com/ruvnet/agentdb) (vector store + bandit
self-learning) and local [bge-small-en-v1.5](https://huggingface.co/Xenova/bge-small-en-v1.5)
embeddings via `@huggingface/transformers`. Pure local, no API keys, no network
required after the first model download.

## Quick start

```bash
npm install
npm run build
```

Smoke-test end-to-end (saves seven records, searches them, cites the top hit):

```bash
npm run smoke
```

## Wire it to Claude Code

The server runs as a long-lived **HTTP daemon** under launchd. Multiple Claude Code
sessions all connect to the same daemon, which owns the single RVF writer lock —
no more lock collisions across concurrent chats.

```bash
npm run build
./bin/install-agent.sh        # generates .env token, installs LaunchAgents, boots them
```

`install-agent.sh` is idempotent and installs three launchd jobs: the **daemon**
(`com.pcamarajr.memory-mcp`, always-on), the **morning digest**
(`com.pcamarajr.memory-digest`, 07:00), and the **dashboard**
(`com.pcamarajr.memory-dashboard`, always-on — it builds the Astro SSR bundle and
serves it at `127.0.0.1:7346`). Daemon and dashboard both run `RunAtLoad` +
`KeepAlive`, so they come back after a reboot and restart on crash.

Then point Claude Code at the daemon (user scope so every project sees it):

```bash
TOKEN=$(grep '^MEMORY_AUTH_TOKEN=' .env | cut -d= -f2)
claude mcp add --transport http --scope user memory http://127.0.0.1:7345/mcp \
  -H "Authorization: Bearer $TOKEN"
```

Confirm:

```bash
claude mcp list | grep memory     # → memory: http://127.0.0.1:7345/mcp (HTTP) - ✓ Connected
curl -s http://127.0.0.1:7345/health
```

Logs: `~/Library/Logs/memory-mcp.{out,err}.log` (daemon) and
`~/Library/Logs/memory-dashboard.{out,err}.log` (dashboard). Both auto-restart on
crash and on login. Remove both with `./bin/uninstall-agent.sh`.

## Dashboard & menu-bar status

The **dashboard** (read-mostly Astro UI: overview, browse, graph, detail) is
auto-started by `install-agent.sh` at `http://127.0.0.1:7346` (or
`http://memory.test:7346` once you've run `sudo ./bin/install-hostname.sh` — `.test`
rather than `.local`, which macOS routes to mDNS and won't resolve from /etc/hosts).
Edit it
and pick up changes with `npm --prefix dashboard run build && launchctl kickstart
gui/$(id -u)/com.pcamarajr.memory-dashboard`.

Host and port are env-driven (`MEMORY_DASHBOARD_HOST`, `MEMORY_DASHBOARD_PORT`,
default `127.0.0.1:7346`) — set them in `.env` and they flow through `astro.config`,
the launchd plist, the start wrapper, and the menu-bar plugin.

The **menu-bar plugin** is an ambient health indicator via
[SwiftBar](https://swiftbar.app): a 🟢/🟡/🔴 dot (daemon × dashboard up) with
sessions + memory count and restart/log actions, refreshing every 60s.

```bash
brew install --cask swiftbar     # one-time; never auto-installed
./bin/install-menubar.sh         # symlinks bin/memory.60s.sh into SwiftBar's plugin folder
```

Remove with `./bin/install-menubar.sh --uninstall`.

### Fresh-machine setup (everything, in order)

```bash
npm install && npm run build     # daemon deps + build
./bin/install-agent.sh           # daemon + digest + dashboard LaunchAgents (auto-start on boot)
brew install --cask swiftbar     # menu-bar host
./bin/install-menubar.sh         # menu-bar status plugin
```

### Stdio (legacy / smoke tests)

The original stdio transport is still supported when `MEMORY_TRANSPORT` is unset
or `stdio` — used by `npm run smoke` and useful when you want a one-shot
subprocess instead of a shared daemon:

```bash
claude mcp add memory -- node /Users/pcamarajr/Projects/pcamarajr/personal-knowledge/dist/src/server.js
```

Only one stdio instance can hold the RVF lock at a time, so don't mix it with
the daemon.

## The tools

| Tool | Purpose |
|---|---|
| `answer` | **Preferred for questions** — synthesizes a prose answer from the top memories and cites the records it actually used |
| `save_self` | Identity, preferences, goals, values, skills |
| `save_directive` | Explicit rules with scope + reason + severity |
| `save_state` | Active projects, people, tools, threads, places |
| `save_knowledge` | Facts about the world, definitions, references |
| `save_episode` | Decisions, outcomes, incidents, milestones |
| `save_inquiry` | Open questions and hypotheses |
| `search_memory` | Semantic search across all types with filters (summaries by default; `include_body=true` for full records) |
| `get_memory` | Fetch a record by id |
| `list_recent` | Recently updated memories — session-start orientation |
| `link_memory` | Connect two memories with a typed relation |
| `cite_memory` | Mark a memory as useful — feeds the self-learning signal |
| `supersede_memory` | Mark a memory as replaced by a newer one |
| `get_stats` | Counts by type |
| `analyze_subject` | Tiered subject snapshot for dashboards/LLM synthesis |
| `list_pending` | Open inquiries, stalled states, decisions without outcome |

### The `answer` tool (and its privacy trade-off)

`answer(question, scope?, k?)` runs `search → synthesize → cite`: it retrieves
the top-k memories, has a small Claude model (default `claude-haiku-4-5`)
synthesize a grounded prose answer, and calls the internal cite path for every
record the synthesis actually used. That citation stream is the **genuine usage
signal** the self-learning re-rank needs — and the synthesized answer keeps raw
record bodies out of the calling AI's context. If the memories don't cover the
question, it refuses instead of fabricating.

**Privacy:** the matched record bodies are sent to the Anthropic API for
synthesis. This is on by default; configure via `.env`:

| Env var | Default | Meaning |
|---|---|---|
| `MEMORY_ANSWER_ENABLED` | `1` (on) | Set `0`/`false` to not register the tool at all (nothing ever leaves the machine) |
| `ANTHROPIC_API_KEY` | — | Required for synthesis; without it the tool errors at call time |
| `MEMORY_ANSWER_MODEL` | `claude-haiku-4-5` | Synthesis model override |

## How the schema works

See [`docs/instructions.md`](docs/instructions.md) for the full schema, the
"when to save" rules, and the retrieval guidance shipped to AI consumers. That
file is the contract between this server and every AI that uses it.

## Storage

- Vectors and metadata live in `./data/memory.rvf` (AgentDB single-file format).
- Per-record metadata mirror lives in `./data/memory.rvf.meta.json` (sidecar; the
  underlying RVF store uses u64 ids and flat metadata, so we keep the rich record
  state in JSON next to it).
- Both files are gitignored.
- Override the location via `MEMORY_DATA_PATH=/some/other/path.rvf`.

## Self-learning

Signals are split into **exposure vs use** so the ranking can't feed on itself:

- being returned by a search bumps `surface_count` only (exposure);
- an explicit `get_memory` bumps `access_count` (use);
- `cite_memory(id, weight)` — called directly or via the `answer` tool —
  bumps `cite_count`/`reward_total` (the primary reward signal);
- `supersede_memory(old, new)` penalizes the older record.

Searches tagged `source: 'dashboard'` or `source: 'eval'` are exposure-only:
they don't feed co-access/co-surface edges or the auto-cite tracker, so UI
browsing and eval replays can't pollute the learning signals.

## Consolidation

```bash
npm run consolidate
```

V1 just reports stats. V2 will add LLM-driven dedup, link inference, and TTL
pruning.

## Scope of v1

In: local-only MCP server, 6 typed memory types, semantic search with 1-hop
linkage, bandit-based citation feedback.

Out (v2+): public hosting for Claude web, multi-hop Cypher graph queries via
the AgentDB causal graph controller, scheduled consolidation, clustering,
backfill from existing markdown.

Maintenance

ActivityStale
ResponsivenessNo issues