superlocalmemory 4.1.2 → 4.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.claude-plugin/marketplace.json +19 -1
  2. package/CHANGELOG.md +65 -0
  3. package/README.md +3 -3
  4. package/ide/configs/codex-mcp.toml +3 -1
  5. package/package.json +1 -1
  6. package/plugin/.claude-plugin/plugin.json +1 -1
  7. package/plugin/.mcp.json +1 -3
  8. package/plugin/CLAUDE.md +3 -3
  9. package/plugin/agents/slm-governance-advisor.md +1 -1
  10. package/plugin/agents/slm-loop-runner.md +1 -1
  11. package/plugin/agents/slm-memory-advisor.md +1 -1
  12. package/plugin/agents/slm-optimize-advisor.md +1 -1
  13. package/plugin/requirements.txt +1 -1
  14. package/plugin/scripts/slm-launch +100 -31
  15. package/plugin/skills/slm-cache/SKILL.md +1 -1
  16. package/plugin/skills/slm-compress/SKILL.md +1 -1
  17. package/plugin/skills/slm-governance/SKILL.md +1 -1
  18. package/plugin/skills/slm-graph/SKILL.md +1 -1
  19. package/plugin/skills/slm-loop/SKILL.md +1 -1
  20. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  21. package/plugin/skills/slm-profile/SKILL.md +1 -1
  22. package/plugin/skills/slm-recall/SKILL.md +1 -1
  23. package/plugin/skills/slm-remember/SKILL.md +1 -1
  24. package/plugin/skills/slm-scope/SKILL.md +1 -1
  25. package/plugin/skills/slm-session/SKILL.md +1 -1
  26. package/plugin/skills/slm-status/SKILL.md +1 -1
  27. package/plugin-src/rules/AGENTS.md +1 -1
  28. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-governance/SKILL.md +1 -1
  31. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  32. package/plugin-src/skills/slm-loop/SKILL.md +1 -1
  33. package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
  34. package/plugin-src/skills/slm-profile/SKILL.md +1 -1
  35. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  36. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  37. package/plugin-src/skills/slm-scope/SKILL.md +1 -1
  38. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  39. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  40. package/pyproject.toml +1 -1
  41. package/src/superlocalmemory/__init__.py +1 -1
  42. package/src/superlocalmemory/cli/commands.py +8 -2
  43. package/src/superlocalmemory/cli/daemon.py +69 -6
@@ -21,7 +21,25 @@
21
21
  "license": "AGPL-3.0-or-later",
22
22
  "name": "superlocalmemory",
23
23
  "source": "./plugin",
24
- "version": "4.1.2"
24
+ "version": "4.1.4"
25
+ },
26
+ {
27
+ "author": {
28
+ "name": "Qualixar"
29
+ },
30
+ "description": "Local-first agent memory with auditable hybrid retrieval — the Codex build, with Codex-shaped rules, hooks and launcher.",
31
+ "homepage": "https://github.com/qualixar/superlocalmemory",
32
+ "keywords": [
33
+ "memory",
34
+ "mcp",
35
+ "agents",
36
+ "local-first",
37
+ "context-compression"
38
+ ],
39
+ "license": "AGPL-3.0-or-later",
40
+ "name": "superlocalmemory-codex",
41
+ "source": "./codex-plugin",
42
+ "version": "4.1.4"
25
43
  }
26
44
  ]
27
45
  }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,71 @@ All notable changes to SuperLocalMemory will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.1.4] — A busy daemon is not a dead daemon
