Repository navigation
feat(v2.6): write-gate + scarcity quota + optional auto-recall hooks - #1
Merged
Merged
Conversation
Motivated by Ombre-Brain competitive research plus a real snapshot of an
active mneme DB: 71% of memories were memory_level=meta_knowledge and 61%
had importance>=8 — the "default semi_abstract" knob wasn't enough on its
own; we needed write-time enforcement and hard quotas.
Anti-inflation (v2.5 -> v2.6, migration 008)
* meta-gate.mjs (new): write-time gate. store_memory with meta_knowledge
whose content has concrete bindings — ISO date, mem-rowid ref, commit
hash, absolute path, version string, project or multi-person names —
auto-downgrades to semi_abstract. Signal words like "cross-project" or
"heuristic" exempt soft bindings (project/person) but never hard ones.
* migrations/008-add-anchor-pinned.sql: is_anchor (cap 40) / is_pinned
(cap 30) boolean columns + partial indices. Scarcity forces callers to
trade off instead of inflating importance to 9-10 for everything.
* index.mjs storeMemory: adds an opts parameter that carries downgrade
and quota signals back to the caller via opts.out. Quota check on
is_anchor/is_pinned; over-quota resets the flag to 0 and pushes
opts.out.quotaRejected up for the MCP layer to surface. Migration 008
is inlined into initMemory alongside the earlier migrations, so
upgrading a live DB is transparent.
* mcp-server.mjs store_memory: tool description now explains the gate,
clarifies importance is a display-only weak prior (not fed into decay
or auto-forget), exposes is_anchor / is_pinned params, and surfaces
both metaDowngrade and quotaRejected in the response body.
* scripts/anchor-pinned-candidates.mjs: dry-run helper to pick migration
candidates from an existing DB by (access_count DESC, importance DESC).
Optional Claude Code auto-recall hooks (new hooks/ directory)
* hooks/prompt-recall.mjs: UserPromptSubmit hook. Fires on generic
"how do I / where is / config / daemon" triggers (EN + ZH), injects top
hits with importance >= 6 via hookSpecificOutput.additionalContext.
Fully env-var configurable (MNEME_DB_PATH / MNEME_MIN_IMPORTANCE / ...).
* hooks/tool-recall-pre.mjs: PreToolUse hook for Bash | Grep | Read | Glob.
Extracts a short query from the tool args (command head, grep pattern,
file basename, glob stem) and surfaces relevant memories before the
tool runs. Fail-soft: any error or timeout exits 0 silently.
* hooks/hooks.test.mjs: 9/9 end-to-end tests covering trigger, no-trigger,
garbage stdin, glob-pure-pattern skip, unknown tool skip.
* docs/configuring-your-agent.{md,zh-CN.md} section 5: wiring examples,
PreToolUse matcher reference, env-var table, opt-in debug patterns
(stderr echo, jsonl trace), troubleshooting.
Bump package version to 2.6.0.
[prediction] fix: writing-side enforcement + hard quotas should compress
the current 71% meta / 61% imp>=8 skew starting from the point of ingest.
[prediction] risk: (1) meta-gate may over-downgrade domain-specific
jargon — signal-word exemption exists but user has to know about it;
(2) hook trigger set is generic EN+ZH — if the user's prompts use
domain jargon, they'll see fewer injections and need to fork TRIGGERS;
(3) 40/30 quotas are opinionated — future data may show they're too
tight, in which case bumping them is a one-line change.
[prediction] verify: rerun the same stats query a month from now — new
memory_level=meta should be < 40% (currently 71%), new importance>=9
should be < 10% (currently 16.4%); hook e2e 9/9 passes locally.
Co-Authored-By: 千夏 <chinatsu@clawgamers.com>
… scope Address 6 findings from the independent review on #1: P0 * meta-gate.mjs: PROJECT_NAMES and PERSON_NAMES no longer ship any hardcoded team roster or product names. Both default to empty and load from MNEME_META_GATE_PROJECT_NAMES / MNEME_META_GATE_PERSON_NAMES env vars (comma-separated). Signal words also load from MNEME_META_GATE_SIGNAL_WORDS with a small EN+ZH default set. * meta-gate.mjs commit_hash regex: /\b[a-f0-9]{7,40}\b/ over-matched pure digits (port 8080000, seven-digit counters). Now requires at least one [a-f] via a lookahead: /\b(?=[a-f0-9]*[a-f])[a-f0-9]{7,40}\b/. * index.mjs storeMemory: is_anchor / is_pinned COUNT check moved INSIDE the INSERT transaction so two concurrent writers can't both pass a full cap and produce a 41st anchor. quotaRejected is populated in the tx and surfaced on opts.out after commit. * index.mjs migration 008: bare `catch {}` around ALTER TABLE was swallowing disk-full, permission, and WAL-lock errors. Narrowed to only swallow "duplicate column name" (the second-run replay case). The CREATE INDEX IF NOT EXISTS calls are naturally idempotent and no longer need a catch at all. P1 * docs/configuring-your-agent.{md,zh-CN.md}: env-var table added MNEME_TOOL_QUERY_LEN (was exposed in tool-recall-pre.mjs but missing from docs). * hooks/hooks.test.mjs: swapped E:/Project/chinatsu-workspace/... test fixture paths for relative ones (mcp-server.mjs, scripts/...). Regenerated meta-gate.test.mjs to use dynamic import + env-configured vocabs so the multi-person case still exercises soft bindings. 13/13 unit cases pass; 4/4 anchor-pinned integration cases pass (including the 41-row over-quota rejection); 9/9 hook e2e cases pass. [prediction] fix: closes the leak vector (soft-binding vocabs) that let team names into a public MIT repo AND closes the false-positive commit-hash match on pure-digit content. [prediction] risk: (1) callers who relied on the old defaults will see fewer downgrades until they set the env vars — I'll add those to chinatsu-workspace/.env.local separately; (2) the tx-internal COUNT adds one query per is_anchor/is_pinned store, but only when the flag is requested. [prediction] verify: rerun meta-gate.test.mjs (13/13) and anchor-pinned.integration.test.mjs (4/4) — see the case4 log showing `41/40` rejection from inside the transaction. Co-Authored-By: 千夏 <chinatsu@clawgamers.com>
MXAntian
marked this pull request as ready for review
July 9, 2026 14:09
Second-round independent review (DeepSeek + Codex, focus complementary to
the first round). 8 findings addressed:
P1
* scripts/anchor-pinned-candidates.mjs: DB path was hardcoded to the
repo-relative engram.db, ignoring TOKENMEM_DB_PATH. Now uses the same
resolution order as index.mjs — env override → existing engram.db →
fresh-install tokenmem.db default — and also accepts a --db=<path> flag.
* meta-gate.mjs: abs_path hard binding was Windows-only. `/etc/mneme/config`
and `/home/user/foo.js` never matched, so POSIX users silently kept
meta_knowledge on path-bound content. Added a POSIX branch (leading /,
≥ 2 path segments); Windows drive-letter branch preserved.
P2
* hooks/{prompt-recall,tool-recall-pre}.mjs: parseInt was unchecked. A bad
env like MNEME_TIMEOUT_MS=abc yielded NaN, which Node's setTimeout maps
to `1ms` and prints TimeoutNaNWarning to stderr — violating the "silent
fail-soft" contract. Introduced intEnv() using Number.isFinite; bad
values silently fall back to defaults.
* hooks/tool-recall-pre.mjs extractQuery: the Read case only stripped the
last extension, so foo.test.mjs → foo.test kept noise tokens in the
recall query. Now strips all trailing extensions.
* hooks/tool-recall-pre.mjs extractQuery: the Glob stem length floor was
≥ 5, which rejected useful short patterns like src/**/*.ts. Lowered to
≥ 3 with a letter requirement so short dir stems (src, lib, api) pass
while pure short ext stems (js, ts) don't.
* hooks/{prompt-recall,tool-recall-pre}.mjs saveInjected: the dedup state
file was written with a single writeFileSync — a kill mid-write left
truncated JSON, and the next load lost all dedup for the session. Now
writes to a per-pid `.tmp-<pid>` then rename() — atomic, previous state
survives crashes.
* Public-repo hygiene: cleaned residual `千夏` / `chinatsu-workspace` /
`[2026-05-29 千夏]` attributions in comments across index.mjs,
mcp-server.mjs, and hooks.test.mjs. Comments now describe the mechanism
neutrally.
* docs/configuring-your-agent.{md,zh-CN.md}: added § 4b documenting
MNEME_META_GATE_PROJECT_NAMES / PERSON_NAMES / SIGNAL_WORDS. Previously
only mentioned in the meta-gate.mjs source header — public users would
never discover soft matching without reading the code.
Tests
* meta-gate 14/14 (added POSIX abs_path case)
* anchor-pinned integration 4/4
* hooks e2e 10/10 (split glob-pure-pattern into short-stem-skip and
dir-stem-fire cases)
* Manual probe: MNEME_TIMEOUT_MS=abc + prompt trigger → exit 0, no
TimeoutNaNWarning on stderr, hook injects normally.
[prediction] fix: v2.6 write-gate now behaves consistently across POSIX
and Windows for absolute paths, the migration helper obeys env-set
DB paths, hooks are truly silent on bad env, and the public repo no
longer carries private-workspace attribution.
[prediction] risk: (1) POSIX abs_path regex may match phrases where a
noun happens to be `/foo/bar` inside prose — worst case is unnecessary
downgrade, not data loss; (2) stem length ≥ 3 accepts short ext-like
patterns (mjs, mid, cpp) as queries — cheap recall, low harm.
[prediction] verify: meta-gate.test.mjs 14/14, hooks.test.mjs 10/10, and
`MNEME_TIMEOUT_MS=abc echo '...' | node hooks/prompt-recall.mjs 2>&1`
shows no NaN warning.
Co-Authored-By: 千夏 <chinatsu@clawgamers.com>
Runs on push to main and every PR. Matrix: Node 20 and 22 on ubuntu-latest.
Stages
* npm install — verifies package.json deps resolve + native better-sqlite3
builds on the runner (no lockfile in-repo, so `install` not `ci`).
* Syntax check — `node --check` on every .mjs (excluding node_modules).
Catches typos and broken imports before test binaries even start.
* Unit — meta-gate.test.mjs (pure JS, no DB, 14 cases including the POSIX
abs_path case added in the round-2 fix).
* Integration — meta-gate + anchor-pinned quota + hooks e2e, each pointed
at a per-run temp DB via TOKENMEM_DB_PATH (${runner.temp}).
Test surface adjustments
* anchor-pinned.integration.test.mjs now resolves DB_PATH the same way
index.mjs does (env → repo engram.db → tokenmem.db fallback) so
TOKENMEM_DB_PATH lets CI point it at a fresh temp DB.
* Same file now pads the memories table with 40 raw-INSERT rows when the
fresh DB doesn't have enough to trip the quota — the case-4 assertion
needed 40 existing rows to flip to anchor=1, and a fresh DB only has
the 3 rows from cases 1-3.
* Both write connections optionally loadExtension('simple') so a machine
that DID load libsimple during initMemory (which then migrates
memories_fts to tokenize='simple 0') can still INSERT via a fresh
connection without hitting "no such tokenizer: simple". CI on ubuntu
never has the .so and stays on unicode61 — the loadExtension is
gated on existsSync.
Verified locally on both configurations
* With lib/libsimple-windows-x64/ present → 4/4 anchor-pinned pass
* With lib/ renamed away → 4/4 anchor-pinned pass (unicode61 fallback)
[prediction] fix: mneme now has automated regression protection instead
of relying on reviewers to remember to run the tests.
[prediction] risk: (1) better-sqlite3 needs a native build on the runner
— expected ~30s on ubuntu with cached apt tools; (2) node 20/22 matrix
catches ABI drift up front but doubles CI minutes.
[prediction] verify: check the Actions tab on
https://github.com/AgentGameLab/mneme/actions after this push — expect
both matrix cells green within ~2 minutes.
Co-Authored-By: 千夏 <chinatsu@clawgamers.com>
DottytheHomeless
added a commit
that referenced
this pull request
Aug 3, 2026
The conversations table and the recall conversation-fallback shipped a while back, but nothing fed them — the table sat at 0 rows, so the verbatim fallback layer effectively did not exist. What kept it broken was structural, not a bug: the only call site of indexSessionTranscripts() lived in one contributor's private harness repo, so fixing the engine reached nobody else. Shipping the sweep next to the engine closes that gap — clone mneme and the ingest is already there, no per-machine path wiring. Log path is overridable via MNEME_SWEEP_LOG (use "-" for stderr only); logs/ is gitignored. Verified on merged main: files=1494 rescanned=4 skipped=669 newMessages=22 in 0.4s. Follow-up to #18 review caveat #1. Co-authored-by: 小梦 <xiaomeng@clawgamers.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
meta_knowledge(meta-gate.mjs): content with concrete bindings (ISO date, mem-rowid ref, commit hash, absolute path, version, project/multi-person names) auto-downgrades tosemi_abstract. Signal words like cross-project / heuristic exempt soft bindings but never hard ones.is_anchor(cap 40) +is_pinned(cap 30) boolean columns with partial indices. Hard quotas force callers to trade off instead of inflatingimportanceto 9-10 for everything.hooks/dir):prompt-recall.mjs(UserPromptSubmit) +tool-recall-pre.mjs(PreToolUse for Bash/Grep/Read/Glob). Fail-soft, env-var configured, session-scoped dedup.metaDowngrade+quotaRejectedalongsidenearDuplicates.Why
A real snapshot of an active mneme DB measured:
The v2.4 "default `semi_abstract`" knob only affected the tool parameter default — self-discipline on wording didn't hold. This PR moves enforcement from documentation to code: gate at write, quota at write, both detection-only (never reject the store — same philosophy as the v2.4 near-dup gate).
Inspired by Ombre-Brain's scarcity-as-structure design. We kept the write-gate mechanical instead of borrowing Ombre's Russell emotional coordinate (cost-heavy for private single-user use, cross-purposes with our query-driven RRF recall).
Migration path
Test plan
Predictions