Configuration
Configuration lives in the StrataConfig dataclass inside strata/config.py. This page walks through every field, its default value, and how to tweak it at runtime. Think of it as the settings menu you never knew you needed.
StrataConfig Fields
Core Paths
| Field | Type | Default | Description |
|---|---|---|---|
base_dir |
Path |
./strata_data/ or ~/.strata/ |
Root directory for all three strata. Auto-detected: project-local ./strata_data/ wins over global ~/.strata/. Override with $STRATA_HOME. |
active_dir |
str |
"active" |
Directory name for the 1st Stratum (working memory). Relative to base_dir. |
cooled_dir |
str |
"cooled" |
Directory name for the 2nd Stratum (aged-out files). Relative to base_dir. |
stratum_3_archive |
str |
"archive" |
Directory name for the 3rd Stratum (cold JSON storage). Relative to base_dir. |
stratum_3_shadow_db |
str |
"stratum_3_shadow.db" |
Filename for the SQLite FTS5 shadow index. Relative to base_dir. |
Directory names - they're exactly what you'd expect. You can change them if you really want to, but the defaults are fine for almost everyone. The shadow DB filename lives here too, because it's another path under the base directory.
Decay Thresholds (Migration: 1st -> 2nd Stratum)
These control how long a file can sit untouched in the active stratum before the Janitor nudges it over to cooled. Think of it as the "hey, are you still using this?" timer.
| Field | Type | Default | Description |
|---|---|---|---|
decay_thresholds |
dict[str, int] |
{"projects": 14, "entities": 60, "gtd": 7, "pi/conversations": 7, "pi": 30, "*": 30} |
Days before a file is considered stale and eligible for migration from active to cooled. Keys can be multiple path components (e.g. "pi/conversations") for deeper routing; matching uses the longest prefix. "*" is the fallback for unmatched paths. |
Default thresholds by directory:
| Directory | Threshold | Rationale |
|---|---|---|
projects/ |
14 days | Active initiatives. Two weeks without updates means they have cooled off. |
entities/ |
60 days | People, companies, tools. Context that stays relevant longer. |
gtd/ |
7 days | Tasks and quick notes. One week and they are stale. |
pi/conversations/ |
7 days | Raw conversation transcripts. Quick to accumulate, quick to cool. |
pi/ (facts, memos) |
30 days | Distilled facts and memos under pi/. One month without access. |
| Everything else | 30 days | Default. One month without access. |
The matching uses the longest prefix key, so a file in pi/conversations/something.md matches the 7-day pi/conversations rule instead of the 30-day pi rule. That's the kind of detail that sounds obvious once you read it, but made someone debug for an hour. You're welcome.
Promotion (2nd -> 1st Stratum)
Sometimes a cooled file turns out to be more relevant than you thought. These fields control how often it needs to be accessed before it gets promoted back to active.
| Field | Type | Default | Description |
|---|---|---|---|
promotion_threshold |
int |
3 |
Number of accesses to a cooled file before the Janitor promotes it back to active. Once a file crosses this threshold, it is moved from cooled/ to active/ and access tracking is reset. |
Here's how it works: when you read a file in cooled/ (via strata read), its access count ticks up. Hit the threshold, and it gets promoted right then and there - no waiting for the next maintenance cycle. The batch commands (strata promote and strata maintenance) check thresholds too. This keeps frequently-referenced files from getting buried just because they're old.
Rehydration (3rd -> 1st / 3rd -> 2nd)
Archived files don't have to stay archived forever. This field tells the system where they should land when they come back.
| Field | Type | Default | Description |
|---|---|---|---|
rehydration_target |
str |
"active" |
Default target stratum for rehydration. "active" restores to the 1st Stratum (editable). "cooled" restores to the 2nd Stratum (query-only). Can be overridden per-call with --target=active|cooled. |
You might want "cooled" as the default if you're the cautious type - bring it back but don't let anyone edit it until they've proved they need to. Or just blast everything back to active and live on the edge. Your call.
LRU Eviction (2nd -> 3rd Stratum)
This is the "you haven't touched this in a while" trigger for the cooled stratum. Files that nobody reads eventually get shipped off to the archive.
| Field | Type | Default | Description |
|---|---|---|---|
lru_days |
int |
90 |
Days since last access after which a cooled file is eligible for eviction to archive. |
lru_min_access_count |
int |
1 |
Maximum number of accesses a file may have to avoid eviction. Files accessed more often than this are retained. |
lru_decay_thresholds |
dict[str, int] |
{"*": 90} |
Per-directory LRU thresholds. Same pattern as decay_thresholds. Falls back to lru_days when no match. |
A file gets evicted when both conditions are true: it's been more than lru_days since anyone last looked at it, and it's been accessed fewer times than lru_min_access_count. In other words, if nobody's opened it in 90 days and it's only been read once (probably by accident), off it goes.
Search Backend
| Field | Type | Default | Description |
|---|---|---|---|
search_backend |
str |
"qmd" |
Search backend: "qmd" for BM25 + vector hybrid, "fts5" for SQLite FTS5 keyword only. |
qmd_enabled |
bool |
False |
Derived from search_backend (read-only property). True when search_backend == "qmd". |
qmd_reranker |
Optional[str] |
None |
LLM reranker provider URL (e.g., "openai://gpt-4o-mini", "ollama://llama3"). |
qmd_reranker_warning_shown |
bool |
False |
Internal flag to prevent repeated cost warnings. |
qmd_collection_prefix |
str |
"strata_" |
Prefix for QMD collection names (e.g., "strata_active", "strata_cooled"). |
qmd_enabled is derived from search_backend - you don't set it directly. If you switch to fts5, it's False. Switch back to qmd, it's True. Simple. The qmd_reranker_warning_shown flag is an internal detail you'll probably never touch. Consider it the "I told you this API call costs money" checkbox that gets ticked once and stays ticked.
File Patterns
| Field | Type | Default | Description |
|---|---|---|---|
active_file_patterns |
list[str] |
["*.md", "*.txt", "*.json", "*.yaml", "*.yml"] |
Glob patterns for files the Janitor considers when scanning the active stratum. Files not matching any pattern are ignored during migration. |
Got some weird file extension from that one experiment you ran in 2022? Add it here, or watch the Janitor pretend it doesn't exist.
Environment Variable Overrides
| Variable | Overrides | Description |
|---|---|---|
$STRATA_HOME |
base_dir |
Forces the data directory to this path. The highest-priority override. Also used in detect_base_dir(). |
Resolution order for base_dir:
$STRATA_HOMEenvironment variable (always wins - it's the nuclear option)./strata_data/in the current directory (project-local)~/.strata/(global fallback)
Most folks never set $STRATA_HOME. The auto-detection works well enough. But if you have Opinions about where your data lives, this is your escape hatch.
Persisted Configuration
After strata init or strata config set, configuration gets written to strata.json in the base directory. It looks like this:
{
"search_backend": "qmd",
"qmd_reranker": null,
"lru_days": 90,
"lru_min_access_count": 1,
"decay_thresholds": {
"projects": 14,
"entities": 60,
"gtd": 7,
"*": 30
},
"active_file_patterns": ["*.md", "*.txt", "*.json", "*.yaml", "*.yml"]
}
Fields you don't include here fall back to StrataConfig defaults. The persisted file gets merged with defaults at load time, so you only need to store the things you're changing. It's the "don't repeat yourself" principle, applied to config storage. You're welcome.
Runtime Configuration (CLI)
You can view and change config from the command line without ever opening a JSON file. Here's how:
# Show all configuration
strata config
# Get a specific value (supports dotted paths for nested fields)
strata config get decay_thresholds.projects
# Output: 14
strata config get lru_days
# Output: 90
# Set a value (value is auto-parsed: int, float, bool, JSON, or string)
strata config set decay_thresholds.projects 21
strata config set lru_days 60
strata config set search_backend fts5
# JSON mode for scripting
strata config --json
The value parser in strata config set is smarter than you'd expect:
- Integers:
14,0,-1 - Floats:
3.14,0.5 - Booleans:
true,false,yes,no,1,0 - JSON arrays/objects:
'["*.md", "*.txt"]','{"projects": 21}' - Everything else: treated as a string
Changes hit the disk immediately - no --save flag, no Are you sure? prompt. If you typo a value, that's on you. (Okay, that's a little harsh. It'll probably be fine.)
Pi Extension Configuration
The Pi extension (skills/pi/strata.ts) uses its own config file: <strataBaseDir>/pi-config.json. It's separate from strata.json because the Pi extension is written in TypeScript, not Python, and its settings are meaningless to the CLI anyway.
File Location
| Strata Store | Config Path |
|---|---|
Global (~/.strata/) |
~/.strata/pi-config.json |
Project-local (./strata_data/) |
./strata_data/pi-config.json |
Schema
{
"llm": {
"enabled": false,
"provider": "openai",
"model": "gpt-4o-mini",
"apiKey": "",
"temperature": 0.0,
"maxTokens": 500
}
}
Fields
| Field | Default | Description |
|---|---|---|
llm.enabled |
false |
Enable LLM-powered memory classification. Off by default. When disabled, the extension uses the heuristic regex fallback. |
llm.provider |
"openai" |
LLM provider: "openai", "anthropic", or "openrouter". |
llm.model |
Provider default | Model identifier. Defaults: gpt-4o-mini (OpenAI), claude-3-5-haiku-latest (Anthropic), openai/gpt-4o-mini (OpenRouter). |
llm.apiKey |
"" |
API key or env var reference ("${VAR_NAME}"). When empty, falls back to well-known env vars per provider. |
llm.temperature |
0.0 |
LLM temperature (0.0 = deterministic classification). |
llm.maxTokens |
500 |
Maximum tokens for the classification response. |
LLM classification is off by default. There's a reason for that - it costs money, it's slower, and the heuristic regex fallback actually works pretty well for most people. Turn it on if you want better classification. Leave it off if you like free.
API Key Resolution
The extension hunts for an API key in this order:
- Direct string in
llm.apiKey - Env var reference in
llm.apiKey(e.g.,"${STRATA_OPENAI_API_KEY}") - Well-known env vars per provider:
OPENAI_API_KEY,ANTHROPIC_API_KEY,OPENROUTER_API_KEY - Empty → LLM classification disabled, falls back to heuristic
Step 4 is the "we give up" case. If you don't set a key anywhere, the extension quietly shrugs and uses the heuristic instead. No errors, no drama.
Example
{
"llm": {
"enabled": true,
"provider": "openai",
"apiKey": "${STRATA_OPENAI_API_KEY}"
}
}
This file is only consumed by the Pi extension (the TypeScript one), not the Python CLI. If you change it, you'll need a /reload in Pi before the new settings kick in.
Cross-Reference
- CLI Reference -- commands to view and modify config at runtime
- Architecture -- how thresholds drive Janitor behaviour
- Tracking -- how configuration affects cost calculations
- Search -- search backend configuration details
- Pi Integration -- Pi extension configuration and LLM setup