Skip to content

memory: warm ANN generation counter is process-local — external writers (kkernel --atomic, kkernel reindex) cannot invalidate a running daemon's warm cache #752

Description

@ohdearquant

Summary

The memory pack's warm-ANN freshness gate (AnnState::generations, khive-pack-memory/src/ann.rs) is a plain in-process Mutex<HashMap<AnnKey, u64>> — no persistence, no cross-process visibility. The note-mutation hook seam introduced for #750 guarantees freshness for all mutation paths within one process, but an external writer process cannot invalidate a running daemon's warm cache.

Affected paths

  1. kkernel exec --ops-file --atomic (crates/kkernel/src/atomic_apply.rs): runs in its own short-lived process. Its in-process hook wiring is now correct (registry installs the hook, atomic update/delete post-commit effects fire it), but the bumped generation dies with the process. A concurrently-running daemon serving memory.recall keeps trusting its warm index.

  2. kkernel reindex (crates/kkernel/src/reindex.rs): constructs a bare KhiveRuntime with no packs loaded, so it has no AnnState at all. It rewrites note/entity vectors directly against the DB, then invalidates persisted Vamana snapshot rows (invalidate_vamana_snapshots, ~line 628) — a different mechanism from the daemon's in-memory generation counter. A running daemon's already-loaded warm index is untouched and can serve pre-reindex vectors until its own next generation bump or restart.

  3. Any other external process writing memory notes against the same DB while a daemon holds a warm index.

Why this was not fixed alongside #750

#750 was an in-process race (mid-write corpus snapshot installed permanently by entry().or_insert()); the generation-checked install plus the mutation-hook seam close every in-process path. Cross-process coherence is architecturally distinct: no hook fired inside a separate OS process can reach the daemon's in-memory state. It requires one of:

  • a persisted generation counter (DB row bumped transactionally with corpus writes; daemon compares persisted vs in-memory generation on the recall path or on a cheap poll),
  • a daemon notification channel (external writers signal the daemon to invalidate), or
  • startup/periodic revalidation (daemon re-derives corpus fingerprint and discards stale warm entries).

The persisted-counter option is the natural fit — the daemon already reads the DB on every recall, and the existing persisted-snapshot invalidation in kkernel reindex shows the pattern.

Scope note

The practical exposure today is bounded: routine agent traffic goes through the daemon (single-writer), and kkernel exec non-atomic local fallback constructs the full server (hook installed) in-process. The gap matters for --atomic batch applies and admin reindex runs executed while a daemon is live.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions