Concepts
Ranking — ACT-R and Hebbian
Relevance alone is a weak signal for memory. Two facts can be equally relevant to a query while one of them is the thing you actually use every day. Sigil re-ranks with two models borrowed from how human memory is understood to work — and, importantly, bounds both so they stay tiebreakers rather than taking over.
ACT-R base-level activation
Section titled “ACT-R base-level activation”ACT-R (Adaptive Control of Thought—Rational) models memory availability as a function of how often something has been used and how recently. Sigil applies the base-level learning equation to facts:
activation ≈ ln( Σ t_i^(-0.5) )Practically: frequency raises activation, recency raises it, and it decays as -0.5 × ln(t) in days.
The one-day floor
Section titled “The one-day floor”The decay term is only decay for t > 1. Below a day it flips sign and becomes a bonus.
That mattered in practice. Retrieval itself bumps last_accessed_at, so every fact returned by a search immediately sat at the floor — and at an early 0.01-day floor that added a flat +2.30 to activation. The term stopped discriminating entirely and activation collapsed into raw frequency.
Anderson’s formulation assumes t is large in its native units; expressing it in days is what exposes the sub-1 region. Flooring at one full day keeps the term in [-∞, 0], where it belongs.
ACTIVATION_MIN_AGE_DAYS = 1.0The weight is bounded, deliberately
Section titled “The weight is bounded, deliberately”Activation used to multiply the RRF score directly, which handed frequency a wider dynamic range than relevance. On a real store, access counts spanned 1.91× while RRF spanned 1.75× — so a fact with 29 accesses outranked a near-exact semantic match with one.
Frequency is a tiebreaker, not the signal. So:
final = rrf × (1 + 0.10 × activation)The weight isn’t a feel-good number. Across 0–29 accesses — the range observed on a real store — the multiplier spans:
| Weight | Multiplier span | Verdict |
|---|---|---|
0.15 | 1.37× | Flips a 30% relevance gap. Not a tiebreaker. |
0.10 | 1.26× | Flips only within ~25%. Chosen. |
0.05 | 1.13× | Effectively inert |
0.10 is the largest value at which the observed misranking cannot recur: the weakest fact in that case led its rival on relevance by 1.30×, which has to survive a 29-versus-1 access deficit.
Hebbian co-retrieval
Section titled “Hebbian co-retrieval”Neurons that fire together, wire together. When a set of facts is retrieved together, Sigil strengthens the edges between every pair in that set.
INSERT INTO hebbian_edge (fact_a_id, fact_b_id, strength, …)VALUES …ON CONFLICT DO UPDATE SET strength = strength + 1, last_seen_at = NOW()Pairs are canonicalized (fact_a_id < fact_b_id) so (a,b) and (b,a) can’t both exist. Writes are fire-and-forget from the search path and applied to the top-K only — it’s O(K²), so K is 5–10, never the full retrieval set.
The graph that emerges is “facts you think about together”, built passively from real query behaviour rather than from anything anyone declared. It feeds the graph_boost term during ranking and backs “related facts” traversal.
You can see it in sigil why output as hebbian=N.
Entity-level Hebbian
Section titled “Entity-level Hebbian”The same mechanism runs at the entity layer, with its own tunable contract:
| Setting | Default | Meaning |
|---|---|---|
hebbian.entity.enabled | true | Strengthen co-retrieved entities |
hebbian.entity.eta | 1 | How much one co-retrieval strengthens an edge |
hebbian.entity.halfLifeDays | 30 | Edges decay; unused associations fade |
hebbian.entity.cap | 50 | Ceiling on strength, so nothing runs away |
hebbian.entity.minEffective | 0.5 | Below this, an edge is ignored |
hebbian.entity.rrfWeight | 0.3 | How much edge strength influences ranking |
hebbian.entity.maxWriteEntities | 12 | Entities strengthened per search — bounds the O(n²) |
hebbian.entity.expandPerSeed | 3 | Expansion breadth per seed entity |
The half-life is the piece that keeps it honest. An association you formed six months ago and never used again shouldn’t still be steering results.
Fact lifecycle and ranking
Section titled “Fact lifecycle and ranking”Only stable and editing facts are in the canonical ranking pool. fresh facts are recently captured and not yet trusted.
Promotion needs an hour plus either vital importance or at least one access. Low-importance facts nobody ever touched stay fresh forever — effectively forgotten, with no LLM call needed to decide it and no deletion to regret. See Memory decay.
What you can tune
Section titled “What you can tune”Most of this is fixed on purpose. What’s exposed:
| Setting | Default | Effect |
|---|---|---|
memory.minFactSimilarity | 0.45 | Search relevance floor |
memory.injectionFloor | 0.6 | Automatic injection floor — higher, deliberately |
hebbian.entity.* | see above | Entity co-retrieval behaviour |
RRF constants and the activation weight are code-level, because they were derived from measured behaviour rather than preference — and a wrong value there degrades recall quietly instead of failing loudly.
Reading it
Section titled “Reading it”sigil why "your query" 1. [rrf=0.0731 hebbian=2] [architecture] [supplementary] [conf=high] pods: project:payments-api content: Webhook retries are idempotent on event_id — never on payment_idrrf is post-fusion, pre-activation. hebbian is the co-retrieval boost. Absolute values only mean something relative to the other results in the same query.