superlocalmemory 4.0.2 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +36 -40
  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 +4 -4
  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 +3 -3
  24. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  25. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  26. package/plugin-src/skills/slm-governance/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  28. package/plugin-src/skills/slm-loop/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-profile/SKILL.md +4 -4
  31. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  32. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  33. package/plugin-src/skills/slm-scope/SKILL.md +1 -1
  34. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  35. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  36. package/pyproject.toml +1 -1
  37. package/scripts/postinstall-interactive.js +1 -0
  38. package/scripts/postinstall.js +4 -0
  39. package/src/superlocalmemory/__init__.py +1 -1
  40. package/src/superlocalmemory/cli/commands.py +10 -19
  41. package/src/superlocalmemory/cli/host_upgrades.py +175 -0
  42. package/src/superlocalmemory/cli/main.py +29 -4
  43. package/src/superlocalmemory/integrations/bounded_loops_mcp.py +184 -0
  44. package/src/superlocalmemory/learning/database.py +2 -1
  45. package/src/superlocalmemory/mcp/profiles.py +28 -13
  46. package/src/superlocalmemory/mcp/server.py +7 -6
  47. package/src/superlocalmemory/mcp/tools_brain.py +89 -4
  48. package/src/superlocalmemory/server/routes/brain.py +6 -1
  49. package/src/superlocalmemory/storage/_migration_internals.py +4 -0
  50. package/src/superlocalmemory/storage/_schema_version.py +2 -2
  51. package/src/superlocalmemory/storage/agent_experience.py +26 -4
  52. package/src/superlocalmemory/storage/external_evidence.py +359 -0
  53. package/src/superlocalmemory/storage/migration_runner.py +5 -0
  54. package/src/superlocalmemory/storage/migrations/M041_external_evidence_receipts.py +189 -0
  55. package/src/superlocalmemory/storage/migrations/__init__.py +2 -0
  56. package/src/superlocalmemory/ui/js/od-brain.js +9 -0
  57. package/src/superlocalmemory/ui/js/od-mcp.js +6 -6
