One call, and the model already knows

Retrieval is where a memory system is won or lost. Ask Memory OS a question and it decides which layers matter, searches them in parallel, fuses three independently ranked lists, drops what a newer fact has already contradicted, and hands back a block sized to the budget you asked for — with a manifest naming every record inside it.

  • 4 retrieval modes
  • 15 pipeline stages
  • 3 fused candidate lists
  • Source manifest on every call

Three searches, one answer

Vector search finds text that means something similar. Full-text search finds the invoice number you typed. The graph finds the record that never mentioned your keyword but sits one edge away from the client who did. Running one of the three is a demo; reconciling all three is the product.

Vector · pgvector cosine over embeddings top‑k ranked Lexical · tsvector Postgres full‑text, GIN top‑k ranked Graph candidates entity paths, optional top‑k ranked RRF Σ wᵢ / (k + rankᵢ) weights per source Rerank Diversity · MMR Temporal intent budget
Vector, full-text and graph candidates are ranked separately, fused by reciprocal rank fusion with independent per-source weights, then reranked, diversified and read in priority order until the token budget is spent. Everything below the cut is dropped rather than truncated mid-record.

What comes back is a pack, not a result set

POST /v1/context does not return rows for you to assemble. It returns the assembled block, ready to place in a system message, plus the machinery to audit it: the plan that was chosen, the manifest of what was used, the memory and fact ids that survived packing, and whether the whole thing came from cache.

The pack opens with a fixed line telling the model that everything below is evidence and not instruction. That sentence is the memory firewall — a poisoned document that reaches storage still cannot issue orders, because it is never presented as one.

Pass debug: true and the response carries a trace with per-layer counts, per-layer errors, the cache key and the temporal-intent signals that fired. Retrieval that you cannot inspect is retrieval you cannot fix.

The memory firewall in full

The planner reads the question before anything reads the database

Before a single row is touched, the query is classified into one of six types — fabric_why, decision_recall, knowledge_recall, preference_recall, conversation_recall, general_recall — and a mode is chosen. The mode sets a token budget; the type sets which of ten layer flags come on.

Mode Token budget Character ceiling Chosen when
cheap 1,800 7,200 Short questions about preferences or style. Skips episodic recall and the graph.
balanced 3,500 8,000 The default. Facts, semantic, episodic and profiles, with the graph when entities are named.
deep 8,000 8,000 Triggered by “all”, “everything”, “why do you think”. Turns on every layer including KB and Fabric.
forensic 14,000 8,000 Triggered by “audit”, “trace”, “evidence”, “prove”, “where did”. Appends the source manifest into the pack itself.

The character ceiling is min(token_budget × 4, MAX_MEMORY_CONTEXT_CHARS), and MAX_MEMORY_CONTEXT_CHARS defaults to 8,000. That means the deep and forensic budgets are not fully spendable out of the box — raise the setting, or pass an explicit max_chars on the request, if you want the larger modes to stretch. We would rather say that here than let you discover it in production.

You can also skip inference entirely. Send layer_filter and the planner pins exactly the layers you named — semantic, episodic, facts, fabric, sessions, kb, graph, entities, profiles — and ignores its own guess.

POST /v1/context/plan runs the planner and nothing else. It is a dry run: no tenant, no database, no cost. Use it to see what a question would have cost before you spend it.

Fifteen stages, and you can name the file for every one

This is the whole pipeline. Nothing on this list is aspirational, and nothing is hidden behind a marketing word.

StageWhat it doesWhere it lives
Adaptive query planning Six query types, four modes, ten layer flags pcnaid_os/query_planner.py
PII pseudonymisation Applied to the query before it is embedded memory/pii.py
Vector search pgvector cosine over HNSW; DiskANN when enabled memory/backends/postgres_backend.py
Lexical search Postgres tsvector with a GIN index memory/backends/postgres_backend.py
Graph candidates Entity paths as a third independently ranked list memory/graph_facade.py
Reciprocal rank fusion Σ wᵢ / (k + rankᵢ), k = 60, per-source weights memory/retrieval/rrf.py
Reranking Heuristic by default; cross-encoder optional pcnaid_os/rerank.py
Diversity Maximal marginal relevance over the reranked set pcnaid_os/rerank.py::_mmr
Temporal intent detection current · historical · both · unknown, with a confidence memory/temporal_intent.py
Stale-truth suppression Demotes memories a current fact has superseded services/chat.py
Token-aware packing Priority order, whole blocks, hard ceiling services/chat.py
Trust scoring Ten-tier source authority with recency and corroboration pcnaid_os/confidence.py
Source manifest Every layer, id and label that reached the pack services/unified_context.py
TTL context cache Keyed on tenant, user, query hash, mode and layer filter pcnaid_os/context_cache.py
Named recipes Six declarative presets over the same knobs cognitive/slots.py

