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.
- package/CHANGELOG.md +73 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/summary_cmd.py +23 -3
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +7 -1
- package/src/superlocalmemory/core/consolidation_engine.py +14 -15
- package/src/superlocalmemory/core/recall_worker.py +4 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +16 -1
- package/src/superlocalmemory/hooks/hook_handlers.py +38 -11
- package/src/superlocalmemory/learning/pattern_miner.py +12 -7
- package/src/superlocalmemory/mcp/profiles.py +10 -3
- package/src/superlocalmemory/mcp/server.py +7 -0
- package/src/superlocalmemory/mcp/tools_summaries.py +147 -0
- package/src/superlocalmemory/server/consolidation_runner.py +140 -0
- package/src/superlocalmemory/server/recall_serializer.py +34 -2
- package/src/superlocalmemory/server/routes/agents.py +52 -8
- package/src/superlocalmemory/server/routes/brain.py +108 -0
- package/src/superlocalmemory/server/routes/memories.py +153 -0
- package/src/superlocalmemory/server/routes/v3_api.py +24 -46
- package/src/superlocalmemory/server/unified_daemon.py +107 -0
- package/src/superlocalmemory/summaries/base.py +159 -0
- package/src/superlocalmemory/summaries/daily_reflection.py +55 -8
- package/src/superlocalmemory/summaries/project_work_log.py +23 -7
- package/src/superlocalmemory/summaries/session_summary.py +9 -5
- package/src/superlocalmemory/ui/index.html +9 -3
- package/src/superlocalmemory/ui/js/od-boundedloops.js +324 -0
- package/src/superlocalmemory/ui/js/od-memories.js +337 -12
- package/src/superlocalmemory/ui/js/od-mesh.js +97 -5
- package/src/superlocalmemory/ui/js/od-operations.js +1 -150
- package/src/superlocalmemory/ui/js/od-optimize.js +36 -9
- 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.
|
|
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.
|
|
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 & 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> · MCP: add <code>slm_compress</code> to your config · 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.
|
|
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
package/plugin/CLAUDE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- BEGIN SuperLocalMemory v4.0.
|
|
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.
|
|
42
|
+
<!-- END SuperLocalMemory v4.0.8 -->
|
|
43
43
|
|
|
44
|
-
SuperLocalMemory v4.0.
|
|
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.
|
|
80
|
+
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.
|
|
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.
|
|
44
|
+
SuperLocalMemory v4.0.8 · Qualixar · AGPL-3.0-or-later
|
package/plugin/requirements.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
superlocalmemory==4.0.
|
|
1
|
+
superlocalmemory==4.0.8
|
|
@@ -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.
|
|
132
|
+
SuperLocalMemory v4.0.8 · Qualixar · AGPL-3.0-or-later
|
package/pyproject.toml
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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=
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
379
|
+
"SELECT fact_id_a, fact_id_b, co_count "
|
|
372
380
|
"FROM co_retrieval_edges "
|
|
373
|
-
"WHERE profile_id = ? AND
|
|
374
|
-
"ORDER BY
|
|
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({ #
|
|
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({ #
|
|
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 #
|
|
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
|
|