Skip to content

feat(memory): optional exact-match namespace param on recall + bench arm-isolation recipe (#733)#751

Merged
oceanwaves630 merged 3 commits into
mainfrom
feat/733-recall-namespace
Jul 9, 2026
Merged

feat(memory): optional exact-match namespace param on recall + bench arm-isolation recipe (#733)#751
oceanwaves630 merged 3 commits into
mainfrom
feat/733-recall-namespace

Conversation

@oceanwaves630

Copy link
Copy Markdown
Collaborator

Implements #733 slice 1 (the read-side escape hatch; brain.auto_feedback event scoping remains open on the issue).

What

  • memory.recall gains an optional exact-match namespace param. Absent → byte-identical to prior behavior (token namespace, default local, per ADR-007 Rev 6's multi-record-op model). Present → the entire read pipeline (FTS + vector candidate fetch, the ANN over-fetch retry loop's visibility gate, the supersedes graph read, and the serve-ledger namespace stamp) scopes to exactly that namespace.
  • Defense-in-depth seam mirroring handle_remember: dispatch's ADR-007 Rule-3 explicit-namespace escape already pre-mints the token; the handler re-derives for direct (non-dispatch) callers. The namespace ParamDef declaration keeps dispatch's strip-vs-pass-through behavior consistent.
  • Verbose diagnostics hardened: vector_hits_per_model / vector_candidates_per_model are filtered through the visible-namespace id set at both serialization sites (memory.recall include_breakdown and memory.recall_candidates), so off-namespace candidate UUIDs cannot leak through the breakdown paths.
  • resolve_explicit_namespace errors now name the supplied invalid value.
  • New operator doc: docs/benchmarks/recall-arm-isolation.md — how to run isolated measurement arms (per-arm namespace writes + reads, profile pinning via the serving-profile override), with explicit notes on what is not yet isolated.

Tests

12 new tests: absent-param regression (exact id-set equality against a mixed-namespace corpus), explicit-namespace scoping, no-match-empty-ok, invalid-value error naming the literal input, ANN over-fetch widening with a widening-disabled control, and mutation-verified breakdown-leak regressions for both diagnostic paths (two embedding models, closer-ranked off-namespace fillers).

Note: two tests use a bounded fresh-runtime retry to tolerate a pre-existing ANN warm-cache race, tracked separately as #750 — retries occur only before response settlement; assertions stay strict.

Review

Codex rounds: r1 APPROVE-WITH-FIXES (1 High, 2 Medium) → fixes applied → r2 REJECT (1 Medium: missing mutation coverage on the candidates path) → fix applied → r3 clean APPROVE.

Gates: fmt, clippy (khive-pack-memory, khive-runtime) with -D warnings, cargo test -p khive-pack-memory (222 passed / 1 ignored), cargo test -p khive-runtime (59 passed) — all exit 0.

Closes nothing (leaves #733 open for the feedback-scoping remainder).

🤖 Generated with Claude Code

oceanwaves630 and others added 3 commits July 8, 2026 23:58
Adds an optional exact-match namespace string param to memory.recall.
Absent: unchanged (reads the caller token's default visible namespace
set). Present: candidate fetch (FTS + vector + the ANN over-fetch retry
loop) scopes to exactly that namespace via NamespaceToken::with_namespace,
mirroring handle_remember's existing write-namespace override pattern.
Invalid values are rejected via Namespace::parse, never silently coerced.

This is largely defense-in-depth: VerbRegistry::dispatch's Rule-3
explicit-namespace escape (ADR-007 Rev 4/6) already mints the token with
the effective namespace before the handler runs, for any caller going
through the registry. The explicit RecallParams.namespace field + ParamDef
entry make the escape hatch handler-visible/documented and safe for direct
(non-dispatch) callers.

Adds regression + coverage tests for the absent/present/no-match/invalid
cases and a dedicated ANN over-fetch retry-loop test proving the widening
loop still respects the effective (narrowed) namespace, with a widening-
disabled control case.

Adds docs/benchmarks/recall-arm-isolation.md: recipe for an isolated
recall measurement arm via memory.remember/recall namespace + the
ADR-104 profile_id override, with an explicit list of what remains
un-isolated (brain.auto_feedback posteriors, knowledge.compose).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…aram

Codex reviewed the #733 branch (APPROVE-WITH-FIXES) and returned three
findings, all applied here:

1. [High] memory.recall's verbose multi-model breakdown
   (candidates.vector_candidates_per_model, include_breakdown=true) leaked
   raw, pre-hydration ANN candidate IDs from outside the effective
   namespace, in both handle_recall and the handle_recall_candidates
   sub-handler. Fixed by filtering each per-model hit list through the
   same memory_ids visible-namespace set that already scopes `results`,
   in both locations. Added a regression test verified non-vacuous by
   reverting the fix and confirming the leak reproduces.

2. [Medium] resolve_explicit_namespace (khive-runtime/src/pack.rs)
   dropped the caller's supplied namespace value from its error message.
   Fixed by including it in the Some(Value::String(ns_str)) arm only, per
   review scope. Strengthened the existing invalid-namespace test to
   assert the literal supplied value appears in the message (previously
   only checked for the word "namespace", which passed vacuously).

3. [Medium] docs/benchmarks/recall-arm-isolation.md corrected against
   source: namespace grammar (single-`:`-separated segments, no `::`),
   delete's actual param shape (id/kind/hard, not type=), and
   knowledge.compose's actual namespace scoping (token-scoped, not
   global -- it lacks an exact-match override, not all scoping).

Also hardened two ANN-backed recall tests (the new breakdown regression
test and the pre-existing ANN over-fetch retry test from e57cd98)
against a pre-existing, unrelated production race in ann.rs's
fire-and-forget per-model warm cache: whichever queued rebuild acquires
the per-model lock first wins the shared index slot permanently, even if
it raced ahead of a still-in-flight sibling write. Confirmed via targeted
debug capture that this is not a consequence of the namespace filter
under test. No production ann.rs/pack.rs code changed for this; both
tests now retry their whole seed-and-recall flow against a fresh runtime
(bounded, 5 attempts) instead of touching the underlying cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ak regression

Codex re-review (round 2, REJECT/Medium): the fix-round-1 regression
dispatched only memory.recall, which is mutation-sensitive for
handle_recall's per-model namespace filter but not for
handle_recall_candidates's independent filter (sub_handlers.rs:182) --
pack.rs routes the two verbs to two separate handler functions with two
separate vector_hits_per_model serialization sites. Reverting the
sub_handlers.rs filter alone would not have failed any existing test.

Adds ns733b_recall_candidates_multi_model_excludes_off_namespace_candidates,
dispatching memory.recall_candidates directly against the same two-model
/ local-fillers / bench-a-target corpus, asserting no leaked local
filler UUID and the target present in handle_recall_candidates's
object-keyed vector_candidates_per_model shape (distinct from
handle_recall's array-of-entries shape). Verified non-vacuous by
temporarily reverting the sub_handlers.rs filter and confirming the
test fails, listing the leaked UUIDs, then restoring.

Extracted the shared corpus-seeding loop (5 local fillers + 1 bench-a
target across two custom embedding models) into
ns733b_seed_two_model_corpus, reused by both the existing memory.recall
regression and the new memory.recall_candidates one. Applies the same
bounded fresh-runtime retry as fix round 1 for the pre-existing,
unrelated ANN-warm-cache race documented there (handle_recall_candidates
reads the same shared per-model ANN index via the same
collect_recall_candidates path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@oceanwaves630 oceanwaves630 enabled auto-merge (squash) July 9, 2026 04:46

@ohdearquant ohdearquant left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at content level: the effective-token shadowing flows the namespace override uniformly through FTS/vector fetch, ANN over-fetch gate, supersedes read, and serve-ledger stamp; absent param stays byte-identical per ADR-007 Rev 6's escape-hatch model with dispatch Rule-3 doing the primary enforcement and this as defense-in-depth (mirroring handle_remember). The High fix is correct at the root: the ANN index is namespace-global, so the verbose diagnostic had to filter through the same memory_ids visible-namespace set as results — and codex's r2 REJECT for guarding only one site, answered with a mutation-verified second regression, is the review lane doing exactly its job. #750 (or_insert mid-write snapshot race) noted as tonight's fifth defect — good that it's filed as production-correctness, not test-flake. Approved.

@oceanwaves630 oceanwaves630 merged commit c49d9f7 into main Jul 9, 2026
14 checks passed
@oceanwaves630 oceanwaves630 deleted the feat/733-recall-namespace branch July 9, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants