superlocalmemory 4.0.9 → 4.1.0

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 (165) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +245 -0
  3. package/README.md +7 -7
  4. package/package.json +4 -2
  5. package/plugin/.claude-plugin/plugin.json +2 -2
  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 +4 -4
  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 +2 -2
  17. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  18. package/plugin/skills/slm-profile/SKILL.md +5 -5
  19. package/plugin/skills/slm-recall/SKILL.md +102 -15
  20. package/plugin/skills/slm-remember/SKILL.md +35 -3
  21. package/plugin/skills/slm-scope/SKILL.md +1 -1
  22. package/plugin/skills/slm-session/SKILL.md +29 -3
  23. package/plugin/skills/slm-status/SKILL.md +1 -1
  24. package/plugin-src/rules/AGENTS.md +16 -8
  25. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  26. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-governance/SKILL.md +1 -1
  28. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-loop/SKILL.md +2 -2
  30. package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
  31. package/plugin-src/skills/slm-profile/SKILL.md +5 -5
  32. package/plugin-src/skills/slm-recall/SKILL.md +102 -15
  33. package/plugin-src/skills/slm-remember/SKILL.md +35 -3
  34. package/plugin-src/skills/slm-scope/SKILL.md +1 -1
  35. package/plugin-src/skills/slm-session/SKILL.md +29 -3
  36. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  37. package/pyproject.toml +1 -1
  38. package/src/superlocalmemory/__init__.py +1 -1
  39. package/src/superlocalmemory/cli/commands.py +308 -20
  40. package/src/superlocalmemory/cli/daemon.py +30 -0
  41. package/src/superlocalmemory/cli/db_migrate.py +71 -1
  42. package/src/superlocalmemory/cli/gdpr_cmd.py +15 -2
  43. package/src/superlocalmemory/cli/main.py +26 -4
  44. package/src/superlocalmemory/code_graph/bridge/maintenance.py +8 -0
  45. package/src/superlocalmemory/code_graph/database.py +44 -0
  46. package/src/superlocalmemory/compliance/gdpr.py +449 -39
  47. package/src/superlocalmemory/core/admission.py +231 -11
  48. package/src/superlocalmemory/core/backend_orchestrator.py +190 -84
  49. package/src/superlocalmemory/core/config.py +90 -11
  50. package/src/superlocalmemory/core/consolidation_engine.py +34 -0
  51. package/src/superlocalmemory/core/engine.py +140 -11
  52. package/src/superlocalmemory/core/fact_consolidator.py +316 -125
  53. package/src/superlocalmemory/core/graph_analyzer.py +76 -112
  54. package/src/superlocalmemory/core/graph_metrics.py +597 -0
  55. package/src/superlocalmemory/core/graph_pruner.py +121 -0
  56. package/src/superlocalmemory/core/maintenance.py +44 -6
  57. package/src/superlocalmemory/core/maintenance_scheduler.py +205 -0
  58. package/src/superlocalmemory/core/memory_health.py +266 -0
  59. package/src/superlocalmemory/core/mode_capability.py +111 -0
  60. package/src/superlocalmemory/core/ollama_validator.py +315 -0
  61. package/src/superlocalmemory/core/operation_policy_registry.py +1 -1
  62. package/src/superlocalmemory/core/operation_request.py +1 -1
  63. package/src/superlocalmemory/core/ops_remediation.py +2 -2
  64. package/src/superlocalmemory/core/projection_drain.py +380 -0
  65. package/src/superlocalmemory/core/recall_pipeline.py +390 -3
  66. package/src/superlocalmemory/core/recall_worker.py +6 -3
  67. package/src/superlocalmemory/core/scale_autopromote.py +196 -0
  68. package/src/superlocalmemory/core/scale_engine.py +16 -2
  69. package/src/superlocalmemory/core/score_contract.py +21 -1
  70. package/src/superlocalmemory/core/session_identity.py +85 -0
  71. package/src/superlocalmemory/core/status_contract.py +108 -0
  72. package/src/superlocalmemory/core/store_pipeline.py +78 -3
  73. package/src/superlocalmemory/core/worker_pool.py +4 -4
  74. package/src/superlocalmemory/core/working_memory.py +288 -0
  75. package/src/superlocalmemory/encoding/cognitive_consolidator.py +51 -7
  76. package/src/superlocalmemory/encoding/context_generator.py +1 -1
  77. package/src/superlocalmemory/encoding/entity_resolver.py +38 -0
  78. package/src/superlocalmemory/encoding/fact_extractor.py +18 -14
  79. package/src/superlocalmemory/encoding/prospective_markers.py +262 -0
  80. package/src/superlocalmemory/encoding/type_router.py +12 -12
  81. package/src/superlocalmemory/evolution/mutation_generator.py +30 -4
  82. package/src/superlocalmemory/graph/cozo_adjacency.py +122 -0
  83. package/src/superlocalmemory/graph/cozo_backend.py +103 -138
  84. package/src/superlocalmemory/hooks/portable_kit.py +10 -2
  85. package/src/superlocalmemory/learning/bandit.py +43 -0
  86. package/src/superlocalmemory/learning/consolidation_worker.py +54 -0
  87. package/src/superlocalmemory/learning/database.py +60 -3
  88. package/src/superlocalmemory/learning/entity_compiler.py +21 -58
  89. package/src/superlocalmemory/learning/feedback.py +3 -1
  90. package/src/superlocalmemory/learning/outcomes.py +47 -16
  91. package/src/superlocalmemory/learning/pattern_miner.py +28 -3
  92. package/src/superlocalmemory/learning/pattern_miner_constants.py +43 -0
  93. package/src/superlocalmemory/learning/pcos.py +291 -0
  94. package/src/superlocalmemory/learning/reward_from_outcomes.py +365 -0
  95. package/src/superlocalmemory/learning/reward_proxy.py +100 -10
  96. package/src/superlocalmemory/learning/signal_kinds.py +79 -0
  97. package/src/superlocalmemory/mcp/profiles.py +14 -2
  98. package/src/superlocalmemory/mcp/server.py +1 -1
  99. package/src/superlocalmemory/mcp/session_binding.py +92 -0
  100. package/src/superlocalmemory/mcp/tools_active.py +2 -1
  101. package/src/superlocalmemory/mcp/tools_core.py +71 -42
  102. package/src/superlocalmemory/mcp/tools_ops.py +2 -2
  103. package/src/superlocalmemory/mcp/tools_v28.py +20 -1
  104. package/src/superlocalmemory/parameterization/pattern_extractor.py +14 -1
  105. package/src/superlocalmemory/parameterization/soft_prompt_generator.py +98 -0
  106. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -11
  107. package/src/superlocalmemory/retrieval/channel_status.py +117 -0
  108. package/src/superlocalmemory/retrieval/engine.py +106 -11
  109. package/src/superlocalmemory/retrieval/entity_channel.py +217 -257
  110. package/src/superlocalmemory/retrieval/graph_adjacency.py +219 -0
  111. package/src/superlocalmemory/retrieval/scope_policy.py +42 -1
  112. package/src/superlocalmemory/retrieval/semantic_channel.py +47 -5
  113. package/src/superlocalmemory/retrieval/spreading.py +288 -0
  114. package/src/superlocalmemory/retrieval/temporal_channel.py +13 -1
  115. package/src/superlocalmemory/retrieval/vector_store.py +63 -0
  116. package/src/superlocalmemory/server/api.py +26 -2
  117. package/src/superlocalmemory/server/asset_versions.py +171 -0
  118. package/src/superlocalmemory/server/bandit_loops.py +17 -1
  119. package/src/superlocalmemory/server/rbac_enforce.py +26 -6
  120. package/src/superlocalmemory/server/recall_serializer.py +9 -0
  121. package/src/superlocalmemory/server/routes/abstraction.py +201 -0
  122. package/src/superlocalmemory/server/routes/behavioral.py +75 -10
  123. package/src/superlocalmemory/server/routes/compliance.py +98 -18
  124. package/src/superlocalmemory/server/routes/config_api.py +186 -4
  125. package/src/superlocalmemory/server/routes/data_io.py +29 -1
  126. package/src/superlocalmemory/server/routes/entity.py +13 -1
  127. package/src/superlocalmemory/server/routes/evolution.py +178 -0
  128. package/src/superlocalmemory/server/routes/ingest.py +8 -0
  129. package/src/superlocalmemory/server/routes/learning_telemetry.py +2 -1
  130. package/src/superlocalmemory/server/routes/memories.py +49 -7
  131. package/src/superlocalmemory/server/routes/mesh.py +1 -1
  132. package/src/superlocalmemory/server/routes/timeline.py +4 -0
  133. package/src/superlocalmemory/server/routes/v3_api.py +193 -17
  134. package/src/superlocalmemory/server/ui.py +24 -1
  135. package/src/superlocalmemory/server/unified_daemon.py +292 -9
  136. package/src/superlocalmemory/storage/_migration_internals.py +35 -0
  137. package/src/superlocalmemory/storage/_schema_version.py +24 -3
  138. package/src/superlocalmemory/storage/database.py +598 -82
  139. package/src/superlocalmemory/storage/embedding_codec.py +71 -0
  140. package/src/superlocalmemory/storage/lineage_retention.py +236 -0
  141. package/src/superlocalmemory/storage/logical_edges.py +43 -2
  142. package/src/superlocalmemory/storage/migration_runner.py +130 -0
  143. package/src/superlocalmemory/storage/migrations/M043_quarantine_display_summaries.py +488 -0
  144. package/src/superlocalmemory/storage/migrations/M044_play_carries_its_own_evidence.py +127 -0
  145. package/src/superlocalmemory/storage/migrations/M045_fact_outcome_score.py +158 -0
  146. package/src/superlocalmemory/storage/migrations/M046_prospective_memory_has_its_own_name.py +620 -0
  147. package/src/superlocalmemory/storage/migrations/M047_fisher_vectors_are_stored_like_every_other_vector.py +306 -0
  148. package/src/superlocalmemory/storage/migrations/M048_upcoming_holds_only_what_is_upcoming.py +207 -0
  149. package/src/superlocalmemory/storage/migrations/M049_a_schema_version_marker_is_one_row.py +201 -0
  150. package/src/superlocalmemory/storage/migrations.py +18 -2
  151. package/src/superlocalmemory/storage/models.py +40 -1
  152. package/src/superlocalmemory/storage/projection_outbox.py +346 -0
  153. package/src/superlocalmemory/storage/retention_policy.py +860 -0
  154. package/src/superlocalmemory/storage/schema.py +110 -1
  155. package/src/superlocalmemory/storage/write_coordinator.py +19 -2
  156. package/src/superlocalmemory/summaries/base.py +1 -1
  157. package/src/superlocalmemory/summaries/non_answer.py +223 -0
  158. package/src/superlocalmemory/trust/scorer.py +43 -1
  159. package/src/superlocalmemory/ui/index.html +10 -19
  160. package/src/superlocalmemory/ui/js/event-delegation.js +12 -1
  161. package/src/superlocalmemory/ui/js/od-health.js +28 -6
  162. package/src/superlocalmemory/ui/js/od-memories.js +209 -1
  163. package/src/superlocalmemory/ui/js/od-ops-health.js +1 -1
  164. package/src/superlocalmemory/ui/js/od-settings.js +87 -1
  165. package/src/superlocalmemory/ui/js/recall-lab.js +78 -3
