Product Memory
# Product Memory
A what/why memory server for coding agents, over MCP. It answers what a
piece of a system *means* and *why it was built that way* — down to the
function level — so an agent (or you) stops re-deriving or re-breaking a
decision someone already made. The current code stays the source of truth
for *how*; this store never tries to replace it.
This repo ships with a small **synthetic demo store** (`memory-store/`) —
two fictional services, `orbitcart` (checkout/payments) and `beacon`
(notification dispatch) — so `pm eval`, the tests, and the MCP tools all run
out of the box without pointing at anyone's real codebase. Point
`projects.yaml` at your own repos to use it for real.
## Get it running — no coding experience needed
**1. Download it.** Pick whichever is easier:
- If you have Git: open Terminal and run `git clone <this repo's URL>`
- If you don't: on the GitHub page, click the green **Code** button →
**Download ZIP**, then unzip it.
**2. Open a terminal inside the folder you just downloaded.**
- **Mac:** find the folder in Finder, right-click it, choose *New Terminal
at Folder* (or open Terminal and type `cd ` followed by dragging the
folder in, then press Enter).
- **Windows:** open the folder in File Explorer, hold Shift and
right-click inside it, choose *Open PowerShell window here*.
- **Linux:** right-click inside the folder in your file manager, choose
*Open Terminal Here* (varies by desktop).
**3. Run the setup script.**
- **Mac / Linux:** type `bash setup.sh` and press Enter.
- **Windows:** type `.\setup.ps1` and press Enter. If it says the script
is blocked, run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`
once first, then try again.
That's it — it installs everything this project needs (nothing system-wide,
no admin password), builds the included demo, and runs a real search to
prove it works. You'll see something like:
```
✓ Python 3 found (3.13.5)
✓ uv found
✓ Dependencies installed
✓ Demo memory store indexed
Trying a real search against the demo store...
8.75 [adr/verified] adr-0004-idempotency-keys-generated-client-side
ADR-0004: idempotency keys are generated client-side, not server-side
```
If Claude Code is already on your machine, the script will offer to connect
Product Memory to it — say yes, restart Claude Code, and it's live for every
project. If not, or if you use a different coding agent, see **MCP tools**
below and point your agent's MCP config at
`uv run --directory <this folder> python -m product_memory.server`.
Once it's running, try:
```bash
uv run pm serve # a local web page to browse the memory
uv run pm search "your question here"
```
When you're ready to use it for real (not the demo), open `projects.yaml`
and point it at your own repositories instead.
## The two design bets
**Nothing an agent writes is trusted on arrival.** Every fact proposed via
`propose_memory` gets `status: proposed` — never `verified` — until a human
runs `pm review`. Trusting a wrong memory costs more than missing a right
one, so the default is "written," not "true."
**Ranking is measured, not assumed.** `pm eval` scores keyword search (BM25
over SQLite FTS5) against a semantic vector index on a fixed set of real
questions with known answers, and re-checks it on every run rather than
settling it once. Whichever ranks better *this run* is the one that ranks —
in the author's private corpus (1,192 items) that's keyword at 0.785 MRR vs.
0.436 for semantic-only — with the vector index only appended below it as
extra recall, never reordering keyword's result. On this repo's small
12-question demo set, keyword alone already finds all 12 (`pm eval` →
`0.819 MRR, 12/12`); run `pm embed` first if you want the semantic/fusion
rows in the comparison too. See `eval/queries.json` and
`product_memory/evaluate.py`.
## How memory gets populated
Never a full backfill — it would be stale before it finished. Four channels:
| # | Channel | When | What lands |
|---|---------|------|------------|
| 1 | Docs import | once per repo | pointers/summaries of CLAUDE.md, CONVENTIONS.md, planning docs — never forked copies |
| 1b | Doc-tree import | once per large docs tree | bulk import with hard filtering (drops vendored docs, stubs, duplicates, "✅ Fixed!" session reports) |
| 2 | Change-time capture | every finished agent task | agent calls `propose_memory` → lands as `proposed` → promoted with `pm review` |
| 3 | Ask-time backfill | whenever you ask "why does X work like this?" | the agent researches once, answers you, and proposes the answer as a memory |
## Layout
```
memory-store/ canonical store — markdown files in git, one fact each
_inbox/ agent proposals awaiting human promotion (or auto-approved, see below)
<project>/<repo>/ verified + promoted items
demo-repos/ tiny stub repos the demo store's code_symbol entries point at
projects.yaml registry: project -> repos -> disk paths
product_memory/
models.py data contracts (MemoryItem, TaskContext, WhyCard, ...)
store.py parse/iterate/propose store files
index.py SQLite FTS5 build + ranked search (disposable index)
semantic.py chunking + vector index, used for recall only
evaluate.py `pm eval` — MRR per retrieval mode, the ranking gate
conventions.py derive a repo's house style (declared + observed)
retrieval.py packet assembly (deterministic, no LLM)
staleness.py flags memories whose source code/doc changed since
server.py FastMCP stdio server — the MCP tools
webapp.py FastAPI local server (`pm serve`), loopback only
dashboard.py the review queue UI
ingest/ importers + secret redaction
cli.py `pm` — the commands below
eval/queries.json retrieval cases with known answers
tests/
```
## Commands
```bash
pm serve # live local server: real search, feedback, persisted marks
pm dashboard --open # generate the standalone review-queue file
pm search "query" # ranked search from the terminal
pm eval # score retrieval against eval/queries.json — run before ranking changes
pm conventions --project beacon --repo beacon # derive a repo's house style
pm review # the only path from proposed to verified
pm index && pm embed # rebuild the keyword index and the chunked vector index
pm stale # notes whose source moved on
```
## MCP tools
`get_task_context` · `search_product_memory` · `get_project_overview` ·
`get_domain_rules` · `get_related_decisions` · `why_code(file, symbol)` ·
`get_recent_work` · `propose_memory` (writes `proposed`, or auto-approves with
redaction — see `PM_REVIEW=1` to force quarantine instead)
## Setup
New to this and just want it running? Use `bash setup.sh` (`.\setup.ps1` on
Windows) instead — see **Get it running** above. The manual steps below are
the same thing, spelled out:
```bash
git clone <this repo>
cd product-memory
uv sync
uv run pytest
uv run python -m product_memory.cli eval # or: pm eval, once installed
# register for ALL repos (user scope):
claude mcp add --scope user product-memory -- \
uv run --directory "$PWD" python -m product_memory.server
```
Then point `projects.yaml` at your own repositories, delete or keep the demo
`orbitcart`/`beacon` entries, and start capturing real memories with
`propose_memory` as you work.
## Secrets
Anything written into the store is passed through `redact_secrets` — a
known-literals list (`secret-literals.txt`, gitignored, or `PM_SECRET_LITERALS`)
plus a generic credential-shape heuristic (label + high-entropy value in
proximity). The demo store ships with nothing to redact; `pm eval`'s test
suite includes a CI guard (`test_demo_store_is_clean`) asserting exactly that.
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 9 tools
The tools have mostly clear, distinct purposes: search vs. read, context retrieval vs. rule retrieval, and write vs. read. However, the context-retrieval tools (get_project_overview, get_task_context, get_domain_rules, get_related_decisions) overlap somewhat and rely on descriptions to tell them apart, and search_product_memory with full=True partially duplicates read_memory.
Most tools follow a consistent get_/search_/read_/propose_ verb pattern, which makes the set predictable. why_code breaks the pattern as a noun-style tool name, and the mix of get_ with search_/read_ is a minor deviation.
Nine tools is well within the ideal range for a product memory server, and each tool covers a distinct retrieval or write need. The count feels appropriately scoped without being bloated or thin.
The surface covers the core lifecycle well: search, read, targeted context retrieval, recent work, and recording new memories. There is no explicit update/delete/invalidation tool for memories, though the append-only/id-suffix design suggests this may be intentional; still, explicit correction would make it more complete.