Skip to content

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 (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 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.0

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:

WeightMultiplier spanVerdict
0.151.37×Flips a 30% relevance gap. Not a tiebreaker.
0.101.26×Flips only within ~25%. Chosen.
0.051.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.

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.

The same mechanism runs at the entity layer, with its own tunable contract:

SettingDefaultMeaning
hebbian.entity.enabledtrueStrengthen co-retrieved entities
hebbian.entity.eta1How much one co-retrieval strengthens an edge
hebbian.entity.halfLifeDays30Edges decay; unused associations fade
hebbian.entity.cap50Ceiling on strength, so nothing runs away
hebbian.entity.minEffective0.5Below this, an edge is ignored
hebbian.entity.rrfWeight0.3How much edge strength influences ranking
hebbian.entity.maxWriteEntities12Entities strengthened per search — bounds the O(n²)
hebbian.entity.expandPerSeed3Expansion 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.

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.

Most of this is fixed on purpose. What’s exposed:

SettingDefaultEffect
memory.minFactSimilarity0.45Search relevance floor
memory.injectionFloor0.6Automatic injection floor — higher, deliberately
hebbian.entity.*see aboveEntity 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.

Terminal window
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_id

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