Releases: memtomem/memtomem
v0.1.36 — Windows MCP server + tags service + security guards + transactional uninstall
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.flockswapped ontoportalockerat 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.pidintentionally never created on Windows. mm indexstreams discovery + chunking progress viaclick.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-unitCI 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 uninstalldeletion is transactional via stage-then-rmtree (closes #757).mm uninstall --forcerefuses 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()forcesgc.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-labelon row-as-clickable surfaces (#700, #808/#809/#810). /api/sourcesresolvesmemory_dirpaths instead of returning raw config strings (closes #675).mm initfilesystem ops wrap intofail_step(closes #664).- Web tab activation re-applies
hidden=trueon deactivation (closes #699, #701). - Sync All classifies every non-
okSettings 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_fetchto defeat rebinding (#794).
Install / upgrade
uvx --refresh --from memtomem mm --version
# or
uvx --from memtomem memtomem-server # MCP stdio server, now Windows-nativeFor full CHANGELOG entries see CHANGELOG.md.
v0.1.35
[0.1.35] — 2026-05-02
Added
-
Model-readiness banner for
mm web(PR #703, closes #696). A new
GET /api/system/model-readinessendpoint reports per-component
(embedder + reranker) load state derived from_loading/
_load_errorflags onOnnxEmbedderandFastEmbedReranker, plus a
filesystem probe of the fastembed cache. The header banner polls the
endpoint and rendersDownloading bge-m3 (~2300 MB)…/
Loading model…/Model failed to load — check Settingsso the
first search after a cold-cache boot no longer feels like a hung UI.
Boot hydrate,visibilitychangere-hydrate, anddoSearch()
pre-flight cover the three entry points; non-onnx/fastembed providers
short-circuit toskipped. -
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 matchingdata-i18n/
data-i18n-placeholderattributes 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_allkey (closes #681).
Fixed
-
Duplicate chunk inserts on shared SQLite DBs (#691, PR #705). When
mm web's file watcher and a separatemmMCP / CLI process indexed
the same file at the same time, both processes' independent
asyncio.Locks let them eachINSERTchunks 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 IGNOREso 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 oldestcreated_atthen
id), with the matchingchunks_ftsandchunks_vecsidecar rows
removed in lockstep. The migration body is wrapped in
BEGIN IMMEDIATE/COMMITso 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 newidx_chunks_unique_contentindex.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 webSPA +/api/docswork fully offline (PR #706, PR #712,
closes #693). The SPA pulled DOMPurify, marked, and Prism (core +
five language plugins + theme) fromcdnjs.cloudflare.com, and
FastAPI's default/api/docspulled Swagger UI bundle/css from
cdn.jsdelivr.netplus a favicon fromfastapi.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) nointegrity=
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 underweb/static/vendor/with
pinned SHA-256 inTHIRD_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/docsis hand-rolled (instead of
get_swagger_ui_html) so the bootstrap is an external
swagger-init.jsand 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_syncstates (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 bothclaude_commands(in sync)
andgemini_commands(no target) reported3/3 syncedwhile
missing_targetwas 3. The badge now sumsmissing_target + missing_canonical + out_of_sync + parse_errorand surfaces the most
actionable status (precedence:
parse_error → missing_target → missing_canonical → out_of_sync) so
the user sees3 missinginbadge-warningcolour. Five new
settings.ctx.badge_*i18n keys (en + ko). -
Home bar chart shows full namespace on hover (PR #694). Long
auto-namespaces likeclaude:-Users-<user>-Work-<project>truncated
toclaude:-Users-...under the 120 px CSS clip with no way to see
the rest. Atitleattribute on each row makes the full string
reachable via the browser's native tooltip. -
Context:
installed_atcaptured from filesystem mtime (PR #733,
closes #732). On Windows, capturinginstalled_atvia Python's
wall clock and comparing against NTFSFILETIMEcould place
just-installed files strictly later than the captured stamp,
false-positiving every fresh install asdirty. 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; thedirty.pystrict->invariant is preserved. -
mm initwizard 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,
somm initraisedUnicodeEncodeError: 'charmap' codec can't encode character '─' (U+2500)at the very first banner line. The CLI entry
point now reconfiguressys.stdout/sys.stderrto UTF-8 with
errors="replace"onsys.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_prefixappended a hardcoded/
to aPath.resolve()-ed path, yieldingC:\Users\foo/on Windows
that nochunks.source_filerow 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/sourceskind
attribution, anddelete_chunks=trueremoval. Fix usesos.sepso
the prefix shape matchesnorm_path's native-separator output. No
DB migration required. -
Search:
source_filternormalises path separators across seven
MCP tools (PR #722, closes #720).source_filterwas 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.mdon Windows. Three contract-specific
helpers insearch/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, andmem_entity_scan. Persisted
chunks.source_filerows 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_dirmatched againststr(path)directly — on
Windows that's backslash-separated, so every Windows provider
directory silently fell through to"user"and themm 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_globrules expand viaas_posix()
(PR #726, cluster E of #643).
NamespacePolicyRule._expand_and_validate_globstored the expanded
glob viastr(Path(v).expanduser()), which yielded backslashes on
Windows. The downstream consumerpathspec.GitIgnoreSpecinterprets
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 todefault_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} overrideprints consistent
separators (PR #719). TheSeeded …line interpolated aPath
object (native separator) while the adjacentgit add …hint
hardcoded/— same logical path, two different shapes on Windows.
NowSeeded {rel.as_posix()}. Absolute paths handed to$EDITOR
viaclick.edit(filename=str(...))and shell pipelines via
click.echo(str(result.path))intentionally stay platform-native.
Documentation
mm embedding-resetwarns about the same-dim model-swap race
(PR #710, closes #707). PR #705'sINSERT OR IGNOREpath 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
inconfiguration.md#reset-flowand a one-line warning in
embeddings.md"Switching Models on an Existing Index" document the
single-process invarian...
v0.1.34
[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.mdor
<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/andcommands/),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
skillsexits 0 with an informational message rather than an error. - Dry-run by default;
--applymutates 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_atis preserved so dirty detection (Invariant 2) keeps
working across migrations. - Eight-row truth table: the classifier surfaces every combination
offlat? × 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
withmtime > installed_atare refused unless--apply --forceis
passed. With--force, a.baksibling is written before mutation
so the user's edits survive in a forensic snapshot. Forflat+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_dirtyis dir-scoped) and so flat-only installs surface
asmissingrows;mm context migrateis the verb to normalize
them in place.
- Three signatures:
-
Multi-project read-only discovery for
mm webSkills/Commands/Agents
(PR2 of the multi-project context UI series). Each tab now renders
collapsible scope groups so a user runningmm webfrommemtomem
can also browse skills/commands/agents under~/Edu/inflearn/(or
any other project root) without restarting the server.GET /api/context/projectsenumerates 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; thescope_idisp-<sha12>of the
case-normalized resolved path so it survives refresh and restart.POST /api/context/known-projectsregisters a project root
(atomic write + sidecar lockfile). Validates absolute path +
is_dir(); returns awarningfield but still HTTP 200 when no
.claude/.gemini/.agents/.memtomemmarker 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]withknown_projects_path
(default~/.memtomem/known_projects.json),
experimental_claude_projects_scan(defaultfalse), and
user_tier_enabled(defaultfalse, forward-compat for PR3).
-
Web UI Skills/Commands/Agents empty-state surfaces "why nothing
happened". Previouslymm web→ Settings → Skills/Commands/Agents
showed a genericSync completedtoast even when the canonical root
(.memtomem/skills/etc.) was empty, and a genericImport 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}/syncresponses gain
canonical_root: strand each entry inskipped[]carries
reason_code: "no_canonical_root" | "unknown_runtime" | "parse_error" | …(machine-readable, stable across i18n changes).POST /api/context/{skills,commands,agents}/importresponses 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 carriesreason_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, andNo 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/*.tomlto populate the panel. - Sync handler also now reads
data.skipped(in addition to
data.dropped); previously skills'skippedwas silently ignored
because skills don't carrydroppedfield-level omissions.
All response changes are additive — existing clients that only read
imported/generated/skipped[].name|runtime|reasonkeep working. -
mm index --debounce-window/--flush/--statusflags (closes
PR #536 documented gap).mm indexnow 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 forPostToolUse[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.--flushsynchronously 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'sStophook
now chainsmm index --flushbeforemm session end --autoso the
final burst doesn't get left in the queue.--statusprints 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;--flushis 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/--forcewhen the same
path is enqueued twice.MEMTOMEM_INDEX_DEBOUNCE_QUEUEenv 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_allalready accepts an optional
paths=filter so adding the CLI surface is additive.Plugin
hooks.jsonand theclaude-code.mdHooks Automation Setup
snippet are updated byte-for-byte (parity test catches drift). The
PostToolUse[Write] hook now callsmm index --debounce-window 5;
the Stop hook chainsmm index --flushbefore
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 vialocalStorage. Backend
/api/memory-dirs/statusgainscreated_at(OS filesystem birth
time, ISO-8601 UTC) andlast_indexed(maxchunks.updated_at
under the dir prefix) — bothnullfor missing or un-indexed dirs.
Linux without statx-birthtime falls back tost_ctime, which can
shift onchmod/chownbut 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} chunksbadge 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 bothcreated_at
andlast_indexed. -
Optional chunk cleanup on memory_dir remove —
POST /api/memory-dirs/removeacceptsdelete_chunks: bool(default
false). Whentrue, every chunk whosesource_fileis 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...
v0.1.33
[0.1.33] — 2026-04-29
Added
mm upgradeCLI (#443) — wrapsuv tool install --refresh --reinstall memtomemwith process-level hygiene so an in-memory
pre-upgradememtomem-servercan't keep running the old code next
to the freshly written disk bytes (the v0.1.25 → v0.1.26 stale
.server.pidrepro that motivated this command). Probes the server
pid lock, sendsSIGTERM, escalates toSIGKILLafter--grace
seconds (default 5), unlinks the stale pid file, then runs the
reinstall. Extras are preserved automatically by reading the
currentuv toolreceipt — amemtomem[all]install stays[all]
across upgrades instead of silently regressing to a BM25-only base;
override with--extras onnx,webor suppress with--extras none.
--version X.Y.Zpins a release;--dry-runprints the plan;
--jsonemits 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_linksprovenance from session summary → source chunks
(RFC P1 Phase B-2). When the auto-summary path runs on
mem_session_end, the server now writeslink_type="summarizes"
rows from the newarchive:session:<id>chunk back to each source
chunk it summarized, bounded bysession_summary.max_summary_links
(default 50, newest first, tail dropped per RFC Open-Question-1).
Manualsummary=callers do not collect source chunks and so do
not write links. Each row is best-effort: a singleadd_chunk_link
failure is logged and skipped rather than abortingmem_session_end.- Auto LLM session summary on
mem_session_end(RFC P1 Phase B-1).
Whenmem_session_endis called withoutsummary=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. Newsession_summaryconfig
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 startSessionStart 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:--idempotentreturns 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;
--jsonemits 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(staleorcross_agent) carries the
per-row distinction. The plugin'shooks.json
ships a SessionStart entry callingmm session start --idempotent --auto-end-stale 24h --agent-id claude-codeand the
claude-code.mdHooks Automation Setup snippet matches byte-for-byte
via theTestPluginHooksDocsParityguard 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 inlinemm indexcommand in
packages/memtomem-claude-plugin/hooks/hooks.jsonwas indexing every
Writeregardless 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 —casestatements,
no external script. Adjust the patterns inhooks.jsonfor
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_snippetparity test
lockshooks.jsonand the docs snippet against silent drift on
future edits. Existing installs that copy-pasted the previous
snippet into~/.claude/settings.jsoncontinue 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
[0.1.32] — 2026-04-26
Changed (BREAKING)
-
Caller-supplied
namespace=/target=overrides on every
session-start entry point andmem_agent_sharenow reject malformed
shapes via a newvalidate_namespacegate. PR #491 / #494 / #498
closed the bypass onagent_iditself, but each session-start surface
also accepted an explicitnamespace=argument that landed verbatim
in storage — a Python / MCP / CLI caller could write
"agent-runtime:foo:bar"even thoughagent_idwas clean (the
bypass shape PR #495 review flagged as Concern 3). The new validator
now runs atmem_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), andmm 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 throughvalidate_agent_idso the override path can't widen
the contract that the directagent_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 viamem_ns_renamebefore
running session-start with the override. Closes #496 and #497. -
mem_agent_register,mem_agent_search, andmm agent register
now reject malformedagent_idvalues loudly instead of silently
rewriting them. PR #491 had wiredvalidate_agent_idinto 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 underagent-runtime:foo_bar,
while starting a session for that same id rejected it. The read /
write contract is now symmetric: anagent_ideither 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 hostileagent_idshapes viavalidate_agent_id
instead of the priorif not agent_idempty-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 genericValueError("agent_id must be a non-empty string")to
InvalidNameError("invalid agent-id 'X': ...")—InvalidNameError
is aValueErrorsubclass, soexcept 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_charsand removed it from
memtomem.storage.sqlite_backend.__all__. Disambiguates from the
strict caller-input validator
memtomem.constants.validate_namespace(value) -> strintroduced in
PR #491–#503, which raisesInvalidNameError. 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_namespacewrapper, 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
[0.1.31] — 2026-04-26
Added
- Web UI Context Gateway tabs (Artifact Sync, Skills, Commands,
Agents) graduate from--devto the polished prod surface. The
hardening that landed in #482 (round-trip## *-Specific
preservation), #483 (project-scopecodex_agentsdefault), and
#484 (settings-sync host-write confirm) closed the rough edges that
justified hiding these frommm webusers by default. The four
/api/context/*routers and matching settings nav buttons now
mount inprod; dev-only stays dev-only forNamespaces,
Sessions,Working Memory,Procedures,Health Report, and
Hook Files. Trust boundary unchanged — the loopback + single-user
Tier 1 deployment shape fromfeedback_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=settingsnow confirms before
writing settings files outside the project root (today only
~/.claude/settings.json); pass--yes/-yto skip the prompt.
The same gate is enforced insidegenerate_all_settings, so callers
that bypass the CLI —mem_context_generate/mem_context_sync
(MCP) andPOST /settings-sync(Web) — also refuse host writes
unless they passallow_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.claudecannot smuggle a host write past the gate.
Changed (BREAKING)
-
mm context syncnow writes Codex sub-agents to project-scope
.codex/agents/<name>.tomlinstead of user-scope
~/.codex/agents/<name>.toml. Previously everymm 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 syncfrom 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_agentssymmetric withclaude_agents(.claude/agents/)
andgemini_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 futuremm context synconce--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_dirsnow also picks up.codex/agents/<name>.toml
alongside Claude/Gemini, somm context detectand the Web UI
Context Gateway list project-scope Codex agents in their inventory. -
mem_add,mem_batch_add,mem_index, andmem_fetchnow
inherit the agent scope frommem_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_searchalready uses:- explicit
namespace=argument (escape hatch — wins everything), app.current_agent_id→agent-runtime:<id>(set by
mem_session_start),app.current_namespace(legacymem_ns_setpath),- config default.
Previously these write tools consulted only step 3, so calling
mem_session_start(agent_id="planner")followed bymem_add/
mem_index/mem_fetchsilently wrote todefaultinstead 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
withmem_agent_search's read-side resolution.mem_consolidate_applyalready routed through_mem_add_core, so
it picks up the new contract automatically.Who is affected:
- Pre-multi-agent users (no
mem_session_startcall): no
change.current_agent_idstaysNone; 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_setandmem_session_start
set in the same session: write target changes from the
mem_ns_setnamespace to the agent's namespace. Pass
namespace="<old-ns>"explicitly on the call to keep the old
destination, or callmem_session_end()before writing.
mem_addnow echoes the resolvedNamespace:line in its
confirmation output (mem_batch_add/mem_fetchalready did),
so the resolution is observable on the first call.Out of scope (tracked separately):
mem_searchandmem_recall(single-agent reads) —mem_search
keeps itscurrent_namespacesemantic; usemem_agent_searchto
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.
- explicit
Fixed
-
mm contextround-trip no longer silently drops## <Agent>-Specific
sections.extract_sections_from_agent_filemapped 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, …). Onmm context init(extract-existing)
followed bymm context generate, the override section was discarded
andmm context diffshowed[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,CopilotGeneratoremitted the override content without
any##heading, so a second round-trip absorbed it into the
preceding section; it now writes## Copilot-Specificlike the
other generators. -
mm session startandmm session wrapnow derive
agent-runtime:<id>namespace from--agent-id, mirroring the MCP
mem_session_startbehavior shipped in PR #475. Previously both
CLI surfaces silently lost--agent-idfor namespace derivation,
leaving sessions in thedefaultnamespace despite the multi-agent
contract the public page advertises (Persona-A trap from the
2026-04-26 surface review, gap G2).mm session wrapwas the more
consequential half — its default--agent-id headlessmeant every
mm session wrap -- claude -p ...invocation also landed in
defaultregardless of the wrapped command. Both surfaces now use a
shared_derive_session_namespacehelper with the same priority
chain as MCP: explicit--namespacewins; otherwise
agent-runtime:<agent-id>for non-default agents; otherwise
default.mm session startalso echoes the resolved namespace so
users can verify before continuing. -
mm webSync All button no longer toasts "Settings sync failed"
inprodmode. 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; inprodthe 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-onlyhooks-sync
section, so clicking the card landed on a dead tab. Both code
paths now self-gate onSTATE.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
[0.1.30] — 2026-04-26
Fixed
-
mm uninstallno longer reportspid Noneafter 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
secondmemtomem-serverstarted 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 — leavingmm uninstallwith no recorded pid to surface.
Fixed by switching to"a+"(no open-time truncate) and
performingseek(0); truncate(); write(pid)only after the
flock is held.mm uninstallnow distinguishes apid unknown
branch (truncate-race fingerprint or a partial-write startup
crash) frompid Noneand points atlsof <pidfile>for
diagnosis. Latent since the pid lock was introduced; predates
the #412 runtime-dir relocation. (PR #476) -
mem_agent_shareexample in serverinstructions=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, thenmem_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 tightenedtests/test_server_instructions.pywith
a signature-parity check: anytool(arg=...)form in INSTRUCTIONS
is now cross-checked againstinspect.signature(...), so future
parameter renames force the example to follow.
Added
- MCP
initializeresponse now carries server-levelinstructions.
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_register→mem_session_start→
mem_agent_search/mem_agent_share→mem_session_end),
namespace conventions (default/agent-runtime:<id>/shared:),
and common pitfalls (e.g.mem_addwithoutnamespace=consults
current_namespace, not the session'sagent-runtime:*scope).
Source of truth:memtomem/server/instructions.py; pinned by
tests/test_server_instructions.pyso 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 tomem_addinstead 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 fromagent_idwhen the caller doesn't pass an
explicitnamespace=. Previously the session record's
namespace fell back toapp.current_namespaceor"default",
ignoring the suppliedagent_ideven though the LangGraph
adapterMemtomemStore.start_agent_sessionalready auto-derived
agent-runtime:<id>. The MCP tool now matches that semantic.
Priority order: explicitnamespace=>agent-runtime:<id>
(whenagent_id != "default") >app.current_namespace>
"default". Only the session record's namespace field
changes;app.current_namespaceis untouched, so subsequent
mem_add/mem_searchwithout explicitnamespace=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: defaulteven thoughcurrent_agent_idwas set to
"planner", which the tester reasonably expected to flow into
the session row.
v0.1.29
[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 listno longer hides registered-but-empty agents.
list_namespace_metapreviously sourced rows from
chunks LEFT JOIN namespace_metadata, so a namespace with a
metadata row but zero chunks was filtered out byGROUP BY c.namespace. Aftermm agent register plannerthe agent
stayed invisible until someone wrote into
agent-runtime:planner. The Web UI'sGET /namespaces
response had the same blind spot through the same storage
method. Fixed by sourcing from the union of
namespace_metadata.namespaceandchunks.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.mdwith file:line
references against the v0.1.28 layout, an inbound link from
themem_addsection of the reference guide, and a reverse
cross-link from the v0.1.28 changelog entry. (PR #472)
v0.1.28
[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_addno 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_sectionstill seeded its line
counter from the heading line — so sub-chunks 2..N reported
start_line3–5 lines earlier than the body they actually
covered.mem_editof 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_linestill 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_entryemits> tags: ["a", "b"](explicit>prefix
on every line, JSON / double-quoted list) instead of the legacy
lazy-continuationtags: ['a', 'b'](Pythonrepr()). Old
files still parse — the chunker's section-leading parser accepts
both shapes — but a freshmem_addno longer relies on
CommonMark lazy continuation for the metadata block. -
mem_editand Web UI chunk edit preserve the per-entry header.
Newreplace_chunk_bodyhelper keeps the heading line and the
section-leading> created:/> tags:blockquote intact when
the caller passes body-onlynew_content. Bothmem_editand
the Web UIPATCH /api/chunks/{id}route use it. The Web UI
editor surfaceschunk.content(header-stripped by the chunker),
so this matters for Save-from-browser. To override the heading
explicitly, prefixnew_contentwith##and the call
reverts to a full replacement (preserving the pre-RFC semantic). -
mem_add(tags=...)now round-trips throughmem_search(tag_filter=...).
The markdown chunker promotes the per-entry blockquote header
(> created: .../> tags: [...]/ legacy lazy-continuation
tags: [...]) to first-classChunkMetadata.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 onmetadata.tagsforshared-from=<src>. Reindex
to backfill tags onto memories added by oldermem_addcalls.Note: re-indexed chunks regenerate UUIDs. Stripping the
blockquote header changescontent, which changes
content_hash = sha256(content)(models.py:97), which the differ
treats as a new chunk and assigns a freshuuid4(). After
reindex: any external pinning ofchunk_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_linkstable (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
passingnamespace=on every call.search(include_shared=...)is a
3-state toggle:None(auto: include shared if an agent is bound),
True(force include — raisesValueErrorif no agent is bound,
surfacing programming errors instead of degrading to a silent
un-pinned search),False(private only).add()defaults
namespace=Noneto the bound agent's private bucket; pass an
explicitnamespace="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 agentCLI:register,list,share, plus hiddendebug-resolve.
Mirrors themem_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
sharednamespace;mm agent share <chunk-id> [--target ...]
performs the same content copy +shared-from=<src>audit tag as the
MCP tool. The hiddenmm agent debug-resolvedumps the namespace
filtermem_agent_searchwould resolve given simulated
current_agent_id/current_namespace/--include-sharedinputs,
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_linkstable.
mem_agent_sharenow records a source→destination row in
chunk_links(indexed FK,ON DELETE SET NULLonsource_id,
ON DELETE CASCADEontarget_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 anO(fanout)indexed lookup
and stays correct across source delete. NewStoragePython 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-existingshared-from=<uuid>tags and populates link rows
so audit chains authored on older versions resolve
structurally; unresolvable source UUIDs storesource_id=NULL.
(PR #469, PR #470) -
mem_importgainson_conflictandpreserve_ids(bundle schema v2).
Bundles now carry per-chunkchunk_id+content_hashso importers can
dedup by content across instances.on_conflictaccepts"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=Truereuses 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_importMCP tool,import_chunks(), andPOST /api/export/import
(on_conflict/preserve_idsmultipart fields). (PR #451 follow-up)
Changed
-
agent-runtime:is now hidden from defaultmem_search(behavior
change).search.system_namespace_prefixesdefault extended from
["archive:"]to["archive:", "agent-runtime:"]so per-agent
private chunks (created bymem_agent_register/mem_agent_search)
no longer leak intonamespace=Nonesearch results — restoring the
isolation guarantee advertised on the multi-agent guide. The hidden
count surfaces through the existinghidden_system_nshint, and
mem_agent_searchis unaffected because it pinsnamespace=
explicitly. To restore the pre-change behaviour, override
search.system_namespace_prefixes: []inconfig.json(or drop
agent-runtime:from the list while keepingarchive:). New
memtomem.constantsmodule exportsAGENT_NAMESPACE_PREFIX,
SHARED_NAMESPACE, anddefault_system_prefixesso 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 passon_conflict="duplicate"explicitly.
Fixed
mem_agent_sharenow stamps ashared-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...
v0.1.27
[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_flocktookLOCK_EXand
calledsys.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'sLOCK_EXstill 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)