Skip to content

fix(v2.11.1): stop the recall feedback loop; recall on what the user typed - #44

Merged
DottytheHomeless merged 2 commits into
mainfrom
fix/recall-feedback-loop
Sep 23, 2026
Merged

DottytheHomeless merged 2 commits into
mainfrom
fix/recall-feedback-loop

Conversation

@MXAntian

@MXAntian MXAntian commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Four fixes from auditing a long-lived store (10k rows, 14 days of recall_log).

What was wrong

# Bug Evidence
1 recallForClients bumped access_count for its whole over-fetched pool (up to 30), not the rows it returned New test on main: 2 returned, 12 bumped
2 freqScore at 0.10 is ~38 rank positions once saturated (20 accesses) — with #1 inflating counts, the ranking fed itself One row reached 46% of two weeks of hook recalls
3 DB_PATH evaluated at import time — a host setting TOKENMEM_DB_PATH after import silently opens the fallback DB (wrong tenant) New test on main: store opened the fallback path
4 prompt-recall hook queried on wrapper text (<task-notification>, <cross-session-message>, <system-reminder>), filtered to meta only, and trimmed before requiring vector evidence 631 of 688 fast-path queries were wrapper text; operational answers are semi_abstract by the meta gate's own rules

Changes

  • index.mjs: _deferAccessBump in recallForClients + bump only returned rows; freqScore 0.02; lazy resolveDbPath().
  • hooks/prompt-recall-trigger.mjs: userPromptText() strips non-user blocks.
  • hooks/prompt-recall.mjs: default level meta_knowledge,semi_abstract; new prefer_vec / --prefer-vec (fail-open require_vec: vector-backed rows when any exist, else plain FTS — one round trip; older servers ignore it); MNEME_MAX_VEC_DISTANCE (0.95) gate when vectors are present.
  • Docs (en/zh) env table, version 2.11.1.

Tests

  • New: access-bump-scope.integration.test.mjs, db-path-late-env.integration.test.mjs (both red on main, green here), userPromptText cases. Wired into CI.
  • Green locally: level-rank-offset, ranking-importance, recall-endpoint, recall-contract, cold-pool-gate, anchor-pinned, injection-hygiene, embedding-timeout, meta-gate, hooks, prompt-recall-trigger, server-version.

Self-review

pr-self-check: no P0. P1 (require-then-retry doubled latency on zero-config installs and could starve the CLI fallback) fixed in the second commit via prefer_vec. P2 (unterminated <system-reminder>) fixed; pasted-tag trade-off documented.

Behaviour change to note

Prompt-recall now injects less often and includes semi_abstract rows by default. Set MNEME_LEVEL=meta_knowledge to restore the old filter.

🤖 Generated with Claude Code

MXAntian and others added 2 commits September 23, 2026 11:25
…typed

Four fixes found auditing a long-lived store (10k rows, 14 days of hook logs).

1. recallForClients bumped access for its whole candidate pool.
   It over-fetches (up to 30 rows when filtering) and trims to the caller's
   limit, but never passed _deferAccessBump — so every hook call added +1 to
   ~30 rows while returning 3. buildMemoryContext got this fix in #7; this path
   was missed. Now only returned rows are bumped.

2. freqScore weight 0.10 -> 0.02.
   It saturates at 20 accesses, so on a long-lived store it is effectively
   binary, and 0.10 is ~38 rank positions at RRF's ~0.0026 spacing. With (1)
   inflating the counts, the loop fed itself: one row reached 46% of two weeks
   of hook recalls. At 0.02 it is a tiebreak (~8 positions max).

3. The DB path is resolved when the store opens, not at import.
   A host that imports mneme and then loads its own env sets TOKENMEM_DB_PATH
   after ES import hoisting has evaluated the module-level const, so the store
   silently opened the fallback DB. With several tenants on one machine, one
   tenant's writes landed in another's store.

4. prompt-recall hook: recall on user-authored text only; default to
   meta+semi; require vector evidence before the trim.
   - UserPromptSubmit also delivers <task-notification>, <cross-session-message>
     and prepended <system-reminder> blocks. 631 of 688 fast-path queries were
     that wrapper text. New userPromptText() strips it.
   - The triggers are operational (paths, ports, restarts), and the write-time
     meta gate downgrades anything carrying those to semi_abstract — so a
     meta-only default could never match the answers the hook is for.
   - Ask for require_vec first so the few slots are not taken by FTS/entity
     matches on a shared common noun; retry without it on zero rows so
     zero-config installs keep plain FTS + the consensus gate. When vectors are
     present, gate on MNEME_MAX_VEC_DISTANCE (0.95) instead of hit count.

Tests: access-bump-scope and db-path-late-env integration tests (both red on
main: 12 rows bumped for 2 returned; store opened the fallback path), plus
userPromptText cases. Wired into CI. Existing ranking / endpoint / contract /
cold-pool / anchor / hygiene / embedding-timeout / meta-gate / hooks suites green.

Co-authored-by: 千夏 <qianxia@clawgamers.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Self-review P1: the zero-row retry made every zero-config install (no
embeddings) pay two sequential HTTP round trips on every trigger, and in the
worst case the pair ate the whole safety timer before the CLI fallback could
run.

prefer_vec / --prefer-vec is the fail-open twin of require_vec: keep only
vector-backed rows when any exist, otherwise return the unfiltered rows — one
call either way. Servers older than 2.11.1 ignore the field and answer as before.

Also: an unterminated <system-reminder> (truncated upstream) is treated as
non-user input instead of surviving whole as the query; the known trade-off of
pasting text that starts with these tags is documented.

Tests: access-bump-scope gains requireVec/preferVec cases on a no-embedding DB
(0 rows vs FTS fallback); userPromptText gains the truncated case.

Co-authored-by: 千夏 <qianxia@clawgamers.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MXAntian
MXAntian marked this pull request as ready for review September 23, 2026 03:31
@DottytheHomeless
DottytheHomeless merged commit 47cdd20 into main Sep 23, 2026
2 checks passed
@DottytheHomeless
DottytheHomeless deleted the fix/recall-feedback-loop branch September 23, 2026 03:41
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