9
+
10
+ ### Fixed
11
+ - **`slm serve stop` and `slm restart` could report a live daemon as "not
12
+ running."** `daemon_request()` preflights every call with `GET /health`,
13
+ which needs the daemon's single-threaded event loop to be free to answer.
14
+ `/maintenance/run` and `/consolidate/cognitive` run multi-second (sometimes
15
+ multi-minute) work directly inline in their handlers with no thread
16
+ offload — unlike `/recall`, fixed for exactly this in v3.4.52 — so every
17
+ request blocks for as long as they run, health included. Reproduced live: a
18
+ genuine `/maintenance/run` call held the loop long enough that 15 of 15
19
+ health polls in that window timed out at the 2s cap while the process never
20
+ stopped listening. `stop_daemon()` now checks process liveness directly
21
+ (PID plus a clock-independent start token, no HTTP) before deciding whether
22
+ a stop is owed, and sends the stop itself without the health preflight —
23
+ the daemon still authenticates the request by its capability header on
24
+ arrival, so no ownership guarantee is lost, only the stall-prone round
25
+ trip. `cmd_restart`'s Step 1 uses the same liveness check, so a busy-but-alive
26
+ daemon is no longer skipped as "already stopped," which previously cascaded
27
+ into Step 3 refusing to start a second daemon and failing the whole restart.
28
+ The underlying blocking handlers are unchanged by this fix — recall and
29
+ remember calls can still be delayed by a running maintenance pass, just not
30
+ misreported as a dead daemon.
31
+
32
+ ### Added
33
+ - **A one-command Antigravity plugin install.** `agy`'s plugin installer only
34
+ accepts a local directory — there is no marketplace or remote-install
35
+ path — so Claude Code and Codex had a real one-liner and Antigravity did
36
+ not. `scripts/install-antigravity-plugin.sh` closes the gap: a shallow,
37
+ sparse checkout of `antigravity-plugin/` into a local cache, then
38
+ `agy plugin install` against it. Idempotent; safe to re-run to update.
39
+
40
+ ## [4.1.3] — Your install, your tools, your memories
41
+
42
+ ### Fixed
43
+ - **Installing the plugin could point your editor at a different, empty set of
44
+ memories.** Its server configuration pinned a data directory of its own, so on
45
+ a machine already using SLM the editor read somewhere else entirely — measured
46
+ here, that directory held 28 KB while the real one held 611 MB and 5,370
47
+ memories. Nothing failed and nothing warned; it simply looked like everything
48
+ was gone. The plugin no longer says where your memories live.
49
+ - **Installing the plugin could take away tools you had turned on.** Three of the
50
+ four editor builds forced a narrower tool set — 31 tools, without the eight for
51
+ coordinating between sessions. If you had deliberately enabled more, installing
52
+ something removed them. No build states an opinion about this any more.
53
+ - **Installing the plugin could give you a second copy of SuperLocalMemory.** Two
54
+ launchers preferred a private environment of their own over the one you already
55
+ had from `pip`, `pipx` or `npm`. So a machine that had ever created one kept
56
+ using it — including after you upgraded properly — and two copies read the same
57
+ memories, with whichever started first deciding which one answered. Both now
58
+ use what you already have, in either install order, and fall back to their own
59
+ only on a machine with none. When a leftover copy is found beside a real
60
+ install, it is named, with both version numbers, instead of being used quietly.
61
+ - **The plugin would not start at all outside Claude Code.** The path it built
62
+ came from a setting only Claude Code provides, so on Codex and Antigravity it
63
+ pointed nowhere and the server never came up — with no error explaining why.
64
+ - **Codex reported itself as Claude Code.** The catalogue offered one build, the
65
+ Claude Code one, so Codex installed that and every memory written from Codex was
66
+ filed under the wrong tool. Codex now has its own entry and its own build,
67
+ with the Codex-shaped rules, hooks and launcher that were never being delivered.
68
+
69
+ ### Changed
70
+ - **Antigravity is supported.** It had no plugin at all. It now has the same
71
+ twelve skills, four sub-agents, command and hooks as every other editor.
72
+
8
73
  ## [4.1.2] — The monitor that was watching nothing
9
74
 
10
75
  ### Fixed
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  </picture>
6
6
  </p>
7
7
 
8
- <h1 align="center">SuperLocalMemory V4.1.2</h1>
8
+ <h1 align="center">SuperLocalMemory V4.1.4</h1>
9
9
 
10
10
  <h2 align="center">Rent the LLM. Own the memory.</h2>
11
11
 
@@ -27,12 +27,12 @@ guarantee here is stated as a falsifiable invariant, tested under an adversarial
27
27
  negative control, and shipped with the harness that regenerates the evidence:
28
28
  <code>python benchmark/run_all.py --trials 200 --output-dir results/</code>. What each experiment
29
29
  does <em>not</em> exercise is stated too.</p>
30
- <p align="center"><code>v4.1.2</code> — one control plane: <strong>SLM-Mesh</strong> peer coordination · multi-scope memory (personal / shared / global) · profiles · Cache · Compress · 7-layer retrieval · code graph · Entity Explorer · skill evolution · Modes A/B/C · GDPR retention &amp; audit chain · bounded loops — across CLI, MCP, dashboard, the <strong>Claude plugin</strong>, the <strong>Codex add-on</strong>, and documented IDE integrations.<br/>
30
+ <p align="center"><code>v4.1.4</code> — one control plane: <strong>SLM-Mesh</strong> peer coordination · multi-scope memory (personal / shared / global) · profiles · Cache · Compress · 7-layer retrieval · code graph · Entity Explorer · skill evolution · Modes A/B/C · GDPR retention &amp; audit chain · bounded loops — across CLI, MCP, dashboard, the <strong>Claude plugin</strong>, the <strong>Codex add-on</strong>, and documented IDE integrations.<br/>
31
31
  Proxy: <code>slm wrap claude</code> &nbsp;·&nbsp; MCP: add <code>slm_compress</code> to your config &nbsp;·&nbsp; Skill: zero-config</p>
32
32
  <p align="center"><strong>Four public arXiv preprints</strong> · V4: <a href="https://arxiv.org/abs/2608.08253">arXiv:2608.08253</a> · companion archive: <a href="https://zenodo.org/records/21853302">Zenodo 21853302</a> (<a href="https://doi.org/10.5281/zenodo.21853302">DOI 10.5281/zenodo.21853302</a>) · prior preprints: <a href="https://arxiv.org/abs/2603.02240">2603.02240</a> · <a href="https://arxiv.org/abs/2603.14588">2603.14588</a> · <a href="https://arxiv.org/abs/2604.04514">2604.04514</a>.</p>
33
33
 
34
34
  <p align="center">
35
- <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.1.2-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.1.2 — Current Release"/></a>
35
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.1.4-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.1.4 — Current Release"/></a>
36
36
  <a href="https://arxiv.org/abs/2608.08253"><img src="https://img.shields.io/badge/arXiv-2608.08253-b31b1b?style=for-the-badge&logo=arxiv&logoColor=white" alt="SuperLocalMemory 4.0 paper on arXiv:2608.08253"/></a>
37
37
  <a href="https://zenodo.org/records/21853302"><img src="https://img.shields.io/badge/Zenodo-10.5281%2Fzenodo.21853302-1682D4?style=for-the-badge&logo=zenodo&logoColor=white" alt="V4 paper on Zenodo: 10.5281/zenodo.21853302"/></a>
38
38
  <a href="https://arxiv.org/abs/2603.14588"><img src="https://img.shields.io/badge/arXiv-2603.14588-b31b1b?style=for-the-badge&logo=arxiv&logoColor=white" alt="arXiv Paper"/></a>
@@ -8,4 +8,6 @@
8
8
  [mcp_servers.superlocalmemory]
9
9
  command = "slm"
10
10
  args = ["mcp"]
11
- env = { SLM_MCP_PROFILE = "code", SLM_AGENT_ID = "codex" }
11
+ # Agent id only. Pinning a profile here would remove tools you had enabled;
12
+ # unset resolves to the full tool set.
13
+ env = { SLM_AGENT_ID = "codex" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "4.1.2",
3
+ "version": "4.1.4",
4
4
  "description": "Local-first agent memory with MCP and an agent-native CLI. Documented clients include Claude Code, Cursor, and Windsurf.",
5
5
  "keywords": [
6
6
  "ai-memory",
@@ -15,5 +15,5 @@
15
15
  "mcpServers": "./.mcp.json",
16
16
  "name": "superlocalmemory",
17
17
  "repository": "https://github.com/qualixar/superlocalmemory",
18
- "version": "4.1.2"
18
+ "version": "4.1.4"
19
19
  }
package/plugin/.mcp.json CHANGED
@@ -4,9 +4,7 @@
4
4
  "command": "${CLAUDE_PLUGIN_ROOT}/scripts/slm-launch",
5
5
  "args": [],
6
6
  "env": {
7
- "SLM_MCP_PROFILE": "code",
8
- "SLM_AGENT_ID": "claude_code",
9
- "SLM_DATA_DIR": "${CLAUDE_PLUGIN_DATA}"
7
+ "SLM_AGENT_ID": "claude_code"
10
8
  }
11
9
  }
12
10
  }
package/plugin/CLAUDE.md CHANGED
@@ -1,4 +1,4 @@
1
- <!-- BEGIN SuperLocalMemory v4.1.2 -->
1
+ <!-- BEGIN SuperLocalMemory v4.1.4 -->
2
2
 
3
3
  ## SuperLocalMemory (SLM) — Agent Rules
4
4
 
@@ -39,6 +39,6 @@ slm-recall · slm-remember · slm-session · slm-status · slm-cache · slm-comp
39
39
  ### Subagents
40
40
  slm-memory-advisor (memory decisions, session hygiene, scope/profile guidance) · slm-optimize-advisor (context compression + KV cache) · slm-governance-advisor (scope/roles/compliance/GDPR)
41
41
 
42
- <!-- END SuperLocalMemory v4.1.2 -->
42
+ <!-- END SuperLocalMemory v4.1.4 -->
43
43
 
44
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
44
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -77,4 +77,4 @@ slm-scope · slm-governance · slm-profile · slm-remember · slm-recall
77
77
  # What NOT to do
78
78
  Never session_init twice; never forget without dry-run preview; never store secrets; never bypass role checks; never claim an erasure succeeded without verifying via recall.
79
79
 
80
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
80
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -68,4 +68,4 @@ assessment. The gate is the authority.
68
68
 
69
69
  ---
70
70
 
71
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
71
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -46,4 +46,4 @@ slm-recall · slm-remember · slm-session · slm-scope · slm-profile · slm-gov
46
46
  # What NOT to do
47
47
  Never session_init twice; never forget dry_run=False without reporting preview; never dump a whole file into remember; never invent a memory; never claim "saved" without success:true / clean CLI exit; never bypass scope or governance restrictions.
48
48
 
49
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
49
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -41,4 +41,4 @@ slm-compress · slm-cache · slm-status · slm-profile
41
41
  # What NOT to do
42
42
  Never compress code-for-edit/JSON-to-parse/<500 chars; never store secrets/ccr_ids; never let optimize failure block/alter the task; never claim a specific savings %; never carry ccr_ids across profile switches.
43
43
 
44
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
44
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -1 +1 @@
1
- superlocalmemory==4.1.2
1
+ superlocalmemory==4.1.4
@@ -1,62 +1,131 @@
1
1
  #!/usr/bin/env bash
2
- # slm-launch — WP-F SuperLocalMemory MCP launcher (POSIX)
2
+ # slm-launch — SuperLocalMemory MCP launcher (POSIX)
3
3
  #
4
4
  # Cross-platform counterpart: slm-launch.bat (Windows)
5
- # Referenced by plugin/.mcp.json as the MCP server command.
5
+ # Referenced by .mcp.json as the MCP server command.
6
6
  #
7
- # Resolves the correct slm binary for POSIX and joins the namespace daemon
8
- # before opening the MCP stdio transport. This preserves one writer for all
9
- # parallel Claude sessions that share CLAUDE_PLUGIN_DATA.
7
+ # WHAT THIS RESOLVES, AND WHY THE DEFAULT CHANGED
10
8
  #
11
- # On Windows, Claude Code invokes slm-launch.bat instead (same-stem, .bat extension).
9
+ # It used to default to a venv the plugin owns:
10
+ # "${CLAUDE_PLUGIN_DATA}/venv/bin/slm". So anyone who already had SuperLocalMemory
11
+ # — from pip, pipx or npm — got a SECOND installation the moment they installed
12
+ # the plugin, on its own schedule, upgraded separately, and easy to leave behind
13
+ # a release. Installing a plugin should not fork your installation.
12
14
  #
13
- # Environment:
14
- # CLAUDE_PLUGIN_DATA persistent data dir where the plugin venv lives
15
- # SLM_DATA_DIR — optional; SLM data dir (default: ~/.superlocalmemory)
16
- # SLM_LAUNCHER — optional; which slm binary to use:
17
- # plugin (default) the plugin venv binary
18
- # system — the system `slm` on PATH
19
- # <path> — an explicit slm binary (leading ~ expanded)
15
+ # It was also Claude-Code-only. ${CLAUDE_PLUGIN_DATA} is set by Claude Code and
16
+ # by nothing else, so on Codex or Antigravity the path collapsed to
17
+ # "/venv/bin/slm" and the server simply did not start.
18
+ #
19
+ # The default is now: USE THE SLM THAT IS ALREADY INSTALLED. A plugin-owned venv
20
+ # is the fallback for a machine that has none, not the first choice.
21
+ #
22
+ # Environment (all optional):
23
+ # SLM_LAUNCHER auto (default) — prefer an installed slm, else the plugin venv
24
+ # system — require the slm on PATH
25
+ # plugin — require the plugin's own venv
26
+ # <path> — an explicit slm binary (leading ~ expanded)
27
+ # SLM_DATA_DIR SLM data dir (default: ~/.superlocalmemory)
28
+ # CLAUDE_PLUGIN_DATA where a plugin-owned venv lives, when there is one
29
+ #
30
+ # Deliberately NOT set here: the MCP profile. Which tools a host exposes is the
31
+ # user's decision, and a plugin that narrows it takes away tools they configured.
20
32
 
21
33
  _SLM_DATA="${SLM_DATA_DIR:-$HOME/.superlocalmemory}"
34
+ _PLUGIN_BIN="${CLAUDE_PLUGIN_DATA:+${CLAUDE_PLUGIN_DATA}/venv/bin/slm}"
22
35
 
23
- # If a system daemon is already running (its pidfile points at a live process),
24
- # there is no need to boot the plugin's own venv daemon — we can open an MCP
25
- # writer against the daemon that is already up. This skips a redundant, slow
26
- # venv bootstrap on every session start.
36
+ # Is a daemon already up for this data dir? If so we join it rather than boot a
37
+ # second one one writer for every session that shares the store.
27
38
  _daemon_running=0
