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
@@ -39,8 +39,12 @@ from collections import Counter
39
39
  from pathlib import Path
40
40
 
41
41
  from .base import (
42
+ clean_llm_summary,
43
+ format_highlight,
44
+ SUMMARY_SYSTEM_PROMPT,
42
45
  COVERAGE_FULL,
43
46
  COVERAGE_INSUFFICIENT,
47
+ COVERAGE_PARTIAL,
44
48
  COVERAGE_UNAVAILABLE,
45
49
  GENERATED_BY_EXTRACTIVE,
46
50
  GENERATED_BY_LLM_B,
@@ -121,8 +125,19 @@ def generate_project_work_log(
121
125
  event_count = len(tool_rows)
122
126
  fact_count = len(facts_rows)
123
127
 
124
- coverage = COVERAGE_FULL if (event_count >= _MIN_EVENTS or fact_count >= _MIN_FACTS) \
125
- else COVERAGE_INSUFFICIENT
128
+ # A project work log has two inputs: what was DONE (tool events) and what was
129
+ # LEARNED (facts). "full" means both were there.
130
+ #
131
+ # The previous rule was `events >= 1 OR facts >= 1`, which reported "full" for
132
+ # a project with 86 tool events and zero facts — rendered in the dashboard as
133
+ # "Built from 0 memories · coverage: full". Claiming full coverage of nothing
134
+ # is precisely the dishonest summary issue #113 exists to prevent.
135
+ if event_count >= _MIN_EVENTS and fact_count >= _MIN_FACTS:
136
+ coverage = COVERAGE_FULL
137
+ elif event_count >= _MIN_EVENTS or fact_count >= _MIN_FACTS:
138
+ coverage = COVERAGE_PARTIAL
139
+ else:
140
+ coverage = COVERAGE_INSUFFICIENT
126
141
 
127
142
  extractive_content = _build_extractive_content(
128
143
  project_path, tool_rows, facts_rows, event_count, fact_count
@@ -302,8 +317,7 @@ def _build_extractive_content(
302
317
  lines.append("Key facts from project sessions:")
303
318
  for f in facts_rows[:_TOP_FACTS]:
304
319
  content = f.get("content", "")
305
- if len(content) > _MAX_FACT_CHARS:
306
- content = content[:_MAX_FACT_CHARS - 3] + "..."
320
+ content = format_highlight(content)
307
321
  lines.append(f" - {content}")
308
322
  if fact_count > _TOP_FACTS:
309
323
  lines.append(f" ... and {fact_count - _TOP_FACTS} more facts.")
@@ -373,6 +387,7 @@ def _call_ollama(
373
387
  payload = json.dumps({
374
388
  "model": model,
375
389
  "prompt": full_prompt,
390
+ "system": SUMMARY_SYSTEM_PROMPT,
376
391
  "stream": False,
377
392
  "options": {"num_predict": 300},
378
393
  }).encode()
@@ -383,7 +398,7 @@ def _call_ollama(
383
398
  )
384
399
  resp = urllib.request.urlopen(req, timeout=timeout)
385
400
  data = json.loads(resp.read().decode())
386
- text = data.get("response", "").strip()
401
+ text = clean_llm_summary(data.get("response", ""))
387
402
  return text if text and len(text) > 20 else None
388
403
  except Exception as exc:
389
404
  logger.debug("Ollama project work log failed: %s", exc)
@@ -414,11 +429,12 @@ def _call_cloud_llm(
414
429
  )
415
430
  text = llm.generate(
416
431
  prompt=full_prompt,
417
- system="You are a concise project activity summariser.",
432
+ system=SUMMARY_SYSTEM_PROMPT,
418
433
  max_tokens=300,
419
434
  temperature=0.1,
420
435
  )
421
- return text.strip() if text and len(text.strip()) > 20 else None
436
+ cleaned = clean_llm_summary(text or "")
437
+ return cleaned if len(cleaned) > 20 else None
422
438
  except Exception as exc:
423
439
  logger.debug("Cloud LLM project work log failed: %s", exc)
424
440
  return None
@@ -37,6 +37,9 @@ from datetime import date, timezone
37
37
  from pathlib import Path
38
38
 
39
39
  from .base import (
40
+ clean_llm_summary,
41
+ format_highlight,
42
+ SUMMARY_SYSTEM_PROMPT,
40
43
  COVERAGE_FULL,
41
44
  COVERAGE_INSUFFICIENT,
42
45
  COVERAGE_NO_SESSION,
@@ -209,8 +212,7 @@ def _build_extractive_content(
209
212
  ]
210
213
  for f in facts[:_BODY_FACTS]:
211
214
  content = f.get("content", "")
212
- if len(content) > _MAX_FACT_CHARS:
213
- content = content[:_MAX_FACT_CHARS - 3] + "..."
215
+ content = format_highlight(content)
214
216
  lines.append(f" - {content}")
215
217
  if fact_count > _BODY_FACTS:
216
218
  lines.append(f" ... and {fact_count - _BODY_FACTS} more facts.")
@@ -263,6 +265,7 @@ def _call_ollama(
263
265
  payload = json.dumps({
264
266
  "model": model,
265
267
  "prompt": full_prompt,
268
+ "system": SUMMARY_SYSTEM_PROMPT,
266
269
  "stream": False,
267
270
  "options": {"num_predict": 200},
268
271
  }).encode()
@@ -273,7 +276,7 @@ def _call_ollama(
273
276
  )
274
277
  resp = urllib.request.urlopen(req, timeout=timeout)
275
278
  data = json.loads(resp.read().decode())
276
- text = data.get("response", "").strip()
279
+ text = clean_llm_summary(data.get("response", ""))
277
280
  return text if text and len(text) > 20 else None
278
281
  except Exception as exc:
279
282
  logger.debug("Ollama session summary failed: %s", exc)
@@ -297,11 +300,12 @@ def _call_cloud_llm(
297
300
  full_prompt = f"{prompt}\n\nFacts:\n{fact_texts}\n\nRespond in 2-4 sentences."
298
301
  text = llm.generate(
299
302
  prompt=full_prompt,
300
- system="You are a concise memory summariser.",
303
+ system=SUMMARY_SYSTEM_PROMPT,
301
304
  max_tokens=200,
302
305
  temperature=0.1,
303
306
  )
304
- return text.strip() if text and len(text.strip()) > 20 else None
307
+ cleaned = clean_llm_summary(text or "")
308
+ return cleaned if len(cleaned) > 20 else None
305
309
  except Exception as exc:
306
310
  logger.debug("Cloud LLM session summary failed: %s", exc)
307
311
  return None
@@ -1017,6 +1017,11 @@
1017
1017
  <div id="skill-detail-panel" style="display:none;margin-top:24px"></div>
1018
1018
  </div>
1019
1019
 
1020
+ <!-- Bounded Loops (v4.0.8) — moved out of Governance: it is a
1021
+ separate product SLM optionally observes, i.e. an integration.
1022
+ Rendered entirely by od-boundedloops.js (window.odRenderLoops). -->
1023
+ <div class="tab-pane fade" id="loops-pane"></div>
1024
+
1020
1025
  <!-- Mesh Peers (v3.4.3 — Neural Glass) -->
1021
1026
  <div class="tab-pane fade" id="mesh-pane">
1022
1027
  <div class="ng-content-header">
@@ -1599,13 +1604,14 @@
1599
1604
  <script src="static/js/od-ops-health.js?v=400"></script>
1600
1605
  <script src="static/js/od-team.js?v=379"></script>
1601
1606
  <script src="static/js/od-graph.js?v=6812bf6c"></script>
1602
- <script src="static/js/od-memories.js?v=379"></script>
1607
+ <script src="static/js/od-memories.js?v=022ff653"></script>
1603
1608
  <script src="static/js/od-entities.js?v=379"></script>
1604
1609
  <!-- Multi-Agent Memory pane (v3.8.0): visualises memory written by multiple agents -->
1605
1610
  <script src="static/js/od-agents.js?v=c75ff3c2"></script>
1606
1611
  <script src="static/js/od-skills.js?v=379"></script>
1607
- <script src="static/js/od-mesh.js?v=379"></script>
1608
- <script src="static/js/od-optimize.js?v=379"></script>
1612
+ <script src="static/js/od-boundedloops.js?v=b4a014a1"></script>
1613
+ <script src="static/js/od-mesh.js?v=a7bc694a"></script>
1614
+ <script src="static/js/od-optimize.js?v=1f511698"></script>
1609
1615
  <script src="static/js/od-settings.js?v=386"></script>
1610
1616
  <script src="static/js/od-backup.js?v=379"></script>
1611
1617
  <!-- MCP & Integrations pane (v3.8.0): shows exposed MCP tool profile + counts -->
@@ -0,0 +1,324 @@
1
+ // Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ // Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ // Part of SuperLocalMemory | https://qualixar.com
4
+ //
5
+ // Bounded Loops pane.
6
+ //
7
+ // Moved out of Governance in 4.0.8. Every other Governance tab governs SLM's
8
+ // OWN data — lifecycle, access, trust, compliance, ingestion. Bounded Loops
9
+ // governs none of it: it is a SEPARATE product that SLM optionally observes
10
+ // over the published contract bounded-loops.dev/slm-bridge/v1. Neither product
11
+ // depends on the other and installing either alone is complete, which makes
12
+ // this an integration, not a governance function.
13
+ //
14
+ // Reads GET /api/v3/bounded-loops/evidence for live bridge status and observed
15
+ // terminal runs. The guarantees are rendered FROM THE DATA rather than asserted
16
+ // in prose: every document carries eligible_for_learning=false, so the pane can
17
+ // show that SLM is not permitted to learn from these runs instead of merely
18
+ // promising that it does not.
19
+
20
+ (function () {
21
+ 'use strict';
22
+
23
+ function esc(s) {
24
+ return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
25
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
26
+ });
27
+ }
28
+
29
+ function scaffold() {
30
+ return (
31
+
32
+ // ══════ BOUNDED LOOPS TAB ════════════════════════════════════════
33
+ // Static info card — no backend calls.
34
+ // A bounded loop advances only when an INDEPENDENT gate passes, not
35
+ // when the agent claims success. Laps are persisted as SLM memory
36
+ // (tag loop:<name>) so the full run history is queryable.
37
+ '<div>' +
38
+
39
+ // Live bridge status first: whether Bounded Loops is installed, the
40
+ // negotiated contract, and which runs were observed. The explanation
41
+ // below it is the same for everyone; this part is about YOUR install.
42
+ '<div class="card" style="margin-bottom:16px">' +
43
+ '<div class="card-head"><h3>Bridge status</h3>' +
44
+ '<span class="sub">observation only \u2014 never learns from these runs</span>' +
45
+ '</div>' +
46
+ '<div class="card-pad" id="od-bl-evidence">' +
47
+ '<div style="font-size:13px;color:var(--fg-2)">Checking bridge\u2026</div>' +
48
+ '</div>' +
49
+ '</div>' +
50
+
51
+ '<div class="page-head" style="margin-bottom:16px">' +
52
+ '<h2 style="font-size:20px;margin-bottom:6px">Bounded Loops</h2>' +
53
+ '<p style="font-size:13.5px">An iteration control pattern for agentic ' +
54
+ 'frameworks &mdash; the loop advances only when an <strong>independent ' +
55
+ 'gate</strong> passes, not when the agent claims success. Prevents ' +
56
+ 'rationalisation: the model cannot self-certify completion.</p>' +
57
+ '</div>' +
58
+
59
+ // Two-column: concept + CLI reference
60
+ '<div class="grid" style="grid-template-columns:1fr 1fr;align-items:start;margin-bottom:16px">' +
61
+
62
+ '<div class="card">' +
63
+ '<div class="card-head"><h3>How it works</h3></div>' +
64
+ '<div class="card-pad">' +
65
+ '<p style="font-size:13px;line-height:1.6;margin-bottom:12px">' +
66
+ 'Standard agentic loops let the model declare itself done &mdash; ' +
67
+ 'a known failure mode when the model rationalises instead of verifying. ' +
68
+ 'A bounded loop separates <em>execution</em> (the agent) from ' +
69
+ '<em>verification</em> (an independent gate such as a test suite, ' +
70
+ 'linter, or judge LLM).' +
71
+ '</p>' +
72
+ '<p style="font-size:13px;line-height:1.6;margin-bottom:12px">' +
73
+ 'The loop terminates only when the gate returns <code>DONE</code>, ' +
74
+ 'or when a hard lap cap is reached (<code>HALT</code>). Each lap is ' +
75
+ 'persisted as a queryable SLM memory tagged ' +
76
+ '<code>loop:&lt;name&gt;</code>.' +
77
+ '</p>' +
78
+ '<div style="background:var(--card-2);border-radius:var(--r-md);' +
79
+ 'padding:10px 14px;font-size:12.5px;line-height:1.7">' +
80
+ '<div><span class="badge ok" style="margin-right:8px">DONE</span>' +
81
+ 'Gate passed &mdash; loop succeeded cleanly</div>' +
82
+ '<div style="margin-top:6px"><span class="badge warn" style="margin-right:8px">HALT</span>' +
83
+ 'Lap cap reached &mdash; hard stop applied</div>' +
84
+ '<div style="margin-top:6px"><span class="badge cyan" style="margin-right:8px">PAUSE</span>' +
85
+ 'Awaiting external input or approval</div>' +
86
+ '<div style="margin-top:6px"><span class="badge danger" style="margin-right:8px">KILLED</span>' +
87
+ 'Manually stopped by the operator</div>' +
88
+ '<div style="margin-top:6px"><span class="badge neutral" style="margin-right:8px">ERROR</span>' +
89
+ 'Unrecoverable failure during a lap</div>' +
90
+ '</div>' +
91
+ '</div>' +
92
+ '</div>' +
93
+
94
+ '<div class="card">' +
95
+ '<div class="card-head"><h3>Run it: CLI &middot; command &middot; MCP</h3></div>' +
96
+ '<div class="card-pad">' +
97
+ '<p style="font-size:13px;margin-bottom:14px">' +
98
+ 'Bounded loops ship on three surfaces &mdash; the same engine and ' +
99
+ 'the same queryable ledger behind each.' +
100
+ '</p>' +
101
+
102
+ '<div style="font-size:12px;font-weight:600;color:var(--fg-2);margin-bottom:6px">CLI</div>' +
103
+ '<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:14px">' +
104
+ '<div class="list-row">' +
105
+ '<span class="mono" style="min-width:190px;font-size:13px">slm loop demo</span>' +
106
+ '<span style="font-size:12.5px;color:var(--fg-2)">Run a live demo bounded loop</span>' +
107
+ '</div>' +
108
+ '<div class="list-row">' +
109
+ '<span class="mono" style="min-width:190px;font-size:13px">slm loop history</span>' +
110
+ '<span style="font-size:12.5px;color:var(--fg-2)">List loop runs for this profile</span>' +
111
+ '</div>' +
112
+ '<div class="list-row">' +
113
+ '<span class="mono" style="min-width:190px;font-size:13px">slm loop show &lt;run_id&gt;</span>' +
114
+ '<span style="font-size:12.5px;color:var(--fg-2)">Inspect a run lap-by-lap</span>' +
115
+ '</div>' +
116
+ '</div>' +
117
+
118
+ '<div style="font-size:12px;font-weight:600;color:var(--fg-2);margin-bottom:6px">Command (Claude Code / Codex)</div>' +
119
+ '<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:14px">' +
120
+ '<div class="list-row">' +
121
+ '<span class="mono" style="min-width:190px;font-size:13px">/slm-loop</span>' +
122
+ '<span style="font-size:12.5px;color:var(--fg-2)">Slash command bound to the slm-loop skill + runner agent</span>' +
123
+ '</div>' +
124
+ '</div>' +
125
+
126
+ '<div style="font-size:12px;font-weight:600;color:var(--fg-2);margin-bottom:6px">MCP tools (code / full / power profiles)</div>' +
127
+ '<div style="display:flex;flex-direction:column;gap:8px">' +
128
+ '<div class="list-row">' +
129
+ '<span class="mono" style="min-width:190px;font-size:13px">slm_loop_run</span>' +
130
+ '<span style="font-size:12.5px;color:var(--fg-2)">Run a gated loop &mdash; the gate is an independent SLM recall</span>' +
131
+ '</div>' +
132
+ '<div class="list-row">' +
133
+ '<span class="mono" style="min-width:190px;font-size:13px">slm_loop_history</span>' +
134
+ '<span style="font-size:12.5px;color:var(--fg-2)">List runs (read-only)</span>' +
135
+ '</div>' +
136
+ '<div class="list-row">' +
137
+ '<span class="mono" style="min-width:190px;font-size:13px">slm_loop_show</span>' +
138
+ '<span style="font-size:12.5px;color:var(--fg-2)">Show a run lap-by-lap (read-only)</span>' +
139
+ '</div>' +
140
+ '</div>' +
141
+ '<div style="margin-top:18px;padding:10px 14px;background:var(--card-2);' +
142
+ 'border-radius:var(--r-md);font-size:12.5px;line-height:1.6">' +
143
+ '<b>Memory tagging:</b> each lap is stored with tag ' +
144
+ '<code>loop:&lt;name&gt;</code>. Recall the full history with ' +
145
+ '<br><code>slm recall --tag loop:my-loop-name</code>' +
146
+ '</div>' +
147
+ '</div>' +
148
+ '</div>' +
149
+
150
+ '</div>' + // end two-column grid
151
+
152
+ // Framework adapters note
153
+ '<div class="card">' +
154
+ '<div class="card-head"><h3>Framework adapters</h3></div>' +
155
+ '<div class="card-pad">' +
156
+ '<p style="font-size:13px;line-height:1.6;margin-bottom:14px">' +
157
+ 'Bounded loops integrate with any agentic framework that supports ' +
158
+ 'tool-call round-trips. The gate is an ordinary SLM memory check &mdash; ' +
159
+ 'no special framework wiring required. Each framework stamps ' +
160
+ '<code>SLM_AGENT_ID</code> so lap history is attribution-aware.' +
161
+ '</p>' +
162
+ '<div style="display:flex;flex-wrap:wrap;gap:8px">' +
163
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
164
+ 'background:var(--card-2);color:var(--fg-2)">CrewAI</span>' +
165
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
166
+ 'background:var(--card-2);color:var(--fg-2)">LangChain</span>' +
167
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
168
+ 'background:var(--card-2);color:var(--fg-2)">LangGraph</span>' +
169
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
170
+ 'background:var(--card-2);color:var(--fg-2)">Semantic Kernel</span>' +
171
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
172
+ 'background:var(--card-2);color:var(--fg-2)">LlamaIndex</span>' +
173
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
174
+ 'background:var(--card-2);color:var(--fg-2)">Microsoft Agent Framework</span>' +
175
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
176
+ 'background:var(--card-2);color:var(--fg-2)">AutoGen</span>' +
177
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
178
+ 'background:var(--card-2);color:var(--fg-2)">Google ADK</span>' +
179
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
180
+ 'background:var(--card-2);color:var(--fg-2)">OpenAI Agents</span>' +
181
+ '<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
182
+ 'background:var(--card-2);color:var(--fg-2)">Any MCP-compatible agent</span>' +
183
+ '</div>' +
184
+ '<p style="margin-top:12px;font-size:12.5px;color:var(--fg-2)">' +
185
+ 'Learn the full pattern: run <code>/slm-loop</code> (the slm-loop skill) ' +
186
+ 'inside Claude Code for an interactive walkthrough with live examples.' +
187
+ '</p>' +
188
+ '</div>' +
189
+ '</div>' +
190
+
191
+ '</div>'
192
+ );
193
+ }
194
+
195
+ /* Bounded Loops bridge status + observed runs.
196
+ *
197
+ * Until 4.0.8 this tab was a static essay about loop gating. It said nothing
198
+ * about the bridge, which is the part that concerns SLM: Bounded Loops is a
199
+ * SEPARATE product, and what SLM does with it is import read-only evidence
200
+ * over the published contract bounded-loops.dev/slm-bridge/v1.
201
+ *
202
+ * The guarantees are the point, and they must be stated from the DATA rather
203
+ * than asserted in prose — `eligible_for_learning` is a hard field in every
204
+ * document, always false in v1, so the pane can show that SLM is not allowed
205
+ * to learn from these runs rather than merely promising it doesn't.
206
+ */
207
+ var _blLoaded = false;
208
+ function loadEvidence() {
209
+ var box = document.getElementById('od-bl-evidence');
210
+ if (!box || _blLoaded) return;
211
+ _blLoaded = true;
212
+
213
+ fetch('/internal/token', { credentials: 'same-origin' })
214
+ .then(function (r) { return r.ok ? r.json() : null; })
215
+ .catch(function () { return null; })
216
+ .then(function (t) {
217
+ var tok = t && (t.token || t.install_token);
218
+ return fetch('/api/v3/bounded-loops/evidence', {
219
+ credentials: 'same-origin',
220
+ headers: tok ? { 'X-Install-Token': tok } : {},
221
+ });
222
+ })
223
+ .then(function (r) { return r.ok ? r.json() : null; })
224
+ .then(function (d) {
225
+ if (!d) { box.innerHTML = _blNote('Bridge status unavailable.'); return; }
226
+ box.innerHTML = renderBoundedLoopsEvidence(d);
227
+ })
228
+ .catch(function () {
229
+ _blLoaded = false; // allow a retry on the next tab visit
230
+ box.innerHTML = _blNote('Could not read bridge status.');
231
+ });
232
+ }
233
+
234
+ function _blNote(msg) {
235
+ return '<div style="font-size:13px;color:var(--fg-2)">' + esc(msg) + '</div>';
236
+ }
237
+
238
+ function renderBoundedLoopsEvidence(d) {
239
+ var installed = d.installed;
240
+ var runs = d.runs || [];
241
+ var badge = installed === true
242
+ ? '<span class="badge ok">installed</span>'
243
+ : (installed === false
244
+ ? '<span class="badge neutral">not installed</span>'
245
+ : '<span class="badge warn">unknown</span>');
246
+
247
+ var head =
248
+ '<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">' +
249
+ badge +
250
+ '<span class="mono" style="font-size:12px;color:var(--fg-2)">' +
251
+ esc(d.contract || '') + '</span>' +
252
+ '</div>';
253
+
254
+ // Optional by design — "not installed" is a normal, complete state.
255
+ if (installed === false) {
256
+ return head + '<p style="font-size:13px;line-height:1.6;color:var(--fg-2)">' +
257
+ 'Bounded Loops is a separate product and SuperLocalMemory does not ' +
258
+ 'require it. Install it and SLM can import read-only evidence about ' +
259
+ 'finished runs — it never sends anything back.</p>';
260
+ }
261
+
262
+ var guarantees =
263
+ '<div style="background:var(--card-2);border-radius:var(--r-md);' +
264
+ 'padding:12px 14px;font-size:12.5px;line-height:1.75;margin-bottom:12px">' +
265
+ '<div><strong>Observation only.</strong> Evidence records what a run did. ' +
266
+ 'It never authorises SLM to learn from it, re-rank memories, or change ' +
267
+ 'routing — every document carries <code>eligible_for_learning: false</code>.</div>' +
268
+ '<div style="margin-top:6px"><strong>No paths leave the workspace.</strong> ' +
269
+ 'Locations travel as digests, so a project directory name never reaches ' +
270
+ 'the memory store. Gate text and artifact contents are excluded at source.</div>' +
271
+ '<div style="margin-top:6px"><strong>Tamper-evident, not verified.</strong> ' +
272
+ 'Receipts form a local append-only hash chain (<code>local_hash_chain_only</code>). ' +
273
+ 'That makes edits detectable — it is not authentication or independent audit.</div>' +
274
+ '</div>';
275
+
276
+ if (!runs.length) {
277
+ return head + guarantees +
278
+ '<p style="font-size:13px;color:var(--fg-2)">' +
279
+ 'No runs observed yet. Import one with the <code>observe_bounded_loop_evidence</code> ' +
280
+ 'tool — nothing is imported automatically.</p>';
281
+ }
282
+
283
+ var demoNote = d.demonstration_count
284
+ ? '<div style="font-size:12px;color:var(--fg-3);margin-bottom:8px">' +
285
+ esc(String(d.demonstration_count)) + ' of ' + esc(String(d.total)) +
286
+ ' observed run' + (d.total === 1 ? '' : 's') + ' ' +
287
+ (d.demonstration_count === 1 ? 'is a demonstration' : 'are demonstrations') +
288
+ ' — wiring proofs, not real work.</div>'
289
+ : '';
290
+
291
+ var rows = runs.map(function (r) {
292
+ var cls = r.outcome === 'SUCCEEDED' ? 'ok'
293
+ : (r.outcome === 'CANCELLED' ? 'neutral' : 'danger');
294
+ // run_state, not just outcome: HALTED (budget/policy stop) and FAILED
295
+ // (gate rejected the work) are different events and both map to FAILED.
296
+ var state = r.run_state && r.run_state !== r.outcome
297
+ ? ' <span style="color:var(--fg-3)">(' + esc(r.run_state) + ')</span>' : '';
298
+ // min-width:0 + ellipsis: a run ref is a single unbroken token, and in a
299
+ // narrow pane `flex:1` alone shreds it one character per line
300
+ // ("demo / — / proo / f"). Truncate with the full value in the tooltip.
301
+ return '<div class="list-row" style="align-items:baseline;gap:8px">' +
302
+ '<span class="mono" title="' + esc(r.run_ref || r.run_id) + '" ' +
303
+ 'style="flex:1;min-width:0;font-size:12.5px;overflow:hidden;' +
304
+ 'text-overflow:ellipsis;white-space:nowrap">' +
305
+ esc(r.run_ref || r.run_id) + '</span>' +
306
+ (r.demonstration
307
+ ? '<span class="badge neutral" style="margin-right:8px">demo</span>' : '') +
308
+ '<span class="badge ' + cls + '" style="margin-right:8px">' + esc(r.outcome) + '</span>' +
309
+ '<span style="font-size:12px;color:var(--fg-2)">' + state + '</span>' +
310
+ '<span style="font-size:12px;color:var(--fg-3);margin-left:10px">seq ' +
311
+ esc(String(r.receipt_sequence)) + '</span>' +
312
+ '</div>';
313
+ }).join('');
314
+
315
+ return head + guarantees + demoNote + rows;
316
+ }
317
+
318
+ window.odRenderLoops = function (container) {
319
+ if (!container) return;
320
+ container.innerHTML = scaffold();
321
+ _blLoaded = false;
322
+ loadEvidence();
323
+ };
324
+ })();