superlocalmemory 4.1.7 → 4.1.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 (43) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +3 -3
  4. package/package.json +1 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/CLAUDE.md +3 -3
  7. package/plugin/agents/slm-governance-advisor.md +1 -1
  8. package/plugin/agents/slm-loop-runner.md +1 -1
  9. package/plugin/agents/slm-memory-advisor.md +1 -1
  10. package/plugin/agents/slm-optimize-advisor.md +1 -1
  11. package/plugin/requirements.txt +1 -1
  12. package/plugin/skills/slm-cache/SKILL.md +1 -1
  13. package/plugin/skills/slm-compress/SKILL.md +1 -1
  14. package/plugin/skills/slm-governance/SKILL.md +1 -1
  15. package/plugin/skills/slm-graph/SKILL.md +1 -1
  16. package/plugin/skills/slm-loop/SKILL.md +1 -1
  17. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  18. package/plugin/skills/slm-profile/SKILL.md +1 -1
  19. package/plugin/skills/slm-recall/SKILL.md +1 -1
  20. package/plugin/skills/slm-remember/SKILL.md +1 -1
  21. package/plugin/skills/slm-scope/SKILL.md +1 -1
  22. package/plugin/skills/slm-session/SKILL.md +1 -1
  23. package/plugin/skills/slm-status/SKILL.md +1 -1
  24. package/plugin-src/agents/slm-memory-advisor.md +1 -1
  25. package/plugin-src/agents/slm-optimize-advisor.md +1 -1
  26. package/plugin-src/rules/AGENTS.md +1 -1
  27. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  28. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-governance/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  31. package/plugin-src/skills/slm-loop/SKILL.md +1 -1
  32. package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
  33. package/plugin-src/skills/slm-profile/SKILL.md +1 -1
  34. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  35. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  36. package/plugin-src/skills/slm-scope/SKILL.md +1 -1
  37. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  38. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  39. package/pyproject.toml +1 -1
  40. package/src/superlocalmemory/__init__.py +1 -1
  41. package/src/superlocalmemory/core/recall_pipeline.py +16 -13
  42. package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +122 -0
  43. package/src/superlocalmemory/storage/migrations/M048_upcoming_holds_only_what_is_upcoming.py +22 -0
@@ -21,7 +21,7 @@
21
21
  "license": "AGPL-3.0-or-later",
22
22
  "name": "superlocalmemory",
23
23
  "source": "./plugin",
24
- "version": "4.1.7"
24
+ "version": "4.1.8"
25
25
  },
26
26
  {
27
27
  "author": {
@@ -39,7 +39,7 @@
39
39
  "license": "AGPL-3.0-or-later",
40
40
  "name": "superlocalmemory-codex",
41
41
  "source": "./codex-plugin",
42
- "version": "4.1.7"
42
+ "version": "4.1.8"
43
43
  }
44
44
  ]
45
45
  }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ 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.8] — The same question, answered the same way
9
+
10
+ ### Fixed
11
+ - **Recall could return the same memories in a different order each time it was
12
+ asked.** Adaptive ranking became the default in 4.1.7, which let a contextual
13
+ bandit choose how much to weigh each retrieval channel per query. That choice
14
+ is sampled from what an arm has learned, and arms that have settled no rewards
15
+ yet hold only their starting assumption — so the sample varied, and two
16
+ identical questions could weigh semantic against keyword evidence quite
17
+ differently for no reason drawn from anything observed. Adaptive ranking is
18
+ opt-in again: set `SLM_RANKING=v2-ensemble` to enable it. Nothing else about
19
+ retrieval changed, and no stored memory is affected.
20
+ - **One out-of-date label could make the whole store refuse to answer.** A
21
+ memory filed as a plan stops reading as a plan once its date passes — ordinary
22
+ use, and the maintenance pass re-reads those on its own. That check was
23
+ treated like a missing table: every route answered `503 Service unavailable`
24
+ until the daemon was restarted, and because the readiness answer was decided
25
+ once at startup, the repair that had already run could not lift it. The check
26
+ now reports what it is, a data quality signal, and the store keeps serving
27
+ while the next maintenance pass settles it.
28
+ - **Tool usage stopped being recorded.** Behavioural patterns, per-skill
29
+ performance, and the engagement measures that tell a recall whether it helped
30
+ all read the same record of which tools ran. Nothing on a normal install was
31
+ writing it, so those readings kept reporting from progressively older activity
32
+ and eventually stood still. Each completed tool call is recorded again, with
33
+ credential-shaped text removed and summaries capped before they are stored.
34
+
8
35
  ## [4.1.7] — Recall that improves with use