28
39
  if [ -f "${_SLM_DATA}/daemon.pid" ] \
29
40
  && kill -0 "$(cat "${_SLM_DATA}/daemon.pid" 2>/dev/null)" 2>/dev/null; then
30
41
  _daemon_running=1
31
42
  fi
32
43
 
33
- case "${SLM_LAUNCHER:-plugin}" in
34
- plugin)
35
- SLM_BIN="${CLAUDE_PLUGIN_DATA}/venv/bin/slm"
36
- # Only bootstrap the owned daemon when one is not already running.
37
- if [ "${_daemon_running}" -eq 0 ]; then
38
- if ! "${SLM_BIN}" serve start >&2; then
44
+ # Expand a leading ~ WITHOUT eval — eval on an environment value is a
45
+ # command-injection foot-gun. Only tilde expansion; pass expanded paths for vars.
46
+ _expand_tilde() {
47
+ case "$1" in
48
+ "~"*) printf '%s' "${HOME}${1#\~}" ;;
49
+ *) printf '%s' "$1" ;;
50
+ esac
51
+ }
52
+
53
+ # Start a daemon with the chosen binary unless one is already serving.
54
+ _ensure_daemon() {
55
+ [ "${_daemon_running}" -eq 1 ] && return 0
56
+ "$1" serve start >&2
57
+ }
58
+
59
+ case "${SLM_LAUNCHER:-auto}" in
60
+ auto)
61
+ # 1. An slm already on PATH — the pip, pipx or npm install the user has.
62
+ # This is the common case and the one that must not be forked.
63
+ if command -v slm >/dev/null 2>&1; then
64
+ # Both orderings end up here, and both must converge on ONE
65
+ # environment:
66
+ # installed first, plugin second -> nothing was ever forked;
67
+ # plugin first, installed second -> the plugin venv is now the
68
+ # older copy and must stop being used.
69
+ # They already share one store, because this launcher sets no
70
+ # SLM_DATA_DIR and both resolve ~/.superlocalmemory. What is left to
71
+ # go wrong is two DIFFERENT VERSIONS serving that one store: whoever
72
+ # starts the daemon first decides which code the other one talks to.
73
+ # So when a leftover plugin venv is found beside a real install, say
74
+ # so, with both versions named. Silent duplication is how a store
75
+ # ends up served by a release nobody thinks they are running.
76
+ if [ -n "${_PLUGIN_BIN}" ] && [ -x "${_PLUGIN_BIN}" ]; then
77
+ _sys_v="$(slm --version 2>/dev/null | awk '{print $NF}')"
78
+ _plg_v="$("${_PLUGIN_BIN}" --version 2>/dev/null | awk '{print $NF}')"
79
+ if [ -n "${_plg_v}" ] && [ "${_sys_v}" != "${_plg_v}" ]; then
80
+ echo "SLM plugin: using the installed slm ${_sys_v:-?} and IGNORING a leftover plugin venv at ${_PLUGIN_BIN} (${_plg_v})." >&2
81
+ echo " One store, one version. Remove the venv to stop it drifting: rm -rf \"${CLAUDE_PLUGIN_DATA}/venv\"" >&2
82
+ fi
83
+ fi
84
+ _ensure_daemon "$(command -v slm)" || \
85
+ echo "SLM plugin: daemon did not start; continuing as a direct MCP writer." >&2
86
+ exec slm mcp
87
+ fi
88
+ # 2. No system install. Use the plugin's own venv if this host gave us
89
+ # somewhere to keep one.
90
+ if [ -n "${_PLUGIN_BIN}" ] && [ -x "${_PLUGIN_BIN}" ]; then
91
+ if ! _ensure_daemon "${_PLUGIN_BIN}"; then
39
92
  echo "SLM plugin: unable to start the owned daemon; refusing a direct MCP writer." >&2
40
93
  exit 1
41
94
  fi
95
+ exec "${_PLUGIN_BIN}" mcp
42
96
  fi
43
- exec "${SLM_BIN}" mcp
97
+ # 3. Neither. Say which two things were looked for, so the fix is obvious.
98
+ echo "SLM plugin: no SuperLocalMemory found." >&2
99
+ echo " Looked for: 'slm' on PATH, and ${_PLUGIN_BIN:-a plugin venv (this host sets no plugin data dir)}." >&2
100
+ echo " Install it with: pipx install superlocalmemory" >&2
101
+ exit 1
44
102
  ;;
45
103
  system)
