Skip to content

Configuration Reference

GiulioDER edited this page Jul 23, 2026 · 3 revisions

Configuration Reference

Every RECALL_* environment variable, grouped by what it protects.

Default values are not listed here. They live in .env.example, which is generated alongside the code and cannot drift from it. This page covers what each variable is for, when you would change it, and what breaks if you get it wrong — which is the part a config dump cannot tell you.

Precedence: real environment → .env file → built-in default. The .env loader never overrides a variable that is already set.


Connection

RECALL_DSN

The PostgreSQL connection string. Everything else assumes this points somewhere real.

When you'd change it: always, for anything but the local dev container.

If you get it wrong: the repo publishes default credentials in its docker-compose.yml, and using them against a non-local host makes your memory corpus readable by anyone who can reach the port. The MCP server therefore refuses to boot in that situation rather than warning — see RECALL_ALLOW_INSECURE_DSN. The CLI prints a loud stderr warning instead, because a CLI has no listener to protect.

RECALL_ALLOW_INSECURE_DSN

Accepts the risk above deliberately.

When you'd change it: you genuinely intend to run with published credentials — a throwaway environment, a demo on a trusted network.

If you get it wrong: you have re-enabled exactly the failure the refusal exists to prevent, and nothing will tell you again.

RECALL_POOL_SIZE

Selects the connection mode, which is a bigger switch than it looks.

Unset means one long-lived connection: correct for a CLI or any single-threaded caller, and the mode the store's reconnect semantics are built around. Set to an integer means a connection pool: what a server needs, so concurrent callers actually proceed concurrently.

When you'd change it: any server process. Requires the pool extra.

If you get it wrong: sharing a single connection across threads serialises them — the MCP server previously served exactly one request at a time for this reason. In the other direction, a pool starts a background maintenance thread that a one-shot CLI invocation should not pay for or have to shut down.

RECALL_STATEMENT_TIMEOUT_MS

Bounds every statement server-side.

When you'd change it: raise it if legitimate queries are being cancelled; lower it if you need tighter tail latency.

If you get it wrong: without a bound, a single runaway query occupies a connection until the process dies, with nothing able to cancel it. That is the difference between a slow request and an exhausted pool. Schema DDL is the one documented exception — it lifts the bound, because an HNSW build legitimately outlasts any sane query timeout, and restores it afterwards.

RECALL_SCHEMA_LOCK_TIMEOUT_MS

How long schema DDL may wait for a lock before giving up.

Read this carefully: it is not a bound on the work. An index build is deliberately unbounded. It bounds only queueing behind another transaction.

When you'd change it: set it to wait forever if you have a deployment that legitimately contends on this and you would rather block than retry.

If you get it wrong: with an unbounded wait, a concurrent transaction turns store startup into a silent stall — which presents as a hang, not an error. The DDL is idempotent and retried on the next store open, so failing fast loses nothing and is diagnosable where a stall is not.


Embedding

RECALL_EMBEDDER

Which embedder to use — the local ONNX model or the fully-offline hashing embedder.

When you'd change it: the hashing embedder for tests, CI and offline work; it is deterministic and needs no model download. Never for retrieval quality — it is weak on purpose.

If you get it wrong: the embedder's name is part of both the embedding cache key and the calibration file, so switching cannot silently reuse the previous model's vectors or its threshold. What you will see is a full re-embed on the next index and an uncalibrated fallback until you re-run calibrate. Both are correct; neither is silent.

RECALL_CALIBRATION

Path to the per-embedder calibration file written by recall calibrate.

When you'd change it: you keep calibrations per environment or per corpus.

If you get it wrong: a missing, unreadable, malformed, out-of-range, or wrong-embedder calibration is ignored, with an uncalibrated fallback and calibrated=False flagged in every result. That defensiveness is deliberate — a corrupt file must never be able to silently disable abstention, and a threshold fitted in another model's cosine regime must never be applied. → The-Trust-Layer


Indexing bounds

Two different jobs here, and conflating them is the usual mistake: per-request caps bound one call; budgets bound the aggregate per tenant, so a client staying politely under the per-request cap cannot simply issue it in a loop.

RECALL_INDEX_ROOT

Bounds where the MCP recall_index tool may read from.

When you'd change it: narrow it to exactly the corpus directory you intend to expose.

If you get it wrong: a client-callable indexer with no root confinement can read anything the server process can. The confinement survives symlinks.

RECALL_INDEX_MAX_FILES · RECALL_INDEX_MAX_BYTES

Per-request caps on candidate file count and candidate bytes. Checked before anything is embedded, so an oversized request costs nothing.

When you'd change it: larger corpora indexed in one call; or tighter, if your embedder is a paid API.

If you get it wrong: a client-callable indexer with no cap is unbounded spend on a cloud embedder.

RECALL_INDEX_BYTES_PER_HOUR

The aggregate indexing budget, per tenant, per hour. This is the load-bearing one: request count is a poor proxy for spend when one call can carry twenty megabytes and the next two hundred bytes.

When you'd change it: matching it to what you are willing to pay.

⚠️ Keep it at or above RECALL_INDEX_MAX_BYTES. If the hourly budget is smaller than the per-request cap, requests sized between the two can never succeed — they pass the per-request check and are then refused by a budget they cannot fit into.

RECALL_MAX_PRUNE_FRACTION

The fraction of a root's indexed sources that a re-index may delete before it refuses and deletes nothing.

When you'd change it: rarely. Lower it if your corpus should never shrink much.

If you get it wrong: set too high, a missing corpus — wrong path, unmounted volume, half-finished checkout — becomes indistinguishable from a deleted one, and a single bad run empties your index. The guard also applies above a small floor of indexed sources, so tiny corpora are not constantly tripping it. Override per-run with --allow-prune once you have confirmed the files really are gone.


Retrieval / ANN

RECALL_HNSW_EF_SEARCH_FILTERED · RECALL_HNSW_ITERATIVE_SCAN_FILTERED

pgvector search parameters applied to the source-filtered query path only.

Why they exist: at pgvector's defaults, the filtered path measured poor recall with most queries truncated — a filtered search can exhaust its candidate list before finding enough matching rows. Widening the search and relaxing iterative scan fixes it, at a latency cost.

When you'd change them: tuning the recall/latency trade-off on filtered queries.

Stated honestly: the unfiltered path still runs at pgvector's defaults, where it measured well — but every query now also carries a tenant predicate, and as of the README's last revision that combination had not been measured on a multi-tenant table. Background: issue #11. Check the README for the current status.


Multi-tenancy

RECALL_TENANT

The tenant namespace. A store is bound to one tenant for its lifetime — tenancy is a property of which store you hold, not an argument you pass to a query.

When you'd change it: operating on a different tenant's memory. Note this applies to stdio only; over HTTP the token carries the tenant, and this variable is ignored.

If you get it wrong: the row-level-security policy means a wrong tenant returns nothing rather than someone else's data — provided you connect as an unprivileged role. → Tenancy-and-Auth


MCP transport and authentication

Full treatment: Tenancy-and-Auth and docs/AUTH.md.

RECALL_TRANSPORT

stdio (a private pipe to one client) or an HTTP transport (a network listener).

If you get it wrong: the two have deliberately different security postures. stdio needs no authentication because there is no remote caller — the pipe is the boundary. An HTTP transport opens a socket, and starting one without tokens refuses to boot.

RECALL_AUTH_TOKENS_FILE

The only source of bearer tokens.

Note what is absent: there is no environment variable that accepts a raw token, and the omission is the point. Environment variables leak through /proc/<pid>/environ, ps e, container inspection APIs, crash dumps, and every child process the server spawns. A file is what Kubernetes and Docker mount for secrets anyway, and unlike an env var it can be permission-checked — which the server does, warning if the file is world-readable.

The file may hold plaintext tokens or their SHA-256 digests, so provisioning access never requires writing a live credential to disk in recoverable form.

RECALL_AUTH_ISSUER_URL · RECALL_AUTH_RESOURCE_URL

Identity metadata advertised to MCP clients for the resource-server auth flow.

RECALL_RATE_READ_PER_MIN · RECALL_RATE_WRITE_PER_MIN · RECALL_RATE_FORGET_PER_MIN

Per-tenant call rates, split by the risk each tool class carries — searching and reading stats, indexing, and erasure.

Why per tenant and not per principal: two tokens issued to the same tenant are the same blast radius and the same bill. Letting a tenant multiply its budget by minting another token would make the quota advisory.

Each takes a number or the literal off. A malformed value, a non-finite one, or one too small to yield a non-zero rate falls back to its default rather than being read as "unlimited". A typo must not silently remove a cap.

⚠️ Buckets are per process. Nothing is shared across workers, so N server processes admit roughly N times these rates. This is honest for the deployment the auth work targets — one process behind TLS — and it is the first thing to revisit before running a fleet. A shared limiter needs Redis or the database, and a network round trip on every call.

⚠️ Read once at startup. Changing a budget takes effect on restart.


Logging

RECALL_LOG_LEVEL · RECALL_LOG_FORMAT

Level, and text or JSON output.

Important: these configure the CLI and the MCP server. The library itself never attaches logging handlers — that is the host application's job, and a library that calls basicConfig hijacks its host's logging configuration. If you embed recall in your own service, configure logging yourself; these variables will not do it for you.

RECALL_HOST · RECALL_PORT

Bind address for the HTTP transports.


Reading this list operationally

If you change one thing before going to production, make it RECALL_DSN — specifically, the role it connects as. Row-level security is bypassed by a superuser or a BYPASSRLS role, which means the second isolation layer is decoration unless you connect as an unprivileged one. store.check_rls_effective() tells you which you have. → Tenancy-and-Auth

If you change two, add RECALL_POOL_SIZE for any server process.

Clone this wiki locally