-
Notifications
You must be signed in to change notification settings - Fork 0
Python API and MCP
Two ways to call RE-call: as a library, or as an MCP server an agent can invoke as a tool.
from recall.generation_store import GenerationStore
from recall.embeddings import FastEmbedEmbedder
from recall.trust import trusted_search
emb = FastEmbedEmbedder()
with GenerationStore(DSN, dim=emb.dim, tenant="acme", pool_size=8) as store:
store.check_schema()
result = trusted_search(store, emb, "what is the rate limit?")trusted_search is the recommended agent-facing entry point: it runs hybrid retrieval, loads the
supersession graph, applies the trust layer, and returns a judged result. The lower-level pieces
(HybridRetriever, trust.evaluate) are public and composable, but you should not need them
unless you are building something unusual.
For local experiments against a plain chunk table, PgVectorStore remains available. In production,
use generation-routed serving so the query sees an active generation, lineage and certified
calibration binding rather than an ad hoc table.
Check abstained first. Always.
if result.abstained:
# Say you don't know. Do NOT answer from result.hits — none of them earned a clean verdict.
log.info("no trustworthy memory: %s", result.reason)
return None
for hit in result.hits:
hit.verdict # ok | superseded | expired | not_yet_valid | low_confidence | ...
hit.confidence # calibrated; 0.5 sits exactly on the abstention boundary
hit.cosine # raw dense cosine
hit.validity.superseded_by # the successor, when superseded
hit.validity.valid_until
hit.provenance.file # where it came from
hit.provenance.indexed_at # when it entered the index
hit.chunk.textThree things that are easy to get wrong:
hits is not filtered. Verdict-ok hits are ordered first, and the rest follow. That
ordering is the answer — a retrieved successor outranking the stale memory it replaced is the
whole thesis — but if you iterate the list without checking verdict, you will happily read a
superseded memory in position two. Filter on verdict == "ok", or stop at the first non-ok.
abstained is not the same as an empty result. It means no hit earned verdict ok. There
may still be plenty of hits — superseded ones, expired ones, near-misses — and reason tells you
which case you are in. That is deliberate: an agent that can see why it is abstaining can act
differently on "the answer exists but was superseded and I could not retrieve the successor" than
on "this corpus has nothing about penguins".
calibrated=False is a real signal. It means no usable calibration file was found for this
embedder, so the abstention boundary is a default rather than fitted. Worth logging; worth
alerting on in production.
Two further flags: gap_warning (every dense candidate scored below the threshold, a probable
corpus gap) and staleness (the index itself has not been updated recently, which no individual
hit would reveal). MCP responses also include tenant, generation, calibration identity, retrieval
profile and latency metadata so an operator can tell which process profile answered.
Full semantics of every field: The-Trust-Layer.
from recall.rerank import CrossEncoderReranker
from recall.entailment import QnliEntailmentJudge
result = trusted_search(
store, emb, query,
calibration=cal, # legacy local table mode only
reranker=CrossEncoderReranker(), # needs the `rerank` extra
entailment=QnliEntailmentJudge(), # needs the `entail` extra — read the caveat below
)Reranking helps when a relevant document is in the candidate pool but ranked too low. It is the largest single retrieval gain measured here, and it remains opt-in because it is a latency and model-artifact cost. → Retrieval-Pipeline
Entailment is off by default for a measured reason: it substantially improves near-miss
detection and degrades far-gap detection. The two stages stack; neither replaces the other. It
costs one judge pass per ok hit. → The-Trust-Layer
PgVectorStore(DSN, dim=emb.dim) # one long-lived connection, CLI and scripts
PgVectorStore(DSN, dim=emb.dim, pool_size=8) # pool, any server processThis is not a performance dial, it is a correctness one. A single connection shared across
threads serialises them, and a reconnect can swap the connection underneath a thread that is
using it. Pooling requires the pool extra.
Also worth passing in a server: statement_timeout_ms, so a runaway query cannot occupy a
connection until the process dies.
from recall.index import Indexer
stats = Indexer(store, emb).index_path("./notes")Incremental by content hash, prunes sources that vanished from disk, and refuses a mass prune — see Retrieval-Pipeline for what each guard is protecting against.
-
check_schema()is the runtime call. Apply ordered migrations separately withrecall schema apply, usingRECALL_MIGRATION_DSN. -
ensure_schema()is deprecated compatibility for disposable local stores and evaluation harnesses. -
check_rls_effective()tells you whether row-level security is actually enforced for the role you connected as. If you connect as a superuser or aBYPASSRLSrole, RLS is bypassed and the second isolation layer is decoration. → Tenancy-and-Auth -
The store is a context manager. Use
with, or callclose(). - A store is bound to one tenant for its lifetime. To serve several, hold several stores.
{ "mcpServers": { "recall": {
"command": "python", "args": ["-m", "recall_mcp.server"],
"env": { "RECALL_SERVING_DSN": "postgresql://...", "RECALL_TENANT": "acme" } } } }Works with Claude Code and Claude Desktop — the same block. Full setup guide:
docs/USING_WITH_CLAUDE.md.
| Tool | Scope required | Does |
|---|---|---|
recall_search |
recall:read |
Search memory. Returns hits with verdict, confidence, provenance and validity — or an abstention. Also returns an advice field stating what to do. |
recall_evidence |
recall:read |
Builds a citable evidence bundle from trusted hits and returns generator-neutral system/user messages. The client remains the generator. |
recall_index |
recall:write |
Index a file or folder into memory. Re-indexing a file replaces its chunks completely, so a shrunk file leaves nothing stale behind. |
recall_forget |
recall:forget |
Permanently delete a source's chunks. Irreversible, tenant-scoped. |
recall_stats |
recall:read |
Size and freshness of the memory. |
Each carries MCP annotations that describe its real risk — recall_forget is marked destructive,
the read tools are marked read-only — and the scopes mirror those annotations, so a
principal's permissions line up with the risk each tool actually carries rather than with an
arbitrary grouping.
recall_search returns advice. Not just data — an explicit statement of what the agent
should do. A model handed a JSON blob with abstained: true will often answer from the hits
anyway; a model handed "no valid hit survived, say you don't know" is much more likely to comply.
The tool docstring reinforces it: call this before proposing an idea, and if a closed decision
surfaces, do not re-litigate it. The anti-re-litigation guard is as much a prompt-design problem
as a retrieval one.
recall_forget reports sources_not_found separately. A source that does not exist is not
silently counted as removed. If you asked to forget three things and two existed, you need to know
that — otherwise an erasure request looks satisfied when a third of it silently did nothing. Check
that list before assuming a name was forgotten.
recall_evidence stops before generation. It returns only trusted passages plus the prompt
messages needed by a caller-owned generator, and an abstained retrieval produces an empty bundle.
Validation checks citation structure only: every cited chunk ID must come from the supplied
evidence.
| Transport | Listener | Auth | Tenancy |
|---|---|---|---|
stdio (default) |
none — a private pipe to one client | not required | one tenant, from RECALL_TENANT
|
streamable-http, sse
|
TCP socket | required, enforced at startup | one tenant per token or OIDC claim |
stdio needs no authentication because there is no remote caller: the client owns the process,
and the pipe is the boundary. Starting an HTTP transport without configured authentication
raises and refuses to boot — chosen over logging a warning, because a warning produces a server
that comes up looking healthy with every memory in it readable by anything that can reach the port.
→ Tenancy-and-Auth for static tokens, OIDC, scopes and the rate limits.
-
Tools are async and offloaded to threads. FastMCP awaits async tools and calls sync ones
inline — there is no thread pool doing it for you — so a synchronous tool body would serve
exactly one request at a time. Combined with
pool_size, this is what makes the server actually concurrent. - Rate limiting runs after authorisation, so an unauthorised caller cannot burn a tenant's budget by hammering a scope it does not hold.
- The indexing budget is debited pre-flight — the tenant is charged for what is about to be embedded, before it is embedded, so a refusal has spent nothing.
- Logs go to stderr with propagation off. stdout carries JSON-RPC; a stray log line there would corrupt the protocol.
-
Metrics for abstention, verdicts, reconnects and latency percentiles are surfaced through
recall_statsand the metrics registry. The library never attaches logging handlers itself — that is the host's job. - Strict trust refuses unreadiness. In production, a missing generation, lineage mismatch, missing or uncertified calibration, stale calibration, or unavailable dependency is a refusal, not an empty result. Development mode can return degraded results for local work, but those hits are unverified context rather than a trust-layer claim.
See also: The-Trust-Layer for what the verdicts mean · Configuration-Reference for every environment variable · CLI-Reference for the same operations from a shell.
This wiki explains design and intent. Measured figures, deployment defaults and CLI flag values
live in the repository, versioned with the code that produced them:
FINDINGS ·
RESULTS ·
MIGRATIONS ·
.env.example · --help.
If a page here disagrees with the repo, the repo is right.
Concepts
Using it
- Installation-and-Setup
- Embedders-and-Rerankers
- Configuration-Reference
- CLI-Reference
- Python-API-and-MCP
- Tenancy-and-Auth
Evidence
Contributing
In the repo