Add local semantic vector search for recall
The DM's recall tool could only do substring and fuzzy word-overlap matching,
so "big explosion spell" couldn't find Fireball and "that merchant we met"
couldn't find Henrik. This adds a local vector search engine using fastembed
(ONNX, ~100MB) and sqlite-vec so recall can handle natural language queries
against the full SRD, world entities, player knowledge, and transcripts.
The SRD gets embedded once via `storied index srd` / `make search-index`, and
that search.db acts as the seed for every world — just a file copy instead of
re-embedding 862 files each time. Write-through keeps the index current as the
DM establishes entities, marks events, and records player discoveries.
Transcript and player knowledge results get age-decayed (3 game-day half-life)
so recent conversations rank higher than old ones. The on_empty callback on
VectorIndex auto-populates from the SRD seed on first search if the index is
missing or was deleted.
Also introduced ToolContext to bundle the infrastructure params (world_id,
player_id, base_path, campaign_log, entity_index, vector_index) that were
threaded through every tool function individually. This cleaned up the tool
signatures and removed a bunch of defensive None-checks — all index objects are
now required, not optional.
Dropped the old ContentResolver.search/fuzzy fallback since vector search
subsumes it. ContentResolver still handles direct name→file lookups (find/load)
but the keyword search methods are gone.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>