9
36
 
10
37
  ### Added
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  </picture>
6
6
  </p>
7
7
 
8
- <h1 align="center">SuperLocalMemory V4.1.7</h1>
8
+ <h1 align="center">SuperLocalMemory V4.1.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.1.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.1.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.1.7-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.1.7 — Current Release"/></a>
35
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.1.8-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.1.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.1.7",
3
+ "version": "4.1.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.1.7"
18
+ "version": "4.1.8"
19
19
  }
package/plugin/CLAUDE.md CHANGED
@@ -1,4 +1,4 @@
1
- <!-- BEGIN SuperLocalMemory v4.1.7 -->
1
+ <!-- BEGIN SuperLocalMemory v4.1.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.1.7 -->
42
+ <!-- END SuperLocalMemory v4.1.8 -->
43
43
 
44
- SuperLocalMemory v4.1.7 · Qualixar · AGPL-3.0-or-later
44
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
80
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
71
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
49
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
44
+ SuperLocalMemory v4.1.8 · Qualixar · AGPL-3.0-or-later
@@ -1 +1 @@
1
- superlocalmemory==4.1.7
1
+ superlocalmemory==4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
148
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
150
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later*
248
+ *SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
315
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
99
+ SuperLocalMemory v4.1.8 · Qualixar · AGPL-3.0-or-later
@@ -279,4 +279,4 @@ mesh availability.
279
279
 
280
280
  ---
281
281
 
282
- *SuperLocalMemory v4.1.7 · Qualixar · AGPL-3.0-or-later*
282
+ *SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later*
149
+ *SuperLocalMemory v4.1.8 · 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.7 · Qualixar · AGPL-3.0-or-later*
326
+ *SuperLocalMemory v4.1.8 · 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.7 · Qualixar · AGPL-3.0-or-later*
273
+ *SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later*
176
+ *SuperLocalMemory v4.1.8 · 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.7 · Qualixar · AGPL-3.0-or-later*
256
+ *SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
166
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
49
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
44
+ SuperLocalMemory v4.1.8 · 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.7 · Qualixar · AGPL-3.0-or-later
140
+ SuperLocalMemory v4.1.8 · 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.7 · Qualixar · AGPL-3.0-or-later
148
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
150
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later*
248
+ *SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
315
+ SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
99
+ SuperLocalMemory v4.1.8 · Qualixar · AGPL-3.0-or-later
@@ -279,4 +279,4 @@ mesh availability.
279
279
 
280
280
  ---
281
281
 
282
- *SuperLocalMemory v4.1.7 · Qualixar · AGPL-3.0-or-later*
282
+ *SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later*
149
+ *SuperLocalMemory v4.1.8 · 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.7 · Qualixar · AGPL-3.0-or-later*
326
+ *SuperLocalMemory v4.1.8 · 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.7 · Qualixar · AGPL-3.0-or-later*
273
+ *SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later*
176
+ *SuperLocalMemory v4.1.8 · 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.7 · Qualixar · AGPL-3.0-or-later*
256
+ *SuperLocalMemory v4.1.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.1.7 · Qualixar · AGPL-3.0-or-later
166
+ SuperLocalMemory v4.1.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.1.7"
3
+ version = "4.1.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.1.7"
35
+ __version__ = "4.1.8"
36
36
 
