RememberStackremember.dev/docs

Configuration

How to bind a single deployment to stores, model seats, budgets, and optional exporters. This page is operator-facing and agent-scannable. Run-through: Getting started · Deployment.

TL;DR

NeedDo
Local full stackCopy .env.example.env, set OpenRouter key, docker compose up
Reproducible modelsPin explicit model IDs — never a rotating free router
Client / MCP onlypip install remember (or uvx remember setup)
Production engineDocker Compose / ghcr.io/writeitai/remember-stack:0.17.0
SecretsNever commit real keys; local examples are disposable

One deployment id = one trust domain. Changing REMEMBERSTACK_SELFHOST_DEPLOYMENT_ID creates a different deployment identity.

Distribution surfaces

Distribution / ArtifactRole
remember (PyPI package)Client SDK (RememberClient), platform CLI (remember), MCP server (remember mcp), and agent setup (remember setup). Sub-second install, zero C-extension compilation.
ghcr.io/writeitai/remember-stack (Docker)Pre-built engine container: HTTP API, workers (E0–E3), PostgreSQL 19 migrations, and bitemporal spine.
Docker Compose (docker-compose.yml)Single-command local orchestration: PostgreSQL 19 + SQL/PGQ, MinIO object store, API, and workers.
remember[benchmark]LoCoMo / scorer deps
remember[observability]Sentry-protocol + Langfuse SDKs (still opt-in by env)
GHCR image / ComposeFresh single-deployment self-host

Environment map (self-host)

Authoritative template: repository .env.example.

Identity and API

VariablePurpose
REMEMBERSTACK_SELFHOST_DEPLOYMENT_IDStable UUID for this deployment
REMEMBERSTACK_SELFHOST_DEPLOYMENT_SLUGShort slug (also default Sentry environment)
REMEMBERSTACK_SELFHOST_DEPLOYMENT_NAMEHuman label
REMEMBERSTACK_SELFHOST_API_PORTAPI listen port (Compose)
REMEMBERSTACK_SELFHOST_API_BEARER_BINDOptional {issued_deployment_uuid}:{sha256hex} of the API Bearer secret. When set, /operations and other memory routes require that Bearer. GET /healthz stays unauthenticated
REMEMBERSTACK_SELFHOST_API_BEARER_TOKENOptional plaintext Bearer for local self-host; hashed at startup and bound to SELFHOST_DEPLOYMENT_ID. Must match BIND when both are set
REMEMBERSTACK_SELFHOST_API_SIGNING_KEYSOptional JWKS (Ed25519 public keys only) this deployment verifies signed credentials against. Set beside or instead of API_BEARER_BIND: a caller may present a shared secret, a signed credential, or either. An explicit {"keys":[]} denies signed credentials while keeping the perimeter configured. Remove any separate bearer binding/token to deny all credentials. Refused at startup if it carries private key material, a non-Ed25519 key, or a key that half loads
REMEMBERSTACK_SELFHOST_API_REVOKED_CREDENTIAL_IDSOptional comma-separated credential ids to refuse. A signature is verified by arithmetic, so its issuer cannot withdraw one — this is how a revoked but unexpired credential is refused. Meaningless without API_SIGNING_KEYS, and rejected if set alone
REMEMBERSTACK_SELFHOST_REQUIRE_API_AUTHWhen true, the API process refuses to start unless it has a perimeter — API_BEARER_BIND, API_SIGNING_KEYS, or both. Default false (open quickstart)
REMEMBERSTACK_SELFHOST_TRUSTED_PRINCIPAL_SOURCEWhether X-Ingest-Principal-* attribution is believed. Default false; enable only behind a network perimeter that authenticates the asserted actor. With API auth configured, the presenting credential also needs full write authority
REMEMBERSTACK_SELFHOST_BROWSER_ORIGINSOptional comma-separated https:// origins allowed to call this deployment from a browser. Empty by default, which advertises no CORS at all. Each entry must be an exact scheme-and-host origin — no wildcard, no path, no http://
REMEMBERSTACK_API_URLSDK and memory-command target; defaults to http://127.0.0.1:8000must match if you change the port. remember login instead uses its explicit --api-url or the live hostname advertised by the token host
REMEMBERSTACK_TOKEN_HOSTDevice-grant host for remember login (required unless --token-host is passed). Never derived from the query API URL
REMEMBERSTACK_CONFIG_DIROptional override for the CLI credential directory. Holds credentials.json and, while a replaced credential is still awaiting revocation, pending-revocation.json — both 0600 inside a 0700 directory
REMEMBERSTACK_COST_EXPORT_BINDOptional second listen address for HTTP cost export (127.0.0.1:8001, [::1]:8001, or unix:/path). Unset = no HTTP export
REMEMBERSTACK_COST_EXPORT_TOKENBearer for the export listener. Required and ≥32 bytes when the bind is set

