feat(memory): deferred task-conditioned injection (flag-gated) + three-leg contextual gate#84
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…latch, all-tools matcher Wires the three-leg evidence-gated scorer (BM25/vector/keyword-fallback) into contextual_inject.py: builds a SyncEmbedder(down_state_file=...) when embeddings_backend=ollama and calls retrieve_relevant with the new conn/sync_embedder/vec_floor signature (no more min_hits). Adds SeenStore.pretool_fired()/mark_pretool_fired() so PreToolUse does real retrieval work only once per session; later PreToolUse events short-circuit via a module-local _SkipInjection sentinel before touching the DB. Widens install_hooks.py's PreToolUse matcher from "Skill|Task|Write" to None (unscoped) since the latch makes an all-tools matcher cheap. Also restores the blank-query short-circuit in retrieve_relevant that the Task 4 rewrite dropped (a contentless query has no evidence by definition), and fixes a SeenStore._load() bug found while testing the latch: it silently dropped the pretool_fired key on every reload because it only carried forward "turn"/"seen". Updates the 5 tests in test_contextual_inject.py that assumed the old keyword min-hits gate (BM25 now matches on token overlap, so test_below_floor_injects_nothing needed genuinely zero-evidence content), adds latch coverage, and fixes the golden hook-shape assertions in test_install_hooks.py (both) and test_setup_sh.py for the matcher change. Also updates test_agentcore_semantic_parity.py's stale min_hits= call, collateral from the Task 4 signature change but outside its declared scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tests/hooks/test_contextual_inject.py's bm_home fixture never pinned BETTER_MEMORY_EMBEDDINGS_BACKEND, so every non-blank-query test relied on config.py's actual default (ollama): the hook built a real SyncEmbedder and issued an HTTP call to localhost:11434 on every run. This worked only because no test seeds embeddings, so a connection-refused/failed embed degrades silently to the qvec=None fallback path with no observable difference — a live Ollama on that port would have gone unnoticed too. Pins BETTER_MEMORY_EMBEDDINGS_BACKEND=sqlite in the fixture, mirroring tests/ui/conftest.py's pin, and corrects the stale comment on test_below_floor_injects_nothing that claimed sqlite was the config default rather than the fixture's pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cy fixes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ktick detection Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sync website/architecture.md, configuration.md, mcp-tools.md with the feat/deferred-injection branch: BETTER_MEMORY_INJECT_MODE (legacy default | deferred bootstrap), the three-leg evidence-gated contextual scorer (BM25 / vector cosine against BETTER_MEMORY_CONTEXT_VEC_FLOOR / keyword fallback), deprecate BETTER_MEMORY_CONTEXT_MIN_HITS, document the PreToolUse-widened-to-all-tools once-per-session latch, the file-persisted Ollama outage cooldown shared across hook processes, and via_exploration exposure tagging excluded from the headline usefulness metric.
…string, import hoist Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔴 Claude BugBot Analysis
Found 1 potential bug in this PR.
medium: 1
Found one confirmed cross-backend logic bug: the new three-leg evidence-gated scorer in services/relevant.py computes a Wilson-score ranking prior assuming times_overlooked/times_ignored fields exist on reflection/semantic records, but the AgentCore backend never populates (or even exposes) those fields for reflections, silently inflating agentcore-backed reflection rankings.
…icts for the Wilson prior Addresses PR #84 review: missing keys made the contextual prior compute a falsely-perfect hit rate on the AgentCore backend. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Claude BugBot Analysis
No new bugs found. The previously-reported Wilson-score bug is fixed: AgentCore's reflection dict now carries a real times_overlooked count and semantic memories fall through to the SemanticMemory dataclass's times_ignored=0 default (both intentional, documented, and consistent with the protocol contract in storage/protocol.py).
No bugs were detected in this PR.
Summary
Implements the deferred-injection design (
docs/superpowers/specs/2026-07-23-deferred-injection-design.md); absorbs the planned PR-C (CLAUDE.md rewrite + drift sentinel).BETTER_MEMORY_INJECT_MODE—legacy(default; today's bootstrap, byte-identical, test-pinned) |deferred(SessionStart injects only general-scope semantic memories + one index line; ~2 exposures/session instead of 7). Flag misparse fails safe to legacy.BETTER_MEMORY_CONTEXT_VEC_FLOOR=0.55 + Wilson prior) behind an evidence gate — a memory injects only with BM25 or vec evidence; the prior ranks but can never qualify alone; silent when nothing qualifies. Keyword fallback covers semantics-without-vectors and agentcore mode.CONTEXT_MIN_HITSdeprecated.SeenStore._loaddropped unknown keys, so any persisted flag silently reset per hook process).SyncEmbedder(hooks are a fresh process per firing — an in-process breaker resets every prompt).via_exploration, migration 0015) — excluded from the headline usefulness metric; conversion rate becomes measurable.Validation
Rollout
Merges with legacy as the live default — bootstrap behaviour unchanged on merge day. Post-merge: env-flag A/B (identical code, only the flag differs per arm, 24 sessions/arm) gates the flip to
deferred; fail-twice = stay legacy.🤖 Generated with Claude Code