Skip to content

fix(hooks): resolve AgentDB memory state newest-wins across every candidate store - #67

Open
markt-heximal wants to merge 1 commit into
stuinfla:mainfrom
markt-heximal:fix/memory-detection-false-positives
Open

fix(hooks): resolve AgentDB memory state newest-wins across every candidate store#67
markt-heximal wants to merge 1 commit into
stuinfla:mainfrom
markt-heximal:fix/memory-detection-false-positives

Conversation

@markt-heximal

@markt-heximal markt-heximal commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #66.

Two false positives in the memory-detection block of plugin/scripts/ground-ruvnet.sh, both of
which 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 consulted

Candidates at L121 are relative, and the only fallback was the primary git worktree. So any
project subdirectory without its own .swarm/ reported:

AgentDB persistent project memory is NOT set up (.swarm/memory.db does not exist) — decisions
made here are being lost between sessions

…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 such
directories 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() 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 "your
memory 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):

# So: consider every store, and each one's -wal, and take the newest.
  • Accumulate MEM_FOUND / MEM_FRESH across the whole candidate set instead of mutating
    MEM_STATE per candidate, then 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 — no later candidate
    can 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 no git fork. That matters on a hook
    that 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 $HOME fixtures, plus a real install whose store lives at $HOME/.swarm/:

scenario expected result
stale local .swarm/ + fresh $HOME store silent — newest wins silent ✅
fresh local + stale $HOME silent silent ✅
both stale "over 90 minutes" fires ✅
no store anywhere, ruflo stack present "NOT set up" fires ✅
no local store, fresh $HOME silent silent ✅ (previously "NOT set up")
cwd $HOME, store present locally silent silent ✅
cwd a project with its own stale .swarm/ silent silent ✅ (previously "miswired")
cwd /tmp silent silent ✅

sh -n and bash -n clean; the added code is POSIX with no bashisms, consistent with the rest of
the 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 different
behaviour — 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 is
readable in one place. If you'd rather have the three-line gate fix instead, I'm happy to reduce it.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

@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.

@markt-heximal
markt-heximal force-pushed the fix/memory-detection-false-positives branch from f9cfd23 to 2bfd5ff Compare July 30, 2026 13:16
@markt-heximal markt-heximal changed the title fix(hooks): stop ground-ruvnet reporting healthy AgentDB memory as absent fix(hooks): resolve AgentDB memory state newest-wins across every candidate store Jul 30, 2026
…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.
@markt-heximal
markt-heximal force-pushed the fix/memory-detection-false-positives branch from 2bfd5ff to cfc998b Compare July 30, 2026 13:18
@markt-heximal

Copy link
Copy Markdown
Contributor Author

Revised to the newest-wins approach I raised as the open question, and rebased onto db94e59 (v4.0.1) — force-pushed, so the diff is still one commit.

Rather than gating the existing first-match structure, the block now accumulates MEM_FOUND / MEM_FRESH across the whole candidate set and resolves state once, which is what the L107 comment describes. The short-circuit is kept but moved to FRESH rather than FOUND: on is terminal so no later candidate can beat it, which means the healthy common case still costs one stat, one find, and no git fork — worth preserving on a hook that runs every prompt inside a 5s budget.

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 check_memory_db()'s early return can no longer silently reintroduce shadowing. If you prefer the minimal gate fix for reviewability, say so and I'll reduce it.

Confirmed the bug is still present on db94e59 before rebasing, so this is still needed.

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.

ground-ruvnet.sh: memory-detection false positives — cwd-relative store lookup misses $HOME/.swarm/, and a stale local store shadows a fresh one

1 participant