37
37
  _REQUIRED_VERSIONS = {
38
38
  "sentence_transformers": "5.3.0",
@@ -340,23 +340,26 @@ class _ReadOnlyLearningView:
340
340
  def _resolve_ranking_mode(env: "dict[str, str] | os._Environ[str]") -> str:
341
341
  """Map the ``SLM_RANKING`` env var to a canonical mode.
342
342
 
343
- ``SLM_RANKING`` is an explicit operator policy. Absence of that policy now
344
- means the full pipeline, not ``off``.
345
-
346
- It defaulted to ``off`` because a stored signal that had gone stale must
347
- not start reordering results merely because someone upgraded. That risk
348
- depended on a settler that scored a recall it could not observe, which no
349
- longer happens: an unobserved recall leaves the ranking untouched instead
350
- of being recorded as an average one, and each observation counts for less
351
- the more predictable it was. A ranking layer nobody switches on is a
352
- ranking layer that never learns, so the default now enables it.
353
-
354
- Set ``SLM_RANKING=off`` to opt out.
343
+ ``SLM_RANKING`` is an explicit operator policy, and the absence of one
344
+ means ``off``: an upgrade must not start reordering results on its own.
345
+
346
+ Fixing the settlement path was necessary for the ensemble to be worth
347
+ enabling, but it is not sufficient. Settlement decides what an arm learns
348
+ *after* a query; the channel weights come from sampling that arm's
349
+ posterior *before* it. Until arms have posteriors shaped by real
350
+ observations, that sample is drawn from the prior, so two identical
351
+ queries can be weighted differently for no reason the store can justify
352
+ variance with nothing bought by it. A memory system's answers should move
353
+ because the evidence moved.
354
+
355
+ So the ensemble stays opt-in until arms carry settled rewards. Set
356
+ ``SLM_RANKING=v2-ensemble`` to enable it, or ``v1``/``v2`` for the
357
+ narrower passes.
355
358
  """
356
359
  raw = (env.get("SLM_RANKING", "") or "").strip().lower()
357
360
  if raw in _RANKING_MODES:
358
361
  return raw
359
- return "v2-ensemble"
362
+ return "off"
360
363
 
361
364
 
362
365
  def apply_ranking(
@@ -83,6 +83,122 @@ def _validate(marker: str) -> str | None:
83
83
  return None
84
84
 
85
85
 
86
+ # --- Behavioural telemetry -------------------------------------------------
87
+ # ``tool_events`` is the record of what the agent did, and several readers
88
+ # depend on it: assertion mining, skill-performance mining, and the engagement
89
+ # features that settle a recall against the actions which followed it. Each of
90
+ # those reads the table; none of them writes it. The only writers were an
91
+ # explicit ``log_tool_event`` call and a bulk importer, so on an ordinary
92
+ # install the table simply stopped receiving invocations and every reader
93
+ # quietly aged out with it.
94
+ #
95
+ # This hook already runs on exactly the right edge -- after each tool completes,
96
+ # with the session that ran it -- so it records the invocation here. It happens
97
+ # before the marker scan and independently of it: whether a recalled memory was
98
+ # named in the output decides what a *reward* is worth, not whether the action
99
+ # occurred at all.
100
+ _MAX_SUMMARY_LEN = 500
101
+
102
+ # Compiled once at import. This path now runs on every tool call rather than
103
+ # only on the ~1-in-5 that carry a marker, so per-call ``re`` compilation or a
104
+ # module import would be paid on the hot path for no benefit.
105
+ _SECRET_PATTERNS = (
106
+ (re.compile(r"\b(?:sk-|pk-|api[_-]?key[_-]?)[A-Za-z0-9_-]{10,}\b"),
107
+ "[REDACTED]"),
108
+ (re.compile(r"\b[A-Za-z0-9+/]{40,}={0,2}\b"), "[REDACTED]"),
109
+ (re.compile(r"password\s*[=:]\s*\S+", re.IGNORECASE),
110
+ "password=[REDACTED]"),
111
+ )
112
+
113
+
114
+ def _scrub(text: str) -> str:
115
+ """Strip credential-shaped substrings from telemetry text."""
116
+ for pattern, replacement in _SECRET_PATTERNS:
117
+ text = pattern.sub(replacement, text)
118
+ return text
119
+
120
+
121
+ def _summary(raw: object) -> str:
122
+ """Truncate-then-scrub ``raw`` for a summary column.
123
+
124
+ Truncation comes first so the regex cost is bounded by
125
+ ``_MAX_SUMMARY_LEN`` and not by the size of a tool response.
126
+ """
127
+ if raw is None:
128
+ return ""
129
+ if not isinstance(raw, str):
130
+ try:
131
+ import json as _json
132
+ raw = _json.dumps(raw, default=str)
133
+ except Exception:
134
+ try:
135
+ raw = str(raw)
136
+ except Exception:
137
+ return ""
138
+ return _scrub(raw[:_MAX_SUMMARY_LEN])
139
+
140
+
141
+ def _record_tool_event(
142
+ session_id: str,
143
+ tool_name: str,
144
+ payload: dict,
145
+ response_text: str,
146
+ ) -> bool:
147
+ """Append one ``tool_events`` row. Never raises; returns whether it wrote.
148
+
149
+ Failure is silent by design: telemetry must never be the reason a tool call
150
+ reports a problem to the user, and the hook contract is to exit 0 whatever
151
+ happens.
152
+ """
153
+ if not tool_name:
154
+ return False
155
+ try:
156
+ import sqlite3
157
+ from datetime import datetime, timezone
158
+
159
+ try:
160
+ from superlocalmemory.hooks.session_registry import (
161
+ resolve_active_profile,
162
+ )
163
+ profile_id = resolve_active_profile() or "default"
164
+ except Exception:
165
+ profile_id = "default"
166
+
167
+ project_path = ""
168
+ raw_cwd = payload.get("cwd")
169
+ if isinstance(raw_cwd, str):
170
+ project_path = raw_cwd[:_MAX_SUMMARY_LEN]
171
+
172
+ conn = sqlite3.connect(str(_memory_db_path()), timeout=0.05)
173
+ try:
174
+ conn.execute("PRAGMA busy_timeout=50")
175
+ conn.execute(
176
+ "INSERT INTO tool_events "
177
+ "(session_id, profile_id, project_path, tool_name, event_type,"
178
+ " input_summary, output_summary, duration_ms, metadata,"
179
+ " created_at) "
180
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
181
+ (
182
+ session_id,
183
+ profile_id,
184
+ project_path,
185
+ tool_name,
186
+ "complete",
187
+ _summary(payload.get("tool_input")),
188
+ _summary(response_text),
189
+ 0,
190
+ "{}",
191
+ datetime.now(timezone.utc).isoformat(),
192
+ ),
193
+ )
194
+ conn.commit()
195
+ finally:
196
+ conn.close()
197
+ return True
198
+ except Exception:
199
+ return False
200
+
201
+
86
202
  def _inner_main() -> str:
87
203
  """Return an ``outcome`` string (for perf log); never raises."""
88
204
  payload = read_stdin_json()
@@ -119,6 +235,12 @@ def _inner_main() -> str:
119
235
 
120
236
  # Response scan — capped BEFORE regex (bound O(cap)).
121
237
  response_text = summarize_response(payload.get("tool_response"))
238
+
239
+ # The invocation is recorded before anything is decided about markers. An
240
+ # action the agent took is a fact about the session on its own terms; the
241
+ # marker only decides whether it also settles a pending recall.
242
+ _record_tool_event(session_id, tool_name, payload, response_text)
243
+
122
244
  if not response_text:
123
245
  return "no_response"
124
246
 
@@ -205,3 +205,25 @@ def verify(conn: sqlite3.Connection) -> bool:
205
205
  )
206
206
  return False
207
207
  return True
208
+
209
+
210
+ def blocks_serving(conn: sqlite3.Connection) -> bool:
211
+ """Should a daemon refuse to serve while this check does not hold? No.
212
+
213
+ Nothing here is about schema. ``verify()`` reads ``content`` and compares
214
+ today's reading of it against the ``fact_type`` already stored, so a False
215
+ means some memories are filed as plans that no longer read as plans. Every
216
+ table and column a query needs is present either way; the store answers
217
+ normally, and at worst a handful of memories carry a stale label until the
218
+ next maintenance pass re-reads them.
219
+
220
+ The distinction matters because this is a standing guard over data that
221
+ ordinary use re-violates by design: a plan whose date passes stops reading
222
+ as upcoming, which is the rule working, not a fault. Treating that like a
223
+ missing table let one drifted row answer 503 on every route for as long as
224
+ the process lived — and because the readiness snapshot is taken once at
225
+ startup, the background pass that repairs the data could not lift the
226
+ refusal it caused. An outage produced by a quality check is worse than the
227
+ thing the check is for. Same reasoning as ``M043.blocks_serving``.
228
+ """
229
+ return False