Skip to content

Releases: memtomem/memtomem

v0.1.36 — Windows MCP server + tags service + security guards + transactional uninstall

06 May 07:54

Choose a tag to compare

Headline: memtomem-server (the MCP server) now runs natively on Windows. Pre-#818 the two fcntl.flock callsites in server/__init__.py crashed memtomem-server on Windows even though the mm CLI was Windows-clean from #652 / 0.1.34 — Windows MCP hosts (Claude Desktop / Code) had to go through WSL. v0.1.36 closes that gap (#817/#818/#819/#820).

Added

  • Windows MCP server target (#817/#818). fcntl.flock swapped onto portalocker at the legacy-flock probe + XDG-runtime pid lock; SIGTERM handler POSIX-gated (Windows teardown is stdin-EOF + atexit); pid lock fd closed before unlinking on Windows (NTFS refuses to delete an open handle, WinError 32). Pid file lands at %LOCALAPPDATA%\Temp\memtomem-0\server.pid; ~/.memtomem/.server.pid intentionally never created on Windows.
  • mm index streams discovery + chunking progress via click.progressbar (#748, closes #656).
  • Tags rename / delete / merge service on Web + MCP (mem_tag_rename / mem_tag_delete / mem_tag_list) — #688 PR1, #795.
  • JS unit-test layer for static modules (vitest + jsdom, closes #641) — new js-unit CI job.
  • CSRF / Origin / Host guard middleware (observe-only mode, #787 stage 1, #793).
  • Privacy redaction at the LTM trust boundary across every user-driven write surface (#784, #789, #802).
  • Browser test harness for tag-filter mutation sites (closes #751).

Changed

  • Context Gateway editor: 3-button conflict dialog on 409 mtime mismatch (closes #763).
  • Tag pill click: no longer overwrites search query (closes #672); now runs tag-only search on a fresh session (closes #750).
  • Settings Hooks sync surface promoted to prod web UI (refs #761).

Fixed

  • mm uninstall deletion is transactional via stage-then-rmtree (closes #757).
  • mm uninstall --force refuses cleanly on Windows when a writer is alive (closes #730).
  • Windows runtime dir resolution is NTFS-aware (closes #637).
  • Windows pytest cleanup of components fixture (closes #206) — ORT-backed close() forces gc.collect().
  • Concurrent-merge abort test forces distinguishable mtimes on Windows (closes #645).
  • Web i18n race-free dispatch + listener registration (closes #698).
  • Web a11y role="button" + aria-label on row-as-clickable surfaces (#700, #808/#809/#810).
  • /api/sources resolves memory_dir paths instead of returning raw config strings (closes #675).
  • mm init filesystem ops wrap into fail_step (closes #664).
  • Web tab activation re-applies hidden=true on deactivation (closes #699, #701).
  • Sync All classifies every non-ok Settings status (closes #799, #774).

Security

  • Privacy scan covers full content at the LTM trust boundary, not just the first 10K chars (#792).
  • DNS resolution pinned in mem_fetch to defeat rebinding (#794).

Install / upgrade

uvx --refresh --from memtomem mm --version
# or
uvx --from memtomem memtomem-server   # MCP stdio server, now Windows-native

For full CHANGELOG entries see CHANGELOG.md.

v0.1.35

02 May 11:58
487c328

Choose a tag to compare

[0.1.35] — 2026-05-02

Added

  • Model-readiness banner for mm web (PR #703, closes #696). A new
    GET /api/system/model-readiness endpoint reports per-component
    (embedder + reranker) load state derived from _loading /
    _load_error flags on OnnxEmbedder and FastEmbedReranker, plus a
    filesystem probe of the fastembed cache. The header banner polls the
    endpoint and renders Downloading bge-m3 (~2300 MB)… /
    Loading model… / Model failed to load — check Settings so the
    first search after a cold-cache boot no longer feels like a hung UI.
    Boot hydrate, visibilitychange re-hydrate, and doSearch()
    pre-flight cover the three entry points; non-onnx/fastembed providers
    short-circuit to skipped.

  • i18n wired up across Export, Import, Tags sort, Auto-Tag, and
    Timeline (PR #695, PR #727).
    All target keys already lived in
    en.json / ko.json; the matching data-i18n /
    data-i18n-placeholder attributes were missing on Export filters,
    Import bundle/result rows, Tags sort buttons, the Auto-Tag form/result,
    and Timeline date-range/filter labels — Korean-mode users saw mixed
    English/Korean labels (Source Filter, Today, Last 7 days,
    Count↓, Dry run, Limit, etc.). The "Load All" button on the
    Sources chunk browser is also localized via a new
    chunks.load_all key (closes #681).

Fixed

  • Duplicate chunk inserts on shared SQLite DBs (#691, PR #705). When
    mm web's file watcher and a separate mm MCP / CLI process indexed
    the same file at the same time, both processes' independent
    asyncio.Locks let them each INSERT chunks with fresh UUIDs that
    shared (namespace, source_file, content_hash, start_line). The
    differ then reused only one of the IDs per re-index, leaving the rest
    as silent ghosts — one user's DB had 77 such groups (~5% of total
    chunks) before the fix. The storage layer now enforces
    UNIQUE(namespace, source_file, content_hash, start_line) and uses
    INSERT OR IGNORE so race losers are silently dropped at insert time.

    Migration is automatic on first startup: existing duplicate groups
    are collapsed to one row each (keeper rule: highest accumulated
    access_count + use_count, tie-break on oldest created_at then
    id), with the matching chunks_fts and chunks_vec sidecar rows
    removed in lockstep. The migration body is wrapped in
    BEGIN IMMEDIATE / COMMIT so two processes booting simultaneously
    don't both run the cleanup. A log line — Cleaned up N duplicate chunk row(s) across M group(s) — is emitted once per DB; subsequent
    startups short-circuit on the new idx_chunks_unique_content index.

    tools.export_import.import_chunks(on_conflict="duplicate") is
    preserved for back-compat but the storage invariant is now
    authoritative — the mode no longer materialises duplicate rows.

  • mm web SPA + /api/docs work fully offline (PR #706, PR #712,
    closes #693).
    The SPA pulled DOMPurify, marked, and Prism (core +
    five language plugins + theme) from cdnjs.cloudflare.com, and
    FastAPI's default /api/docs pulled Swagger UI bundle/css from
    cdn.jsdelivr.net plus a favicon from fastapi.tiangolo.com — three
    failure modes hit at once on every page load: (a) silent breakage on
    offline / firewalled / air-gapped deployments, (b) per-load beacon to
    the CDN with the visitor's IP / UA / request time, (c) no integrity=
    hashes so a CDN compromise served arbitrary code into the same origin
    as /api/.... All eleven assets (DOMPurify 3.1.6, marked 9.1.6, Prism
    1.29.0 + 5 lang plugins + tomorrow theme, swagger-ui-dist 5.32.5
    bundle.js + css) are now vendored under web/static/vendor/ with
    pinned SHA-256 in THIRD_PARTY_LICENSES.md. CSP tightens to
    default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none' and a paired positive/negative regression
    guard pins it. /api/docs is hand-rolled (instead of
    get_swagger_ui_html) so the bootstrap is an external
    swagger-init.js and the favicon reuses the SPA's own — no
    'unsafe-inline' needed. Footprint: ~1.7 MB total minified
    (~89 KB SPA + ~1.6 MB Swagger UI).

  • Context overview badge surfaces non-in_sync states (PR #704,
    closes #692).
    The Context tab's overview badge previously checked
    inSync < total, which under-counted multi-runtime divergence —
    e.g. three commands tracked under both claude_commands (in sync)
    and gemini_commands (no target) reported 3/3 synced while
    missing_target was 3. The badge now sums missing_target + missing_canonical + out_of_sync + parse_error and surfaces the most
    actionable status (precedence:
    parse_error → missing_target → missing_canonical → out_of_sync) so
    the user sees 3 missing in badge-warning colour. Five new
    settings.ctx.badge_* i18n keys (en + ko).

  • Home bar chart shows full namespace on hover (PR #694). Long
    auto-namespaces like claude:-Users-<user>-Work-<project> truncated
    to claude:-Users-... under the 120 px CSS clip with no way to see
    the rest. A title attribute on each row makes the full string
    reachable via the browser's native tooltip.

  • Context: installed_at captured from filesystem mtime (PR #733,
    closes #732).
    On Windows, capturing installed_at via Python's
    wall clock and comparing against NTFS FILETIME could place
    just-installed files strictly later than the captured stamp,
    false-positiving every fresh install as dirty. Capture now reads
    max(st_mtime_ns) from the destination tree itself, and ceiling-
    divides ns→µs before ISO-8601Z formatting so the round-trip stays
    monotonic across NTFS's 100-ns residual. POSIX is byte-identical for
    ordinary writes; the dirty.py strict-> invariant is preserved.

  • mm init wizard no longer crashes on Windows consoles (PR #728,
    cluster H-1 of #643).
    The default Windows codepage (cp1252 / cp437)
    cannot encode the box-drawing and em-dash glyphs the wizard prints,
    so mm init raised UnicodeEncodeError: 'charmap' codec can't encode character '─' (U+2500) at the very first banner line. The CLI entry
    point now reconfigures sys.stdout / sys.stderr to UTF-8 with
    errors="replace" on sys.platform == "win32" (POSIX is a no-op),
    so missing glyphs degrade to ? instead of crashing.

  • Indexing: Windows memory_dir prefixes match stored chunk paths
    (PR #717, closes #647).
    norm_dir_prefix appended a hardcoded /
    to a Path.resolve()-ed path, yielding C:\Users\foo/ on Windows
    that no chunks.source_file row could match under
    target.startswith(prefix). This bricked
    resolve_owning_memory_dir (every chunk classified as orphan),
    memory_dir_stats (all dirs returned 0), the /api/sources kind
    attribution, and delete_chunks=true removal. Fix uses os.sep so
    the prefix shape matches norm_path's native-separator output. No
    DB migration required.

  • Search: source_filter normalises path separators across seven
    MCP tools (PR #722, closes #720).
    source_filter was substring
    / glob-matched directly against stored paths with no separator fold,
    so a caller-supplied filter like /tmp/keep/ never matched a chunk
    stored as \tmp\keep\policy.md on Windows. Three contract-specific
    helpers in search/pipeline.py
    (match_source_filter / match_source_filter_substring /
    match_source_filter_glob) now centralise the fold across
    mem_search, mem_list, mem_consolidate, mem_decay,
    mem_auto_tag, mem_export_chunks, and mem_entity_scan. Persisted
    chunks.source_file rows are unchanged — only the comparison picks
    a canonical form.

  • Config: Windows backslash paths in categorize_memory_dir
    (PR #716, closes #316).
    The provider-pattern table is forward-slash
    regex (r"/\.claude/projects/[^/]+/memory/?$", etc.) but
    categorize_memory_dir matched against str(path) directly — on
    Windows that's backslash-separated, so every Windows provider
    directory silently fell through to "user" and the mm init
    wizard could not auto-detect Claude Code / Codex / Gemini memory
    folders. Fix normalises the input via .replace("\\", "/"); UNC
    paths and mixed separators covered.

  • Config: ~/... path_glob rules expand via as_posix()
    (PR #726, cluster E of #643).

    NamespacePolicyRule._expand_and_validate_glob stored the expanded
    glob via str(Path(v).expanduser()), which yielded backslashes on
    Windows. The downstream consumer pathspec.GitIgnoreSpec interprets
    gitignore syntax — gitignore is POSIX-only and treats \ as an
    escape character, so any rule with a leading ~/ (a common config
    shape: ~/.claude/projects/**, ~/Documents/notes/**) silently
    matched zero files on Windows and fell back to default_namespace
    with no warning. Path.expanduser().as_posix() keeps the tilde
    expansion and forces / separators; POSIX no-op for absolute paths.

  • Wiki: mm wiki {skill,agent,command} override prints consistent
    separators (PR #719).
    The Seeded … line interpolated a Path
    object (native separator) while the adjacent git add … hint
    hardcoded / — same logical path, two different shapes on Windows.
    Now Seeded {rel.as_posix()}. Absolute paths handed to $EDITOR
    via click.edit(filename=str(...)) and shell pipelines via
    click.echo(str(result.path)) intentionally stay platform-native.

Documentation

  • mm embedding-reset warns about the same-dim model-swap race
    (PR #710, closes #707).
    PR #705's INSERT OR IGNORE path keeps
    whichever embedding commits first when two processes embed the same
    chunk under different models; the dim-mismatch gate catches
    cross-dimension swaps but not same-dimension model swaps. A callout
    in configuration.md#reset-flow and a one-line warning in
    embeddings.md "Switching Models on an Existing Index" document the
    single-process invarian...
Read more

v0.1.34

02 May 03:12
4b82fba

Choose a tag to compare

[0.1.34] — 2026-05-02

Added

  • mm context migrate (PR-D C4, ADR-0008) — converts agents and
    commands from the legacy flat layout (<type>/<name>.md) to the
    canonical directory layout (<type>/<name>/agent.md or
    <type>/<name>/command.md) introduced in PR-C. Pre-PR-C installs and
    reverse-imports left flat files on disk; this verb normalizes them so
    the dir-only paths can simplify in a future cleanup PR.

    • Three signatures: mm context migrate (every flat asset across
      agents/ and commands/), mm context migrate <type> (one type),
      mm context migrate <type> <name> (single asset). Skills are always
      directory layout (Agent Skills spec); invoking the verb on
      skills exits 0 with an informational message rather than an error.
    • Dry-run by default; --apply mutates the filesystem via
      os.replace (atomic single-rename). The lockfile is not touched —
      layout is inferred from the filesystem authoritatively
      (list_canonical_agents / list_canonical_commands), and
      installed_at is preserved so dirty detection (Invariant 2) keeps
      working across migrations.
    • Eight-row truth table: the classifier surfaces every combination
      of flat? × dir? × lockfile-entry? × dirty? as one of six states
      (migrate, noop, cleanup_flat, refuse_dirty, skip_manual,
      skip_orphan). Manual flat files (no lockfile entry) and orphan
      lockfile entries are surfaced and left untouched — those are out of
      scope for the install/upgrade lifecycle.
    • Dirty handling mirrors mm context update --force — flat files
      with mtime > installed_at are refused unless --apply --force is
      passed. With --force, a .bak sibling is written before mutation
      so the user's edits survive in a forensic snapshot. For flat+dir
      collisions the dir layout is left untouched (it carries the
      canonical wiki bytes per PR-C policy); the user reviews the .bak
      manually if they want to merge.
    • Pairs with mm context status: status walks the dir-only dest tree
      (is_asset_dirty is dir-scoped) and so flat-only installs surface
      as missing rows; mm context migrate is the verb to normalize
      them in place.
  • Multi-project read-only discovery for mm web Skills/Commands/Agents
    (PR2 of the multi-project context UI series).
    Each tab now renders
    collapsible scope groups so a user running mm web from memtomem
    can also browse skills/commands/agents under ~/Edu/inflearn/ (or
    any other project root) without restarting the server.

    • GET /api/context/projects enumerates every discovered scope
      with the response shape {scope_id, label, root, tier, sources, missing, experimental, counts: {skills, commands, agents}}. Sources
      union "server-cwd", "known-projects", and the opt-in
      "claude-projects" scan; the scope_id is p-<sha12> of the
      case-normalized resolved path so it survives refresh and restart.
    • POST /api/context/known-projects registers a project root
      (atomic write + sidecar lockfile). Validates absolute path +
      is_dir(); returns a warning field but still HTTP 200 when no
      .claude/.gemini/.agents/.memtomem marker is present.
    • DELETE /api/context/known-projects/{scope_id} drops a
      registration (stale entries are removable too).
    • GET /api/context/{skills,commands,agents} accept an optional
      ?scope_id= query that targets a different project root; without
      it the legacy single-cwd contract is preserved exactly. Mutating
      routes (POST/PUT/DELETE/sync/import) stay cwd-only — multi-scope
      writes ship in PR3.
    • Web UI gains an "Add Project" button in each tab header and
      renders <details> scope groups with item counts, source/missing/
      experimental badges, and a per-scope remove (×) button. Server CWD
      items keep their click-to-edit behavior; non-cwd scope items render
      as read-only cards in PR2.
    • Config adds [context_gateway] with known_projects_path
      (default ~/.memtomem/known_projects.json),
      experimental_claude_projects_scan (default false), and
      user_tier_enabled (default false, forward-compat for PR3).
  • Web UI Skills/Commands/Agents empty-state surfaces "why nothing
    happened"
    . Previously mm web → Settings → Skills/Commands/Agents
    showed a generic Sync completed toast even when the canonical root
    (.memtomem/skills/ etc.) was empty, and a generic Import completed
    even when no .claude/skills/, .gemini/skills/, or .agents/skills/
    directory existed under the project root. The toast looked successful
    but nothing actually moved on disk, so users perceived the buttons as
    broken. Now:

    • POST /api/context/{skills,commands,agents}/sync responses gain
      canonical_root: str and each entry in skipped[] carries
      reason_code: "no_canonical_root" | "unknown_runtime" | "parse_error" | … (machine-readable, stable across i18n changes).
    • POST /api/context/{skills,commands,agents}/import responses gain
      project_root: str + scanned_dirs: list[str] so the UI can name
      the cwd and the runtime directories that were inspected. Each
      skipped[] entry carries reason_code (invalid_name,
      already_imported, canonical_exists, toml_parse_error).
    • Web UI matches on reason_code (not the human prose) and shows
      info-tone toasts: No canonical {type} under {canonical}. Create one first. for empty-canonical syncs, and No runtime {type} found in {project_root}. Scanned: {scan_dirs}. for empty-runtime imports.
    • Empty list state under each tab now points at the canonical and
      runtime paths instead of the generic "Create one or import from
      existing runtimes." line — users see exactly where to drop a
      SKILL.md / *.md / *.toml to populate the panel.
    • Sync handler also now reads data.skipped (in addition to
      data.dropped); previously skills' skipped was silently ignored
      because skills don't carry dropped field-level omissions.

    All response changes are additive — existing clients that only read
    imported/generated/skipped[].name|runtime|reason keep working.

  • mm index --debounce-window / --flush / --status flags (closes
    PR #536 documented gap). mm index now exposes a file-system-backed
    debounce queue under ~/.memtomem/index_debounce_queue.json
    (flock-protected). Three new flags:

    • --debounce-window <SECONDS> records PATH and drains any entries
      silent at least SECONDS. Designed for PostToolUse[Write] hook
      callers — rapid consecutive writes to the same file restart the
      window, so a codegen burst indexes the final state once at the end
      rather than once per Write.
    • --flush synchronously drains every queued entry. Blocks until
      each queued file is indexed (or recorded as an error).
      Worst-case
      latency ≈ queue depth × per-file index cost. Plugin's Stop hook
      now chains mm index --flush before mm session end --auto so the
      final burst doesn't get left in the queue.
    • --status prints a snapshot of the queue (depth, oldest entry).
      Race-prone by design — concurrent hooks may add or drain
      entries between the read and any caller action. Use as telemetry
      only; --flush is the only correctness primitive for "drain the
      queue".

    Indexer errors leave the entry in the queue for retry on the next
    hook fire. Last-write-wins for --namespace/--force when the same
    path is enqueued twice. MEMTOMEM_INDEX_DEBOUNCE_QUEUE env var
    overrides the queue path (test-only).

    Future-extensibility (RFC-B PreCompact, deferred): when the
    PreCompact hook contract lands and a checkpoint handler wants to
    flush only the files Claude Code reports as in-flight, --flush
    will gain a --paths <list> form for selective drain. The current
    --flush (drain all) remains the default; the API in
    memtomem.indexing.debounce.drain_all already accepts an optional
    paths= filter so adding the CLI surface is additive.

    Plugin hooks.json and the claude-code.md Hooks Automation Setup
    snippet are updated byte-for-byte (parity test catches drift). The
    PostToolUse[Write] hook now calls mm index --debounce-window 5;
    the Stop hook chains mm index --flush before
    mm session end --auto.

  • Web UI Memory Dirs sort — Sources tab → Memory Dirs panel now
    exposes a sort dropdown on any product leaf with at least 6 entries
    (the dominant case is "Claude projects" once a few dozen
    per-project dirs are auto-discovered). Six modes: newest first,
    oldest first, path A→Z, most files, most chunks, recently indexed.
    Selection persists per product via localStorage. Backend
    /api/memory-dirs/status gains created_at (OS filesystem birth
    time, ISO-8601 UTC) and last_indexed (max chunks.updated_at
    under the dir prefix) — both null for missing or un-indexed dirs.
    Linux without statx-birthtime falls back to st_ctime, which can
    shift on chmod / chown but is monotonic for fresh dirs in
    normal workflows.

  • Memory Dirs item-level metadata — each row now shows the
    directory creation date (locale-formatted) and a {files} files · {chunks} chunks badge mirroring the group total, so sort modes
    like "Most files" / "Recently indexed" can be verified row-by-row.
    Hovering the date shows full ISO timestamps for both created_at
    and last_indexed.

  • Optional chunk cleanup on memory_dir remove
    POST /api/memory-dirs/remove accepts delete_chunks: bool (default
    false). When true, every chunk whose source_file is under the
    resolved dir prefix is dropped (cascades to FTS / vector / link
    tables); the underlying files on disk are never touched. The Web
    UI's delete confirm shows an opt-in checkbox labeled "Also delete N
    indexed chunks" only when the dir has chunks — the safe default
    remains unregister-only.

Cha...

Read more

v0.1.33

29 Apr 05:53
5e20199

Choose a tag to compare

[0.1.33] — 2026-04-29

Added

  • mm upgrade CLI (#443) — wraps uv tool install --refresh --reinstall memtomem with process-level hygiene so an in-memory
    pre-upgrade memtomem-server can't keep running the old code next
    to the freshly written disk bytes (the v0.1.25 → v0.1.26 stale
    .server.pid repro that motivated this command). Probes the server
    pid lock, sends SIGTERM, escalates to SIGKILL after --grace
    seconds (default 5), unlinks the stale pid file, then runs the
    reinstall. Extras are preserved automatically by reading the
    current uv tool receipt — a memtomem[all] install stays [all]
    across upgrades instead of silently regressing to a BM25-only base;
    override with --extras onnx,web or suppress with --extras none.
    --version X.Y.Z pins a release; --dry-run prints the plan;
    --json emits a structured result. On Windows the kill stage is
    skipped automatically (POSIX advisory flock + signals are
    unavailable) and the reinstall runs alone with a warning.
  • chunk_links provenance from session summary → source chunks
    (RFC P1 Phase B-2). When the auto-summary path runs on
    mem_session_end, the server now writes link_type="summarizes"
    rows from the new archive:session:<id> chunk back to each source
    chunk it summarized, bounded by session_summary.max_summary_links
    (default 50, newest first, tail dropped per RFC Open-Question-1).
    Manual summary= callers do not collect source chunks and so do
    not write links. Each row is best-effort: a single add_chunk_link
    failure is logged and skipped rather than aborting mem_session_end.
  • Auto LLM session summary on mem_session_end (RFC P1 Phase B-1).
    When mem_session_end is called without summary= and an LLM
    provider is configured, the server summarizes chunks added during
    the session and persists the result through Phase A's
    archive:session:<id> chunk path. New session_summary config
    block (auto, min_chunks=5, max_summary_tokens=500,
    max_input_chars=60000) gates the behavior; skip reasons
    (disabled, no llm, below min_chunks, too large,
    empty output, llm error) surface in the tool response. See
    Session Summary.
  • mm session start SessionStart hook primitives (#541, RFC
    memtomem-docs#24). New flags so a Claude Code SessionStart hook
    can resume the active session instead of orphaning it on every
    start: --idempotent returns the existing active session for the
    same --agent-id (cross-agent collisions auto-end the prior
    session); --auto-end-stale=<duration> (e.g. 24h, 7d) closes
    active sessions older than the duration before the idempotency
    check, with a 100-row per-call cap so a backlog of orphans drains
    across multiple hook fires instead of stalling boot synchronously;
    --json emits one line of {"session_id": ..., "resumed": bool, "auto_ended": [...]} for hook parsing — the list groups both
    cutoff-based stale cleanup and cross-agent forced-end UUIDs;
    sessions.metadata.reason (stale or cross_agent) carries the
    per-row distinction. The plugin's hooks.json
    ships a SessionStart entry calling mm session start --idempotent --auto-end-stale 24h --agent-id claude-code and the
    claude-code.md Hooks Automation Setup snippet matches byte-for-byte
    via the TestPluginHooksDocsParity guard added in #536.
    Single-process safe; concurrent SessionStart fires are not locked
    (Claude Code's hook runner serializes them per session, which is
    the supported case).

Changed

  • Plugin PostToolUse[Write] hook now filters by extension and
    path
    — the inline mm index command in
    packages/memtomem-claude-plugin/hooks/hooks.json was indexing every
    Write regardless of file type or location, which fanned out to
    node_modules/, dist/, __pycache__/, lock files, binaries, and
    images in monorepo checkouts (embedding-cost amplifier + search
    noise). The hook now allowlists canonical source extensions
    (md, py, ts/tsx, js/jsx, go, rs, rb, java, kt,
    swift, c/cpp/h/hpp, sh, toml, yaml/yml, json)
    and blocklists build / cache / VCS paths (node_modules, dist,
    build, target, .next, .nuxt, __pycache__, .git,
    .venv/venv, coverage, .cache) inline — case statements,
    no external script. Adjust the patterns in hooks.json for
    project-specific needs. Other hooks (UserPromptSubmit,
    PostToolUse activity log, Stop) are unchanged. Docs at
    docs/guides/integrations/claude-code.md
    Hooks Automation section synced to match. A
    test_plugin_hooks_command_matches_docs_snippet parity test
    locks hooks.json and the docs snippet against silent drift on
    future edits. Existing installs that copy-pasted the previous
    snippet into ~/.claude/settings.json continue with the old
    unfiltered behavior until the user re-pulls the snippet. Documented
    gap
    : rapid consecutive writes still re-index the same file; native
    debounce support is tracked separately.

v0.1.32

26 Apr 10:07
b004024

Choose a tag to compare

[0.1.32] — 2026-04-26

Changed (BREAKING)

  • Caller-supplied namespace= / target= overrides on every
    session-start entry point and mem_agent_share now reject malformed
    shapes via a new validate_namespace gate.
    PR #491 / #494 / #498
    closed the bypass on agent_id itself, but each session-start surface
    also accepted an explicit namespace= argument that landed verbatim
    in storage — a Python / MCP / CLI caller could write
    "agent-runtime:foo:bar" even though agent_id was clean (the
    bypass shape PR #495 review flagged as Concern 3). The new validator
    now runs at mem_session_start(namespace=) (MCP),
    mm session start --namespace (CLI),
    MemtomemStore.start_agent_session(namespace=) and
    MemtomemStore.start_session(namespace=) (Python adapter),
    mem_agent_share(target=) (MCP), and mm agent share --target
    (CLI — closes #497, the kin gap PR #499 review flagged on the share
    surface). agent-runtime:<seg> overrides re-route the trailing
    segment through validate_agent_id so the override path can't widen
    the contract that the direct agent_id= path enforces.

    Migration: callers passing structured-but-unsupported namespace
    shapes (anything containing slashes, whitespace, comma, control
    characters, leading dash, or more than one colon under the
    agent-runtime: prefix) will now see
    Error: invalid namespace 'X': ... instead of the prior silent
    store. Existing in-tree shapes (default, shared,
    archive:summary, claude-memory:project-x,
    agent-runtime:planner, custom:scope) are unchanged. The bare
    single-segment "agent-runtime" (no trailing colon) is also rejected
    now — it shadows the multi-agent prefix and the strict-arity rule
    requires exactly one trailing segment after the prefix. Anyone
    holding such a namespace should rename it via mem_ns_rename before
    running session-start with the override. Closes #496 and #497.

  • mem_agent_register, mem_agent_search, and mm agent register
    now reject malformed agent_id values loudly instead of silently
    rewriting them.
    PR #491 had wired validate_agent_id into the
    three session-start surfaces (mem_session_start / mm session start / mm session wrap) so a hostile shape like "foo:bar" or
    "../x" raised
    Error: invalid agent-id 'foo:bar': must match [A-Za-z0-9._-]+ ....
    The matching multi-agent registration / search surfaces still ran
    sanitize_namespace_segment, so the same input produced two UX
    outcomes depending on which tool the caller hit first — registering
    an agent rewrote the id in place under agent-runtime:foo_bar,
    while starting a session for that same id rejected it. The read /
    write contract is now symmetric: an agent_id either works on
    every surface or fails on every surface, with the same
    invalid agent-id 'X' error fragment regardless of entry point.

    Migration: callers passing values that were previously rewritten
    (anything containing :, /, .., whitespace, or characters
    outside [A-Za-z0-9._-]) will now see a hard error. Those callers
    were already storing memories under unexpected namespaces — pick a
    canonical id matching the documented charset and re-register. The
    LangGraph adapter parity gap remains tracked separately. Closes #493.

  • MemtomemStore.start_agent_session (LangGraph adapter) now
    rejects the same hostile agent_id shapes via validate_agent_id
    instead of the prior if not agent_id empty-only check.
    Closes
    the last in-process surface where a Python caller could land
    "agent-runtime:foo:bar" in storage even though the MCP / CLI
    surfaces refuse the same shape. The raised exception narrows from
    the generic ValueError("agent_id must be a non-empty string") to
    InvalidNameError("invalid agent-id 'X': ...")InvalidNameError
    is a ValueError subclass, so except ValueError: callers keep
    working, but any code substring-matching the old message text will
    break and should switch to matching "invalid agent-id". Closes
    #492.

Internal

  • Renamed the legacy storage-layer helper
    memtomem.storage.sqlite_namespace.validate_namespace(name) -> bool
    to _is_valid_ns_chars and removed it from
    memtomem.storage.sqlite_backend.__all__. Disambiguates from the
    strict caller-input validator
    memtomem.constants.validate_namespace(value) -> str introduced in
    PR #491#503, which raises InvalidNameError. The two had the same
    name but different signatures and contracts (charset-only bool vs.
    shape-aware raises), which made debugging harder when a stack trace
    pointed at "validate_namespace". The storage helper has no in-tree
    callers outside its sibling _ensure_valid_namespace wrapper, so
    the rename is internal-only — it was nominally exported via
    __all__, but a repo-wide grep showed zero external imports.

v0.1.31 — multi-agent G1/G2 + Context Gateway prod tier

26 Apr 04:08
38c3eec

Choose a tag to compare

[0.1.31] — 2026-04-26

Added

  • Web UI Context Gateway tabs (Artifact Sync, Skills, Commands,
    Agents) graduate from --dev to the polished prod surface.
    The
    hardening that landed in #482 (round-trip ## *-Specific
    preservation), #483 (project-scope codex_agents default), and
    #484 (settings-sync host-write confirm) closed the rough edges that
    justified hiding these from mm web users by default. The four
    /api/context/* routers and matching settings nav buttons now
    mount in prod; dev-only stays dev-only for Namespaces,
    Sessions, Working Memory, Procedures, Health Report, and
    Hook Files. Trust boundary unchanged — the loopback + single-user
    Tier 1 deployment shape from feedback_tier2_web_gating_deferred.md
    is still the only supported one, and these are the first
    mutator-heavy endpoints to ship in the prod surface, so a future
    Tier 2 hardening pass starts here.
  • mm context {generate,sync} --include=settings now confirms before
    writing settings files outside the project root
    (today only
    ~/.claude/settings.json); pass --yes / -y to skip the prompt.
    The same gate is enforced inside generate_all_settings, so callers
    that bypass the CLI — mem_context_generate / mem_context_sync
    (MCP) and POST /settings-sync (Web) — also refuse host writes
    unless they pass allow_host_writes=true. Closes one of the
    2026-04-26 audit P0 items: a stray sync from a worktree no longer
    silently edits the real home directory regardless of which front-end
    drove it. Symlink-aware (Path.resolve() both sides) so a
    symlinked .claude cannot smuggle a host write past the gate.

Changed (BREAKING)

  • mm context sync now writes Codex sub-agents to project-scope
    .codex/agents/<name>.toml instead of user-scope
    ~/.codex/agents/<name>.toml.
    Previously every mm context sync
    wrote into the user's home directory regardless of which project the
    command ran from, so two projects with same-named canonical agents
    silently overwrote each other in ~/.codex/agents/, and any
    mm context sync from a worktree leaked onto the real host home —
    defeating worktree isolation. The OpenAI Codex CLI supports both
    scopes
    ("standalone
    TOML files under ~/.codex/agents/ for personal agents or
    .codex/agents/ for project-scoped agents"), and project scope is
    the right default for a per-repo canonical source. This makes
    codex_agents symmetric with claude_agents (.claude/agents/)
    and gemini_agents (.gemini/agents/).

    Migration: if you relied on the old user-scope output, copy any
    agents you want to keep with
    cp ~/.codex/agents/*.toml <project>/.codex/agents/, or restore
    user-scope behavior on a future mm context sync once --scope=user
    ships (tracked separately). Existing ~/.codex/agents/ files are
    left in place; memtomem just stops writing there. After copying the
    ones you want to keep, delete the originals (rm ~/.codex/agents/*.toml)
    — Codex CLI still loads them, but memtomem no longer manages them, so
    any future edits in the canonical source won't reach the user-scope
    copy and the two will drift silently.

    detect_agent_dirs now also picks up .codex/agents/<name>.toml
    alongside Claude/Gemini, so mm context detect and the Web UI
    Context Gateway list project-scope Codex agents in their inventory.

  • mem_add, mem_batch_add, mem_index, and mem_fetch now
    inherit the agent scope from mem_session_start(agent_id="...").

    Following gap G1 of the 2026-04-26 multi-agent surface review, the
    session-aware MCP write tools join the same priority chain
    mem_agent_search already uses:

    1. explicit namespace= argument (escape hatch — wins everything),
    2. app.current_agent_idagent-runtime:<id> (set by
      mem_session_start),
    3. app.current_namespace (legacy mem_ns_set path),
    4. config default.

    Previously these write tools consulted only step 3, so calling
    mem_session_start(agent_id="planner") followed by mem_add /
    mem_index / mem_fetch silently wrote to default instead of
    agent-runtime:planner, contradicting the multi-agent contract the
    public page advertises. The change is now symmetric with the
    LangGraph adapter (which has had this semantic since PR #460) and
    with mem_agent_search's read-side resolution.

    mem_consolidate_apply already routed through _mem_add_core, so
    it picks up the new contract automatically.

    Who is affected:

    • Pre-multi-agent users (no mem_session_start call): no
      change. current_agent_id stays None; resolution falls
      through to step 3 / step 4 exactly as before.
    • Multi-agent users using only mem_session_start: writes
      now land in the agent's namespace — the documented behavior,
      finally honored. The 940-line e2e guide's workaround (passing
      namespace="agent-runtime:<id>" on every write) is no longer
      required.
    • Rare combination — both mem_ns_set and mem_session_start
      set in the same session
      : write target changes from the
      mem_ns_set namespace to the agent's namespace. Pass
      namespace="<old-ns>" explicitly on the call to keep the old
      destination, or call mem_session_end() before writing.

    mem_add now echoes the resolved Namespace: line in its
    confirmation output (mem_batch_add / mem_fetch already did),
    so the resolution is observable on the first call.

    Out of scope (tracked separately):

    • mem_search and mem_recall (single-agent reads) — mem_search
      keeps its current_namespace semantic; use mem_agent_search to
      read inside the agent scope.
    • mem_import_notion / mem_import_obsidian — these intentionally
      fall back to source-tagged namespaces (notion / obsidian)
      rather than the session scope, since their value is tagging
      ingested content by source.

Fixed

  • mm context round-trip no longer silently drops ## <Agent>-Specific
    sections.
    extract_sections_from_agent_file mapped agent-override
    headings (## Claude-Specific, ## Cursor-Specific,
    ## Gemini-Specific, ## Codex-Specific, ## Copilot-Specific) to
    their literal heading strings, but the generators look for canonical
    keys (Claude, Cursor, …). On mm context init (extract-existing)
    followed by mm context generate, the override section was discarded
    and mm context diff showed [in sync] because both sides had the
    same loss — masking the data loss. Added the five aliases to the
    reverse-extract map so override sections survive the round-trip.
    Separately, CopilotGenerator emitted the override content without
    any ## heading, so a second round-trip absorbed it into the
    preceding section; it now writes ## Copilot-Specific like the
    other generators.

  • mm session start and mm session wrap now derive
    agent-runtime:<id> namespace from --agent-id, mirroring the MCP
    mem_session_start behavior shipped in PR #475.
    Previously both
    CLI surfaces silently lost --agent-id for namespace derivation,
    leaving sessions in the default namespace despite the multi-agent
    contract the public page advertises (Persona-A trap from the
    2026-04-26 surface review, gap G2). mm session wrap was the more
    consequential half — its default --agent-id headless meant every
    mm session wrap -- claude -p ... invocation also landed in
    default regardless of the wrapped command. Both surfaces now use a
    shared _derive_session_namespace helper with the same priority
    chain as MCP: explicit --namespace wins; otherwise
    agent-runtime:<agent-id> for non-default agents; otherwise
    default. mm session start also echoes the resolved namespace so
    users can verify before continuing.

  • mm web Sync All button no longer toasts "Settings sync failed"
    in prod mode.
    Follow-up to the #488 Context Gateway tabs prod
    graduation. The "Sync All" overview button fanned out to
    /api/context/settings/sync (the settings-hook merge), but that
    router intentionally stays dev-only; in prod the hop returned 404
    and the whole button toasted failure even though the artifact
    fanout (skills / commands / agents) succeeded — making the prod
    surface look broken. The same shape hit the overview's 4th
    "Settings" card, which deep-linked into the dev-only hooks-sync
    section, so clicking the card landed on a dead tab. Both code
    paths now self-gate on STATE.uiMode === 'dev' (matching the Home
    dashboard pattern); prod gets a clean 3-card overview and a Sync
    All that completes silently for the artifacts it actually has a UI
    for. JS pin tightened to count both gate sites so a future
    refactor can't silently drop one. (PR #489)

v0.1.30

25 Apr 22:59
a91de7e

Choose a tag to compare

[0.1.30] — 2026-04-26

Fixed

  • mm uninstall no longer reports pid None after a contended
    server start.
    The runtime pid file was opened with
    open(pid_file, "w"), which truncates at open before the
    fcntl.flock(LOCK_EX | LOCK_NB) probe decides ownership. When a
    second memtomem-server started while the first was still
    running (multiple Claude Code / Codex / Gemini MCP clients
    spawning in parallel, or any restart race), the second process
    zeroed out the live server's pid file, then bailed on the flock
    probe — leaving mm uninstall with no recorded pid to surface.
    Fixed by switching to "a+" (no open-time truncate) and
    performing seek(0); truncate(); write(pid) only after the
    flock is held. mm uninstall now distinguishes a pid unknown
    branch (truncate-race fingerprint or a partial-write startup
    crash) from pid None and points at lsof <pidfile> for
    diagnosis. Latent since the pid lock was introduced; predates
    the #412 runtime-dir relocation. (PR #476)

  • mem_agent_share example in server instructions= had wrong
    parameter name.
    The recipe shipped in #477 showed
    mem_agent_share(memory_id=...) but the real signature is
    mem_agent_share(chunk_id=..., target=...). Weak LLMs (reproduced
    on Claude Haiku 4.5) followed the example verbatim, hit a
    parameter-name rejection, then mem_do(action="help")-recovered to
    the correct args — burning an extra round trip per multi-agent
    share. Stronger LLMs (Sonnet 4.5+) inferred past the typo. Fixed
    the example and tightened tests/test_server_instructions.py with
    a signature-parity check: any tool(arg=...) form in INSTRUCTIONS
    is now cross-checked against inspect.signature(...), so future
    parameter renames force the example to follow.

Added

  • MCP initialize response now carries server-level instructions.
    The MCP server passes a workflow-recipe string to
    FastMCP(instructions=...), which clients auto-inject into every
    LLM session alongside the tool list. The string covers the
    single-agent quickstart (mem_add / mem_search), the multi-agent
    recipe (mem_agent_registermem_session_start
    mem_agent_search / mem_agent_sharemem_session_end),
    namespace conventions (default / agent-runtime:<id> / shared:),
    and common pitfalls (e.g. mem_add without namespace= consults
    current_namespace, not the session's agent-runtime:* scope).
    Source of truth: memtomem/server/instructions.py; pinned by
    tests/test_server_instructions.py so renames or convention drift
    fail loud. Motivation: tool docstrings alone left LLMs guessing
    which tool to call when users asked for "agent isolation" — clients
    were silently falling back to mem_add instead of
    mem_agent_share. The instructions field gives the LLM a workflow
    hint without anyone having to paste a system snippet.

Changed

  • mem_session_start(agent_id="<id>") now derives the session
    namespace from agent_id when the caller doesn't pass an
    explicit namespace=.
    Previously the session record's
    namespace fell back to app.current_namespace or "default",
    ignoring the supplied agent_id even though the LangGraph
    adapter MemtomemStore.start_agent_session already auto-derived
    agent-runtime:<id>. The MCP tool now matches that semantic.
    Priority order: explicit namespace= > agent-runtime:<id>
    (when agent_id != "default") > app.current_namespace >
    "default". Only the session record's namespace field
    changes; app.current_namespace is untouched, so subsequent
    mem_add / mem_search without explicit namespace= keep the
    legacy fallback. Backward compat: callers passing explicit
    namespace= are unaffected (priority 1); callers not passing
    agent_id (defaults to "default") are unaffected (priority 3
    fallback). Caught while walking the multi-agent test scenarios
    mem_session_start(agent_id="planner") reported
    Namespace: default even though current_agent_id was set to
    "planner", which the tester reasonably expected to flow into
    the session row.

v0.1.29

25 Apr 08:37
a794dcf

Choose a tag to compare

[0.1.29] — 2026-04-25

Hotfix release. mm agent register <id> now surfaces in
mm agent list immediately, even before any chunks land in the
agent's namespace. Caught during the first external walkthrough
of the v0.1.28 multi-agent test scenarios — exactly the gap the
walkthrough was designed to expose.

Fixed

  • mm agent list no longer hides registered-but-empty agents.
    list_namespace_meta previously sourced rows from
    chunks LEFT JOIN namespace_metadata, so a namespace with a
    metadata row but zero chunks was filtered out by GROUP BY c.namespace. After mm agent register planner the agent
    stayed invisible until someone wrote into
    agent-runtime:planner. The Web UI's GET /namespaces
    response had the same blind spot through the same storage
    method. Fixed by sourcing from the union of
    namespace_metadata.namespace and chunks.namespace, then
    joining for chunk count + description/color. Three states now
    surface correctly: metadata only (registered, 0 chunks),
    chunks only (legacy / un-registered), and both. Return shape
    unchanged so callers (CLI, Web route) pick up the fix
    transparently. (PR #473)

Docs

  • ADR-0002 graduates to public. The blockquote-tags
    reader/writer contract shipped in v0.1.28 is now documented at
    docs/adr/0002-mem-add-blockquote-tags.md with file:line
    references against the v0.1.28 layout, an inbound link from
    the mem_add section of the reference guide, and a reverse
    cross-link from the v0.1.28 changelog entry. (PR #472)

v0.1.28

25 Apr 04:03
30e87a1

Choose a tag to compare

[0.1.28] — 2026-04-25

Multi-agent share provenance + per-entry tag round-trip release.
Largest behavior change: the markdown chunker now promotes per-entry
> tags: blockquotes into ChunkMetadata.tags and strips the
header from chunk content, so mem_add(tags=...) finally
round-trips through mem_search(tag_filter=...). This regenerates
chunk UUIDs on first reindex — a one-time discontinuity; the new
chunk_links table closes the structural gap for future shares,
and a one-shot back-fill populates link rows from existing
shared-from=<uuid> audit tags so older audit chains resolve.

Also flips agent-runtime: into system_namespace_prefixes by
default (restores the isolation guarantee from the multi-agent
guide), and defaults mem_import(on_conflict) to "skip" so
re-imports become idempotent.

Fixed

  • mem_batch_add no longer over-applies tags across entries or onto
    pre-existing chunks.
    Pre-fix the tool collected the union of every
    entry's tags after indexing and broadcast that union onto every chunk
    the file produced — including chunks added in earlier sessions and
    unrelated entries inside the same batch. With the chunker now
    promoting per-entry blockquote tags directly (PR #463), the
    post-index broadcast is removed; per-entry tags now stay attached to
    the entry that declared them, and pre-existing chunks in the same
    file are no longer retagged on a subsequent batch.
  • Oversized sections with a per-entry blockquote header no longer
    drift sub-chunks' file line numbers.
    When the chunker stripped the
    > created: / > tags: blockquote from section text before
    paragraph-splitting, _split_section still seeded its line
    counter from the heading line — so sub-chunks 2..N reported
    start_line 3–5 lines earlier than the body they actually
    covered. mem_edit of a non-first sub-chunk would then pull in
    real body lines from the previous sub-chunk and silently drop them
    on save. The chunker now tracks how many lines the strip consumed
    and seeds the line counter accordingly; the first sub-chunk's
    start_line still anchors at the heading (preserving
    mem_edit's header-preservation contract for it). Single-chunk
    sections and sections without a blockquote header are unchanged.

Changed

  • mem_add(tags=...) now writes a canonical blockquote header.
    append_entry emits > tags: ["a", "b"] (explicit > prefix
    on every line, JSON / double-quoted list) instead of the legacy
    lazy-continuation tags: ['a', 'b'] (Python repr()). Old
    files still parse — the chunker's section-leading parser accepts
    both shapes — but a fresh mem_add no longer relies on
    CommonMark lazy continuation for the metadata block.

  • mem_edit and Web UI chunk edit preserve the per-entry header.
    New replace_chunk_body helper keeps the heading line and the
    section-leading > created: / > tags: blockquote intact when
    the caller passes body-only new_content. Both mem_edit and
    the Web UI PATCH /api/chunks/{id} route use it. The Web UI
    editor surfaces chunk.content (header-stripped by the chunker),
    so this matters for Save-from-browser. To override the heading
    explicitly, prefix new_content with ## and the call
    reverts to a full replacement (preserving the pre-RFC semantic).

  • mem_add(tags=...) now round-trips through mem_search(tag_filter=...).
    The markdown chunker promotes the per-entry blockquote header
    (> created: ... / > tags: [...] / legacy lazy-continuation
    tags: [...]) to first-class ChunkMetadata.tags, and strips it
    from chunk content so it no longer leaks into BM25 / embedding
    inputs. File-level YAML frontmatter tags compose with per-section
    blockquote tags via union. Mid-section blockquotes (a quoted
    paragraph in body prose) are untouched. Multi-agent integration
    test asserts on metadata.tags for shared-from=<src>. Reindex
    to backfill tags onto memories added by older mem_add calls.

    Note: re-indexed chunks regenerate UUIDs. Stripping the
    blockquote header changes content, which changes
    content_hash = sha256(content) (models.py:97), which the differ
    treats as a new chunk and assigns a fresh uuid4(). After
    reindex: any external pinning of chunk_id (notebooks, scripts,
    cross-LTM references) will miss, and existing
    shared-from=<old-uuid> audit-tag chains will reference UUIDs
    that no longer exist. This is a one-time discontinuity; the new
    chunk_links table (see below) closes the structural gap for
    future shares, and a one-shot back-fill in this release
    best-effort recovers pre-existing chains from the
    shared-from= tags.

Added

  • LangGraph adapter (MemtomemStore) gains multi-agent helpers. New
    start_agent_session(agent_id) derives the namespace from
    agent-runtime:<id> and binds _current_agent_id; subsequent
    search() / add() calls inherit the agent scope without the caller
    passing namespace= on every call. search(include_shared=...) is a
    3-state toggle: None (auto: include shared if an agent is bound),
    True (force include — raises ValueError if no agent is bound,
    surfacing programming errors instead of degrading to a silent
    un-pinned search), False (private only). add() defaults
    namespace=None to the bound agent's private bucket; pass an
    explicit namespace="shared" to publish across agents. Existing
    start_session(agent_id, namespace) is preserved as a low-level
    escape hatch. The adapter still does not implement LangGraph's
    BaseStore (aput / aget / alist_namespaces); that surface
    remains a follow-up.

  • mm agent CLI: register, list, share, plus hidden debug-resolve.
    Mirrors the mem_agent_* MCP tools so operators don't have to spin up
    an MCP client for one-off agent setup. mm agent list [--json] groups
    registered agents (agent-runtime: namespaces) with the cross-agent
    shared namespace; mm agent share <chunk-id> [--target ...]
    performs the same content copy + shared-from=<src> audit tag as the
    MCP tool. The hidden mm agent debug-resolve dumps the namespace
    filter mem_agent_search would resolve given simulated
    current_agent_id / current_namespace / --include-shared inputs,
    as JSON — for use in multi-agent integration scripts so they can
    assert namespace resolution without standing up an MCP client.

  • Structured share lineage via new chunk_links table.
    mem_agent_share now records a source→destination row in
    chunk_links (indexed FK, ON DELETE SET NULL on source_id,
    ON DELETE CASCADE on target_id) alongside the
    shared-from=<src> audit tag it already writes into chunk
    content. Tag-only provenance was a full-table
    LIKE '%shared-from=%' scan with no index and broke on reindex
    UUID churn; the structured link is an O(fanout) indexed lookup
    and stays correct across source delete. New Storage Python API:
    add_chunk_link, get_chunk_link, get_chunks_shared_from,
    walk_share_chain (cycle defence + max_depth). Public MCP
    surface is unchanged — mem_agent_share's signature and
    copy-on-share semantics are identical; the link is a storage
    invariant. A one-shot back-fill on first 0.1.28 startup scans
    pre-existing shared-from=<uuid> tags and populates link rows
    so audit chains authored on older versions resolve
    structurally; unresolvable source UUIDs store source_id=NULL.
    (PR #469, PR #470)

  • mem_import gains on_conflict and preserve_ids (bundle schema v2).
    Bundles now carry per-chunk chunk_id + content_hash so importers can
    dedup by content across instances. on_conflict accepts "skip"
    (default, idempotent re-import and cross-PC merge), "update" (overwrite
    existing row's metadata while preserving UUID), or "duplicate" (pre-v2
    row-duplication, kept for back-compat). preserve_ids=True reuses the
    bundle's original UUIDs for new inserts in v2 bundles. v1 bundles still
    import; hashes are derived from content on the fly. Exposed on the
    mem_import MCP tool, import_chunks(), and POST /api/export/import
    (on_conflict / preserve_ids multipart fields). (PR #451 follow-up)

Changed

  • agent-runtime: is now hidden from default mem_search (behavior
    change).
    search.system_namespace_prefixes default extended from
    ["archive:"] to ["archive:", "agent-runtime:"] so per-agent
    private chunks (created by mem_agent_register / mem_agent_search)
    no longer leak into namespace=None search results — restoring the
    isolation guarantee advertised on the multi-agent guide. The hidden
    count surfaces through the existing hidden_system_ns hint, and
    mem_agent_search is unaffected because it pins namespace=
    explicitly. To restore the pre-change behaviour, override
    search.system_namespace_prefixes: [] in config.json (or drop
    agent-runtime: from the list while keeping archive:). New
    memtomem.constants module exports AGENT_NAMESPACE_PREFIX,
    SHARED_NAMESPACE, and default_system_prefixes so callers
    derive the literal from a single source.

  • Default import behaviour flipped to on_conflict="skip". Previously
    every record got a fresh UUID, so re-importing the same bundle doubled
    row counts and merging bundles with overlapping content produced silent
    duplicates. Callers that relied on the old row-duplication behaviour
    must pass on_conflict="duplicate" explicitly.

Fixed

  • mem_agent_share now stamps a shared-from=<source-uuid> audit
    tag on the copy, with chain dedup.
    The docstring previously
    contradicted itself ("Creates a copy" / "cross-reference link instead
    of copying" / actual implementation: a brand-new chunk via
    mem_add). The new behaviour is a content copy with provenance
    recorded only via the new tag — the function name is...
Read more

v0.1.27

24 Apr 02:09
4fc3767

Choose a tag to compare

[0.1.27] — 2026-04-24

Hotfix release. Closes the multi-instance bug (#444): running
memtomem-server in one Claude Code session used to block every
other session from connecting, because the legacy-flock probe took
LOCK_EX and sys.exit(1)'d on contention. Multiple sessions across
different projects can now coexist.

Changed

  • Legacy flock downgraded to LOCK_SH; two 0.1.26+ servers can now
    coexist.
    Previously _try_hold_legacy_flock took LOCK_EX and
    called sys.exit(1) on contention, which was intended as a
    cross-version mutex against pre-0.1.25 servers (#412 B1) but also
    blocked two 0.1.26+ instances from running at the same time — e.g.
    one MCP server per Claude Code session across multiple projects.
    Shared locks compose with other shared locks but still conflict with
    exclusive, so pre-0.1.25's LOCK_EX still blocks us (and vice
    versa) — cross-version protection is preserved. On contention we
    now log a warning and fall through to the XDG path rather than
    aborting. (#444, PR #445)