Configuration
August 2, 2026 · View on GitHub
Rekal is zero-config by default. When you do want to tune it, there is exactly
one file: .rekal/config.json — gitignored, local to the machine, never
committed, pushed, or synced.
{
"local_import": { "all": true },
"weights": {
"bm25": 0.35,
"lsa": 0.10,
"nomic": 0.55,
"steering_boost": 1.3,
"subagent_downweight": 0.7,
"facet_boost": 0.3,
"recency_boost": 0.15,
"reach_boost": 0.2
},
"embedding": {
"endpoint": "$EMBED_ENDPOINT",
"model": "nomic-embed-text-v1.5",
"api_key_env": "EMBED_API_KEY",
"timeout_seconds": 10
}
}
A local .rekal/config.json deep-merges over an optional global
~/.config/rekal/config.json (honoring $REKAL_CONFIG_HOME then
$XDG_CONFIG_HOME); precedence is local → global → built-in defaults.
weights
Tunes recall ranking (layer mix, steering-turn boost, subagent discount, and
facet_boost — the facet layer over each session's tool paths/commands/steering
text, on by default at 0.3; set 0 to disable). Applied at query time — changing
them takes effect on the next search, no reindex, at any corpus size.
Two more additive ranking layers ship on at gentle defaults (both below
facet_boost), and are safe because each is self-inerting until it has signal:
recency_boost(default0.15) — nudges more recently captured sessions up the ranking (min-max over the candidate set: newest → +boost, oldest → +0). Contributes nothing when the candidates share a timestamp (span 0).reach_boost(default0.2) — nudges sessions an agent has actually drilled up the ranking (max-normalizedsession_reach.drill_count), turning the usage half of the[reached N× drilled M×]hint into ranking. Merely being surfaced does not count: a recall edge records that the engine ranked the session into some window, so boosting on it is the ranker rewarding its own output, and one call surfaces ~20 seeds — most of a small corpus. Self-activating: a cold store has no drill edges, so it is byte-identical until agents start drilling; fails soft on an index with no reach data.
Both are additive terms applied before the subagent discount, exactly like
facet_boost; both reorder within a result set and never feed the silence
gate (a newer or oft-reached session is not inherently more relevant). Set
either to 0 to disable it (byte-identical), or tune per corpus.
The layer mix when there are no semantic vectors
bm25/lsa/nomic are normalized to sum to 1. When the deep semantic layer
has no vectors — the daemon still warming, rekal embed never run, a build
without a model — its share falls to lsa, the remaining semantic signal, and
the pair renormalizes to 0.35/0.65. So on a store without vectors LSA
carries roughly six times the weight it has when nomic is available. That is
deliberate, but worth knowing before you read a ranking.
0 disables a layer, and that holds in the fallback too: setting lsa to 0
leaves keyword search carrying the whole mix rather than handing it the share a
disabled layer would otherwise absorb. On a store with no vectors and a corpus
containing very short sessions, that is a useful knob — LSA cosine over a
40-character session is structurally generous, and at 0.65 it decides
rankings.
embedding
Switches deep semantic embeddings from the embedded nomic model to any OpenAI-compatible endpoint (vLLM, Ollama, LM Studio, TEI). Requests are batched and hard-timeboxed so a slow server can never stall a commit (embedding is always non-fatal). Pointed at localhost, your data still never leaves the machine; pointed at a cloud API, session text leaves — your call, made explicitly.
Switching embedding model/endpoint requires one rekal index to regenerate
vectors. A content-hash-keyed cache (.rekal/embed-cache.db, vectors only,
never text) makes routine rebuilds embed only new sessions — and makes a model
switch cost exactly one full pass.
API key: three ways, pick one
| Form | Example | Where the secret lives |
|---|---|---|
| Real string | "api_key": "sk-abc123" | In the file (gitignored, this machine only) |
| Env reference | "api_key": "$MY_KEY" | In the environment, expanded at run time |
| Env var name | "api_key_env": "EMBED_API_KEY" | In the environment, read directly |
Precedence: api_key_env wins when set and the variable is non-empty; otherwise
api_key (after $VAR expansion) is used; no key at all just omits the
Authorization header — the normal case for a localhost server. endpoint
expands $VAR the same way. One edge: a hardcoded api_key containing a
literal $ would be treated as an env reference — real provider keys never
contain $, and api_key_env is the unambiguous form for anything sensitive.