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 (default 0.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 (default 0.2) — nudges sessions an agent has actually drilled up the ranking (max-normalized session_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

FormExampleWhere 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.