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
@@ -0,0 +1,158 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+
4
+ """M045 — per-fact outcome score (PCOS), so ranking can use what worked.
5
+
6
+ WHY
7
+ ---
8
+ Ranking scores a memory by how much it *looks like* the query. Nothing in the
9
+ pipeline knows whether a memory has ever actually helped. This table holds one
10
+ number per (fact, profile): an exponentially-weighted average of the rewards of
11
+ the settlements it took part in.
12
+
13
+ WHY IT LIVES IN memory.db
14
+ -------------------------
15
+ Beside ``atomic_facts`` and ``action_outcomes``, the backfill source, so the
16
+ whole thing stays in one file. SQLite cannot join across databases without
17
+ ATTACH, and taking an ATTACH on the recall path to reach a single REAL would
18
+ cost more than the number is worth.
19
+
20
+ The ranker reads it with one batched keyed lookup rather than a LEFT JOIN into
21
+ fact hydration: ``_row_to_fact`` builds ``AtomicFact`` from named columns and
22
+ silently drops any it does not declare, so a JOIN would mean adding fields to a
23
+ model used across the whole codebase to carry a value only the ranker reads.
24
+
25
+ profile_id IS PART OF THE PRIMARY KEY, AND THAT IS NOT COSMETIC
26
+ ---------------------------------------------------------------
27
+ A learned per-fact score is derived personal data. If it had no ``profile_id``,
28
+ ``forget_profile()`` would run, report success, and leave the erased user's
29
+ learned outcome history behind — which is the same defect already
30
+ known in ``fact_expansion_fts``. Creating a second table with the same defect
31
+ in the same release would be indefensible. ``compliance/gdpr.py`` deletes from
32
+ this table; a test asserts one profile's erasure leaves another's rows intact.
33
+
34
+ THE BACKFILL, AND A CORRECTION TO THE PLAN
35
+ ------------------------------------------
36
+ An earlier design's backfill read ``SELECT fact_id, profile_id, AVG(reward) FROM
37
+ action_outcomes GROUP BY fact_id, profile_id``. **There is no ``fact_id`` column
38
+ on that table** — it stores ``fact_ids_json``, a JSON array, because one outcome
39
+ covers the set of memories an answer used. So the backfill expands the array with
40
+ ``json_each``, guarded by ``json_valid`` (an unguarded ``json_each`` raises on the
41
+ first malformed row and takes the whole migration with it).
42
+
43
+ On a live store this initialises every touched fact at 0.5 from 162 rows
44
+ that are all 0.5 — which is the neutral prior, so the backfill adds no bias. Its
45
+ only job is to avoid a cold start looking like a penalty.
46
+ """
47
+
48
+ from __future__ import annotations
49
+
50
+ import sqlite3
51
+
52
+ NAME = "M045_fact_outcome_score"
53
+ DB_TARGET = "memory"
54
+
55
+ _TABLE = "fact_outcome_score"
56
+
57
+ DDL = """
58
+ BEGIN IMMEDIATE;
59
+ CREATE TABLE IF NOT EXISTS fact_outcome_score (
60
+ fact_id TEXT NOT NULL,
61
+ profile_id TEXT NOT NULL DEFAULT 'default',
62
+ score REAL NOT NULL DEFAULT 0.5,
63
+ play_count INTEGER NOT NULL DEFAULT 0,
64
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
65
+ PRIMARY KEY (fact_id, profile_id)
66
+ );
67
+ CREATE INDEX IF NOT EXISTS idx_fos_profile
68
+ ON fact_outcome_score (profile_id, fact_id);
69
+ COMMIT;
70
+ """
71
+
72
+ #: Expand ``fact_ids_json`` and average the reward per (fact, profile).
73
+ #:
74
+ #: ``INSERT OR IGNORE`` names no conflict target on purpose: the table has one
75
+ #: unique constraint today, and a targeted ``ON CONFLICT`` silently stops
76
+ #: covering a row the moment a second constraint is added.
77
+ _BACKFILL = """
78
+ INSERT OR IGNORE INTO fact_outcome_score
79
+ (fact_id, profile_id, score, play_count, updated_at)
80
+ SELECT
81
+ j.value AS fact_id,
82
+ o.profile_id,
83
+ AVG(o.reward),
84
+ COUNT(*),
85
+ datetime('now')
86
+ FROM action_outcomes AS o, json_each(o.fact_ids_json) AS j
87
+ WHERE o.reward IS NOT NULL
88
+ AND json_valid(o.fact_ids_json)
89
+ AND o.fact_ids_json IS NOT NULL
90
+ AND j.value IS NOT NULL AND j.value != ''
91
+ GROUP BY j.value, o.profile_id
92
+ """
93
+
94
+
95
+ def _table_exists(conn: sqlite3.Connection, name: str) -> bool:
96
+ try:
97
+ return conn.execute(
98
+ "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
99
+ (name,),
100
+ ).fetchone() is not None
101
+ except sqlite3.Error:
102
+ return False
103
+
104
+
105
+ def _columns(conn: sqlite3.Connection, table: str) -> set[str]:
106
+ try:
107
+ return {row[1] for row in conn.execute(f"PRAGMA table_info({table})")}
108
+ except sqlite3.Error:
109
+ return set()
110
+
111
+
112
+ def verify(conn: sqlite3.Connection) -> bool:
113
+ """End state: the table exists with profile_id in it.
114
+
115
+ Deliberately does NOT assert anything about backfilled rows. The backfill
116
+ depends on ``action_outcomes``, which is bootstrapped at engine init and may
117
+ hold nothing at all — so asserting rows would make ``verify`` fail forever
118
+ on a fresh install. Everything asserted here is produced by every path
119
+ through ``apply``; that is the M043 lesson, learned the hard way.
120
+ """
121
+ if not _table_exists(conn, _TABLE):
122
+ return False
123
+ cols = _columns(conn, _TABLE)
124
+ return {"fact_id", "profile_id", "score", "play_count"} <= cols
125
+
126
+
127
+ def apply(conn: sqlite3.Connection) -> None:
128
+ """Create the table, then backfill from reported outcomes if any exist."""
129
+ conn.executescript(DDL)
130
+ if not verify(conn): # pragma: no cover — defensive
131
+ raise sqlite3.OperationalError(
132
+ "M045 fact_outcome_score did not reach its end-state"
133
+ )
134
+ _backfill(conn)
135
+
136
+
137
+ def _backfill(conn: sqlite3.Connection) -> int:
138
+ """Seed from ``action_outcomes``. Never fatal — the table is what matters.
139
+
140
+ A failure here means facts start at the cold-start default of 0.5, which is
141
+ the same value the backfill would have written on this store anyway. Losing
142
+ the migration over that would be the wrong trade.
143
+ """
144
+ if not _table_exists(conn, "action_outcomes"):
145
+ return 0
146
+ if "reward" not in _columns(conn, "action_outcomes"):
147
+ # reward arrives with M006, which is deferred. Nothing to average yet.
148
+ return 0
149
+ try:
150
+ cur = conn.execute(_BACKFILL)
151
+ return int(cur.rowcount or 0)
152
+ except sqlite3.Error:
153
+ return 0
154
+
155
+
156
+ def repair(conn: sqlite3.Connection) -> None:
157
+ """Re-run apply. Safe: the DDL is IF NOT EXISTS and the backfill IGNOREs."""
158
+ apply(conn)