memory.reranking¶
memory.reranking ¶
Blended recall-then-rerank scoring core (store-agnostic).
Borrows FinMem's idea of fusing three signals — similarity, importance (graph centrality) and access-recency — into ranking via a recall-then-rerank stage:
- A candidate set is recalled by SIMILARITY (the gate — done upstream by the embedding search; this module takes the recalled set as input).
- The candidates are RERANKED by fusing similarity + importance + recency with
weighted Reciprocal Rank Fusion (:func:
memory.ranking.rrf_fuse). - The fused order is DIVERSIFIED with Maximal Marginal Relevance (MMR).
Non-punitive invariant. Importance and recency are boosts within the
already-relevant set. A note that lacks a signal (never accessed, low
centrality) simply does not appear in that channel — it is NEVER pushed down for
the absence. Concretely: with w_importance == w_recency == 0 and
mmr_lambda == 1.0, :func:rerank returns the candidates in pure similarity
order. This is the guardrail that keeps the reranker from degenerating into a
decay/forgetting mechanism.
This module is a pure scoring core: it holds no MCP wiring and reads no
environment or config files. Callers construct a :class:RerankConfig (or take
:func:default_config) and pass it in.
It lives in memory (the base layer) so BOTH the memory tree and the
zettelkasten can use it without violating the one-way dependency direction
(zettelkasten may import memory; memory never imports
zettelkasten). zettelkasten.reranking re-exports these names for
backward compatibility and adds the syllabus-backed source-importance channel.
RerankConfig
dataclass
¶
Tunable weights and knobs for :func:rerank.
Attributes:
| Name | Type | Description |
|---|---|---|
w_sim |
float
|
Weight of the similarity channel. Keep > 0 — it is the gate that carries every recalled candidate into the fusion. |
w_importance |
float
|
Weight of the graph-centrality channel (0 disables it). |
w_recency |
float
|
Weight of the access-recency channel (0 disables it). |
w_calendar_recency |
float
|
Weight of the CALENDAR-recency channel (0 disables
it — the default). Distinct from |
calendar_half_life |
float
|
Half-life in DAYS for the calendar-recency decay
(~90 = one quarter). Consumed by the WIRING that builds the calendar
map, not by :func: |
mmr_lambda |
float
|
MMR trade-off in [0, 1]. 1.0 = pure fused relevance (no diversification); lower values trade relevance for diversity against already-selected notes. 1.0 preserves the non-punitive invariant. |
overfetch |
int
|
Recall-stage over-fetch multiplier (candidates to recall
before reranking). Consumed by the WIRING that builds the candidate
set, not by :func: |
rrf_k |
int | None
|
RRF damping constant. |
use_source_importance |
bool
|
Whether to fold the (expensive) syllabus
source-work importance channel in. OFF by default; the wiring opts in
and supplies the map. :func: |
Source code in memory/reranking.py
Candidate ¶
Bases: NamedTuple
A similarity-recalled candidate.
Matches the hit shape produced by zettelkasten.framing.project_query
((note_id, cosine, source_graph, keyword_tier)). Because it is a
NamedTuple it unpacks positionally, so :func:rerank also accepts plain
tuples of that shape interchangeably.
A candidate's IDENTITY is the composite (source_graph, note_id), not the
bare note_id: zettel ids are not namespaced by box, so the same id can
name distinct notes in different source graphs. Single-graph callers (e.g.
the memory tree) may pass a constant source_graph (such as "").
Source code in memory/reranking.py
default_config ¶
default_config() -> RerankConfig
note_importance ¶
Per-note centrality for a graph, in [0, 1], max-normalized.
v1 (method="degree") is the LOG of undirected degree (outgoing
note.links + incoming backlinks), max-normalized to [0, 1].
NOTE on the log: fusion is RANK-based (:func:memory.ranking.rrf_fuse scores
an id by its RANK within each channel, not by the channel's magnitude), and
log1p is monotonic in degree — so the log does NOT change the importance
channel's ordering versus raw degree. Anti-entrenchment comes instead from
the importance WEIGHT (w_importance) and the MMR diversity pass — never
from this log.
method is the seam for a future directed-PageRank replacement over
note.links; only "degree" is implemented today (any other value
raises ValueError). Results are cached per graph and invalidated when the
graph reloads (see :data:_importance_cache).
zg is duck-typed: any object exposing .notes (a mapping of id ->
object with .id and .links where each link has a .target) works,
so this serves both the zettelkasten graph and the memory tree graph.
Source code in memory/reranking.py
rerank ¶
rerank(candidates: 'list[Any]', *, importance_map: dict[str, float], recency_map: dict[str, float], calendar_map: dict[str, float] | None = None, cfg: RerankConfig | None = None, get_vector: 'Callable[[str, str], list[float] | None] | None' = None, limit: 'int | None' = None) -> list[dict]
Rerank similarity-recalled candidates by fusing sim + importance + recency.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
candidates
|
'list[Any]'
|
The similarity-recalled set, each a :class: |
required |
importance_map
|
dict[str, float]
|
importance keyed by |
required |
recency_map
|
dict[str, float]
|
recency keyed by |
required |
calendar_map
|
dict[str, float] | None
|
CALENDAR-recency keyed by |
None
|
cfg
|
RerankConfig | None
|
Weights/knobs; :func: |
None
|
get_vector
|
'Callable[[str, str], list[float] | None] | None'
|
Optional |
None
|
limit
|
'int | None'
|
Optional cap on MMR-selected notes (see :func: |
None
|
Returns:
| Type | Description |
|---|---|
list[dict]
|
Final-best-first list of per-candidate breakdown dicts: |
list[dict]
|
``{id, source_graph, sim, importance, recency, calendar, fused, |
list[dict]
|
keyword_tier}``. |
Non-punitive invariant: with w_importance == w_recency ==
w_calendar_recency == 0 and mmr_lambda == 1.0 the boost channels
contribute nothing and MMR is skipped, so the output is in pure similarity
order.
Source code in memory/reranking.py
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 | |