The budget is spent in priority order

Packing is not truncation. The pack is built block by block in a fixed order of usefulness, and a block is added only if it fits whole. What does not fit is left out rather than cut in half, so the model never reads the first two lines of a decision and guesses the rest.

  • Temporal intent, with the guidance the detector chose
  • Current facts, labelled authoritative
  • Superseded facts, only when the question asks about the past
  • Graph entity paths
  • Knowledge-base extracts, each carrying its source label
  • Semantic memories, then episodic ones
  • Fabric entries — decision, status, project, evidence
  • Session snippets, with session id and timestamp
  • The source manifest, in forensic mode

The assembled pack is clipped to the character ceiling as a final guard, so a pathological single record cannot blow the budget.

Not every source deserves the same weight

A correction the user typed themselves is not the same kind of evidence as a line scraped from an imported archive. Trust scoring starts from a ten-tier authority table, then adds recency, explicit user confirmation and corroboration from other records, and subtracts a penalty for each contradiction and for sheer age.

The result is a single score between 0 and 1, and — more useful when something goes wrong — the breakdown that produced it, term by term.

SourceAuthority
direct_user_correction 1.00
verified_company_policy 0.95
fabric_decision_verified 0.90
current_fact 0.86
pinned_profile 0.82
semantic_memory 0.70
kb_document 0.66
session_summary 0.58
raw_episode 0.46
imported_unknown 0.28

Six named recipes, for when you want to decide instead of infer

The planner is deliberate about picking a mode for you. When you would rather state the shape of retrieval yourself, the recipes are the declarative form of the same knobs.

Recipe Vector top-k Lexical top-k Graph depth Reranker Token budget
cheap 12 12 0 off 2,000
balanced 32 32 1 on 6,000
deep 80 80 2 on 16,000
forensic 120 120 3 on 30,000
verified_only 48 48 1 on 8,000
low_latency 8 8 0 off 1,200

Every recipe sets prefer_verified; verified_only additionally refuses anything unverified, and forensic carries evidence references through to the output. These presets are served by /v1/memory-controls/retrieval-recipes and are stored by the cognitive tier's append-only file repository, not in Postgres — they configure that surface rather than the /v1/context planner, whose four modes are the table further up this page.

Slots and recipes in detail

Repeat calls, and honest measurement

The cache is keyed on the whole question
A context call is cached against a SHA-256 of the tenant, the user, a hash of the query text, the mode and the sorted layer filter. Default time to live is 30 seconds over at most 1,024 entries. An agent that asks the same thing twice inside a turn pays once, and the pack comes back with cached: true so you always know which it was.
The manifest is not optional
Every context call returns a source_manifest naming the layer and identifier of each record that reached the pack — fact ids, memory ids, Fabric entry ids and types, session ids and timestamps, KB document ids and their source labels. In forensic mode the manifest is also written into the pack itself, so the model can cite what it was given.
The benchmark endpoint is a smoke test
POST /v1/context/benchmark runs up to fifty queries through the real pipeline and reports per-query latency, how many memories and facts were used, the source count and the mode. It is a local harness for your own data — not LoCoMo, not LongMemEval, not BEAM. We publish no benchmark results, because we have not run those suites.
The one number we do quote
On our seeded two-case local suite, a balanced context pack came to 63 tokens against a 3,500-token budget. That is a token-efficiency measurement on a tiny fixture, and it says nothing about accuracy at scale. Treat it as a shape, not a score.

What is heuristic, and what is off until you turn it on

The query planner, the temporal-intent detector and the default reranker are regular expressions and scoring heuristics, not models. That is a deliberate trade: no API key, no per-query cost, no network hop on the hot path, and behaviour you can read in a file. An optional cross-encoder reranker and an optional LLM temporal classifier exist; both default to off.

Graph candidates require ENABLE_GRAPH_MEMORY, which defaults to false. Knowledge-base injection requires ENABLE_KB_RETRIEVAL, which also defaults to false — KB sources are still fetched, chunked and stored, but they do not enter a context pack until you enable it. Hybrid lexical-plus-vector search is on by default; DiskANN indexing is not.

PII pseudonymisation runs on the query before it is embedded, and it detects email addresses and US phone numbers. Not names, not addresses, not account numbers. It is a real control with a narrow scope, and calling it anything broader would be a lie.

Give your agents a memory you can audit

Run the whole system on your own hardware under the MIT licence, or ask us about hosted access. Both start from the same place.