fix(hooks): resolve AgentDB memory state newest-wins across every candidate store - #67
Conversation
|
@markt-heximal is attempting to deploy a commit to the stuart kerr's projects Team on Vercel. A member of the Team first needs to authorize it. |
f9cfd23 to
2bfd5ff
Compare
…didate store The memory-detection block resolved the store relative to the hook's cwd, which is whatever directory the session happens to be working in rather than the ruflo root. Two false positives followed, both on a healthy install: 1. A store under $HOME/.swarm/ was never consulted. Only cwd and the primary git worktree were checked, so any project subdir without a .swarm/ of its own reported "persistent project memory is NOT set up — decisions are being lost" and instructed the model to offer to enable memory that was already enabled and being written. RUFLO_STATE trips on .claude-flow/ alone, which such dirs often have, so the gate and the miss combined into a confident false negative. 2. A stale local store ended the search. check_memory_db() mutated MEM_STATE to "idle" on mere existence and the fallback was gated on MEM_STATE = "off", so a stale local .swarm/ shadowed a fresher store elsewhere and the "hooks look miswired" nag fired while writes were landing fine. Restructured to do what the comment above the block already promised — consider every store and take the newest — rather than first-match-wins: - accumulate MEM_FOUND / MEM_FRESH across the whole candidate set instead of mutating MEM_STATE per candidate, and resolve state once at the end - add $HOME as a third candidate location - keep a short-circuit on FRESH, which is sound because "on" is terminal and no later candidate can beat it, where short-circuiting on FOUND was not. The healthy common case therefore costs what it did before: one stat, one find, and no git fork Taking the newest needs no mtime arithmetic: over a candidate set, "the newest is fresh" is equivalent to "any is fresh" and "the newest exists" to "any exists". Verified against a real install and with synthetic $HOME fixtures. Outcomes are identical to gating the old first-match structure on != "on" — the gain is that the code now expresses newest-wins directly, so a future change to the helper's early return cannot silently reintroduce shadowing. Both true positives still fire: no store anywhere warns "NOT set up", and a store that exists but is genuinely >90 min stale warns about idleness. sh -n and bash -n clean; POSIX only.
2bfd5ff to
cfc998b
Compare
|
Revised to the newest-wins approach I raised as the open question, and rebased onto Rather than gating the existing first-match structure, the block now accumulates One thing I should be straight about: measured outcomes are identical to the three-line version I first pushed. I verified both against the same fixtures. The gain is structural, not behavioural — a future change to Confirmed the bug is still present on |
Fixes #66.
Two false positives in the memory-detection block of
plugin/scripts/ground-ruvnet.sh, both ofwhich fire on a healthy install, and both traceable to the store being resolved relative to the
hook's cwd — which is whatever directory the session happens to be working in, not the ruflo root.
1. A store under
$HOME/.swarm/was never consultedCandidates at L121 are relative, and the only fallback was the primary git worktree. So any
project subdirectory without its own
.swarm/reported:…and instructed the model to offer to turn on memory that was already on and actively being
written.
RUFLO_STATE(L93–94) trips on the presence of.claude-flow/alone, which suchdirectories often have, so the gate and the miss combine into a confident false negative. Acting
on the advice risks initialising a redundant second store.
2. A stale local store ended the search
check_memory_db()mutatedMEM_STATEtoidleon mere existence, and the fallback was gated onMEM_STATE = "off"— so a stale local.swarm/shadowed a fresher store elsewhere and the "yourmemory isn't capturing this session — the hooks look miswired" text fired while writes were landing
fine.
Approach: newest-wins
Rather than patch the gate, this does what the comment above the block already promises (L107):
MEM_FOUND/MEM_FRESHacross the whole candidate set instead of mutatingMEM_STATEper candidate, then resolve state once at the end.$HOMEas a third candidate location.onis terminal — no later candidatecan beat it — where short-circuiting on FOUND was not. The healthy common case therefore
costs exactly what it did before: one stat, one
find, and nogitfork. That matters on a hookthat runs every prompt inside a 5s budget.
Taking the newest needs no mtime arithmetic, because over a candidate set the two questions the
states depend on distribute: "the newest is fresh" ≡ "any is fresh", and "the newest exists" ≡
"any exists".
No change to the warning text, the 90-minute threshold, the WAL handling, or the relative
precedence of the pre-existing candidates.
Verification
Synthetic
$HOMEfixtures, plus a real install whose store lives at$HOME/.swarm/:.swarm/+ fresh$HOMEstore$HOME$HOME$HOME, store present locally.swarm//tmpsh -nandbash -nclean; the added code is POSIX with no bashisms, consistent with the rest ofthe script.
Honest note on scope: measured outcomes are identical to simply gating the old first-match
structure on
!= "on"(I verified both against the table above). The gain here is not differentbehaviour — it is that the code now expresses newest-wins directly, so a future change to
check_memory_db()'s early return cannot silently reintroduce shadowing, and the state machine isreadable in one place. If you'd rather have the three-line gate fix instead, I'm happy to reduce it.
🤖 Generated with Claude Code