Repository navigation
Just-in-time serving: tool-call triggers, a tighter prompt gate, and a measured useful rate - #134
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e default Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…for both memory kinds Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… latch Adds migration 0020 widening the exposure source CHECK to 'trigger'. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Deferred is the default now and exposes nothing at session start; these tests describe the legacy dump's exposure rows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…act useful-rate denominator - SeenStore.claim: O_CREAT|O_EXCL sentinel per (session, kind, id) so parallel hook processes cannot render the same memory twice - PreToolUse returns before connect/build_backend on non-sqlite backends - useful_rate_by_channel: rated = useful + ignored + misled; overlooked shown in its own column, outside the rate - contextual_suppressed_floor is bumped by the prompt channel only Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🔴 Claude BugBot Analysis
Found 1 potential bug in this PR.
medium: 1
One real defect found: the new atomic claim sentinels are permanent, so memory re-injection after N turns (BETTER_MEMORY_CONTEXT_REINJECT_TURNS > 0) silently stops working. The migrations, trigger matcher, relevance gate and UI changes checked out. Part of the 4313-line diff was only skimmed: the tail is mostly tests and docs, and I read just the code and migration hunks closely.
| @@ -1,25 +1,27 @@ | |||
| """UserPromptSubmit / PreToolUse hook: inject curated memories relevant to the | |||
There was a problem hiding this comment.
🟡 MEDIUM: Permanent per-memory claim sentinel defeats BETTER_MEMORY_CONTEXT_REINJECT_TURNS
The hook now calls seen.claim(...) after filter_unseen. claim creates a context_seen_<session>.<kind>-<id>.claim file with O_CREAT|O_EXCL. The sentinel is never removed or expired, and prune_stale only deletes it after 7 days. filter_unseen still lets a memory through again once (turn - last) > reinject_turns, which is the documented BETTER_MEMORY_CONTEXT_REINJECT_TURNS behaviour. On that second pass claim hits FileExistsError, the memory is counted as lost, and it is dropped from items. So when context_reinject_turns > 0, a memory is served at most once per session and re-injection never happens. Fix: make the claim turn-aware, for example by including the injection epoch (turn // max(reinject_turns, 1)) in the sentinel name, or by removing the stale sentinel when filter_unseen re-admits the memory.
⚠️ Could not locate exact line in the diff. The bug is atbetter_memory/hooks/contextual_inject.py:342.
Additional Locations
better_memory/services/context_seen.py:552—SeenStore.claimwrites sentinels that never expire within a session
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…njection works BugBot (PR #134): claims were permanent, so with BETTER_MEMORY_CONTEXT_REINJECT_TURNS > 0 a memory re-admitted by filter_unseen could never win a second claim. The sentinel name now carries the turn the memory was last marked seen at in the caller's snapshot; parallel callers in one window still share a filename. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Addressed the BugBot finding in |
There was a problem hiding this comment.
🟢 Claude BugBot Analysis
The earlier claim-sentinel bug is fixed. SeenStore.claim now puts the memory's last-seen turn (the epoch) in the sentinel filename, so a memory re-admitted by BETTER_MEMORY_CONTEXT_REINJECT_TURNS gets a fresh sentinel and can win the claim again. I read only the first 868 of the 4,363 diff lines plus context_seen.py, so the rest of the diff was not reviewed for new bugs.
No bugs were detected in this PR.
Summary
Serve memories at the moment they apply instead of dumping them at session start, and make the useful rate measurable per channel.
Spec:
docs/superpowers/specs/2026-10-05-just-in-time-serving-design.md(measurements, design, assumptions). Plan:docs/superpowers/plans/2026-10-05-just-in-time-serving.md.Why. The live ledger showed 12 useful ratings out of 63 rated serves (19%). Replaying the real prompts and tool calls of 11 rated sessions showed that every per-prompt or per-tool keyword gate saturates over a long session, that the session-start dump of general-scope rules accounted for 29 of the 51 ignored ratings, and that every useful rating came from a memory matching the activity of the moment. The prompt hook was also firing on peer-agent messages (571 firings against 261 typed prompts plus 296 peer messages).
What changes.
triggers(tool:<Name>,skill:<name|prefix*>,bash:<text>,path:<text>,write:large), stored in a JSON column on both memory tables (migration 0019). The hook fires on every tool call, matches triggers with a pure matcher (services/triggers.py), renders hits with aTriggered by:line and logs them with exposure sourcetrigger(migration 0020 widens the ledger CHECK). The once-per-session latch is gone: the full path measured 184 ms against 175 ms latched. AnO_CREAT|O_EXCLclaim per (session, kind, id) stops parallel hook processes serving the same memory twice. On non-sqlite backends the path returns before any backend work.<orAnother Claude session sent a message) are skipped. One evidence gate for both memory kinds: at leastBETTER_MEMORY_CONTEXT_MIN_HITS(default 2) distinct prompt tokens that are not ubiquitous in the pool (DF_CAP0.5, applied only when the pool has 4+ memories). Replay: 43% useful at 75% recall against 19% today. Agentcore'srelevance_ranksgate is unchanged.caps.supports_triggers), and atriggersparameter onmemory.semantic_observe/memory.semantic_update.rated = useful + ignored + misled; overlooked shown separately.Deployment note. Hooks run from the checkout, so they pick up the new code at once; the MCP server applies migrations 0019/0020 on its next restart. Until then bootstrap and the prompt channel degrade (logged to
hook_errors) rather than crash.Test plan
test_installs_in_worktreeneeds a global git identity; both pre-existing on this machine).trigger.Deferred minors (from review)
🤖 Generated with Claude Code