superlocalmemory 4.0.7 → 4.0.8

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 (53) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +3 -3
  3. package/package.json +1 -1
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/plugin/CLAUDE.md +3 -3
  6. package/plugin/agents/slm-governance-advisor.md +1 -1
  7. package/plugin/agents/slm-loop-runner.md +1 -1
  8. package/plugin/agents/slm-memory-advisor.md +1 -1
  9. package/plugin/agents/slm-optimize-advisor.md +1 -1
  10. package/plugin/requirements.txt +1 -1
  11. package/plugin/skills/slm-cache/SKILL.md +1 -1
  12. package/plugin/skills/slm-compress/SKILL.md +1 -1
  13. package/plugin/skills/slm-governance/SKILL.md +1 -1
  14. package/plugin/skills/slm-graph/SKILL.md +1 -1
  15. package/plugin/skills/slm-loop/SKILL.md +1 -1
  16. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  17. package/plugin/skills/slm-profile/SKILL.md +1 -1
  18. package/plugin/skills/slm-recall/SKILL.md +1 -1
  19. package/plugin/skills/slm-remember/SKILL.md +1 -1
  20. package/plugin/skills/slm-scope/SKILL.md +1 -1
  21. package/plugin/skills/slm-session/SKILL.md +1 -1
  22. package/plugin/skills/slm-status/SKILL.md +1 -1
  23. package/plugin-src/rules/AGENTS.md +1 -1
  24. package/pyproject.toml +1 -1
  25. package/src/superlocalmemory/__init__.py +1 -1
  26. package/src/superlocalmemory/cli/summary_cmd.py +23 -3
  27. package/src/superlocalmemory/code_graph/bridge/maintenance.py +7 -1
  28. package/src/superlocalmemory/core/consolidation_engine.py +14 -15
  29. package/src/superlocalmemory/core/recall_worker.py +4 -0
  30. package/src/superlocalmemory/evolution/skill_evolver.py +16 -1
  31. package/src/superlocalmemory/hooks/hook_handlers.py +38 -11
  32. package/src/superlocalmemory/learning/pattern_miner.py +12 -7
  33. package/src/superlocalmemory/mcp/profiles.py +10 -3
  34. package/src/superlocalmemory/mcp/server.py +7 -0
  35. package/src/superlocalmemory/mcp/tools_summaries.py +147 -0
  36. package/src/superlocalmemory/server/consolidation_runner.py +140 -0
  37. package/src/superlocalmemory/server/recall_serializer.py +34 -2
  38. package/src/superlocalmemory/server/routes/agents.py +52 -8
  39. package/src/superlocalmemory/server/routes/brain.py +108 -0
  40. package/src/superlocalmemory/server/routes/memories.py +153 -0
  41. package/src/superlocalmemory/server/routes/v3_api.py +24 -46
  42. package/src/superlocalmemory/server/unified_daemon.py +107 -0
  43. package/src/superlocalmemory/summaries/base.py +159 -0
  44. package/src/superlocalmemory/summaries/daily_reflection.py +55 -8
  45. package/src/superlocalmemory/summaries/project_work_log.py +23 -7
  46. package/src/superlocalmemory/summaries/session_summary.py +9 -5
  47. package/src/superlocalmemory/ui/index.html +9 -3
  48. package/src/superlocalmemory/ui/js/od-boundedloops.js +324 -0
  49. package/src/superlocalmemory/ui/js/od-memories.js +337 -12
  50. package/src/superlocalmemory/ui/js/od-mesh.js +97 -5
  51. package/src/superlocalmemory/ui/js/od-operations.js +1 -150
  52. package/src/superlocalmemory/ui/js/od-optimize.js +36 -9
  53. package/src/superlocalmemory/ui/js/od-shell.js +10 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,79 @@ 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.0.8] — Where you were actually looking
9
+
10
+ ### Added
11
+ - **Summaries reach agents and the dashboard.** 4.0.7 gave the summary layer a
12
+ command; an agent still had no way to ask for one, and neither did anyone
13
+ reading the dashboard. Adds the `get_memory_summary` tool and a **Summaries**
14
+ tab under Memories with Today, Yesterday, and a picker listing the projects
15
+ SuperLocalMemory has actually recorded activity in. Every result states how
16
+ many memories it was built from and how much it covered.
17
+ - **Bounded Loops has its own page**, alongside the other integrations. It shows
18
+ whether Bounded Loops is installed, which agreement the two products
19
+ negotiated, and which finished runs have been imported — and states plainly
20
+ that imported evidence is read-only: SuperLocalMemory never learns from it, no
21
+ file paths leave your workspace, and the tamper-evidence it carries is not the
22
+ same thing as independent verification.
23
+ - **Mesh shows its history**, not only who is connected this second. Sessions
24
+ leave the mesh when they end, so the page was empty between sessions even on a
25
+ machine with plenty of mesh activity.
26
+
27
+ ### Changed
28
+ - **Recall Lab is the first thing you see under Memories**, with example
29
+ questions to start from. Summaries sits next to it. The three browsing views
30
+ follow.
31
+
32
+ ### Fixed
33
+ - **Memory stopped learning from use.** Recall marks which memories it returned
34
+ so that later use can be credited back, but the mark never reached the
35
+ assistant, so nothing was ever credited. Every recall was scored as neither
36
+ good nor bad, which is why recall quality sat at its starting value and no
37
+ source ever showed a settled quality signal however much the memory was used.
38
+ - **The behavioural half of consolidation had never run.** Nothing triggered it,
39
+ and when it did run, two of its four steps failed without reporting anything.
40
+ SuperLocalMemory now runs it on a schedule while idle — never while you are
41
+ storing or recalling — and at session end.
42
+ - **"This project" summaries could not work.** The button sent no project, and
43
+ the answer was always an error. SuperLocalMemory installs once for your whole
44
+ machine, so a browser tab has no way to know which project you mean; it now
45
+ lists the ones it has seen.
46
+ - **Locally written summaries began with an apology.** The local model was never
47
+ told what it was writing, so it replied as if in a chat. It now returns the
48
+ summary and nothing else.
49
+ - **A summary built from nothing claimed to cover everything.** A project with
50
+ recorded activity but no saved facts is now reported as a partial view.
51
+ - **Multi-Agent Memory showed access tokens instead of agent names**, and because
52
+ one agent uses many tokens over time, a single agent appeared as dozens.
53
+ - **Optimize invented a chart.** The cache hit-rate graph drew a rising trend
54
+ generated from the current value rather than measured; no history is recorded,
55
+ so it is gone until there is something real to plot. "Tokens saved" was
56
+ labelled as a monthly figure with an upward arrow that nothing ever set — it is
57
+ a total since install, and now says so. The page also explains why its numbers
58
+ are zero while its switches are on.
59
+ - **One kind of memory pattern was never produced.** The query that finds it
60
+ asked for columns that do not exist, so it failed on every run and was skipped
61
+ silently.
62
+ - **Code links never appeared for anyone.** Two separate reasons. The pass that
63
+ creates them matched no facts at all on a real store — it looked for an empty
64
+ archive marker, while live facts carry `live`, so every fact was excluded. And
65
+ the panel that displayed them was the search-results view, not the memory
66
+ drawer people actually open. Both fixed; on a real store this produces 7,470
67
+ links across 3,603 memories.
68
+ - **"Atomic facts" always read "No atomic facts recorded for this memory."** The
69
+ drawer looked up child facts using the row's own id, but a row in that pane is
70
+ itself an atomic fact and its parent is a separate field. Records stored
71
+ directly, which have no parent, now say so rather than reporting missing data.
72
+ - **Summaries ignored your mode.** The command never passed the configuration, so
73
+ every summary took the no-model path — a Mode B user got Mode A output. Ollama
74
+ now writes them when configured, falling back if it is unavailable.
75
+ - **Summary highlights were unreadable.** Truncation counted characters but
76
+ ignored newlines, so one fact containing headings and blank lines rendered as
77
+ six lines and the list stopped being a list.
78
+ - **"Active entities" listed internal identifiers** instead of names. It now
79
+ reads `Fixed (127), Gateway (124)`.
80
+
8
81
  ## [4.0.7] — Reachable
9
82
 
10
83
  Everything here was already built. None of it could be used.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  </picture>
6
6
  </p>
7
7
 
8
- <h1 align="center">SuperLocalMemory V4.0.7</h1>
8
+ <h1 align="center">SuperLocalMemory V4.0.8</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.0.7</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.0.8</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.0.7-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.0.7 — Current Release"/></a>
35
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.0.8-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.0.8 — 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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "4.0.7",
3
+ "version": "4.0.8",
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.0.7"
18
+ "version": "4.0.8"
19
19
  }
package/plugin/CLAUDE.md CHANGED
@@ -1,4 +1,4 @@
1
- <!-- BEGIN SuperLocalMemory v4.0.7 -->
1
+ <!-- BEGIN SuperLocalMemory v4.0.8 -->
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.0.7 -->
42
+ <!-- END SuperLocalMemory v4.0.8 -->
43
43
 
44
- SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
44
+ SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later
80
+ SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later
71
+ SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later
49
+ SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later
44
+ SuperLocalMemory v4.0.8 · Qualixar · AGPL-3.0-or-later
@@ -1 +1 @@
1
- superlocalmemory==4.0.7
1
+ superlocalmemory==4.0.8
@@ -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.0.7 · Qualixar · AGPL-3.0-or-later
148
+ SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later
150
+ SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later*
248
+ *SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later
315
+ SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later
99
+ SuperLocalMemory v4.0.8 · Qualixar · AGPL-3.0-or-later
@@ -279,4 +279,4 @@ mesh availability.
279
279
 
280
280
  ---
281
281
 
282
- *SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later*
282
+ *SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later*
149
+ *SuperLocalMemory v4.0.8 · Qualixar · AGPL-3.0-or-later*
@@ -236,4 +236,4 @@ before recalling, then switch back. See `slm-profile` for workspace switching.
236
236
 
237
237
  ---
238
238
 
239
- *SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later*
239
+ *SuperLocalMemory v4.0.8 · Qualixar · AGPL-3.0-or-later*
@@ -238,4 +238,4 @@ different workspace, use `switch_profile` first. See `slm-profile`.
238
238
 
239
239
  ---
240
240
 
241
- *SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later*
241
+ *SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later*
176
+ *SuperLocalMemory v4.0.8 · Qualixar · AGPL-3.0-or-later*
@@ -227,4 +227,4 @@ explicitly and call `recall` with `include_global`/`include_shared` after
227
227
 
228
228
  ---
229
229
 
230
- *SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later*
230
+ *SuperLocalMemory v4.0.8 · 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.0.7 · Qualixar · AGPL-3.0-or-later
166
+ SuperLocalMemory v4.0.8 · Qualixar · AGPL-3.0-or-later
@@ -129,4 +129,4 @@ When the SLM MCP server is unavailable, use these CLI equivalents:
129
129
  - **slm-optimize-advisor** — context compression and KV cache
130
130
  - **slm-governance-advisor** — scope/role compliance, retention policies, GDPR
131
131
 
132
- SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
132
+ SuperLocalMemory v4.0.8 · Qualixar · AGPL-3.0-or-later
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "superlocalmemory"
3
- version = "4.0.7"
3
+ version = "4.0.8"
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.0.7"
35
+ __version__ = "4.0.8"
36
36
 
37
37
  _REQUIRED_VERSIONS = {
38
38
  "sentence_transformers": "5.3.0",
@@ -86,6 +86,25 @@ def _active_profile() -> str:
86
86
  return "default"
87
87
 
88
88
 
89
+ def _load_config() -> Any:
90
+ """The active SLMConfig, so Mode B/C get LLM-written summaries.
91
+
92
+ Without this the generators receive ``config=None``, ``get_mode_str`` returns
93
+ ``"a"``, and the LLM branch is unreachable — every summary comes out
94
+ extractive no matter which mode the user runs. That is not a graceful
95
+ fallback, it is the enrichment path never being offered.
96
+
97
+ Returns None on any failure, which lands on the extractive path. That is the
98
+ right fallback: a deterministic summary beats an error.
99
+ """
100
+ try:
101
+ from superlocalmemory.core.config import SLMConfig
102
+
103
+ return SLMConfig.load()
104
+ except Exception:
105
+ return None
106
+
107
+
89
108
  def _emit(result: Any, as_json: bool) -> None:
90
109
  """Print a SummaryResult as JSON or as prose."""
91
110
  if as_json:
@@ -123,6 +142,7 @@ def cmd_summary(args: Namespace) -> None:
123
142
  as_json = bool(getattr(args, "json", False))
124
143
  profile = getattr(args, "profile", None) or _active_profile()
125
144
  db = _db_path()
145
+ cfg = _load_config() # Mode B/C enrichment; None -> extractive
126
146
 
127
147
  if not db.exists():
128
148
  print(f"No memory database at {db}. Run `slm status` first.")
@@ -131,7 +151,7 @@ def cmd_summary(args: Namespace) -> None:
131
151
  if sub == "session":
132
152
  from superlocalmemory.summaries import generate_session_summary
133
153
 
134
- _emit(generate_session_summary(db, args.session_id, profile), as_json)
154
+ _emit(generate_session_summary(db, args.session_id, profile, cfg), as_json)
135
155
  return
136
156
 
137
157
  if sub == "day":
@@ -142,14 +162,14 @@ def cmd_summary(args: Namespace) -> None:
142
162
  target = (date.today() - timedelta(days=1)).isoformat()
143
163
  elif target == "today":
144
164
  target = date.today().isoformat()
145
- _emit(generate_daily_reflection(db, target, profile), as_json)
165
+ _emit(generate_daily_reflection(db, target, profile, cfg), as_json)
146
166
  return
147
167
 
148
168
  if sub == "project":
149
169
  from superlocalmemory.summaries import generate_project_work_log
150
170
 
151
171
  path = getattr(args, "path", None) or str(Path.cwd())
152
- _emit(generate_project_work_log(db, path, profile), as_json)
172
+ _emit(generate_project_work_log(db, path, profile, cfg), as_json)
153
173
  return
154
174
 
155
175
  print("Usage: slm summary {session <id> | day [DATE] | project [PATH]}")
@@ -96,7 +96,13 @@ def _fact_rows(
96
96
  except Exception: # pragma: no cover - helper absent on an unusual manager
97
97
  has_archive = False
98
98
  if has_archive:
99
- sql += "AND (archive_status IS NULL OR archive_status = '') "
99
+ # COALESCE form, matching storage/database.py:759 and the learning
100
+ # modules. My first version tested `IS NULL OR = ''`, which excluded
101
+ # every fact on a real store: live facts are marked 'live', not blank.
102
+ # The synthetic fixtures I developed against left the column unset, so
103
+ # the pass linked happily in tests and would have linked NOTHING for any
104
+ # actual user — 3,608 of 3,608 facts filtered out on this machine.
105
+ sql += "AND COALESCE(archive_status, 'live') != 'archived' "
100
106
 
101
107
  params: list[Any] = [profile_id]
102
108
  if since:
@@ -198,27 +198,26 @@ class ConsolidationEngine:
198
198
  from superlocalmemory.parameterization.prompt_injector import PromptInjector
199
199
  from superlocalmemory.parameterization.prompt_lifecycle import PromptLifecycleManager
200
200
  from superlocalmemory.learning.behavioral import BehavioralPatternStore
201
- # cross_project and workflow_miner may not exist yet — stub them
202
- try:
203
- from superlocalmemory.parameterization.cross_project import CrossProjectAggregator
204
- except ImportError:
205
- class CrossProjectAggregator:
206
- def __init__(self, db): pass
207
- def get_preferences(self, *a, **kw): return {}
208
- try:
209
- from superlocalmemory.parameterization.workflow_miner import WorkflowMiner
210
- except ImportError:
211
- class WorkflowMiner:
212
- def __init__(self, db): pass
213
- def mine(self, *a, **kw): return []
201
+ from superlocalmemory.parameterization.cross_project import CrossProjectAggregator
202
+ from superlocalmemory.parameterization.workflow_miner import WorkflowMiner
214
203
  from superlocalmemory.hooks.auto_parameterize import AutoParameterizeHook
215
204
  from superlocalmemory.core.config import ParameterizationConfig
216
205
  p_config = getattr(self._config, "parameterization", ParameterizationConfig())
217
206
  from superlocalmemory.infra.data_root import state_path
218
207
  learning_db = str(state_path("learning.db"))
219
208
  beh_store = BehavioralPatternStore(learning_db)
220
- cross_proj = CrossProjectAggregator(self._db)
221
- wf_miner = WorkflowMiner(self._db)
209
+ # Both take a DB PATH, not a DatabaseManager. Passing
210
+ # self._db here made Path(db_path) raise "argument should be
211
+ # a str or an os.PathLike object ... not 'DatabaseManager'",
212
+ # which step 9 caught at debug level — so soft prompts were
213
+ # never generated and nothing said why.
214
+ #
215
+ # The ImportError stubs these replaced were also dead: both
216
+ # modules exist and re-export the canonical classes, so the
217
+ # stub branch could never run and only served to hide the
218
+ # real signatures.
219
+ cross_proj = CrossProjectAggregator(learning_db)
220
+ wf_miner = WorkflowMiner(learning_db)
222
221
  extractor = PatternExtractor(self._db, beh_store, cross_proj, wf_miner, p_config)
223
222
  generator = SoftPromptGenerator(p_config)
224
223
  injector = PromptInjector(self._db, generator, p_config)
@@ -106,6 +106,10 @@ def _handle_recall(
106
106
  memory_map=memory_map,
107
107
  per_fact_max=getattr(_rc, "recall_per_fact_max_chars", 2400),
108
108
  total_max=getattr(_rc, "recall_total_max_chars", 12000),
109
+ # Option B: markers only on session-bearing recalls. A marker can
110
+ # only buy a learning signal when a pending_outcomes row exists
111
+ # to settle, and those exist only when session_id is present.
112
+ include_marker=bool(session_id),
109
113
  )
110
114
  return {
111
115
  "ok": True,
@@ -140,9 +140,24 @@ class SkillEvolver:
140
140
  # automatically. Tests inject their own.
141
141
  if budget is None:
142
142
  slm_home = canonical_data_root()
143
+ # ``learning_db`` must be learning.db: migration M010 creates
144
+ # evolution_llm_cost_log there. Every production caller
145
+ # (consolidation_engine, routes/evolution, MCP, CLI) passes
146
+ # memory.db as db_path, so handing db_path straight to the budget
147
+ # pointed it at a database where that table does not exist. Every
148
+ # evolution cycle then died with "no such table:
149
+ # evolution_llm_cost_log" — swallowed at debug level in
150
+ # consolidation step 11, so skill evolution silently never ran.
151
+ #
152
+ # Redirect only for the exact production filename. Tests pass
153
+ # ":memory:" or their own tmp file and must keep the old behaviour,
154
+ # rather than having a learning.db invented beside them.
155
+ learning_db = Path(self._db_path)
156
+ if learning_db.name == "memory.db":
157
+ learning_db = learning_db.parent / "learning.db"
143
158
  budget = EvolutionBudget(
144
159
  profile_id=profile_id,
145
- learning_db=Path(self._db_path),
160
+ learning_db=learning_db,
146
161
  lock_dir=slm_home,
147
162
  )
148
163
  self._budget = budget
@@ -768,7 +768,29 @@ def _run_quiet(cmd: list[str], timeout: int = 5, postprocess=None) -> str:
768
768
 
769
769
 
770
770
  def _maybe_consolidate() -> None:
771
- """Run cognitive consolidation if last run was >24h ago. Non-blocking."""
771
+ """Ask the daemon for a full consolidation if the last one was >24h ago.
772
+
773
+ Three things were wrong here before 4.0.8, and together they meant the
774
+ behavioural half of consolidation never ran on a real install:
775
+
776
+ 1. **It ran the wrong pipeline.** ``slm consolidate --cognitive`` invokes
777
+ ``CognitiveConsolidator.run_pipeline()``. Behavioural assertion mining,
778
+ soft prompts, skill performance and skill evolution are steps 8-11 of
779
+ ``ConsolidationEngine.consolidate()`` — a different class entirely. On a
780
+ store with thousands of tool events, ``behavioral_assertions`` stayed at
781
+ zero rows while the miner, run once by hand, produced 9 immediately.
782
+ 2. **It marked success before trying.** The 24h timestamp was written
783
+ *before* the subprocess launched, so a run that failed instantly still
784
+ bought a full day of silence. The marker is now written only after the
785
+ daemon confirms it scheduled the work.
786
+ 3. **It could not fail visibly.** stdout and stderr went to DEVNULL and the
787
+ handler ended in a bare ``except: pass``, so nothing anywhere recorded a
788
+ failure. Errors now surface on stderr, where the hook runner logs them.
789
+
790
+ Non-blocking: the daemon schedules the pass and returns immediately, so
791
+ session end is never held up by a run that takes minutes. The daemon's own
792
+ periodic timer is the backstop for sessions where this hook never fires.
793
+ """
772
794
  try:
773
795
  last_ts = 0
774
796
  last_consolidation = _last_consolidation_path()
@@ -780,16 +802,21 @@ def _maybe_consolidate() -> None:
780
802
  if (now - last_ts) < 86400: # 24 hours
781
803
  return
782
804
 
783
- # Update timestamp FIRST to prevent concurrent runs
805
+ started = _daemon_post(
806
+ "/api/v3/consolidation/trigger",
807
+ {"lightweight": False, "background": True},
808
+ )
809
+ if not started:
810
+ # No marker written — the next session end retries. The daemon's
811
+ # consolidation lock makes a duplicate request a cheap no-op.
812
+ print(
813
+ "slm: consolidation could not be scheduled (daemon unreachable)",
814
+ file=sys.stderr,
815
+ )
816
+ return
817
+
784
818
  os.makedirs(os.path.dirname(last_consolidation), exist_ok=True)
785
819
  with open(last_consolidation, "w") as f:
786
820
  f.write(str(now))
787
-
788
- # Run consolidation in background (don't block session end)
789
- subprocess.Popen(
790
- ["slm", "consolidate", "--cognitive"],
791
- stdout=subprocess.DEVNULL,
792
- stderr=subprocess.DEVNULL,
793
- )
794
- except Exception:
795
- pass
821
+ except Exception as exc:
822
+ print(f"slm: consolidation trigger failed: {exc}", file=sys.stderr)
@@ -367,18 +367,23 @@ def _mine_channel_and_coretrieval(
367
367
  gen += 1
368
368
 
369
369
  try:
370
+ # Column names must match learning.db: the table is
371
+ # (fact_id_a, fact_id_b, co_count). This query asked for
372
+ # (fact_a, fact_b, co_access_count) and therefore raised
373
+ # sqlite3.OperationalError on every run since it was written —
374
+ # caught below, logged at WARNING, and otherwise invisible. Net
375
+ # effect: 912 co-retrieval edges on a real store never produced a
376
+ # single pattern, and the Brain pane had one fewer signal with no
377
+ # indication anything was missing.
370
378
  coret_rows = learn_conn.execute(
371
- "SELECT fact_a, fact_b, co_access_count "
379
+ "SELECT fact_id_a, fact_id_b, co_count "
372
380
  "FROM co_retrieval_edges "
373
- "WHERE profile_id = ? AND co_access_count >= 3 "
374
- "ORDER BY co_access_count DESC LIMIT 20",
381
+ "WHERE profile_id = ? AND co_count >= 3 "
382
+ "ORDER BY co_count DESC LIMIT 20",
375
383
  (profile_id,),
376
384
  ).fetchall()
377
385
  if coret_rows and not dry_run:
378
- top_pair = (
379
- dict(coret_rows[0]).get("co_access_count", 0)
380
- if coret_rows else 0
381
- )
386
+ top_pair = dict(coret_rows[0]).get("co_count", 0)
382
387
  store.record_pattern(
383
388
  profile_id=profile_id,
384
389
  pattern_type="co_retrieval_clusters",
@@ -17,13 +17,17 @@ from __future__ import annotations
17
17
  # v3.6.14 WP-01: Named profile definitions
18
18
  # ---------------------------------------------------------------------------
19
19
 
20
- _PROFILE_CORE: frozenset[str] = frozenset({ # 16
20
+ _PROFILE_CORE: frozenset[str] = frozenset({ # 17
21
21
  "remember", "recall", "search", "fetch", "list_recent", "update_memory", "forget",
22
22
  "session_init", "close_session",
23
23
  "slm_compress", "slm_retrieve", "slm_cache_set", "slm_cache_get", "slm_optimize_stats",
24
24
  # A client that can propose a correction must be able to inspect and
25
25
  # authenticate its review; otherwise the core lifecycle is incomplete.
26
26
  "review_correction", "list_corrections",
27
+ # v4.0.8: the readable summary layer (issue #113). In CORE because the
28
+ # natural caller is the agent holding the conversation — an assistant
29
+ # asked "what did I work on yesterday" should not need a power profile.
30
+ "get_memory_summary",
27
31
  })
28
32
 
29
33
  # Portable Brain evidence must reach the coding-host profile shipped by the
@@ -34,7 +38,7 @@ _PROFILE_BRAIN: frozenset[str] = frozenset({
34
38
  "observe_bounded_loop_evidence",
35
39
  })
36
40
 
37
- _PROFILE_CODE: frozenset[str] = _PROFILE_CORE | _PROFILE_BRAIN | frozenset({ # 31
41
+ _PROFILE_CODE: frozenset[str] = _PROFILE_CORE | _PROFILE_BRAIN | frozenset({ # 32
38
42
  "build_code_graph", "get_blast_radius", "query_graph",
39
43
  "semantic_search_code", "get_review_context", "detect_changes",
40
44
  # switch_profile lets a plugin/IDE session change the active workspace over
@@ -64,8 +68,11 @@ _PROFILE_FULL: frozenset[str] = frozenset({
64
68
  "slm_compress", "slm_retrieve", "slm_cache_set", "slm_cache_get", "slm_optimize_stats",
65
69
  # v3.8.0: bounded-loop tools (CLI + /slm-loop command + MCP).
66
70
  "slm_loop_run", "slm_loop_history", "slm_loop_show",
71
+ # v4.0.8: readable summaries (#113). In core, so it must be in full too —
72
+ # full is asserted to be a superset of core.
73
+ "get_memory_summary",
67
74
  # prestage_context remains registered but deliberately raw-server-only.
68
- }) | _PROFILE_FULL_MESH # 49
75
+ }) | _PROFILE_FULL_MESH # 50
69
76
 
70
77
  _PROFILE_POWER: frozenset[str] = _PROFILE_FULL | frozenset({ # 61
71
78
  "get_version", "get_mode", "health", "consistency_check", "recall_trace",
@@ -105,6 +105,11 @@ _ESSENTIAL_TOOLS: set[str] = {
105
105
  "observe_bounded_loop_evidence",
106
106
  # Update, review, and list form one core correction lifecycle.
107
107
  "review_correction", "list_corrections",
108
+ # v4.0.8 (#113): readable summaries. Present here as well as in the named
109
+ # profiles because this set is the FALLBACK surface — it must mirror
110
+ # ``full``, and a tool that ships in the smallest profile ("core") cannot be
111
+ # missing from the fallback without a client silently losing it.
112
+ "get_memory_summary",
108
113
  # Memory management (2)
109
114
  "forget", "run_maintenance",
110
115
  # NOTE: prestage_context IS registered (see register_prestage_tool below)
@@ -280,6 +285,8 @@ from superlocalmemory.mcp.tools_ops import register_ops_tools
280
285
  register_ops_tools(_target, get_engine) # Wave-3: operational recovery & admin remediation
281
286
  from superlocalmemory.mcp.tools_brain import register_brain_tools
282
287
  register_brain_tools(_target, get_engine) # v4.0.2 portable Brain receipts
288
+ from superlocalmemory.mcp.tools_summaries import register_summary_tools
289
+ register_summary_tools(_target, get_engine) # v4.0.8 issue #113 summary reads
283
290
  from superlocalmemory.mcp.tools_context import register_prestage_tool
284
291
 
285
292