HyperMnesia is deliberately small. The bet is that the hard part of agent memory is not storage but delivery — getting the right context in front of the agent at the right moment, deterministically where possible. Everything below serves that.
All of it lives in one Postgres database with the pgvector extension: dense vectors,
full-text, the structural model, and personal memory. No separate vector DB, graph DB, or
cache. Reasons:
- A personal / small-team memory is not at a scale where a dedicated vector store earns its operational cost. Postgres HNSW is plenty, and you get transactions, joins, and one backup.
- Keeping the structural graph, the docs, and their embeddings in the same place lets a single query fuse them. The moment they live in three systems you spend your time syncing them.
Retrieval is layered from deterministic to fuzzy, cheapest and most reliable first:
-
Tier 0 — project map + global invariants. A pinned, human-curated
componentstree and themust/shouldconstraints that always apply. Injected on orientation; no search. -
Tier 1 — path -> component -> constraints. A glob resolver maps a file path to its component (longest-match wins,
prioritybreaks ties), then pulls that component's constraints plus one hop along the dependency graph. This answers "what rules apply to this file?" before an edit, with no model in the loop. It is the highest-value, lowest-variance part of the system, and it is entirely hand-authored (see the example seed). -
Tier 2 — hybrid search. For everything not covered by the map. Reciprocal Rank Fusion of two legs:
- dense: bge-m3 cosine over
pgvectorHNSW; - lexical: a composite
tsvector=<lang>(stemmed)|| simple(exact tokens). Thesimplehalf is what makes code identifiers, IDs, and non-English terms findable — the stemmer alone mangles them.
RRF (
1/(60+rank)) needs no score calibration between the legs. A cross-encoder reranker is an optional final pass over the fused top-N; on our eval set it moved recall@1 by +0.16. - dense: bge-m3 cosine over
Why RRF and not a weighted score blend: the two legs produce incomparable scores (cosine
distance vs ts_rank). Rank fusion sidesteps calibration and is robust. Two deliberate lexical
choices carry it: OR-convert the tsquery (AND is too strict for recall) and length-normalize
the rank (ts_rank_cd(...,1)) so long documents don't dominate.
One installation serves many projects. documents, components, and constraints all carry a
repo column; search and the map are scoped by it (HM_REPO, or the MCP server's working-dir
name). Component slugs are unique per repo, not globally.
Memory is an enhancement, never a gate. The personal-memory hooks always exit 0 and never block a tool call — but they do not answer an outage with silence: an unreachable store is announced once per outage, because "nothing matched" and "nothing answered" read identically to the agent. Doc search degrades gracefully: if the embedder is down it falls back to lexical-only and says so, since a silent half-retrieval makes "few results" read as a gap in the corpus (a slow embedder can't hang it — short query timeout), and if Postgres is down the MCP tool surfaces the error rather than blocking. A broken memory path must never stop the agent from working.
- Not a RAG framework — it ingests markdown docs, not arbitrary connectors.
- Not an autonomous agent — it stores and serves; the agent decides.
- Not multi-tenant/auth'd out of the box — it assumes a trusted local/homelab boundary. Put it behind your own network controls before exposing Postgres.