Configuration reference¶
Environment configuration is resolved at boot, never build time.
The annotated, complete example is
.env.example
in the repository.
Database¶
| Variable | Default | Meaning |
|---|---|---|
CARTULARY_DATABASE_MODE |
pg0 in a release |
pg0 (supervised) or external |
DATABASE_URL |
— | Required in external mode, e.g. ecto://user:pass@host/db |
POOL_SIZE |
10 |
Connection pool size |
CARTULARY_AUTO_MIGRATE |
true |
Run migrations as a supervised startup step |
CARTULARY_PG0_BINARY |
packaged | Path to the pg0 binary |
CARTULARY_PG0_DATA_DIR |
under the data root | PostgreSQL data directory |
CARTULARY_PG0_PORT |
5432 |
Port the supervised server listens on |
CARTULARY_PG0_DATABASE |
memhouse |
Database name |
CARTULARY_PG0_USERNAME / _PASSWORD |
postgres |
Local credentials |
ECTO_IPV6 |
false |
Connect over IPv6 |
CARTULARY_DATABASE_APP_ROLE |
cartulary_app |
Restricted PostgreSQL role every connection switches to |
CARTULARY_ALLOW_UNRESTRICTED_DATABASE_ROLE |
false |
Boot anyway if that role can't be provisioned or reached |
External mode needs PostgreSQL 18 with pgvector available.
The connecting role must be able to reach a restricted role, or boot fails
PostgreSQL skips RLS for superusers and BYPASSRLS roles. MemHouse serves
traffic only through a role that is neither:
- Give
DATABASE_URL's roleCREATEROLE, and MemHouse provisionsCARTULARY_DATABASE_APP_ROLEitself on every boot (idempotent) and switches every pooled connection to it. - Or point
DATABASE_URLat a login already created withNOSUPERUSER NOBYPASSRLS— the stronger arrangement, since that connection then has no path back to elevated access at all.
CARTULARY_ALLOW_UNRESTRICTED_DATABASE_ROLE=true bypasses this guard and
logs an error at every start. It exists only to avoid stranding an upgrade,
not for supported operation.
Identity and secrets¶
| Variable | Meaning |
|---|---|
CARTULARY_FREE_ACCOUNT_KEY |
Key of the single community Account |
CARTULARY_FREE_ACCOUNT_NAME |
Its display name |
CARTULARY_AUTH_SIGNING_SECRET |
At least 64 random bytes. Independent of SECRET_KEY_BASE |
SECRET_KEY_BASE |
Phoenix session and token signing |
CARTULARY_BOOTSTRAP_PASSWORD |
Read only by the one-time bootstrap task |
CARTULARY_DATA_ROOT |
Private data root; defaults to ~/.memhouse in a release |
Generate independent production secrets
Do not reuse SECRET_KEY_BASE as the auth signing secret. The bootstrap
password need not remain in the environment after the first run.
Updates¶
| Variable | Default | Meaning |
|---|---|---|
CARTULARY_UPDATE_CHECK |
true |
Check the official signed release feed at boot and periodically. |
CARTULARY_AUTO_UPDATE |
off |
off or minor; the latter permits an eligible signed patch/minor update before standalone pg0 startup. |
CARTULARY_UPDATE_CHECK_INTERVAL_HOURS |
24 |
Availability-check interval while the application runs. |
CARTULARY_UPDATE_INSTALL_ROOT |
launcher parent | Root holding the current pointer and immutable releases/ directories. |
CARTULARY_UPDATE_SOURCE |
official GitHub API | Release discovery endpoint. Artifact trust still comes from the signed manifest. |
Updates never self-replace Docker or external-PostgreSQL deployments. Those surfaces report the available version and retain their normal deployment flow.
Generation models¶
| Variable | Default | Meaning |
|---|---|---|
CARTULARY_MODEL_PROVIDER |
openrouter |
Provider identity |
CARTULARY_OPENAI_COMPAT_BASE_URL |
provider default | Any OpenAI-compatible endpoint, including self-hosted |
OPENROUTER_API_KEY |
— | Provider credential |
CARTULARY_MODEL_VERSION |
unversioned |
Recorded with every result as provenance |
CARTULARY_MODEL_INGEST |
— | Model for the ingest-extractor role |
CARTULARY_MODEL_DREAM |
— | Model for the dream-reasoner role |
CARTULARY_MODEL_ASK |
— | Model for the dialectic-agent role |
CARTULARY_MODEL_LOCAL_FALLBACK |
true in dev, off in prod |
Deterministic local adapter |
CARTULARY_MODEL_REASONING_EFFORT |
low |
Reasoning-token budget shared by all three generation roles |
CARTULARY_MODEL_MAX_TOKENS |
8192 |
Output-token cap shared by all three generation roles |
CARTULARY_MODEL_RECEIVE_TIMEOUT_MS |
120000 |
Maximum idle wait (ms) between response chunks |
CARTULARY_MODEL_REQUEST_TIMEOUT_MS |
300000 |
Total model-call ceiling (ms) shared by all three generation roles |
CARTULARY_MODEL_STREAM_POOL_SIZE |
16 |
Connections in each shared HTTP/1 shard |
CARTULARY_MODEL_STREAM_POOL_COUNT |
1 |
Shared HTTP/1 shard count; raise only for a measured shard bottleneck |
CARTULARY_MODEL_POOL_TIMEOUT_MS |
120000 |
Maximum wait (ms) to check out a model HTTP connection |
CARTULARY_INGEST_QUEUE_LIMIT |
10 |
Concurrent extraction jobs per node |
Reasoning models can blow the context window or time out without these
Reasoning models, including the default openai/gpt-oss-120b, can consume
their context before producing output. CARTULARY_MODEL_REASONING_EFFORT
bounds reasoning and CARTULARY_MODEL_MAX_TOKENS caps output. ReqLLM only
extends timeouts automatically for recognized OpenAI reasoning families;
CARTULARY_MODEL_RECEIVE_TIMEOUT_MS overrides its 30-second default for
openai/gpt-oss-120b and other vendors. Raise these values only when the
chosen model requires it.
CARTULARY_MODEL_RECEIVE_TIMEOUT_MS bounds the wait between response chunks.
CARTULARY_MODEL_REQUEST_TIMEOUT_MS bounds the complete response, even when a
provider continues sending chunks or keep-alives.
CARTULARY_MODEL_STREAM_POOL_SIZE must cover concurrent hosted model calls,
not just one role. Finch chooses a shard randomly when the count exceeds one,
so use size to add capacity and leave
CARTULARY_MODEL_STREAM_POOL_COUNT=1 unless telemetry shows a single shard is
the bottleneck. CARTULARY_MODEL_POOL_TIMEOUT_MS is the maximum checkout wait
and defaults to the model receive timeout.
For 100 parallel ingestion flows on one node, set
CARTULARY_INGEST_QUEUE_LIMIT=100 and
CARTULARY_MODEL_STREAM_POOL_SIZE=128, then validate the provider's
concurrency/rate limits and the database pool under representative load.
There are exactly four Account-level model roles: embedder,
ingest_extractor, dream_reasoner, and dialectic_agent. Only secret
references are persisted, never secret values.
When CARTULARY_MODEL_PROVIDER=openrouter, structured extraction and reasoning
use OpenRouter's strict JSON-schema response format. This is automatic; it
avoids models that intermittently ignore forced tool calls.
The local fallback is a test aid
Production defaults it off and never switches to it after a live provider error. A silent downgrade from a real model to a deterministic stand-in would corrupt memory quality invisibly.
Embeddings¶
| Variable | Example | Meaning |
|---|---|---|
CARTULARY_EMBEDDING_PROVIDER |
ortex |
ortex (local ONNX) or openai-compatible |
CARTULARY_EMBEDDING_MODEL |
BAAI/bge-small-en-v1.5 |
Model identity |
CARTULARY_EMBEDDING_VERSION |
onnx-1 |
The vector-space version |
CARTULARY_EMBEDDING_DIMENSIONS |
384 |
Vector width |
CARTULARY_ORTEX_MODEL_PATH |
absolute path | Operator-supplied .onnx file |
CARTULARY_ORTEX_TOKENIZER_PATH |
absolute path | Operator-supplied tokenizer.json |
CARTULARY_ORTEX_POOLING |
cls |
Pooling strategy |
CARTULARY_ORTEX_EXECUTION_PROVIDERS |
cpu |
ONNX Runtime execution providers |
CARTULARY_EMBEDDING_BASE_URL / _API_KEY |
— | For an API embedder instead |
The Ortex embedder downloads nothing: artefacts are deliberately operator-supplied and offline.
Bump the embedding version on any artefact change
Provider, model, version, and dimensions together are the vector-space identity. A mismatch takes the explicit re-embed path; vectors are never reused or silently substituted across identities.
Document storage¶
| Variable | Default | Meaning |
|---|---|---|
CARTULARY_BLOB_ADAPTER |
local |
local or s3 |
CARTULARY_BLOB_ROOT |
env-dependent | Absolute local blob path (/var/lib/memhouse/blobs in production) |
CARTULARY_S3_BUCKET |
— | Bucket name |
CARTULARY_S3_PREFIX |
memhouse |
Key prefix |
CARTULARY_S3_HOST / _SCHEME / _PORT |
— | For MinIO or another compatible endpoint |
AWS_REGION and standard AWS variables |
— | ExAws credentials |
CARTULARY_DOCUMENT_CHUNK_SIZE |
1200 |
Characters per chunk |
CARTULARY_DOCUMENT_CHUNK_OVERLAP |
160 |
Overlap between chunks |
CARTULARY_DOCUMENT_MAX_EXTRACT_LENGTH |
500000 |
Extraction cap in characters |
Blob adapter choice is a runtime infrastructure seam. It does not change document semantics.
Budgets and cost¶
| Variable | Meaning |
|---|---|
CARTULARY_BUDGET_LIMITS_JSON |
Daily token counters, e.g. {"input_tokens":1000000,"output_tokens":250000,"embedding_tokens":2000000} |
CARTULARY_MODEL_COSTS_JSON |
Operator rates in USD per million tokens, per role |
Dream-time is throttled first when a limit bites.
Governance¶
| Variable | Default | Meaning |
|---|---|---|
CARTULARY_GOVERNANCE_UNATTENDED |
false |
Declares this whole deployment process has no human governance participant |
When true, personal knowledge above peer level receives an automatic subject
consent record. Normally only the subject's verified grant permits widening;
GateRule cannot waive it. Use this only for benchmarks, evaluations, or
synthetic deployments without real subjects. MemHouse logs it at boot and
reports it on GET /api/ready.
An individual Account can be marked the same way without touching the whole
deployment — see Governance for the
account-level consent_mode setting, which an account administrator
controls from within that Account rather than from the environment.
Observability¶
| Variable | Default | Meaning |
|---|---|---|
CARTULARY_OTEL_ENABLED |
false |
Enable batch OTLP/HTTP trace export |
CARTULARY_ENVIRONMENT |
development |
Environment label |
OTEL_SERVICE_NAME |
memhouse-dev |
Service name in traces |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:14318 |
Collector endpoint |
OTEL_EXPORTER_OTLP_PROTOCOL |
http_protobuf |
|
OTEL_TRACES_SAMPLER / _ARG |
always-on, 1.0 |
Sampling |
CARTULARY_OTEL_*_SPANS_ENABLED |
see Observability | Per-category span switches |
CARTULARY_OTEL_DB_STATEMENT_ENABLED |
false |
SQL text in spans — off because statements can carry sensitive values |
CARTULARY_EXPERIMENT_NAME / _RUN_ID |
Evaluation run labels | |
CARTULARY_RETRIEVAL_VARIANT |
poc-baseline |
Historical label kept for comparability with recorded runs |
Web¶
| Variable | Default | Meaning |
|---|---|---|
PORT |
4000 |
HTTP port |
PHX_HOST |
localhost |
Public hostname |
PHX_SERVER |
false |
Start the endpoint (set by the release launchers) |
DNS_CLUSTER_QUERY |
— | Clustering DNS query |
Retrieval profiles¶
Profiles are configured in application config rather than the environment, because they are behaviour rather than infrastructure. The shipped values:
| Profile | Strategies | Weights | Rerank | Deadline |
|---|---|---|---|---|
fast |
semantic, salience-recency | 1.0, 0.8 | no | 100 ms |
balanced |
semantic, lexical, temporal, entity-match | 1.0, 1.0, 0.7, 0.9 | no | 300 ms |
thorough |
the above plus salience-recency and relation-expand | +0.8, 0.6 | yes | 1500 ms |
Fusion uses reciprocal rank with k = 60. enabled_strategies is a
deployment-level allowlist: a strategy absent from it never runs, whatever a
profile asks for. CARTULARY_RETRIEVAL_RERANK_TIMEOUT_MS defaults to 750.
It is the most time reranking may use, but the request's remaining profile
deadline always wins when it is smaller. Raising it can improve thorough-search
ranking at the cost of tail latency; it cannot extend the 1500 ms hard ceiling.
Profile changes require product review; PostgreSQL location changes do not.