@@ -41,126 +41,90 @@ class GraphAnalyzer:
41
41
  self._db = db
42
42
 
43
43
  def compute_and_store(self, profile_id: str) -> dict[str, Any]:
44
- """Run all analyses and persist to fact_importance.
44
+ """Recompute structural importance for a profile and persist it.
45
+
46
+ The computation and the write now live in ``core/graph_metrics``, which
47
+ is the only thing that writes ``fact_importance``. This method keeps its
48
+ shape for its three callers and adds the community labels the dashboard
49
+ reads, which are a presentation concern and not part of the ranking
50
+ signal.
51
+
52
+ Two behaviours changed with the move, both deliberate. Isolated facts now
53
+ get a row instead of being skipped, so "no position in the graph" stops
54
+ looking like "not computed yet" to the ranker. And the graph is the
55
+ *visible* graph: the previous version read ``atomic_facts`` and
56
+ ``graph_edges`` raw, so on the author's store it ranked 1,299 facts the
57
+ store is not allowed to return, diluting every real score.
58
+ """
59
+ from superlocalmemory.core.graph_metrics import compute_graph_metrics
45
60
 
46
- v3.4.1: Now uses Leiden (falls back to Label Propagation),
47
- generates TF-IDF community labels, computes bridge scores.
61
+ report = compute_graph_metrics(self._db, profile_id)
62
+ if not report.ok:
63
+ logger.warning("Graph metrics: %s", report.summary())
64
+ return {
65
+ "node_count": 0,
66
+ "edge_count": report.edges,
67
+ "community_count": 0,
68
+ "top_5_nodes": [],
69
+ "error": report.error,
70
+ }
48
71
 
49
- Returns summary dict with node_count, community_count, top_5_nodes,
50
- bridge_count, top_bridge_nodes, community_labels.
51
- """
72
+ communities: dict[str, int] = {}
73
+ top_5: list[tuple[str, float]] = []
74
+ bridge_count = 0
75
+ top_bridges: list[tuple[str, float]] = []
52
76
  try:
53
- graph = self._build_networkx_graph(profile_id)
54
- if graph.number_of_nodes() == 0:
55
- return {
56
- "node_count": 0,
57
- "edge_count": 0,
58
- "community_count": 0,
59
- "top_5_nodes": [],
60
- }
61
-
62
- pagerank = self.compute_pagerank(graph)
63
- communities = self.detect_communities_leiden(graph, profile_id)
64
- centrality = self._compute_degree_centrality(graph)
65
- bridge_scores = self.compute_bridge_scores(graph)
66
- labels = self.compute_community_labels(profile_id, communities)
67
-
68
- # v3.4.1: Ensure bridge_score column exists (idempotent migration)
69
- try:
70
- columns = self._db.execute(
71
- "PRAGMA table_info(fact_importance)", (),
72
- )
73
- has_bridge = any(
74
- dict(c).get("name") == "bridge_score" for c in columns
75
- )
76
- if not has_bridge:
77
- self._db.execute(
78
- "ALTER TABLE fact_importance "
79
- "ADD COLUMN bridge_score REAL DEFAULT 0.0",
80
- (),
81
- )
82
- except Exception:
83
- pass
84
-
85
- # Persist to fact_importance (with bridge_score)
86
- for node_id in graph.nodes():
87
- pr_score = pagerank.get(node_id, 0.0)
88
- comm_id = communities.get(node_id)
89
- deg_cent = centrality.get(node_id, 0.0)
90
- br_score = bridge_scores.get(node_id, 0.0)
91
- try:
92
- self._db.execute(
93
- "INSERT OR REPLACE INTO fact_importance "
94
- "(fact_id, profile_id, pagerank_score, community_id, "
95
- " degree_centrality, bridge_score, computed_at) "
96
- "VALUES (?, ?, ?, ?, ?, ?, datetime('now'))",
97
- (node_id, profile_id, round(pr_score, 6),
98
- comm_id, round(deg_cent, 4),
99
- round(br_score, 6)),
100
- )
101
- except Exception:
102
- # Fallback without bridge_score if column doesn't exist
103
- self._db.execute(
104
- "INSERT OR REPLACE INTO fact_importance "
105
- "(fact_id, profile_id, pagerank_score, community_id, "
106
- " degree_centrality, computed_at) "
107
- "VALUES (?, ?, ?, ?, ?, datetime('now'))",
108
- (node_id, profile_id, round(pr_score, 6),
109
- comm_id, round(deg_cent, 4)),
110
- )
77
+ rows = self._db.execute(
78
+ "SELECT fact_id, pagerank_score, community_id, bridge_score "
79
+ "FROM fact_importance WHERE profile_id = ? "
80
+ "ORDER BY pagerank_score DESC",
81
+ (profile_id,),
82
+ )
83
+ ranked = [dict(row) for row in rows]
84
+ for row in ranked:
85
+ if row.get("community_id") is not None:
86
+ communities[row["fact_id"]] = int(row["community_id"])
87
+ top_5 = [
88
+ (row["fact_id"], round(float(row["pagerank_score"] or 0.0), 4))
89
+ for row in ranked[:5]
90
+ ]
91
+ bridges = [
92
+ (row["fact_id"], float(row.get("bridge_score") or 0.0))
93
+ for row in ranked
94
+ ]
95
+ bridge_count = len([1 for _fid, score in bridges if score > 0.1])
96
+ top_bridges = sorted(bridges, key=lambda item: -item[1])[:5]
97
+ except Exception as exc:
98
+ logger.debug("Reading back graph metrics failed: %s", exc)
111
99
 
112
- # v3.4.1: Persist community labels to JSON sidecar
100
+ labels: dict[int, str] = {}
101
+ if communities:
113
102
  try:
103
+ labels = self.compute_community_labels(profile_id, communities)
114
104
  from superlocalmemory.infra.data_root import canonical_data_root
115
105
  labels_dir = canonical_data_root()
116
106
  labels_dir.mkdir(parents=True, exist_ok=True)
117
- labels_path = labels_dir / f"{profile_id}_community_labels.json"
118
- labels_path.write_text(json.dumps(labels, indent=2))
119
- except Exception:
120
- pass
121
-
122
- top_5 = sorted(
123
- pagerank.items(), key=lambda x: x[1], reverse=True,
124
- )[:5]
125
- unique_communities = len(
126
- set(c for c in communities.values() if c is not None),
127
- )
128
-
129
- bridge_count = len(
130
- [s for s in bridge_scores.values() if s > 0.1],
131
- )
132
- top_bridges = sorted(
133
- bridge_scores.items(), key=lambda x: -x[1],
134
- )[:5]
135
-
136
- logger.info(
137
- "GraphAnalyzer: %d nodes, %d communities, %d bridges, "
138
- "labels=%s",
139
- graph.number_of_nodes(), unique_communities,
140
- bridge_count, labels,
141
- )
142
-
143
- return {
144
- "node_count": graph.number_of_nodes(),
145
- "edge_count": graph.number_of_edges(),
146
- "community_count": unique_communities,
147
- "top_5_nodes": [
148
- (nid, round(score, 4)) for nid, score in top_5
149
- ],
150
- "bridge_count": bridge_count,
151
- "top_bridge_nodes": [
152
- (nid, round(s, 4)) for nid, s in top_bridges
153
- ],
154
- "community_labels": labels,
155
- }
156
- except Exception as exc:
157
- logger.debug("GraphAnalyzer.compute_and_store failed: %s", exc)
158
- return {
159
- "node_count": 0,
160
- "edge_count": 0,
161
- "community_count": 0,
162
- "top_5_nodes": [],
163
- }
107
+ (labels_dir / f"{profile_id}_community_labels.json").write_text(
108
+ json.dumps(labels, indent=2),
109
+ )
110
+ except Exception as exc:
111
+ logger.debug("Community labels skipped: %s", exc)
112
+
113
+ logger.info("GraphAnalyzer: %s", report.summary())
114
+ return {
115
+ "node_count": report.written,
116
+ "edge_count": report.edges,
117
+ "community_count": report.communities,
118
+ "top_5_nodes": top_5,
119
+ "bridge_count": bridge_count,
120
+ "top_bridge_nodes": [
121
+ (fid, round(score, 4)) for fid, score in top_bridges
122
+ ],
123
+ "community_labels": labels,
124
+ "engine": report.engine,
125
+ "isolated_facts": report.isolated,
126
+ "duration_ms": report.duration_ms,
127
+ }
164
128
 
165
129
  def compute_pagerank(
166
130
  self,