Configuration Reference
PRME uses pydantic-settings for configuration. All settings can be set via:
- Constructor arguments
- Environment variables (with
PRME_prefix) .envfiles
Nested settings use double underscores: PRME_EMBEDDING__PROVIDER=openai.
Quick Setup
Minimal .env for LLM-powered ingestion:
# Required for ingest() — pick one provider
OPENAI_API_KEY=sk-...
# or
ANTHROPIC_API_KEY=sk-ant-...
# Optional: extraction model
PRME_EXTRACTION__PROVIDER=openai
PRME_EXTRACTION__MODEL=gpt-4o-miniFor store-only usage (no LLM), no configuration is needed.
Storage Paths
| Variable | Default | Description |
|---|---|---|
PRME_DB_PATH | ./memory.duckdb | DuckDB database file |
PRME_VECTOR_PATH | ./vectors.usearch | usearch HNSW index file |
PRME_LEXICAL_PATH | ./lexical_index | Tantivy full-text index directory |
PRME_PACK_PATH | None | Opt-in single-file pack, conventionally a .prme file: one DuckDB database that also holds the vector snapshot and the pack manifest, with the lexical index rebuilt in memory on open. When set, PRME_DB_PATH, PRME_VECTOR_PATH and PRME_LEXICAL_PATH are ignored. It cannot be combined with PRME_DATABASE_URL, and an empty value means unset. See single-file packs |
PRME_DATABASE_URL | None | PostgreSQL URL (overrides DuckDB) |
Without PRME_PACK_PATH, a local pack is the directory layout of the three
paths above, which is the default.
Embedding
| Variable | Default | Description |
|---|---|---|
PRME_EMBEDDING__PROVIDER | fastembed | fastembed (local) or openai |
PRME_EMBEDDING__MODEL_NAME | BAAI/bge-small-en-v1.5 | Embedding model |
PRME_EMBEDDING__DIMENSION | 384 | Embedding dimension |
PRME_EMBEDDING__API_KEY | None | API key for OpenAI embeddings |
The default fastembed provider runs locally with no API key needed. It downloads the model on first use (~130MB).
Extraction (LLM)
| Variable | Default | Description |
|---|---|---|
PRME_EXTRACTION__PROVIDER | openai | openai, anthropic, or ollama |
PRME_EXTRACTION__MODEL | gpt-4o-mini | Model name |
PRME_EXTRACTION__MAX_RETRIES | 3 | Retry count for API failures |
PRME_EXTRACTION__TIMEOUT | 30.0 | Timeout in seconds |
PRME_EXTRACTION__API_KEY | None | Credential; defaults to the provider’s own (OPENAI_API_KEY, ANTHROPIC_API_KEY) |
PRME_EXTRACTION__BASE_URL | None | Endpoint; defaults to the provider’s own (OPENAI_BASE_URL, ANTHROPIC_BASE_URL, or a local Ollama) |
The extraction provider is used by ingest() and ingest_batch(), and by retrieve() only when PRME_ENABLE_QUERY_REFORMULATION=true: query reformulation calls the same provider, model, endpoint and credential, with the same timeout. The store() method does not call an LLM.
Extracted claims (ingest())
These settings change what ingest() stores from an extraction. They do not affect store(), and records stored before a change keep their text.
| Variable | Default | Description |
|---|---|---|
PRME_ENABLE_CLAIM_MERGE | true | Merge an exactly repeated extracted claim into one current record: the new fact keeps the earlier copies’ evidence and supersedes them (issue #209). Quantities, conditional claims, unresolved relative dates and claims that are not observed or asserted stay separate. false stores one fact per extraction |
PRME_ENABLE_CLAIM_SENTENCE_TEXT | true | Store a claim’s own sentences as its text: the shortest run of whole sentences of its passage that mentions its subject and object, with a following sentence that qualifies it (only if, unless, however). The paragraph stays its evidence_quote, so a qualifier elsewhere in the paragraph stays in the evidence but not in the text. false gives every claim from a one-paragraph message the whole message as its text. Default since 2026-10-06; provisional |
PRME_ENABLE_SPEAKER_REFERENCES | true | In a turn that names its speaker (ingest(speaker=...)), bind I, me, my, mine and myself in its claims to that speaker’s entity, so the speaker’s claims connect across messages and a repeated or replaced claim can merge with or supersede the earlier one. Plural, second- and third-person references stay local to their message. Only the caller knows that the named speaker, and not someone the turn quotes, is who says I, so set it to false when a speaker’s turns quote others in the first person. Default since 2026-10-06; provisional. See entity identity |
PRME_ENABLE_SPEAKER_GROUNDING | false | Let a named speaker’s own I, me and my count as a mention of the speaker when grounding checks a claim’s subject and object, so a claim such as “Dana, lives in, Denver” for “I live in Denver” is kept. It completes the graph, but the extra claims cost evidence on the offline evidence gate, so it stays off. Provisional |
PRME_ENABLE_WINDOWED_EXTRACTION | false | Show the extractor the preceding turns of the same session, so it can read what the new turn refers to (issue #91). Only the new turn is extracted and grounded. Needs a session_id; provisional |
PRME_EXTRACTION_WINDOW_TURNS | 4 | How many preceding turns the window shows, from 0 to 32; 0 turns the window off. Each turn adds prompt tokens to every extraction. Provisional |
PRME_ENABLE_FACT_TEXT_RESOLUTION | false | Ask the extractor for fact text that stands alone: a pronoun for a named person becomes the name and a relative date the date it means (issue #91). A deterministic check keeps that text only when those replacements are its sole changes; otherwise the fact keeps its source passage. Names from earlier turns need PRME_ENABLE_WINDOWED_EXTRACTION. Provisional |
Scoring Weights
These control the hybrid retrieval scoring formula. The six additive weights must sum to 1.0. The default fusion is rrf, which ranks by semantic and lexical rank and does not use the additive weights; set PRME_SCORING__FUSION=weighted to score with them. Setting any other PRME_SCORING__* variable keeps rrf and its recency boost and tie-break unless PRME_SCORING__FUSION is set too.
| Variable | Default | Signal |
|---|---|---|
PRME_SCORING__W_SEMANTIC | 0.25 | Vector cosine similarity |
PRME_SCORING__W_LEXICAL | 0.20 | BM25 full-text score |
PRME_SCORING__W_GRAPH | 0.20 | Graph proximity |
PRME_SCORING__W_RECENCY | 0.10 | Time decay: exp(-lambda * days) |
PRME_SCORING__W_SALIENCE | 0.10 | Virtual salience with decay |
PRME_SCORING__W_CONFIDENCE | 0.15 | Virtual confidence with decay |
PRME_SCORING__W_EPISTEMIC | 0.05 | Epistemic type multiplier |
PRME_SCORING__W_PATHS | 0.00 | Path count tiebreaker |
PRME_SCORING__RECENCY_LAMBDA | 0.02 | Decay rate for recency |
PRME_SCORING__TEMPORAL_BOOST | 0.15 | Bonus for temporal queries |
PRME_SCORING__FUSION | rrf | rrf: reciprocal rank fusion of semantic and lexical ranks, which ignores the additive weights (RFC-0005 Section 7.2), or weighted: the sum above. A ScoringWeights object built in code defaults to weighted; PRMEConfig().scoring is rrf. Under rrf a result’s score is rank-based, so a request’s min_score is compared with its semantic_relevance, the semantic cosine similarity, instead; a floor tuned on weighted scores does not carry over, and a low-cosine exact keyword match is filtered out when a floor is set. If vector search fails or detects an embedding model mismatch and no result has a cosine, the floor is skipped and the response metadata sets min_score_skipped |
PRME_SCORING__RRF_K | 60 | Rank constant for rrf; ignored, with a warning, under PRME_SCORING__FUSION=weighted |
PRME_SCORING__RRF_RECENCY_BOOST | 0.25 | rrf only: on current-state questions, multiplies each fused score by 1 + boost x recency relative to the pool’s largest value, where recency is the weighted formula’s current-state recency (exp decay from the newest candidate, doubled for update wording), so the newer of two conflicting memories can rank first. Above 0 and at most 4. 0.25 passed the simulations with no significant change on the offline evidence gate; 1.0 lost LoCoMo evidence (RFC-0005 Section 7.2); provisional. Dropped when PRME_SCORING__FUSION=weighted is set; a ScoringWeights object built in code leaves it unset |
PRME_SCORING__RRF_TIE_BREAK | event_time | rrf only: event_time orders equal fused scores newest first instead of by path count and node ID. Dropped when PRME_SCORING__FUSION=weighted is set; a ScoringWeights object built in code leaves it unset |
PRME_SCORING__RECENCY_TIME | unset | weighted only: event_time dates every memory by when it was stated (its event time, else when it was stored; never when it was last updated, and never its validity start) and measures recency from that against the request’s reference time. Unset, questions that are not about the current state measure it from when the memory was last updated or created, so history imported with a past event time and retrieved at a past reference time gets full recency everywhere. Current-state questions keep measuring back from the newest candidate. Memories without an event time, including entity, consolidation and profile nodes, count from when they were stored. On the offline evidence gate it lost LoCoMo evidence, so it stays off (RFC-0005 Section 7). Needs PRME_SCORING__FUSION=weighted; rank fusion’s recency boost and tie-break already use this clock, and rank fusion drops the setting with a warning. Turning it on or off makes a learned ranking profile built under the other setting inapplicable |
Context Packing
The defaults below are PRMEConfig’s, and environment variables that set only
some of them keep the others. A PackingConfig built in code starts from its
own field defaults instead (auditable, balanced, no rank fusion session
decay and no folding), which stored receipts and configurations rely on.
| Variable | Default | Description |
|---|---|---|
PRME_PACKING__TOKEN_BUDGET | 4096 | Max tokens in packed context |
PRME_PACKING__OVERHEAD_TOKENS | 100 | Reserved for formatting overhead |
PRME_PACKING__CHARS_PER_TOKEN | 4.2 | Estimated chars per token |
PRME_PACKING__CONTEXT_FORMAT | reader | reader (one plain line per record), auditable (one JSON object per record) or compact. Answerability and claim verification need PRME_PACKING__CONTEXT_CITATIONS=true with reader |
PRME_PACKING__MULTIPATH_ORDERING | balanced | Order within the multi-path tier: balanced, score (briefly the default with the reader format) or density |
PRME_PACKING__CONTEXT_CITATIONS | false | Reader format only: add [m3] references and fill context_references |
PRME_PACKING__FOLD_REPEATED_TEXT | true | Reader format only: pack each source text once. A record is left out when a packed record from one of its source events already shows its text, unless its line shows another date, tag or speaker, and its budget goes to other records; the auditable and compact formats ignore the setting. Default since 2026-10-06. Receipts that record it use schema version 22, and with false default retrievals write version 19. See folding repeated text; provisional |
PRME_PACKING__MIN_FIDELITY | reference | Lowest representation a record may fall back to. full, prose or structured keeps the text-free key_value and reference fallbacks, and blank records, out of every context format |
PRME_PACKING__VECTOR_K | 500 | Vector search candidates. Count and list questions multiply it by PRME_PACKING__AGGREGATION_K_MULTIPLIER, up to PRME_PACKING__AGGREGATION_K_MAX. At 500 the saved benchmark packs return nearly every stored turn; the offline evidence gate reports each channel’s recall at smaller limits (issue #87) |
PRME_PACKING__LEXICAL_K | 500 | BM25 search candidates, widened the same way. The entity-name and aggregation keyword scans add hits beyond this limit |
PRME_PACKING__GRAPH_MAX_CANDIDATES | 150 | Graph search candidates, widened the same way |
PRME_PACKING__GRAPH_MAX_HOPS | 3 | Max graph traversal depth |
PRME_PACKING__SESSION_CONTEXT_WINDOW | 3 | Adjacent turns to include |
PRME_PACKING__SESSION_CONTEXT_TOP_K | 20 | Top results for session expansion |
PRME_PACKING__SESSION_CONTEXT_SCORE_DECAY | 0.85 | Fraction of the triggering result’s score that an adjacent turn inherits |
PRME_PACKING__SESSION_CONTEXT_RANK_FUSION_SCORE_DECAY | 0.6 | Fraction for results scored with PRME_SCORING__FUSION=rrf, between 0 (exclusive) and 1, in place of the decay above; weighted scoring never uses it. Fused scores are compressed, so at 0.85 adjacent turns can crowd primary evidence out of the context. The offline evidence gate favored 0.6 (RFC-0005 Section 7.2); provisional |
PRME_PACKING__SESSION_CONTEXT_PACKING | unset | Opt-in (issue #86). trigger_tier counts session expansion as a path on a turn another search found and gives a turn it added the multi-path tier of the match that brought it in; adjacent packs the same records and places each neighbor beside that match in session order. Needs session expansion. Receipts that record it use schema version 21; remove the variable (an empty value is invalid) to turn it off. Provisional |
PRME_PACKING__AGGREGATION_K_MULTIPLIER | 3.0 | Multiplier applied to VECTOR_K, LEXICAL_K and GRAPH_MAX_CANDIDATES for count and list questions |
PRME_PACKING__AGGREGATION_K_MAX | 2000 | Cap on each widened limit |
Organizer
| Variable | Default | Description |
|---|---|---|
PRME_ORGANIZER__OPPORTUNISTIC_ENABLED | true | Run maintenance during retrieve/ingest |
PRME_ORGANIZER__OPPORTUNISTIC_COOLDOWN | 3600 | Seconds between opportunistic runs |
PRME_ORGANIZER__OPPORTUNISTIC_BUDGET_MS | 200 | Budget for opportunistic runs |
PRME_ORGANIZER__DEFAULT_ORGANIZE_BUDGET_MS | 5000 | Default budget for explicit organize() |
PRME_ORGANIZER__PROMOTION_AGE_DAYS | 7.0 | Days before auto-promotion |
PRME_ORGANIZER__PROMOTION_EVIDENCE_COUNT | 1 | Min evidence refs for promotion |
PRME_ORGANIZER__ARCHIVE_SALIENCE_THRESHOLD | 0.10 | Salience below this → archive candidate |
PRME_ORGANIZER__ARCHIVE_CONFIDENCE_THRESHOLD | 0.40 | Confidence below this → archive candidate |
PRME_ORGANIZER__DEDUP_SIMILARITY_THRESHOLD | 0.92 | Vector similarity for dedup |
PRME_ORGANIZER__ALIAS_SIMILARITY_THRESHOLD | 0.85 | Similarity for entity alias detection |
PRME_ORGANIZER__CONSOLIDATION_MIN_CLUSTER_SIZE | 3 | Min cluster size for consolidation |
PRME_ORGANIZER__CONSOLIDATION_SIMILARITY_THRESHOLD | 0.80 | Similarity for consolidation |
Optional Features
| Variable | Default | Description |
|---|---|---|
PRME_ENABLE_STORE_SUPERSEDENCE | false | Auto-detect supersedence on store |
PRME_REINFORCE_SIMILARITY_THRESHOLD | None | Auto-reinforce similar existing nodes |
PRME_ENABLE_SURPRISE_GATING | false | Novelty-based salience adjustment |
PRME_ENABLE_RERANKER | false | Rerank the top scored candidates with a local cross-encoder after scoring. Needs pip install prme[reranker] and the model files. Experimental: it has not improved answer scores so far, and on the offline evidence gate no cross-encoder rank-order variant beat rank fusion alone on both benchmarks at both 4K and 8K (issue #88) |
PRME_RERANKER_MODEL | cross-encoder/ms-marco-MiniLM-L-6-v2 | Hugging Face cross-encoder the reranker loads |
PRME_RERANKER_TOP_K | 100 | How many of the top scored candidates the reranker scores; the rest keep their order |
PRME_RERANKER_POLICY | legacy | score_envelope gives the reranked candidates their original scores in the new order, so later stages see the same score scale; anchored_score_envelope also keeps the original top multi-path record first. legacy puts the blended scores on them. See experimental retrieval policies |
PRME_RERANKER_PRIOR_WEIGHT | 0.3 | Weight of a reranked candidate’s own score in the new order; the cross-encoder gets the rest. 0.0 orders the reranked candidates by the cross-encoder alone (issue #88). A value other than 0.3 needs PRME_RERANKER_POLICY=score_envelope or anchored_score_envelope (the configuration is refused with legacy), is recorded in retrieval receipts and makes a learned ranking profile built under another value inapplicable |
PRME_QUERY_INTENT_ORDER | entity_first | temporal_first treats a question with temporal wording (when, last, before, since and similar) or a date in it as temporal even when it names a person, place or organization, so it gets temporal affinity scoring. A present-tense question that only its wording put on the current-state path (“Who is Caroline dating since the breakup?”) leaves it unless it says current, now, latest or similar, and the temporal context guidance is added where the question’s wording does not already select it. A lone capitalized month or weekday word that is part of a name (“Who is June dating?”) is read as the name, not a date. entity_first checks the entity patterns first, so a temporal question that names someone gets no temporal affinity. Experimental (RFC-0005 Section 3); changing it makes a learned ranking profile built under the other order inapplicable |
Encryption
| Variable | Default | Description |
|---|---|---|
PRME_ENCRYPTION_ENABLED | false | Enable encryption at rest. Takes effect only with PRME_ENCRYPTION_KEY set |
PRME_ENCRYPTION_KEY | None | Passphrase or raw key. The raw_key: prefix marks a raw Fernet key and passphrase: forces derivation; an unprefixed 44-character url-safe base64 value is read as a raw Fernet key, and anything else as a passphrase |
A directory pack uses Fernet file encryption. A single-file pack
(PRME_PACK_PATH) uses DuckDB’s native encryption instead: DuckDB encrypts the
file and its write-ahead log with AES-256-GCM, and PRME turns on DuckDB’s
temp_file_encryption, which is off by default even for an encrypted
database, with temporary files in <pack>.tmp beside the pack. For a
single-file pack, a passphrase becomes the DuckDB key through
PBKDF2-HMAC-SHA256 (600,000 iterations) with the fixed salt
prme-single-file-pack-v1, so the same passphrase gives the same key for every
pack; a raw key is used as given. A missing or wrong key, or encryption
configured for an unencrypted pack, raises EncryptionError and leaves the
file unchanged.
See Deployment for details.
Write Queue
| Variable | Default | Description |
|---|---|---|
PRME_WRITE_QUEUE_SIZE | 1000 | DuckDB write queue size |
PRME_MATERIALIZATION_QUEUE_SIZE | 500 | Graph materialization queue |
PRME_MATERIALIZATION_BUDGET_MS | 100 | Budget per materialization batch |
Programmatic Configuration
from prme.config import PRMEConfig
config = PRMEConfig(
db_path="./custom.duckdb",
vector_path="./custom.usearch",
lexical_path="./custom_lexical",
extraction={"provider": "anthropic", "model": "claude-sonnet-4-5-20250514"},
embedding={"provider": "fastembed", "model_name": "BAAI/bge-small-en-v1.5"},
scoring={"w_semantic": 0.30, "w_lexical": 0.15},
)
from prme import MemoryClient
client = MemoryClient(config=config)Source: documentation/configuration.md in PRME at 58caddb1.