@@ -0,0 +1,189 @@
1
+ """M041 — typed, observation-only external evidence in ``learning.db``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlite3
6
+
7
+ NAME = "M041_external_evidence_receipts"
8
+ DB_TARGET = "learning"
9
+
10
+ DDL = """
11
+ BEGIN IMMEDIATE;
12
+ CREATE TABLE IF NOT EXISTS external_evidence_receipts (
13
+ profile_id TEXT NOT NULL,
14
+ contract_id TEXT NOT NULL,
15
+ workspace_id TEXT NOT NULL,
16
+ run_ref TEXT NOT NULL,
17
+ run_id TEXT NOT NULL,
18
+ outcome TEXT NOT NULL,
19
+ run_state TEXT NOT NULL,
20
+ demonstration INTEGER NOT NULL CHECK (demonstration IN (0, 1)),
21
+ eligible_for_learning INTEGER NOT NULL CHECK (eligible_for_learning IN (0, 1)),
22
+ terminal_at TEXT NOT NULL,
23
+ graph_digest TEXT NOT NULL,
24
+ plan_digest TEXT NOT NULL,
25
+ policy_digest TEXT NOT NULL,
26
+ receipt_sequence INTEGER NOT NULL,
27
+ receipt_head_digest TEXT NOT NULL,
28
+ receipt_trust TEXT NOT NULL,
29
+ nodes_json TEXT NOT NULL,
30
+ artifact_digests_json TEXT NOT NULL,
31
+ payload_sha256 TEXT NOT NULL,
32
+ observed_at TEXT NOT NULL,
33
+ PRIMARY KEY (profile_id, contract_id, workspace_id, run_ref)
34
+ );
35
+ CREATE INDEX IF NOT EXISTS idx_external_evidence_profile_terminal
36
+ ON external_evidence_receipts (profile_id, terminal_at DESC);
37
+ CREATE INDEX IF NOT EXISTS idx_external_evidence_profile_workspace
38
+ ON external_evidence_receipts (profile_id, workspace_id, terminal_at DESC);
39
+ COMMIT;
40
+ """
41
+
42
+ _TABLE = "external_evidence_receipts"
43
+ _COLUMNS = (
44
+ "profile_id",
45
+ "contract_id",
46
+ "workspace_id",
47
+ "run_ref",
48
+ "run_id",
49
+ "outcome",
50
+ "run_state",
51
+ "demonstration",
52
+ "eligible_for_learning",
53
+ "terminal_at",
54
+ "graph_digest",
55
+ "plan_digest",
56
+ "policy_digest",
57
+ "receipt_sequence",
58
+ "receipt_head_digest",
59
+ "receipt_trust",
60
+ "nodes_json",
61
+ "artifact_digests_json",
62
+ "payload_sha256",
63
+ "observed_at",
64
+ )
65
+ _TYPES = (
66
+ "TEXT",
67
+ "TEXT",
68
+ "TEXT",
69
+ "TEXT",
70
+ "TEXT",
71
+ "TEXT",
72
+ "TEXT",
73
+ "INTEGER",
74
+ "INTEGER",
75
+ "TEXT",
76
+ "TEXT",
77
+ "TEXT",
78
+ "TEXT",
79
+ "INTEGER",
80
+ "TEXT",
81
+ "TEXT",
82
+ "TEXT",
83
+ "TEXT",
84
+ "TEXT",
85
+ "TEXT",
86
+ )
87
+ _PRIMARY_KEY = ("profile_id", "contract_id", "workspace_id", "run_ref")
88
+ _INDEXES = {
89
+ "idx_external_evidence_profile_terminal": (
90
+ "external_evidence_receipts", (("profile_id", False), ("terminal_at", True)),
91
+ ),
92
+ "idx_external_evidence_profile_workspace": (
93
+ "external_evidence_receipts",
94
+ (("profile_id", False), ("workspace_id", False), ("terminal_at", True)),
95
+ ),
96
+ }
97
+
98
+
99
+ def apply(conn: sqlite3.Connection) -> None:
100
+ """Install the additive receipt table atomically and idempotently."""
101
+ if _table_exists(conn) and not _table_is_valid(conn):
102
+ raise sqlite3.OperationalError(
103
+ "M041 external evidence table is malformed; refusing rebuild"
104
+ )
105
+ conn.executescript(DDL)
106
+ if not verify(conn):
107
+ repair(conn)
108
+ if not verify(conn):
109
+ raise sqlite3.OperationalError("M041 external evidence schema did not reach its end-state")
110
+
111
+
112
+ def repair(conn: sqlite3.Connection) -> None:
113
+ """Restore only M041's derived indexes without touching stored evidence."""
114
+ if _table_exists(conn) and not _table_is_valid(conn):
115
+ raise sqlite3.OperationalError(
116
+ "M041 external evidence table is malformed; refusing rebuild"
117
+ )
118
+ if not _table_exists(conn):
119
+ apply(conn)
120
+ return
121
+ drops = "\n".join(f"DROP INDEX IF EXISTS {name};" for name in _INDEXES)
122
+ creates = "\n".join(
123
+ f"CREATE INDEX {name} ON {table} ({_index_sql_columns(columns)});"
124
+ for name, (table, columns) in _INDEXES.items()
125
+ )
126
+ conn.executescript(f"BEGIN IMMEDIATE;\n{drops}\n{creates}\nCOMMIT;")
127
+ if not verify(conn):
128
+ raise sqlite3.OperationalError("M041 index repair did not restore required end-state")
129
+
130
+
131
+ def verify(conn: sqlite3.Connection) -> bool:
132
+ if not _table_is_valid(conn):
133
+ return False
134
+ for name, (table, columns) in _INDEXES.items():
135
+ row = conn.execute(
136
+ "SELECT tbl_name FROM sqlite_master WHERE type='index' AND name=?", (name,)
137
+ ).fetchone()
138
+ if row is None or row[0] != table:
139
+ return False
140
+ actual = tuple(
141
+ (item[2], bool(item[3]))
142
+ for item in conn.execute(f"PRAGMA index_xinfo({name})")
143
+ if item[5] and item[2] is not None
144
+ )
145
+ if actual != columns:
146
+ return False
147
+ return True
148
+
149
+
150
+ def _table_exists(conn: sqlite3.Connection) -> bool:
151
+ return (
152
+ conn.execute(
153
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (_TABLE,)
154
+ ).fetchone()
155
+ is not None
156
+ )
157
+
158
+
159
+ def _table_is_valid(conn: sqlite3.Connection) -> bool:
160
+ if not _table_exists(conn):
161
+ return False
162
+ info = conn.execute(f"PRAGMA table_info({_TABLE})").fetchall()
163
+ columns = tuple(row[1] for row in info)
164
+ types = tuple(str(row[2]).upper() for row in info)
165
+ primary_key = tuple(row[1] for row in sorted(info, key=lambda row: row[5]) if row[5])
166
+ not_null = {row[1] for row in info if row[3] or row[5]}
167
+ return (
168
+ columns == _COLUMNS
169
+ and types == _TYPES
170
+ and primary_key == _PRIMARY_KEY
171
+ and not_null == set(_COLUMNS)
172
+ and conn.execute(f"PRAGMA foreign_key_list({_TABLE})").fetchone() is None
173
+ and _required_checks_present(conn)
174
+ )
175
+
176
+
177
+ def _required_checks_present(conn: sqlite3.Connection) -> bool:
178
+ row = conn.execute(
179
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (_TABLE,)
180
+ ).fetchone()
181
+ sql = "" if row is None or row[0] is None else "".join(str(row[0]).lower().split())
182
+ return (
183
+ "check(demonstrationin(0,1))" in sql
184
+ and "check(eligible_for_learningin(0,1))" in sql
185
+ )
186
+
187
+
188
+ def _index_sql_columns(columns: tuple[tuple[str, bool], ...]) -> str:
189
+ return ", ".join(column + (" DESC" if desc else "") for column, desc in columns)
@@ -31,6 +31,7 @@ from . import (
31
31
  M038_learning_feedback_channel,
32
32
  M039_scene_fact_members,
33
33
  M040_agent_experience_receipts,
34
+ M041_external_evidence_receipts,
34
35
  )
