zettelkasten.retrieval¶
zettelkasten.retrieval ¶
Wiring for the blended recall-then-rerank retrieval policy.
Glue between the pure scoring core (:mod:zettelkasten.reranking), the
access-recency sidecar (:mod:zettelkasten.usage), the reranker config
(:mod:zettelkasten.config), and the retrieval surfaces (framing +
server.query_topic / project_search / note search). It builds the three
inputs :func:zettelkasten.reranking.rerank needs from a candidate set:
- an
importance_map— each candidate graph's :func:~zettelkasten.reranking.note_importance(graph centrality), keyed by the composite(source_graph, note_id)and restricted to POSITIVE scores so an isolated note carries no importance signal (absence, never a penalty). WhenRerankConfig.use_source_importanceis on, each note additionally inherits its source graph's syllabus work-importance (blended bymax); - a
recency_map— :func:zettelkasten.usage.recency_scoresper graph against the querying repo's :func:~zettelkasten.usage.session_timeline, keyed by the same composite(source_graph, note_id); and - a
get_vectoraccessor backed by each source graph's embedding index, so the reranker's MMR diversity pass has real vectors (and tolerates theNonevectors of unembedded notes, e.g. quotes).
Everything is parametrized by a get_graph callable (the MCP server and the
dashboard keep separate graph caches), and every step degrades gracefully — a
missing embedder, empty usage DB, or unreadable graph never breaks retrieval, it
just drops that channel's signal.
Federation caveat. Recency for a candidate whose source_graph is
namespaced (<repo_id>:<box> — a federated sibling repo) resolves against the
NOTE's home-repo usage DB, but :func:zettelkasten.usage.recency_scores reads
only the LOCAL <ANGELO_DIR>/zettel/usage.db (it exposes no repo-root arg, and
federated repos gitignore their runtime caches, so a sibling's usage DB is
typically absent anyway). We therefore compute recency for LOCAL candidate graphs
only and skip federated ones — they simply carry no recency signal (absence, not
a penalty), which keeps the non-punitive invariant intact. Importance and MMR are
unaffected (they read the live federated graph the dashboard already builds).
build_get_vector ¶
A (source_graph, note_id) -> vector | None accessor for MMR.
Backed by each source graph's embedding index. Returns None (no vector)
for an unembedded note (e.g. a quote) or any lookup failure, which the
reranker treats as "no diversity contribution" rather than an error.
Source code in zettelkasten/retrieval.py
rerank_hits ¶
rerank_hits(candidates: 'list[Hit]', get_graph: GetGraph, *, cfg: 'Any' = None, top_k: 'int | None' = None, repo_root: 'Any' = None, project: str = '', graphs_dir: 'Any' = None) -> 'list[Hit]'
Rerank a similarity-recalled candidate set, returning the same hit shape.
candidates are (note_id, cosine, source_graph, keyword_tier) tuples
(the shape framing.project_query yields). Blends similarity + graph-centrality
importance + access-recency and diversifies with MMR per cfg (defaults to
:func:zettelkasten.config.rerank_config), then returns the reranked hits —
truncated to top_k when given. The returned cosine is the TRUE
embedding score for each hit (never the fused score), so a caller's cosine
gate stays correct.
project and graphs_dir scope the (opt-in) syllabus source-work
importance channel so its cohort percentiles are project-scoped and cached per
scope; they are ignored unless cfg.use_source_importance is on.
When cfg reproduces pure similarity order (both boost weights zero,
:func:zettelkasten.config.rerank_disabled) the caller should have skipped
this entirely, but this function ALSO self-honors that contract: it
short-circuits to pure similarity order with NO blend and NO MMR, so a future
surface that forgets the call-site guard can't silently reintroduce MMR
diversification. (The low-level :func:reranking.rerank primitive is
UNCHANGED — called directly with zero weights and mmr_lambda < 1.0 it
still MMR-diversifies.)
Source code in zettelkasten/retrieval.py
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 | |
record_notes_access ¶
record_notes_access(pairs: 'list[tuple[str, str]]', *, session_id: 'str | None' = None, weight: float = 1.0) -> None
Record explicit/synthesis access for a batch of (graph, note_id) pairs.
Thin fan-out over :func:zettelkasten.usage.record_access (which never
raises) so the synthesis hooks (remine apply, spine promote, concept-hub
accept) share one call site and one weight convention. Blank entries are
skipped; duplicates within a batch are de-duplicated so a note surfaced twice
in one action is counted once.