CLAUDE.md · diff
git:20261008.155d1ad to git:20261102.84f2889
6 added, 5 removed. Audit A to A.
# Claude Code notes — memtomem
Markdown-first long-term memory MCP server (LTM). For what it does see
`README.md`; for setup, architecture, and project layout see `CONTRIBUTING.md`
and `docs/guides/`. This file only captures the few things Claude Code needs
in context that aren't obvious from those docs.
## Commands
Requires Python 3.12+ and `uv` (workspace-managed monorepo).
```bash
uv pip install -e "packages/memtomem[all]" # install deps
uv run pytest -m "not ollama" # tests (CI filter)
uv run ruff check packages/memtomem/src packages/memtomem/tests packages/memtomem-hermes-memory/*.py tools examples/onboarding/slateharbor/*.py examples/workflows/*.py && \
uv run ruff format --check \
packages/memtomem/src packages/memtomem/tests packages/memtomem-hermes-memory/*.py tools examples/onboarding/slateharbor/*.py examples/workflows/*.py # lint (required)
uv run mypy packages/memtomem/src # typecheck (advisory)
```
The `ollama` marker auto-skips when Ollama isn't running; CI always uses
`-m "not ollama"`. `ruff` and tests must pass to merge; `mypy` is advisory.
Lint covers `tests/`, `tools/`, the Hermes memory provider
(`packages/memtomem-hermes-memory/*.py`), the Slateharbor sample's tooling and the workflow
runner (`examples/onboarding/slateharbor/*.py` and `examples/workflows/*.py`).
The two example globs stop at their tooling directories, excluding Slateharbor's 150 generated
corpus files and the workflow package's hand-authored sample documents.
Linting only `src/` can pass locally and still fail CI
(`.github/workflows/ci.yml`). `test_docs_guards.py` pins these paths to the
workflow, so changing one means changing `CLAUDE.md` and `CONTRIBUTING.md` too.
CLI entry points live in `packages/memtomem/pyproject.toml` — `mm` is an alias
for `memtomem` and both resolve to `memtomem.cli:cli`.
## Invariants when editing
- **No Python-level dependency on `memtomem-stm`.** The STM proxy lives in a
separate repo ([memtomem/memtomem-stm](https://github.com/memtomem/memtomem-stm))
and talks to this LTM server only through the MCP protocol. Don't
`import memtomem_stm` from `packages/memtomem/src/`, and don't hand-roll an
in-process STM client — cross-repo coupling is explicitly forbidden.
- **`memtomem/privacy.py` syncs from STM, asymmetrically.** The redaction
patterns in `packages/memtomem/src/memtomem/privacy.py` are kept in sync with
memtomem-stm's `src/memtomem_stm/proxy/privacy.py:CREDENTIAL_PATTERNS` —
secret-class only; do **not** sync from STM's `DEFAULT_PATTERNS`, which folds
in `PII_PATTERNS` (the SHA pin lives in the module docstring). The trust
boundary is here, not there — STM-bypass must
not be safety-bypass. When STM adds a **secret-class** pattern, sync it
here in the same release window and bump the SHA. **PII-class** patterns
(email, phone, name, address, etc.) do not auto-sync — they would force
`force_unsafe=True` on most legitimate prose ingress; including any new
PII-class pattern requires a separate decision pass.
- **`mm` ≡ `memtomem`.** Both `project.scripts` entries in
`packages/memtomem/pyproject.toml` resolve to `memtomem.cli:cli` — keep them
in sync, don't diverge behavior or add flags to only one name.
- **Search pipeline order is fixed**: query expansion → BM25 + dense (parallel,
- both carrying the scope-context and tag filters) → RRF fusion →
- cross-encoder rerank (optional) → source filter → validity filter →
- time-decay → MMR → access-freq boost → importance boost → entity-match boost
- → context-window expansion. Don't reorder stages in
+ both carrying the scope-context, tag and source filters) → RRF fusion →
+ cross-encoder rerank (optional) → source filter (defensive recheck) →
+ validity filter → time-decay → MMR → access-freq boost → importance boost →
+ entity-match boost → context-window expansion. Don't reorder stages in
`packages/memtomem/src/memtomem/search/pipeline.py` without updating the
Stage comments in that file. Filters that select at retrieval belong on
`SearchMetadataFilter`; before pushing a new one into a leg's SQL, check
that leg's reaction to selectivity — `dense_search`'s adaptive KNN
over-fetch escalates to a full vector scan on a sparse filter, which is why
- `tags_any` is SQL for BM25/recall but post-KNN Python inside `dense_search`.
+ `tags_any` and `source_filter` are SQL for BM25/recall but post-KNN Python
+ inside `dense_search` (a match outside the KNN pool is not seen).
- **MCP tools go through the registry.** New tools use `@register` from
`server/tool_registry.py` + `@tool_handler`. The core-9 tools (`mem_search`,
`mem_add`, `mem_index`, `mem_recall`, `mem_status`, `mem_stats`, `mem_list`,
`mem_read`, `mem_do`) are registered directly via `@mcp.tool()` by design;
everything else MUST use `@register`. Add imports in `server/__init__.py`
and give the tool a `@register` category — `standard` mode exposes the
categories listed in `_STANDARD_PACKS`, `full` exposes all; the `mem_do`
meta-tool routes non-core actions. Don't change the default mode
(`core` = 9 tools).
- **Line length 100**, target `py312` (`tool.ruff`, `tool.mypy` in root
`pyproject.toml`). `.claude/`, `scripts/`, and `CLAUDE.local.md` are
gitignored — don't commit anything under them, and don't assume other
contributors have the same contents there.
## PRs
Branch from `main`, one focused change per PR, add tests for new behavior, and
write commit messages that explain the "why". See `CONTRIBUTING.md` for the
full checklist and the STM decoupling rules in `packages/memtomem/README.md`.