35
36
 
36
37
  # ---------------------------------------------------------------------------
@@ -87,6 +88,7 @@ __all__ = (
87
88
  "M038_learning_feedback_channel",
88
89
  "M039_scene_fact_members",
89
90
  "M040_agent_experience_receipts",
91
+ "M041_external_evidence_receipts",
90
92
  # Legacy re-exports (backward compat):
91
93
  "CURRENT_SCHEMA_VERSION",
92
94
  "get_schema_version",
@@ -230,6 +230,7 @@
230
230
  var feedback = (living && living.feedback) || {};
231
231
  var graph = (living && living.graph) || {};
232
232
  var experience = (living && living.agent_experience) || {};
233
+ var externalEvidence = experience.external_graph_evidence || {};
233
234
 
234
235
  // KPI strip
235
236
  var strip = EL('div', { className: 'kpi-strip', style: 'margin-bottom:16px' });
@@ -318,6 +319,8 @@
318
319
  ['Claimed evidence authority', String(experience.claimed_evidence_experiences || 0)],
319
320
  ['Cognitive turns', String(experience.turns_total || 0) +
320
321
  ' · ' + String((experience.turns_by_state || {}).finalized || 0) + ' finalized'],
322
+ ['Bounded Loop observations', String(externalEvidence.total || 0) +
323
+ (externalEvidence.is_real ? ' terminal receipts' : ' unavailable')],
321
324
  ['Graph evidence', String(graph.fact_nodes || 0) + ' nodes · ' +
322
325
  String(graph.association_edges || 0) + ' edges'],
323
326
  ].forEach(function (row) {
@@ -362,6 +365,12 @@
362
365
  fmtNum((experience.turns_by_state || {}).open || 0) + ' open · ' +
363
366
  fmtNum((experience.turns_by_state || {}).finalized || 0) + ' finalized',
364
367
  Number(experience.turns_total || 0) > 0, undefined, true));
368
+ evGrid.appendChild(kpiCard('account_tree', 'Bounded Loop observations',
369
+ fmtNum(externalEvidence.total || 0),
370
+ externalEvidence.is_real
371
+ ? fmtNum(externalEvidence.demonstrations || 0) + ' demonstrations · no automatic learning'
372
+ : 'connect Bounded Loops to observe terminal runs',
373
+ Number(externalEvidence.total || 0) > 0, undefined, true));
365
374
  evb.appendChild(evGrid);
366
375
  evc.appendChild(evb);
367
376
  sec.appendChild(evc);
@@ -9,9 +9,9 @@
9
9
  // current: "core",
10
10
  // profiles: {
11
11
  // core: { count: 14, tools: [...], description: "..." },
12
- // code: { count: 24, tools: [...], description: "..." },
13
- // full: { count: 42, tools: [...], description: "..." },
14
- // power: { count: 54, tools: [...], description: "..." },
12
+ // code: { count: 28, tools: [...], description: "..." },
13
+ // full: { count: 46, tools: [...], description: "..." },
14
+ // power: { count: 58, tools: [...], description: "..." },
15
15
  // mesh: { count: 8, tools: [...], description: "..." },
16
16
  // },
17
17
  // aliases: { "code21": "code", ... },
@@ -370,9 +370,9 @@
370
370
  codeBlock.textContent =
371
371
  '"env": {\n' +
372
372
  ' "SLM_MCP_PROFILE": "core" // 14 tools — minimal\n' +
373
- ' // "SLM_MCP_PROFILE": "code" // 24 tools — + code graph + loops\n' +
374
- ' // "SLM_MCP_PROFILE": "full" // 42 tools — + mesh\n' +
375
- ' // "SLM_MCP_PROFILE": "power" // 54 tools — + governance\n' +
373
+ ' // "SLM_MCP_PROFILE": "code" // 28 tools — + Brain, code graph, loops\n' +
374
+ ' // "SLM_MCP_PROFILE": "full" // 46 tools — + mesh\n' +
375
+ ' // "SLM_MCP_PROFILE": "power" // 58 tools — + governance\n' +
376
376
  ' // "SLM_MCP_PROFILE": "mesh" // 8 tools — mesh only\n' +
377
377
  '}';
378
378
  body2.appendChild(codeBlock);