Signed credential scopes

A credential verified through REMEMBERSTACK_SELFHOST_API_SIGNING_KEYS names exactly one of three scopes:

ScopeWhat it reaches
readThe enumerated retrieval, search, inventory, readiness, and operation/connector listing routes. It cannot ingest, run an assured operation that has not declared itself non-mutating, or change configuration
ingestOnly POST /ingest. It cannot search or inspect memory, run an assured operation, or create a connector that keeps pulling after the caller leaves
writeThe complete deployment API, including POST /ingest; it also satisfies every read requirement

Unlisted routes require write. A new route therefore stays unavailable to narrow credentials until it is classified deliberately, and an unknown scope on a signed credential is refused rather than downgraded.

An ingest credential also cannot assert X-Ingest-Principal-Kind or X-Ingest-Principal-Ref. Those immutable attribution headers are ignored unless REMEMBERSTACK_SELFHOST_TRUSTED_PRINCIPAL_SOURCE is enabled and the caller has full write authority. The unscoped API_BEARER_BIND shared secret is unrestricted and qualifies; a narrow credential's attribution headers are ignored while its otherwise valid upload still succeeds.

Calling a deployment from a browser

A browser refuses a cross-origin request before it is sent unless the server says the origin is allowed. The credential is never examined and the perimeter never runs, so the failure looks like the deployment being down — which is why this is configuration rather than something that can be worked around in a client.

REMEMBERSTACK_SELFHOST_BROWSER_ORIGINS names the origins permitted to do that. It is empty by default: a deployment nobody told about an app should not advertise anything, and a permissive default would hand every website on the internet the ability to make authenticated requests from a visitor's browser.

Each entry must be an origin exactly as a browser sends it: https://app.example.com, optionally with a port, and nothing else — lowercase scheme and host, no path, no query, no userinfo, no wildcard.

The reason is worth knowing. The match is a byte comparison against the request's Origin header, so a value that is not exactly what a browser sends can never match — it does not widen the perimeter, it silently closes it. An operator who writes https://*.example.com and watches the deployment start cleanly believes they have granted a subdomain tree; they have granted nothing, and will go looking for the fault in their app.

Validation is deliberately small: scheme, structure, case and port, and no finer judgement of hostname syntax than that. A rule strict enough to catch every impossible name also refuses real ones — underscores, trailing dots and IDNA2008 labels are all things browsers send and naive checks reject. DNS fails loudly and specifically for a name that does not exist, which is a better error than a startup refusal for a host that does. The origins actually installed are logged when the API starts, so a typo is something you can see rather than infer.

Credentialed CORS is disabled (allow_credentials is off). Be precise about what that does: it stops the response being shared with a page that made the request in credentials: "include" mode. It does not block an ordinary bearer call — a request carrying Authorization is preflighted, and that preflight succeeds because the header is on the allowlist. What is refused is a page trying to have cookies or client certificates count as authentication across the origin boundary. It does not reach into the browser and stop a page attaching cookies to a simple cross-origin request in the first place; that request may still be sent, and only its response is withheld.

So the honest statement is that no credentialed cross-origin call can succeed here, not that cookies can never leave the browser. The credential this surface expects travels in Authorization, and same-origin traffic is unaffected by any of it.