104
+ if ! command -v slm >/dev/null 2>&1; then
105
+ echo "SLM_LAUNCHER=system but no 'slm' on PATH." >&2
106
+ exit 1
107
+ fi
108
+ _ensure_daemon "$(command -v slm)" || true
46
109
  exec slm mcp
47
110
  ;;
111
+ plugin)
112
+ if [ -z "${_PLUGIN_BIN}" ]; then
113
+ echo "SLM_LAUNCHER=plugin but this host sets no CLAUDE_PLUGIN_DATA." >&2
114
+ exit 1
115
+ fi
116
+ if ! _ensure_daemon "${_PLUGIN_BIN}"; then
117
+ echo "SLM plugin: unable to start the owned daemon; refusing a direct MCP writer." >&2
118
+ exit 1
119
+ fi
120
+ exec "${_PLUGIN_BIN}" mcp
121
+ ;;
48
122
  *)
49
- # Explicit binary path. Expand a leading ~ to $HOME WITHOUT eval — eval
50
- # on an environment value is a command-injection foot-gun. Only tilde
51
- # expansion is supported here; pass an already-expanded path for $VARs.
52
- case "${SLM_LAUNCHER}" in
53
- "~"*) _SLM_BIN="${HOME}${SLM_LAUNCHER#\~}" ;;
54
- *) _SLM_BIN="${SLM_LAUNCHER}" ;;
55
- esac
123
+ _SLM_BIN="$(_expand_tilde "${SLM_LAUNCHER}")"
56
124
  if [ ! -x "${_SLM_BIN}" ]; then
57
125
  echo "SLM_LAUNCHER is not an executable slm binary: ${_SLM_BIN}" >&2
58
126
  exit 1
59
127
  fi
128
+ _ensure_daemon "${_SLM_BIN}" || true
60
129
  exec "${_SLM_BIN}" mcp
61
130
  ;;
62
131
  esac
@@ -145,4 +145,4 @@ These subcommands control daemon-level cache settings. They do not read or write
145
145
 
146
146
  ---
147
147
 
148
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
148
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -147,4 +147,4 @@ Content over 1 MB (1 000 000 bytes UTF-8) is processed but `reversible` is force
147
147
 
148
148
  ---
149
149
 
150
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
150
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -245,4 +245,4 @@ Before running any destructive operation (`forget`, `compact_memories`):
245
245
 
246
246
  ---
247
247
 
248
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
248
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -312,4 +312,4 @@ profile. See `slm-profile` for the full profile switching workflow.
312
312
 
313
313
  ---
314
314
 
315
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
315
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -96,4 +96,4 @@ paused, name the approval needed; when errored, quote the short detail.
96
96
 
97
97
  ---
98
98
 
99
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
99
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -279,4 +279,4 @@ mesh availability.
279
279
 
280
280
  ---
281
281
 
