Skip to content

Releases: 0Mattias/bettermemory

v3.10.0

10 Jun 23:53

Choose a tag to compare

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#/.NET and 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 explicit no_signal instead 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) behind memory_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-transcript no
    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; ingest stamps origin
    from session evidence with a collision-safe cwd cross-check.

Added

  • turn_audited events and MissReport carry no_signal_reason;
    memory_health carries no_signal_total and bettermemory eval
    carries turns_no_signalno_signal audits 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 / consolidate reports carry polarity_skipped
    near-duplicate pairs the negation guard refused to auto-tombstone,
    surfaced suggest-only as possible contradictions.

Docs

  • docs/api.md, README, tool descriptions, and config.toml prose
    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.md rewritten to drained
    status; the JSON remains the archival artifact.

Full changelog: v3.9.0...v3.10.0

v3.9.0

10 Jun 03:37

Choose a tag to compare

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_paths attestation on memory_verify. The mirror
    axis to verified_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_absent bucket instead of missing, 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 on memory_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=/path and --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/share SMB citations are excluded
    as shape claims; single-argument commands (/opt/homebrew/bin/brew upgrade) no longer flag; sentence-final citations flag correctly
    while report (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/x spellings 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 Slack xapp-/xoxc-/xoxe- token families.
  • Auto-scope (HIGH): linked-worktree blackout. Sessions running in
    a git worktree checkout (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 .git file, 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 in docs/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

09 Jun 19:48

Choose a tag to compare

Full Changelog: v3.7.1...v3.8.0

v3.7.1

09 Jun 19:49

Choose a tag to compare

Full Changelog: v3.7.0...v3.7.1

v3.7.0

09 Jun 07:40

Choose a tag to compare

Full Changelog: v3.6.5...v3.7.0

v3.6.5

08 Jun 10:01

Choose a tag to compare

Full Changelog: v3.6.4...v3.6.5

v3.6.4

07 Jun 01:09

Choose a tag to compare

Full Changelog: v3.6.1...v3.6.4

v3.6.1

06 Jun 01:08

Choose a tag to compare

Full Changelog: v3.6.0...v3.6.1

v3.6.0

04 Jun 10:32

Choose a tag to compare

Full Changelog: v3.5.0...v3.6.0

v3.5.0

02 Jun 00:58

Choose a tag to compare

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_payload called index.links_for_with_status with no
    exception guard, so a torn/truncated .index.sqlite
    (sqlite3.DatabaseError) or an on-disk schema_version newer than the
    reader (IndexVersionError) propagated out of the bare-registered
    memory_show tool and failed every id until reindex — even though the
    canonical .md files were intact and memory_search already degrades
    gracefully via the tolerant index.status(). memory_show now catches both
    and falls back to the load_all reverse-link scan.

  • memory_update — an over-cap links list silently lost the whole record.
    model_copy(update=...) skips field validators, so a memory_update with
    more than 64 links committed as status="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
    existing scopes / 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 explicit prior_session_id degrades to an
    empty result
    instead of raising a raw ValueError on the iteration-entry
    hot path, matching the auto-resolution branch and episode_search.

  • Telemetry double-count of hook-attributed retrievals. A Stop-hook
    applied event (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 —
    inflating memory_helped_rate, the dead-weight / cold-endorsement split, and
    the explicit-vs-auto ratio. The dedup now bridges both id spaces.

  • cold_endorsement vs dead_weight overlap. A never-applied memory past
    the retrieval floor was counted in both buckets (and routed to
    acknowledge-debt when 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 flock release.
    prune_old_sessions unlinked the .session-<id>.lock sidecar inside the
    flock block — 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_show counted drift
    via git rev-list --since (committer date, inclusive whole-second) while
    memory_search and memory_health bisect 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_at landed in a commit's UTC second. All four sites now share
    the author-date + bisect_right path with an identical count > 0
    verified-paths narrowing guard, resolving a long-parked divergence.

  • consolidate demotion reads the full event history. The demotion /
    cold-scope passes read only the active event log, so after log rotation a
    genuinely endorsed fact whose applied events had aged into a .gz archive
    was auto-demoted fact → 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 one a–f hex 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.

  • _frontmatter dump — alias expansion is bounded before materializing.
    dumps() disabled YAML aliases, so crafted nested-alias frontmatter (via
    sync pull or 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 / EACCES aborted the loop and over-counted updated. 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 sibling export command.

  • memory_rename_scope — disk errors surface as a structured error. A
    genuine OSError mid-rename leaked raw through the MCP boundary (unlike the
    remove / restore / update / verify handlers); it is now wrapped in a
    clean ValueError.

Changed

  • episode_search scopes to the caller's git worktree by default. The bare
    discovery walk (no swarm_id / parent_session_id) now drops episodes from
    a different worktree of the same repository sharing one memory root —
    mirroring memory_search's auto-scope and the isolation episode_handoff
    enforces. Explicit swarm_id (the N:1 swarm fan-in) and parent_session_id
    lookups are deliberate cross-tree reads and are never worktree-filtered.

Added

  • episode_search(auto_scope=...) (default True) — set False to sweep
    the bare discovery walk across every worktree sharing the memory root.

Full diff: v3.4.2...v3.5.0