Only GET and POST are advertised, with Authorization and Content-Type. Sending Authorization makes the request preflighted, and that preflight succeeds because the header is on the allowlist — it is the browser's credentials: "include" mode, not the presence of a bearer header, that would need credentialed CORS.

Preflight responses are cached for ten minutes, which is worth knowing when you remove an origin: a browser that has already been told "yes" may keep that answer for up to that long, so revoking the credential is what ends access immediately, not editing this list.

The CORS layer is installed outermost, so refusals raised by other layers — an oversize body, an exhausted spend ceiling — reach the browser as the errors they are rather than as opaque network failures.

A non-default port is part of an origin and may be included: https://app.example.com:8443 (1–65535, but not 443 — a browser omits the default port from Origin, so writing it out matches nothing). https://localhost and https://localhost:3000 are accepted, so a self-hoster can develop against a local https listener.

Several origins are comma-separated, and the whole list is validated together — a stray comma is a refusal, not a silently skipped entry, because a list that starts with fewer origins than you wrote hides the missing one until somebody's browser is turned away.

REMEMBERSTACK_SELFHOST_BROWSER_ORIGINS=https://app.remember.dev

Compose passes this through, so it can be set in .env beside the other deployment settings.

Stores (Compose local defaults)

VariablePurpose
REMEMBERSTACK_POSTGRES_*Postgres user/password/db for the stack
REMEMBERSTACK_MINIO_*MinIO access keys for object storage
REMEMBERSTACK_DATABASE_URLUsed by local server-extra CLI tools (budget, ops) when connecting to the spine

Replace every local-only secret before any non-isolated use.

Ingest admission

VariablePurpose
REMEMBERSTACK_SELFHOST_INGEST_BODY_MAX_BYTESOptional POST /ingest request-body ceiling in bytes, enforced before the body is buffered (413 body_too_large; requests without a Content-Length get 411). Unset (default) = no engine-imposed limit — a managed host publishes and sets its own bound

Managed deployments also configure content-free billing receipts. These values are fleet-authored as one all-or-nothing set; ordinary self-host installations leave all of them unset and do not start the Compose managed profile.

VariablePurpose
REMEMBERSTACK_SELFHOST_METER_INGEST_URLHTTPS root of the managed control-plane receipt service
REMEMBERSTACK_SELFHOST_METER_INGEST_TOKENDeployment-scoped umc_mi_ producer credential; never a customer API token
REMEMBERSTACK_SELFHOST_METER_IDENTITY_KEYDeployment-local umc_mik_ HMAC key for stable opaque lineage/version IDs. It never leaves the engine and must survive token rotation
REMEMBERSTACK_SELFHOST_METER_ORG_IDFleet-bound organisation UUID carried in every receipt
REMEMBERSTACK_SELFHOST_METER_PROJECT_IDFleet-bound Project UUID carried in every receipt
REMEMBERSTACK_SELFHOST_REQUIRE_METERINGWhen true, startup fails unless the entire managed receipt configuration is present

Conversion routes

VariablePurpose
REMEMBERSTACK_SELFHOST_CONVERSION_ROUTESJSON object mapping input MIME type to a converter adapter name, e.g. {"text/markdown": "passthrough", "text/plain": "passthrough", "text/html": "markitdown", "application/pdf": "mistral_ocr", "image/png": "image_ocr_description", "image/jpeg": "image_ocr_description"}. Shipped adapter names: passthrough, markitdown, mistral_ocr, image_ocr_description. Setting the variable replaces the whole table (defaults are the stock text table); an otherwise admissible MIME type without a route is stored with conversion parked. After P3 rebuild/publication, stored_raw_uri reaches the original through a configured provider raw mount (see Mounts). Register the converter, restart workers, and run remember ops resume-no-route to release only matching backlog. An unknown adapter name refuses startup

The mistral_ocr route is BYO-key (provider-backed; off unless routed):