282
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
282
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -146,4 +146,4 @@ Name them differently in your MCP config (e.g. `superlocalmemory-personal` and
146
146
 
147
147
  ---
148
148
 
149
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
149
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -323,4 +323,4 @@ before recalling, then switch back. See `slm-profile` for workspace switching.
323
323
 
324
324
  ---
325
325
 
326
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
326
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -270,4 +270,4 @@ different workspace, use `switch_profile` first. See `slm-profile`.
270
270
 
271
271
  ---
272
272
 
273
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
273
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -173,4 +173,4 @@ to review the impact. See `slm-remember` for the full deletion discipline.
173
173
 
174
174
  ---
175
175
 
176
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
176
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -253,4 +253,4 @@ explicitly and call `recall` with `include_global`/`include_shared` after
253
253
 
254
254
  ---
255
255
 
256
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
256
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -163,4 +163,4 @@ multi-profile setup. To switch the active profile, see `slm-profile`.
163
163
 
164
164
  ---
165
165
 
166
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
166
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -137,4 +137,4 @@ When the SLM MCP server is unavailable, use these CLI equivalents:
137
137
  - **slm-optimize-advisor** — context compression and KV cache
138
138
  - **slm-governance-advisor** — scope/role compliance, retention policies, GDPR
139
139
 
140
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
140
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -145,4 +145,4 @@ These subcommands control daemon-level cache settings. They do not read or write
145
145
 
146
146
  ---
147
147
 
148
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
148
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -147,4 +147,4 @@ Content over 1 MB (1 000 000 bytes UTF-8) is processed but `reversible` is force
147
147
 
148
148
  ---
149
149
 
150
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
150
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -245,4 +245,4 @@ Before running any destructive operation (`forget`, `compact_memories`):
245
245
 
246
246
  ---
247
247
 
248
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
248
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -312,4 +312,4 @@ profile. See `slm-profile` for the full profile switching workflow.
312
312
 
313
313
  ---
314
314
 
315
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
315
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -96,4 +96,4 @@ paused, name the approval needed; when errored, quote the short detail.
96
96
 
97
97
  ---
98
98
 
99
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
99
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
@@ -279,4 +279,4 @@ mesh availability.
279
279
 
280
280
  ---
281
281
 
282
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
282
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -146,4 +146,4 @@ Name them differently in your MCP config (e.g. `superlocalmemory-personal` and
146
146
 
147
147
  ---
148
148
 
149
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
149
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -323,4 +323,4 @@ before recalling, then switch back. See `slm-profile` for workspace switching.
323
323
 
324
324
  ---
325
325
 
326
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
326
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -270,4 +270,4 @@ different workspace, use `switch_profile` first. See `slm-profile`.
270
270
 
271
271
  ---
272
272
 
273
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
273
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -173,4 +173,4 @@ to review the impact. See `slm-remember` for the full deletion discipline.
173
173
 
174
174
  ---
175
175
 
176
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
176
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -253,4 +253,4 @@ explicitly and call `recall` with `include_global`/`include_shared` after
253
253
 
254
254
  ---
255
255
 
256
- *SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later*
256
+ *SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later*
@@ -163,4 +163,4 @@ multi-profile setup. To switch the active profile, see `slm-profile`.
163
163
 
164
164
  ---
165
165
 
166
- SuperLocalMemory v4.1.2 · Qualixar · AGPL-3.0-or-later
166
+ SuperLocalMemory v4.1.4 · Qualixar · AGPL-3.0-or-later
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "superlocalmemory"
3
- version = "4.1.2"
3
+ version = "4.1.4"
4
4
  description = "Local-first agent memory with auditable hybrid retrieval"
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-or-later"
@@ -32,7 +32,7 @@ if "OMP_NUM_THREADS" not in os.environ:
32
32
  os.environ["OMP_NUM_THREADS"] = "2"
33
33
  # ---------------------------------------------------------------------------
34
34
 
35
- __version__ = "4.1.2"
35
+ __version__ = "4.1.4"
36
36
 
37
37
  _REQUIRED_VERSIONS = {
38
38
  "sentence_transformers": "5.3.0",
@@ -754,13 +754,19 @@ def cmd_restart(args: Namespace) -> None:
754
754
  # Step 1: stop only the descriptor-owned daemon. Its graceful shutdown
755
755
  # owns worker termination; process-name-wide scans are forbidden.
756
756
  from superlocalmemory.cli.daemon import (
757
- is_daemon_running,
757
+ owned_daemon_process_alive,
758
758
  read_descriptor,
759
759
  stop_daemon,
760
760
  wait_for_owned_daemon_shutdown,
761
761
  )
762
762
 
763
- was_running = is_daemon_running()
763
+ # v4.1.4: process liveness, not HTTP health-readiness, decides whether a
764
+ # stop is owed. is_daemon_running() additionally requires a live /health
765
+ # response, which a daemon busy inside /maintenance/run or
766
+ # /consolidate/cognitive cannot give for the duration of that call even
767
+ # though it is fully alive and holding the port — see stop_daemon()'s
768
+ # docstring for the reproduced failure this fixes.
769
+ was_running = owned_daemon_process_alive()
764
770
  owned_descriptor = read_descriptor() if was_running else None
765
771
  stopped = stop_daemon() if was_running else True
766
772
  if stopped and was_running:
@@ -254,6 +254,29 @@ def is_daemon_running() -> bool:
254
254
  return legacy is not None
255
255
 
256
256
 
257
+ def owned_daemon_process_alive() -> bool:
258
+ """Return whether an owned daemon *process* is alive, HTTP aside.
259
+
260
+ ``is_daemon_running()`` additionally requires a live, matching
261
+ ``/health`` response, which conflates two different questions: "is there
262
+ a process I need to stop" and "is it ready to serve requests right now."
263
+ A daemon whose event loop is synchronously blocked by a long-running
264
+ handler (``/maintenance/run``, ``/consolidate/cognitive``) cannot answer
265
+ the second for the duration of that call, but the answer to the first is
266
+ still yes. Callers that only need to decide whether a stop is owed
267
+ (``slm restart`` Step 1) should use this instead, so a transiently busy
268
+ daemon is not skipped as "already stopped" while it keeps running and
269
+ holding the port — which then made Step 3 refuse to start a second
270
+ daemon on the still-occupied port and fail the whole restart.
271
+ """
272
+ descriptor = read_descriptor()
273
+ if descriptor is not None:
274
+ return _descriptor_process_is_alive(descriptor)
275
+ if descriptor_path().exists():
276
+ return False
277
+ return _verified_legacy_health() is not None
278
+
279
+
257
280
  def _fetch_health(port: int) -> dict | None:
258
281
  """Fetch loopback health without following cross-namespace discovery."""
259
282
  try:
@@ -349,8 +372,25 @@ def daemon_request(
349
372
  timeout_seconds: float = 30.0,
350
373
  expected_descriptor=_EXPECTED_DESCRIPTOR_UNSET,
351
374
  expected_legacy: dict | None = None,
375
+ verify_health: bool = True,
352
376
  ) -> dict | None:
353
- """Send a request only after validating the owned daemon identity."""
377
+ """Send a request only after validating the owned daemon identity.
378
+
379
+ ``verify_health`` — when True (the default), a ``GET /health`` preflight
380
+ must succeed and match the descriptor before the real request is sent.
381
+ That preflight needs the daemon's event loop to be free to answer HTTP,
382
+ which is a *readiness* question, not a *liveness* one: a daemon whose
383
+ loop is synchronously blocked by a long-running handler (e.g.
384
+ ``/maintenance/run``, ``/consolidate/cognitive``, neither of which is
385
+ offloaded to a thread the way ``/recall`` was for exactly this reason in
386
+ v3.4.52) cannot answer /health for the duration of that call even though
387
+ the process is fully alive and listening. Callers that have already
388
+ proven process-level ownership some other way (e.g. ``stop_daemon()`` via
389
+ ``_descriptor_process_is_alive``) should pass ``verify_health=False`` so a
390
+ busy-but-alive daemon does not get misreported as not running. Only
391
+ meaningful for the descriptor path — the legacy bridge has no capability
392
+ header and still needs health to identify its target.
393
+ """
354
394
  legacy = None
355
395
  if expected_legacy is not None:
356
396
  # Legacy daemons have no capability header. Bind the compatibility
@@ -377,11 +417,12 @@ def daemon_request(
377
417
  capability: str | None = None
378
418
  target_instance: str | None = None
379
419
  if descriptor is not None:
380
- health = _fetch_health(descriptor.port)
381
- if health is None or not descriptor_matches_health(descriptor, health):
382
- return None
383
- if method.upper() == "GET" and path == "/health":
384
- return health
420
+ if verify_health:
421
+ health = _fetch_health(descriptor.port)
422
+ if health is None or not descriptor_matches_health(descriptor, health):
423
+ return None
424
+ if method.upper() == "GET" and path == "/health":
425
+ return health
385
426
  port = descriptor.port
386
427
  capability = descriptor.capability
387
428
  target_instance = descriptor.instance_id
@@ -796,16 +837,38 @@ def stop_daemon() -> bool:
796
837
  HTTP capability; the daemon itself terminates its child process tree.
797
838
  Success means the owned process exited and released its listener, not just
798
839
  that the asynchronous stop request was accepted.
840
+
841
+ A busy daemon is not a dead daemon. ``daemon_request()`` normally
842
+ preflights every call with ``GET /health`` before sending it, but that
843
+ preflight needs the daemon's single-threaded event loop to be free to
844
+ answer HTTP. ``/maintenance/run`` and ``/consolidate/cognitive`` run
845
+ multi-second (sometimes multi-minute) synchronous work directly inline in
846
+ their handlers with no thread offload, which blocks *every* request on
847
+ that loop, health included, for as long as they run. Reproduced live: a
848
+ genuine ``/maintenance/run`` call held the loop long enough that 15/15
849
+ health polls during the window timed out at exactly the 2s cap while
850
+ ``ps``/``lsof`` proved the process never stopped listening — which is
851
+ exactly the "Daemon was not running" false report this fixes. Process
852
+ liveness (PID + clock-independent start token, proven below via
853
+ ``_descriptor_process_is_alive``) is the fact that actually matters for
854
+ "should I try to stop this," so it is checked directly and the mutating
855
+ ``/stop`` POST is sent with ``verify_health=False`` once that is proven —
856
+ the daemon still authenticates the request by its capability header on
857
+ arrival, so this loses no ownership guarantee, only the redundant,
858
+ stall-prone preflight round trip.
799
859
  """
800
860
  descriptor = read_descriptor()
801
861
  legacy = _verified_legacy_health() if descriptor is None else None
802
862
  if descriptor is None and legacy is None:
803
863
  return False
804
864
  if descriptor is not None:
865
+ if not _descriptor_process_is_alive(descriptor):
866
+ return False
805
867
  response = daemon_request(
806
868
  "POST",
807
869
  "/stop",
808
870
  expected_descriptor=descriptor,
871
+ verify_health=False,
809
872
  )
810
873
  else:
811
874
  if legacy is None: