Releases: 0Mattias/bettermemory
v3.10.0
The heuristic-correctness release: the parked 146-finding extractor-hunt
backlog drained end to end, then the drain itself adversarially
re-audited and every confirmed finding fixed. ~150 defects closed across
the durability / credential / groundedness / proposals / scope-match /
origin / audit / consolidate / search heuristics, each with a regression
test (~2,100 → 2,540 tests). No breaking changes; additive wire fields
only.
Fixed
- Credential gate: catastrophic-backtracking (ReDoS) in the new
env-prefix clause — a dense snake_case/kebab paste could hang the
write path for tens of seconds; the prefix repetition is now bounded
(lossless for recall) and pinned by a perf regression test. Plus:
connective separators (is set to/was rotated to), quoted/JSON
keys, env-prefixed keywords, connection-URI passwords,Bearer
tokens,:=/=>separators, a datetime-shape guard, the 200→1024
value cap, and snake_case/kebab identifier false positives. - Durability gate: ~20 transient-marker fixes — fronted/medial
"today", "as of ", future-scheduling ("next week", "tonight"),
in-progress vocabulary, git-state phrases ("unpushed",
"uncommitted changes" with copula anchoring), UUID/hex false
positives on the commit-SHA marker, proper-noun guards ("the New
York office", kanban "In Progress" columns), habitual "temporarily". - Groundedness gate: contraction fragments are no longer universal
anchors; a zero-anchor floor catches short hallucinated claims (with
alias/prefix tolerance — "Neovim" grounds on "nvim"); mid-line
speaker labels no longer donate freebie anchors; dotted
abbreviations, ISO-date forms, sentence-split edge cases. - Proposals extractor: smart-apostrophe (U+2018/U+2019)
normalization; negated-contraction question rejects; markdown-bullet
and past-tense/conditional guards; deictic "remember this" requests
no longer mint contentless proposals; explicit capture requests
dropped by the transient gate now emit an observable WARNING. - Search & ranking: per-term body TF saturation (keyword spam can
no longer outrank full-coverage matches; single-term queries keep
discrimination), body-vs-scope idf split, FTS5 MATCH expressions now
built from the ranker's own tokenization (symbol aliases like
C++/C#/.NETand joined tokens no longer miss index
candidates), dotted-version and NFC/NFD normalization, possessive
fragments, suspended hyphenation, diacritic folding, and an
index-level saturation signal so auto-scope filtering on large
stores can't silently starve to zero hits. - Audit / Stop hook: the retrieval shield, attribution window, and
endorsement tally now share one 600s substrate across all three
producers; a separate 60s creation shield (a memory written this
turn isn't a miss, but ten-minute-old memories are visible again);
event-log rotation no longer hides the turn's own search; the shield
counts same-worktree retrievals regardless of session; semantic-mode
configs record an explicitno_signalinstead of silently crashing
the probe; probes rank with the configured half-life/endorsement
knobs (probe-matches-the-ranker). - Consolidate / health: one shared dead-weight predicate
(freshest-touch window, 2-day endorsement grace,
unresolved-contradiction parking) behindmemory_health,
memory_scope_overview, and the demotion pass; the
from-transcript provenance stamp is stripped before dedup
similarity in both paths (two distinct facts from one transcript
can no longer dedup-tombstone each other); scope-merge and demotion
retags preserve verification attestations; scope-typo detection
unified with health's length-scaled rule;--from-transcriptno
longer mines harness-injected synthetic rows. - Origin matching: remote-name-agnostic capture; Azure DevOps /
Bitbucket/scm// SSH-over-443 / scp-form /git+ssh/
insteadOf/ push-mirror normalization (with a bridge for origins
captured under the old idiom); vendor route-prefix stripping gated
to known hosts so GitLab subgroups can't merge distinct repos;
worktree_root captured for remoteless repos;ingeststamps origin
from session evidence with a collision-safe cwd cross-check.
Added
turn_auditedevents andMissReportcarryno_signal_reason;
memory_healthcarriesno_signal_totalandbettermemory eval
carriesturns_no_signal—no_signalaudits are excluded from the
silent-miss-rate denominator so a probe stuck at "declined" can't
read as a healthy 0% miss rate.memory_curate/consolidatereports carrypolarity_skipped—
near-duplicate pairs the negation guard refused to auto-tombstone,
surfaced suggest-only as possible contradictions.
Docs
docs/api.md, README, tool descriptions, andconfig.tomlprose
updated to the real semantic-mode contract (an installed embeddings
extra alone does not enable semantic participation — the config-level
opt-in does) and the shared dead-weight rule.docs/audit/extractor-hunt-2026-06-09.mdrewritten to drained
status; the JSON remains the archival artifact.
Full changelog: v3.9.0...v3.10.0
v3.9.0
3.9.0 - 2026-06-09
A feature release centered on killing path-drift false signals — the
phantom path_drift_missing flags that made healthy memories read as
stale forever. Driven by live false flags found in the dogfood store and
a 4-round multi-agent hunt (224 agents, 10 heuristic surfaces, every
finding adversarially re-verified with a runnable repro). No breaking
changes; one wire-shape addition.
Added
verified_absent_pathsattestation onmemory_verify. The mirror
axis toverified_paths: body-cited paths you confirm are
intentionally absent on this machine — a remote host's path, a
platform-conditional location (~/.config/...cited for Linux while
running on macOS), a path the body cites precisely because it is NOT
the real one. Path-drift reports them under a new
path_drift.expected_absentbucket instead ofmissing, so the
staleness verdict stops nagging about absences that are the expected
state. Persisted in frontmatter, preserved through scope-only updates,
tombstone/restore, and no-arg verifies; surfaced onmemory_show,
expanded search hits, and the web UI detail view. Extraction
heuristics can't read that context — the attestation layer is where
human/agent judgment lands.
Fixed
- Path extractor: spaced directory segments. Bare
~/Library/Application Support/...citations used to truncate at the
space, and the truncated prefix false-flagged missing on every
retrieval. The bare scan now continues through title-cased spaced
segments that resume with a slash; terminal spaced components it
can't capture safely are dropped when missing rather than flagged
(the flag would be manufactured by our own truncation). Drive and
home anchors now count as directory boundaries, so
C:\Program Files\...and~/Calibre Library/...are extracted;
shell-escaped spaces (My\ Drive) are unescaped. - Path extractor: URL routes. A body citing a domain-attached route
(pypi.org/pypi/bettermemory/<ver>/json) no longer gets same-rooted
absolute candidates (/pypi/bettermemory/json) stat'd as local
files; well-known web filenames (/robots.txt,/openapi.json, …)
are recognized as routes despite their extensions. - Path extractor: the rest of the confirmed hunt findings.
Code-citation line suffixes (file.py:407,:445-461,:12:5)
check the underlying file;@/+/%survive in bare paths
(homebrew kegs, systemd templates);VAR=/pathand--flag=/path
assignments, markdown table cells, and smart-quoted paths are
extracted;$HOME/canonicalizes to~/; balanced trailing)is
kept (project (archived)); glob, template-placeholder
(<app>/{service}), and//host/shareSMB citations are excluded
as shape claims; single-argument commands (/opt/homebrew/bin/brew upgrade) no longer flag; sentence-final citations flag correctly
whilereport (2).pdf-style continuations don't; attested paths
always flag when deleted (verified-then-deleted is real drift);
citation order no longer decides whether drift is reported;~/x
and/Users/me/xspellings dedup to one claim; acronym glue
(/etc/hosts TCP/IP) falls back to the real path. - Credential gate (HIGH): sentence-final periods masked real
secrets.my password is <secret>.was read as a dotted module
reference and waved through; trailing prose punctuation is now
stripped before the guards. Coverage also extended: encrypted-PKCS#8
PEM headers and Slackxapp-/xoxc-/xoxe-token families. - Auto-scope (HIGH): linked-worktree blackout. Sessions running in
agit worktreecheckout (spawned agent worktrees, PR-review trees)
could not see ANY memory written in the primary checkout — the
repo's shared knowledge — because the worktree filter required exact
root equality. A caller in a linked worktree now matches memories
from its primary (derived from the worktree's.gitfile, no
subprocess), and memories recorded in since-deleted worktrees degrade
to repo-level matching instead of being invisible forever. Live
sibling worktrees stay isolated — the original leakage fix is
preserved.
Notes
- The hunt that drove this release hit its round cap still finding
fresh issues; the 146 remaining verified findings are parked with
full detail indocs/audit/extractor-hunt-2026-06-09.{md,json}as a
pre-verified queue for future audit passes.
Full diff: v3.8.0...v3.9.0
v3.8.0
Full Changelog: v3.7.1...v3.8.0
v3.7.1
Full Changelog: v3.7.0...v3.7.1
v3.7.0
Full Changelog: v3.6.5...v3.7.0
v3.6.5
Full Changelog: v3.6.4...v3.6.5
v3.6.4
Full Changelog: v3.6.1...v3.6.4
v3.6.1
Full Changelog: v3.6.0...v3.6.1
v3.6.0
Full Changelog: v3.5.0...v3.6.0
v3.5.0
3.5.0 - 2026-06-01
A correctness + robustness release from a whole-codebase audit sweep of the
post-3.4.2 tree: 17 fixes across the store, episodic, telemetry, verification,
consolidation, web, and CLI surfaces — including two that could lose or hide
data. The minor bump reflects one behavioral change: episode_search now
scopes its bare discovery walk to the caller's git worktree by default (new
auto_scope parameter). Explicit swarm_id / parent_session_id reads are
unaffected.
Fixed
-
memory_show— a corrupt or version-newer index no longer crashes the
read._links_payloadcalledindex.links_for_with_statuswith no
exception guard, so a torn/truncated.index.sqlite
(sqlite3.DatabaseError) or an on-diskschema_versionnewer than the
reader (IndexVersionError) propagated out of the bare-registered
memory_showtool and failed every id untilreindex— even though the
canonical.mdfiles were intact andmemory_searchalready degrades
gracefully via the tolerantindex.status().memory_shownow catches both
and falls back to theload_allreverse-link scan. -
memory_update— an over-cap links list silently lost the whole record.
model_copy(update=...)skips field validators, so amemory_updatewith
more than 64 links committed asstatus="committed"but then vanished from
every read surface: load-time re-validation hit the 64-link cap and the
record was skipped. The cap is now enforced on the update path, matching the
existingscopes/verified_*guards against the same bypass. -
episode_search— the most-recent-N window sorts by datetime, not the ISO
string.datetime.isoformat()omits fractional seconds at microsecond 0,
so a whole-second / bare-date episode mis-sorted as the newest of its second
and could drop a genuinely-newer episode from the cap (and surface a
non-chronological order). -
episode_handoff— an invalid explicitprior_session_iddegrades to an
empty result instead of raising a rawValueErroron the iteration-entry
hot path, matching the auto-resolution branch andepisode_search. -
Telemetry double-count of hook-attributed retrievals. A Stop-hook
appliedevent (written under the Claude Code transcript id) was not
deduplicated against the in-process auto-commit (keyed on the server
sess_<hex>id), so every hook-attributed retrieval was counted twice —
inflatingmemory_helped_rate, the dead-weight / cold-endorsement split, and
the explicit-vs-auto ratio. The dedup now bridges both id spaces. -
cold_endorsementvsdead_weightoverlap. A never-applied memory past
the retrieval floor was counted in both buckets (and routed to
acknowledge-debtwhen it belonged on the removal path). The
cold-endorsement bucket is now gated on "at least one apply happened" across
all three surfaces (compute_health,curation_counts,eval). -
Episode prune — the lock-sidecar unlink moved past
flockrelease.
prune_old_sessionsunlinked the.session-<id>.locksidecar inside the
flockblock — the same Windows open-handle class as the 3.4.2 store fix,
dead on Windows and masked only by the post-loop orphan sweep. It is now
deferred to after the lock releases. -
Commit-drift verdict unified across surfaces.
memory_showcounted drift
viagit rev-list --since(committer date, inclusive whole-second) while
memory_searchandmemory_healthbisect over author timestamps
(strictly-greater, microsecond). The same memory could read drifted on one
surface and clean on another — after a rebase, and with zero rebases when
last_verified_atlanded in a commit's UTC second. All four sites now share
the author-date +bisect_rightpath with an identicalcount > 0
verified-paths narrowing guard, resolving a long-parked divergence. -
consolidatedemotion reads the full event history. The demotion /
cold-scope passes read only the active event log, so after log rotation a
genuinely endorsed fact whoseappliedevents had aged into a.gzarchive
was auto-demotedfact → ambient(including on the unattended
auto-consolidate path). They now read the rotated archives too, matching
memory_health's canonical rule. -
Durability gate — decimal numbers are no longer flagged as commit SHAs.
The transient-marker regex matched any 7–40 character run of[0-9a-f],
including pure decimals, so a durable fact mentioning an epoch / phone number
/ numeric id was wrongly rejected as transient. A SHA match now requires at
least onea–fhex letter. -
Web UI — the memory detail page flags stale verification. A memory
verified long outside the staleness window rendered identically to one
verified today; the detail page now shows a stale cue, matching the rest of
the curation surface. -
_frontmatterdump — alias expansion is bounded before materializing.
dumps()disabled YAML aliases, so crafted nested-alias frontmatter (via
sync pullor a hand-edit) expanded to hundreds of MB on a re-dump before
the size cap (checked post-hoc) could reject it — a CPU/memory DoS under the
per-file lock. Expansion is now bounded up front. -
migrate origin— one failing write no longer aborts the directory. The
per-file write was outside the try/except the read already had, so a single
ENOSPC/EACCESaborted the loop and over-countedupdated. Failures are
recorded and skipped; the count reflects what actually persisted. -
CLI —
tombstones list --scope <bad>gives a clean error. It raised a
raw traceback leaking internal paths; it now exits 2 with an argparse-style
message, like the siblingexportcommand. -
memory_rename_scope— disk errors surface as a structured error. A
genuineOSErrormid-rename leaked raw through the MCP boundary (unlike the
remove/restore/update/verifyhandlers); it is now wrapped in a
cleanValueError.
Changed
episode_searchscopes to the caller's git worktree by default. The bare
discovery walk (noswarm_id/parent_session_id) now drops episodes from
a different worktree of the same repository sharing one memory root —
mirroringmemory_search's auto-scope and the isolationepisode_handoff
enforces. Explicitswarm_id(the N:1 swarm fan-in) andparent_session_id
lookups are deliberate cross-tree reads and are never worktree-filtered.
Added
episode_search(auto_scope=...)(defaultTrue) — setFalseto sweep
the bare discovery walk across every worktree sharing the memory root.
Full diff: v3.4.2...v3.5.0