VariablePurpose
REMEMBERSTACK_MISTRAL_OCR_API_KEYMistral API key. Required when any route names mistral_ocr; composition refuses to start without it
REMEMBERSTACK_MISTRAL_OCR_MODELOCR model (default mistral-ocr-latest). Pin a dated model for reproducible representations — the manifest records whatever model identifier the provider echoes
REMEMBERSTACK_MISTRAL_OCR_MAX_DOCUMENT_BYTESDeterministic input ceiling before any provider call (default 50000000)
REMEMBERSTACK_MISTRAL_OCR_CONFIDENCE_GRANULARITYword (default) or page; word-level scores are retained in the provider-response interchange asset
REMEMBERSTACK_MISTRAL_OCR_KEEP_PROVIDER_RESPONSEKeep the sanitized raw OCR response as an interchange asset (default true)
REMEMBERSTACK_MISTRAL_OCR_PRICE_USD_PER_1000_PAGESMetered price per 1000 processed pages recorded in the cost ledger (default 1)

Every output-affecting mistral_ocr option is folded into the converter version, so changing the model or an option re-converts affected documents as new representations instead of replaying old ones.

The image_ocr_description route runs both dedicated OCR (the Mistral OCR adapter) and one independent vision-LLM description call for every supported static image. There is no classifier. OCR-only routing of images via mistral_ocr remains available if you bind it explicitly; the dual-lane adapter is the image route this engine ships for pictures. Supported input MIME types are static image/png and image/jpeg (the documented overlap of Mistral OCR image_url types and OpenRouter image-input types). WebP, GIF, HEIC, TIFF, AVIF, SVG, and animated PNG/JPEG are not in the shipped set. Before either provider call, Pillow inspects the original bytes (header/structure, detected MIME, animation, dimensions, and pixel count) and the tighter of the OCR and image byte ceilings is enforced. The original bytes stay unmodified in raw storage.

Both provider keys are required when any route names image_ocr_description; composition refuses to start without them:

VariablePurpose
REMEMBERSTACK_IMAGE_DESCRIPTION_API_KEYOpenRouter API key for the vision-LLM lane (SecretStr). Required when the dual-lane image route is bound
REMEMBERSTACK_IMAGE_DESCRIPTION_MODELOpenRouter model slug that must accept image inputs. Default google/gemini-2.5-flash, which is explicitly multimodal. Do not point this at a text-only seat such as the extraction model — those completions reject image_url parts
REMEMBERSTACK_IMAGE_DESCRIPTION_BASE_URLOpenRouter endpoint (default https://openrouter.ai/api/v1). Changing it invalidates description checkpoints and representations
REMEMBERSTACK_IMAGE_DESCRIPTION_MAX_IMAGE_BYTESDeterministic input byte ceiling before either provider call (default 10000000); the route uses the minimum of this and REMEMBERSTACK_MISTRAL_OCR_MAX_DOCUMENT_BYTES
REMEMBERSTACK_IMAGE_DESCRIPTION_MAX_IMAGE_PIXELSPillow pixel-count ceiling (width × height, default 40000000; starting point)
REMEMBERSTACK_IMAGE_DESCRIPTION_MAX_IMAGE_WIDTHMaximum accepted pixel width (default 16000; starting point)
REMEMBERSTACK_IMAGE_DESCRIPTION_MAX_IMAGE_HEIGHTMaximum accepted pixel height (default 16000; starting point)
REMEMBERSTACK_IMAGE_DESCRIPTION_MAX_DESCRIPTION_CHARSDisclosed output ceiling for combined observations and interpretations (default 16000); overflow is truncated with a coverage gap
REMEMBERSTACK_IMAGE_DESCRIPTION_MAX_TOKENSProvider completion budget for the description call (default 4096)
REMEMBERSTACK_IMAGE_DESCRIPTION_LANE_CONCURRENCYConcurrent OCR + description calls per image (1 or 2, default 2)

The description lane uses OpenRouter's chat-completions image input (content parts with type: image_url and a data:<mime>;base64,… URL). Description accounting reuses the OpenRouter adapter's fail-closed path: inline usage, then GET generation metadata for the existing generation id — never a second paid generation, never invented zero cost. The representation manifest records the provider-echoed model, including when a description checkpoint is reused. Successful empty OCR (no visible text) is success; a provider failure is not treated as empty. Both lanes must succeed before a representation is published. Successful lane output is checkpointed privately under the source identity (endpoint, MIME, prompt/normalizer versions, model, and acceptance/output bounds) so a retry or worker restart does not repeat an already completed call. A crash between the provider response and durable persistence can still repeat a call. A corrupt occupied checkpoint is a terminal conversion error, not another paid recall.

Every output-affecting description option is folded into the converter version. Changing only one lane's model, endpoint, or options invalidates that lane's checkpoint and produces a new representation.

Live-graph resources

VariablePurpose
REMEMBERSTACK_SELFHOST_GRAPH_POOL_SIZEDedicated PostgreSQL graph-pool connections (default 4)
REMEMBERSTACK_SELFHOST_GRAPH_POOL_TIMEOUT_SMaximum wait for a graph-pool connection (default 1 second)
REMEMBERSTACK_SELFHOST_GRAPH_MAX_CONCURRENCYPer-process admitted graph expansions (default 2)
REMEMBERSTACK_SELFHOST_GRAPH_WORK_MEM_KIBTransaction-local graph work_mem ceiling per memory node (default 16384 KiB)

Size these four values together with PostgreSQL shared memory and host reserve. Increasing pool or concurrency independently multiplies worst-case memory; the managed service applies a stricter host-level admission calculation.

Interactive retrieval resources

VariablePurpose
REMEMBERSTACK_SELFHOST_RETRIEVAL_POOL_SIZEDedicated PostgreSQL P1/fact read connections (default 4)
REMEMBERSTACK_SELFHOST_RETRIEVAL_POOL_TIMEOUT_SMaximum admission wait for an interactive retrieval read (default 1 second, also clamped to the operation deadline)
REMEMBERSTACK_SELFHOST_RETRIEVAL_MAX_CONCURRENCYPer-process P1/fact reads admitted across the shared retrieval pool (default 4)

Keep maximum concurrency at or below pool size. facts_context also clamps every admitted PostgreSQL statement and transaction to its remaining operation deadline; pool saturation returns a typed boundary rather than using the worker/write pool.

Provider (OpenRouter)

VariablePurpose
REMEMBERSTACK_OPENROUTER_API_KEYRequired to start the provider adapter and process a corpus
REMEMBERSTACK_OPENROUTER_MAX_COMPLETION_TOKENSCombined reasoning+content budget (default 32000 in example)
REMEMBERSTACK_OPENROUTER_EMBEDDING_PROVIDEROptional hard pin of embedding host slug
REMEMBERSTACK_OPENROUTER_EMBEDDING_PROVIDER_ORDERPreferred ordered shortlist (wins over hard pin)
REMEMBERSTACK_OPENROUTER_REASONING_EFFORTGlobal effort: nonemax (unset = model default)
REMEMBERSTACK_OPENROUTER_REASONING_EFFORT_MAPJSON map of model-id → effort overrides

Embedding routing policy notes: design/operations/openrouter-embedding-routing.md in the repo.

Model seats (optional overrides)

Pin explicit IDs for reproducible runs. Unset values fall back to deployment profile defaults.

VariableStage
REMEMBERSTACK_E2_EXTRACT_MODELClaim extraction
REMEMBERSTACK_E3_NORMALIZE_MODELNormalization / adjudication seats as bound by profile
REMEMBERSTACK_STRUCTURER_MODELOnly string-anchor structure fallback proposer
REMEMBERSTACK_SKELETON_CHECK_MODELBounded skeleton sanity judge (default flash-class)
REMEMBERSTACK_ROLE_MODELTitle-only section-role classifier
REMEMBERSTACK_SUMMARY_MODELBottom-up section summaries + root placement reduction

Structure honesty: routine skeleton construction is deterministic. Bounded summary/role/check calls still run; STRUCTURER is not “the whole document structure model.”

Smoke conversion route is Markdown unless you register more converters on the deployment.

P1 search indexes (D94)

P1 lives in PostgreSQL. Migrations own the pgvector HNSW and pg_textsearch BM25 indexes, while ordinary ingestion transactions update the search rows and embedding attestations. There is no separate search-store path, maintenance worker, or search-index environment namespace to configure.

Spend ceilings (optional)

export REMEMBERSTACK_WORK_BUDGETS='[
  {
    "deployment_id":"<deployment-uuid>",
    "stage":"extract_claims",
    "lane":"steady",
    "window_seconds":86400,
    "ceiling_usd":"10.00"
  }
]'
  • JSON list; omit a route → unlimited
  • Use null lane for unlaned K/P routes
  • Exhaustion parks healthy work until the window ends — does not burn handler attempts or invent a failure
  • Inspect: remember budget inspect --deployment <uuid> (needs [server])

Observability (optional, fail-closed off)

Exporters stay disabled until env is non-empty.

VariableEffect
REMEMBERSTACK_SENTRY_DSNMetadata-only errors to Sentry/GlitchTip/Bugsink
REMEMBERSTACK_SENTRY_ENVIRONMENTDefaults to deployment slug
REMEMBERSTACK_SENTRY_SAMPLE_RATEDefaults to 1.0
LANGFUSE_PUBLIC_KEY + SECRET_KEY + HOSTLoCoMo answer/judge traces only when all three set

What is not sent: request bodies, local vars, breadcrumbs, PII, prompts/completions, exception message text as free-form dumps. Worker tags stay stage/lane/processing id. Postgres + JSON telemetry remain authoritative for retries.

Conversion and connectors

PieceConfig posture
Markdown smokeDefault Compose conversion route
HTML/PDF/mediaBind converter routes per MIME type via REMEMBERSTACK_SELFHOST_CONVERSION_ROUTES (stock routes are text/markdown and text/plain; anything else is stored and parked until you register a converter). Static JPEG/PNG images use image_ocr_description (OCR + vision description); PDFs use mistral_ocr
Watched directoryConnector extra + connector management API/CLI; credentials stay deployment-side
Lineage identitysource_kind + source_ref together on ingest; `versioning_mode=snapshot

See Ingestion and Lifecycle.

What configuration is not

  • Not multi-tenant control-plane config
  • Not a hosted SLA or billing product
  • Not free-tier model routing for publication benchmarks
  • Not secret storage — use your secret manager outside git

Progressive disclosure

DepthPage
This pageEnv + seats + budgets
DeploymentCompose lifecycle, projections
TroubleshootingWhen config fails in practice
CLI referencebudget / ops command contracts
Repo .env.exampleCanonical variable list

Next

Vertex generation adapter

Library integrations can bind VertexModelProvider explicitly through the model provider port. This does not change the default self-host provider. Generation uses Vertex; embeddings must be routed separately to their configured provider.

VertexSettings reads REMEMBERSTACK_VERTEX_*. Set PROJECT_ID for the billed Google Cloud project; LOCATION defaults to global. MAX_COMPLETION_TOKENS defaults to 128000, the documented Gemma 4 26B A4B IT output maximum, verified September 14, 2026. This is an allowance, not a requested output length. Provider context limits still apply. Set it explicitly when binding another model.

TIMEOUT_S is optional and defaults to no client deadline, allowing slow generations to finish. Set a positive number only when an operator deadline is intended. Monitor running calls: a stalled service can otherwise wait indefinitely. Invalid structured responses remain errors with available token usage retained; allowing more output does not make an invalid response usable.

generate() stays a single synchronous call. The adapter receives the completion as server-sent events on the documented Chat Completions route, assembles the full text, and still requires a complete stop finish, schema-valid JSON, and provider usage before it succeeds. Truncated or dropped streams are errors. Missing usage is never billed as zero. HTTP 429 is retried only before a stream has started.