superlocalmemory 2.7.6 → 2.8.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 (170) hide show
  1. package/CHANGELOG.md +120 -155
  2. package/README.md +115 -89
  3. package/api_server.py +2 -12
  4. package/docs/PATTERN-LEARNING.md +64 -199
  5. package/docs/example_graph_usage.py +4 -6
  6. package/install.sh +59 -0
  7. package/mcp_server.py +83 -7
  8. package/package.json +1 -8
  9. package/scripts/generate-thumbnails.py +3 -5
  10. package/skills/slm-build-graph/SKILL.md +1 -1
  11. package/skills/slm-list-recent/SKILL.md +1 -1
  12. package/skills/slm-recall/SKILL.md +1 -1
  13. package/skills/slm-remember/SKILL.md +1 -1
  14. package/skills/slm-show-patterns/SKILL.md +1 -1
  15. package/skills/slm-status/SKILL.md +1 -1
  16. package/skills/slm-switch-profile/SKILL.md +1 -1
  17. package/src/agent_registry.py +7 -18
  18. package/src/auth_middleware.py +3 -5
  19. package/src/auto_backup.py +3 -7
  20. package/src/behavioral/__init__.py +49 -0
  21. package/src/behavioral/behavioral_listener.py +203 -0
  22. package/src/behavioral/behavioral_patterns.py +275 -0
  23. package/src/behavioral/cross_project_transfer.py +206 -0
  24. package/src/behavioral/outcome_inference.py +194 -0
  25. package/src/behavioral/outcome_tracker.py +193 -0
  26. package/src/behavioral/tests/__init__.py +4 -0
  27. package/src/behavioral/tests/test_behavioral_integration.py +108 -0
  28. package/src/behavioral/tests/test_behavioral_patterns.py +150 -0
  29. package/src/behavioral/tests/test_cross_project_transfer.py +142 -0
  30. package/src/behavioral/tests/test_mcp_behavioral.py +139 -0
  31. package/src/behavioral/tests/test_mcp_report_outcome.py +117 -0
  32. package/src/behavioral/tests/test_outcome_inference.py +107 -0
  33. package/src/behavioral/tests/test_outcome_tracker.py +96 -0
  34. package/src/cache_manager.py +4 -6
  35. package/src/compliance/__init__.py +48 -0
  36. package/src/compliance/abac_engine.py +149 -0
  37. package/src/compliance/abac_middleware.py +116 -0
  38. package/src/compliance/audit_db.py +215 -0
  39. package/src/compliance/audit_logger.py +148 -0
  40. package/src/compliance/retention_manager.py +289 -0
  41. package/src/compliance/retention_scheduler.py +186 -0
  42. package/src/compliance/tests/__init__.py +4 -0
  43. package/src/compliance/tests/test_abac_enforcement.py +95 -0
  44. package/src/compliance/tests/test_abac_engine.py +124 -0
  45. package/src/compliance/tests/test_abac_mcp_integration.py +118 -0
  46. package/src/compliance/tests/test_audit_db.py +123 -0
  47. package/src/compliance/tests/test_audit_logger.py +98 -0
  48. package/src/compliance/tests/test_mcp_audit.py +128 -0
  49. package/src/compliance/tests/test_mcp_retention_policy.py +125 -0
  50. package/src/compliance/tests/test_retention_manager.py +131 -0
  51. package/src/compliance/tests/test_retention_scheduler.py +99 -0
  52. package/src/db_connection_manager.py +2 -12
  53. package/src/embedding_engine.py +61 -669
  54. package/src/embeddings/__init__.py +47 -0
  55. package/src/embeddings/cache.py +70 -0
  56. package/src/embeddings/cli.py +113 -0
  57. package/src/embeddings/constants.py +47 -0
  58. package/src/embeddings/database.py +91 -0
  59. package/src/embeddings/engine.py +247 -0
  60. package/src/embeddings/model_loader.py +145 -0
  61. package/src/event_bus.py +3 -13
  62. package/src/graph/__init__.py +36 -0
  63. package/src/graph/build_helpers.py +74 -0
  64. package/src/graph/cli.py +87 -0
  65. package/src/graph/cluster_builder.py +188 -0
  66. package/src/graph/cluster_summary.py +148 -0
  67. package/src/graph/constants.py +47 -0
  68. package/src/graph/edge_builder.py +162 -0
  69. package/src/graph/entity_extractor.py +95 -0
  70. package/src/graph/graph_core.py +226 -0
  71. package/src/graph/graph_search.py +231 -0
  72. package/src/graph/hierarchical.py +207 -0
  73. package/src/graph/schema.py +99 -0
  74. package/src/graph_engine.py +45 -1451
  75. package/src/hnsw_index.py +3 -7
  76. package/src/hybrid_search.py +36 -683
  77. package/src/learning/__init__.py +27 -12
  78. package/src/learning/adaptive_ranker.py +50 -12
  79. package/src/learning/cross_project_aggregator.py +2 -12
  80. package/src/learning/engagement_tracker.py +2 -12
  81. package/src/learning/feature_extractor.py +175 -43
  82. package/src/learning/feedback_collector.py +7 -12
  83. package/src/learning/learning_db.py +180 -12
  84. package/src/learning/project_context_manager.py +2 -12
  85. package/src/learning/source_quality_scorer.py +2 -12
  86. package/src/learning/synthetic_bootstrap.py +2 -12
  87. package/src/learning/tests/__init__.py +2 -0
  88. package/src/learning/tests/test_adaptive_ranker.py +2 -6
  89. package/src/learning/tests/test_adaptive_ranker_v28.py +60 -0
  90. package/src/learning/tests/test_aggregator.py +2 -6
  91. package/src/learning/tests/test_auto_retrain_v28.py +35 -0
  92. package/src/learning/tests/test_e2e_ranking_v28.py +82 -0
  93. package/src/learning/tests/test_feature_extractor_v28.py +93 -0
  94. package/src/learning/tests/test_feedback_collector.py +2 -6
  95. package/src/learning/tests/test_learning_db.py +2 -6
  96. package/src/learning/tests/test_learning_db_v28.py +110 -0
  97. package/src/learning/tests/test_learning_init_v28.py +48 -0
  98. package/src/learning/tests/test_outcome_signals.py +48 -0
  99. package/src/learning/tests/test_project_context.py +2 -6
  100. package/src/learning/tests/test_schema_migration.py +319 -0
  101. package/src/learning/tests/test_signal_inference.py +11 -13
  102. package/src/learning/tests/test_source_quality.py +2 -6
  103. package/src/learning/tests/test_synthetic_bootstrap.py +3 -7
  104. package/src/learning/tests/test_workflow_miner.py +2 -6
  105. package/src/learning/workflow_pattern_miner.py +2 -12
  106. package/src/lifecycle/__init__.py +54 -0
  107. package/src/lifecycle/bounded_growth.py +239 -0
  108. package/src/lifecycle/compaction_engine.py +226 -0
  109. package/src/lifecycle/lifecycle_engine.py +302 -0
  110. package/src/lifecycle/lifecycle_evaluator.py +225 -0
  111. package/src/lifecycle/lifecycle_scheduler.py +130 -0
  112. package/src/lifecycle/retention_policy.py +285 -0
  113. package/src/lifecycle/tests/__init__.py +4 -0
  114. package/src/lifecycle/tests/test_bounded_growth.py +193 -0
  115. package/src/lifecycle/tests/test_compaction.py +179 -0
  116. package/src/lifecycle/tests/test_lifecycle_engine.py +137 -0
  117. package/src/lifecycle/tests/test_lifecycle_evaluation.py +177 -0
  118. package/src/lifecycle/tests/test_lifecycle_scheduler.py +127 -0
  119. package/src/lifecycle/tests/test_lifecycle_search.py +109 -0
  120. package/src/lifecycle/tests/test_mcp_compact.py +149 -0
  121. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +114 -0
  122. package/src/lifecycle/tests/test_retention_policy.py +162 -0
  123. package/src/mcp_tools_v28.py +280 -0
  124. package/src/memory-profiles.py +2 -12
  125. package/src/memory-reset.py +2 -12
  126. package/src/memory_compression.py +2 -12
  127. package/src/memory_store_v2.py +76 -20
  128. package/src/migrate_v1_to_v2.py +2 -12
  129. package/src/pattern_learner.py +29 -975
  130. package/src/patterns/__init__.py +24 -0
  131. package/src/patterns/analyzers.py +247 -0
  132. package/src/patterns/learner.py +267 -0
  133. package/src/patterns/scoring.py +167 -0
  134. package/src/patterns/store.py +223 -0
  135. package/src/patterns/terminology.py +138 -0
  136. package/src/provenance_tracker.py +4 -14
  137. package/src/query_optimizer.py +4 -6
  138. package/src/rate_limiter.py +2 -6
  139. package/src/search/__init__.py +20 -0
  140. package/src/search/cli.py +77 -0
  141. package/src/search/constants.py +26 -0
  142. package/src/search/engine.py +239 -0
  143. package/src/search/fusion.py +122 -0
  144. package/src/search/index_loader.py +112 -0
  145. package/src/search/methods.py +162 -0
  146. package/src/search_engine_v2.py +4 -6
  147. package/src/setup_validator.py +7 -13
  148. package/src/subscription_manager.py +2 -12
  149. package/src/tree/__init__.py +59 -0
  150. package/src/tree/builder.py +183 -0
  151. package/src/tree/nodes.py +196 -0
  152. package/src/tree/queries.py +252 -0
  153. package/src/tree/schema.py +76 -0
  154. package/src/tree_manager.py +10 -711
  155. package/src/trust/__init__.py +45 -0
  156. package/src/trust/constants.py +66 -0
  157. package/src/trust/queries.py +157 -0
  158. package/src/trust/schema.py +95 -0
  159. package/src/trust/scorer.py +299 -0
  160. package/src/trust/signals.py +95 -0
  161. package/src/trust_scorer.py +39 -697
  162. package/src/webhook_dispatcher.py +2 -12
  163. package/ui/app.js +1 -1
  164. package/ui/js/agents.js +1 -1
  165. package/ui_server.py +2 -14
  166. package/ATTRIBUTION.md +0 -140
  167. package/docs/ARCHITECTURE-V2.5.md +0 -190
  168. package/docs/GRAPH-ENGINE.md +0 -503
  169. package/docs/architecture-diagram.drawio +0 -405
  170. package/docs/plans/2026-02-13-benchmark-suite.md +0 -1349
@@ -1,702 +1,44 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
2
4
  """
3
- SuperLocalMemory V2 - Trust Scorer
4
- Copyright (c) 2026 Varun Pratap Bhardwaj
5
- Licensed under MIT License
5
+ Backward-compatible shim -- delegates to src/trust/ package.
6
6
 
7
- Repository: https://github.com/varun369/SuperLocalMemoryV2
8
- Author: Varun Pratap Bhardwaj (Solution Architect)
9
-
10
- NOTICE: This software is protected by MIT License.
11
- Attribution must be preserved in all copies or derivatives.
12
- """
13
-
14
- """
15
- TrustScorer — Bayesian Beta-Binomial trust scoring for AI agents.
16
-
17
- Scoring Model:
18
- Each agent's trust is modeled as a Beta(alpha, beta) distribution.
19
- - alpha accumulates evidence of trustworthy behavior
20
- - beta accumulates evidence of untrustworthy behavior
21
- - Trust score = alpha / (alpha + beta) (posterior mean)
22
-
23
- Prior: Beta(2.0, 1.0) → initial trust = 0.667
24
- This gives new agents a positive-but-not-maximal starting trust,
25
- well above the 0.3 enforcement threshold but with room to grow.
26
-
27
- This follows the MACLA Beta-Binomial approach (arXiv:2512.18950)
28
- already used in pattern_learner.py for confidence scoring.
29
-
30
- v2.5 BEHAVIOR (this version):
31
- - All agents start at Beta(2.0, 1.0) → trust 0.667
32
- - Signals are collected silently (no enforcement, no ranking, no blocking)
33
- - Trust scores are updated in agent_registry.trust_score
34
- - Dashboard shows scores but they don't affect recall ordering yet
35
-
36
- v2.6 BEHAVIOR (this version):
37
- - Trust scores visible in dashboard
38
- - Active enforcement: agents with trust < 0.3 blocked from write/delete operations
39
- - Quarantine and admin approval deferred to v3.0
40
-
41
- v3.0 BEHAVIOR (future):
42
- - Quarantine low-trust memories for manual review
43
- - Admin approval workflow for untrusted agents
44
-
45
- Trust Signals (all silently collected):
46
- POSITIVE (increase alpha — build trust):
47
- - Memory recalled by other agents (cross-agent validation)
48
- - Memory updated (shows ongoing relevance)
49
- - High importance memories (agent writes valuable content)
50
- - Consistent write patterns (not spam-like)
51
-
52
- NEGATIVE (increase beta — erode trust):
53
- - Memory deleted shortly after creation (low quality)
54
- - Very high write volume in short time (potential spam/poisoning)
55
- - Content flagged or overwritten by user
56
-
57
- NEUTRAL:
58
- - Normal read/write patterns (tiny alpha nudge to reward activity)
59
- - Agent disconnects/reconnects
60
-
61
- Decay:
62
- Every DECAY_INTERVAL signals per agent, both alpha and beta are
63
- multiplied by DECAY_FACTOR (0.995). This slowly forgets very old
64
- signals so recent behavior matters more. Floors prevent total
65
- information loss: alpha >= 1.0, beta >= 0.5.
66
-
67
- Security (OWASP for Agentic AI):
68
- - Memory poisoning (#1 threat): Trust scoring is the first defense layer
69
- - Over-permissioning: Trust scores inform future access control (v3.0)
70
- - Agent impersonation: Agent ID + protocol tracking detects anomalies
7
+ All public symbols are re-exported so existing imports like
8
+ ``from trust_scorer import TrustScorer`` continue to work unchanged.
71
9
  """
72
10
 
73
- import json
74
- import logging
75
- import threading
76
- from datetime import datetime, timedelta
77
- from pathlib import Path
78
- from typing import Optional, Dict, List
79
-
80
- logger = logging.getLogger("superlocalmemory.trust")
81
-
82
- # ---------------------------------------------------------------------------
83
- # Beta-Binomial signal weights
84
- # ---------------------------------------------------------------------------
85
- # Positive signals increment alpha (building trust).
86
- # Negative signals increment beta (eroding trust).
87
- # Neutral signals give a tiny alpha nudge to reward normal activity.
88
- #
89
- # Asymmetry: negative weights are larger than positive weights.
90
- # This means it's harder to build trust than to lose it — the system
91
- # is intentionally skeptical. One poisoning event takes many good
92
- # actions to recover from.
93
- # ---------------------------------------------------------------------------
94
-
95
- SIGNAL_WEIGHTS = {
96
- # Positive signals → alpha += weight
97
- "memory_recalled_by_others": ("positive", 0.30), # cross-agent validation
98
- "memory_updated": ("positive", 0.15), # ongoing relevance
99
- "high_importance_write": ("positive", 0.20), # valuable content (importance >= 7)
100
- "consistent_pattern": ("positive", 0.15), # stable write behavior
101
-
102
- # Negative signals → beta += weight
103
- "quick_delete": ("negative", 0.50), # deleted within 1 hour
104
- "high_volume_burst": ("negative", 0.40), # >20 writes in 5 minutes
105
- "content_overwritten_by_user": ("negative", 0.25), # user had to fix output
106
-
107
- # Neutral signals → tiny alpha nudge
108
- "normal_write": ("neutral", 0.01),
109
- "normal_recall": ("neutral", 0.01),
110
- }
111
-
112
- # Backward-compatible: expose SIGNAL_DELTAS as a derived dict so that
113
- # bm6_trust.py (which imports SIGNAL_DELTAS) and any other consumer
114
- # continues to work. The values represent the *direction* and *magnitude*
115
- # of each signal: positive for alpha, negative for beta, zero for neutral.
116
- SIGNAL_DELTAS = {}
117
- for _sig, (_direction, _weight) in SIGNAL_WEIGHTS.items():
118
- if _direction == "positive":
119
- SIGNAL_DELTAS[_sig] = +_weight
120
- elif _direction == "negative":
121
- SIGNAL_DELTAS[_sig] = -_weight
122
- else:
123
- SIGNAL_DELTAS[_sig] = 0.0
124
-
125
- # ---------------------------------------------------------------------------
126
- # Beta prior and decay parameters
127
- # ---------------------------------------------------------------------------
128
- INITIAL_ALPHA = 2.0 # Slight positive prior
129
- INITIAL_BETA = 1.0 # → initial trust = 2/(2+1) = 0.667
130
- DECAY_FACTOR = 0.995 # Multiply alpha & beta every DECAY_INTERVAL signals
131
- DECAY_INTERVAL = 50 # Apply decay every N signals per agent
132
- ALPHA_FLOOR = 1.0 # Never decay alpha below this
133
- BETA_FLOOR = 0.5 # Never decay beta below this
134
-
135
- # Thresholds
136
- QUICK_DELETE_HOURS = 1 # Delete within 1 hour = negative signal
137
- BURST_THRESHOLD = 20 # >20 writes in burst window = negative
138
- BURST_WINDOW_MINUTES = 5 # Burst detection window
139
-
140
-
141
- class TrustScorer:
142
- """
143
- Bayesian Beta-Binomial trust scorer for AI agents.
144
-
145
- Each agent is modeled as Beta(alpha, beta). Positive signals
146
- increment alpha, negative signals increment beta. The trust
147
- score is the posterior mean: alpha / (alpha + beta).
148
-
149
- Thread-safe singleton per database path.
150
- """
151
-
152
- _instances: Dict[str, "TrustScorer"] = {}
153
- _instances_lock = threading.Lock()
154
-
155
- @classmethod
156
- def get_instance(cls, db_path: Optional[Path] = None) -> "TrustScorer":
157
- """Get or create the singleton TrustScorer."""
158
- if db_path is None:
159
- db_path = Path.home() / ".claude-memory" / "memory.db"
160
- key = str(db_path)
161
- with cls._instances_lock:
162
- if key not in cls._instances:
163
- cls._instances[key] = cls(db_path)
164
- return cls._instances[key]
165
-
166
- @classmethod
167
- def reset_instance(cls, db_path: Optional[Path] = None):
168
- """Remove singleton. Used for testing."""
169
- with cls._instances_lock:
170
- if db_path is None:
171
- cls._instances.clear()
172
- else:
173
- key = str(db_path)
174
- if key in cls._instances:
175
- del cls._instances[key]
176
-
177
- def __init__(self, db_path: Path):
178
- self.db_path = Path(db_path)
179
-
180
- # In-memory signal log for burst detection (agent_id -> list of timestamps)
181
- self._write_timestamps: Dict[str, list] = {}
182
- self._timestamps_lock = threading.Lock()
183
-
184
- # Signal count per agent (for decay interval tracking)
185
- self._signal_counts: Dict[str, int] = {}
186
-
187
- # In-memory cache of Beta parameters per agent
188
- # Key: agent_id, Value: (alpha, beta)
189
- self._beta_params: Dict[str, tuple] = {}
190
- self._beta_lock = threading.Lock()
191
-
192
- self._init_schema()
193
- logger.info("TrustScorer initialized (Beta-Binomial — alpha=%.1f, beta=%.1f prior)",
194
- INITIAL_ALPHA, INITIAL_BETA)
195
-
196
- def _init_schema(self):
197
- """Create trust_signals table and add alpha/beta columns to agent_registry."""
198
- try:
199
- from db_connection_manager import DbConnectionManager
200
- mgr = DbConnectionManager.get_instance(self.db_path)
201
-
202
- def _create(conn):
203
- # Trust signals audit trail
204
- conn.execute('''
205
- CREATE TABLE IF NOT EXISTS trust_signals (
206
- id INTEGER PRIMARY KEY AUTOINCREMENT,
207
- agent_id TEXT NOT NULL,
208
- signal_type TEXT NOT NULL,
209
- delta REAL NOT NULL,
210
- old_score REAL,
211
- new_score REAL,
212
- context TEXT DEFAULT '{}',
213
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
214
- )
215
- ''')
216
- conn.execute('''
217
- CREATE INDEX IF NOT EXISTS idx_trust_agent
218
- ON trust_signals(agent_id)
219
- ''')
220
- conn.execute('''
221
- CREATE INDEX IF NOT EXISTS idx_trust_created
222
- ON trust_signals(created_at)
223
- ''')
224
-
225
- # Add trust_alpha and trust_beta columns to agent_registry
226
- # (backward compatible — old databases get these columns added)
227
- for col_name, col_default in [("trust_alpha", INITIAL_ALPHA),
228
- ("trust_beta", INITIAL_BETA)]:
229
- try:
230
- conn.execute(
231
- f'ALTER TABLE agent_registry ADD COLUMN {col_name} REAL DEFAULT {col_default}'
232
- )
233
- except Exception:
234
- pass # Column already exists
235
-
236
- conn.commit()
237
-
238
- mgr.execute_write(_create)
239
- except ImportError:
240
- import sqlite3
241
- conn = sqlite3.connect(str(self.db_path))
242
- conn.execute('''
243
- CREATE TABLE IF NOT EXISTS trust_signals (
244
- id INTEGER PRIMARY KEY AUTOINCREMENT,
245
- agent_id TEXT NOT NULL,
246
- signal_type TEXT NOT NULL,
247
- delta REAL NOT NULL,
248
- old_score REAL,
249
- new_score REAL,
250
- context TEXT DEFAULT '{}',
251
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
252
- )
253
- ''')
254
- conn.execute('CREATE INDEX IF NOT EXISTS idx_trust_agent ON trust_signals(agent_id)')
255
- conn.execute('CREATE INDEX IF NOT EXISTS idx_trust_created ON trust_signals(created_at)')
256
-
257
- # Add trust_alpha and trust_beta columns (backward compatible)
258
- for col_name, col_default in [("trust_alpha", INITIAL_ALPHA),
259
- ("trust_beta", INITIAL_BETA)]:
260
- try:
261
- conn.execute(
262
- f'ALTER TABLE agent_registry ADD COLUMN {col_name} REAL DEFAULT {col_default}'
263
- )
264
- except sqlite3.OperationalError:
265
- pass # Column already exists
266
-
267
- conn.commit()
268
- conn.close()
269
-
270
- # =========================================================================
271
- # Beta Parameter Management
272
- # =========================================================================
273
-
274
- def _get_beta_params(self, agent_id: str) -> tuple:
275
- """
276
- Get (alpha, beta) for an agent. Checks in-memory cache first,
277
- then database, then falls back to prior defaults.
278
-
279
- Returns:
280
- (alpha, beta) tuple
281
- """
282
- with self._beta_lock:
283
- if agent_id in self._beta_params:
284
- return self._beta_params[agent_id]
285
-
286
- # Not in cache — read from database
287
- alpha, beta = None, None
288
- try:
289
- from db_connection_manager import DbConnectionManager
290
- mgr = DbConnectionManager.get_instance(self.db_path)
291
-
292
- with mgr.read_connection() as conn:
293
- cursor = conn.cursor()
294
- cursor.execute(
295
- "SELECT trust_alpha, trust_beta FROM agent_registry WHERE agent_id = ?",
296
- (agent_id,)
297
- )
298
- row = cursor.fetchone()
299
- if row:
300
- alpha = row[0]
301
- beta = row[1]
302
- except Exception:
303
- pass
304
-
305
- # Fall back to defaults if NULL or missing
306
- if alpha is None or beta is None:
307
- alpha = INITIAL_ALPHA
308
- beta = INITIAL_BETA
309
-
310
- with self._beta_lock:
311
- self._beta_params[agent_id] = (alpha, beta)
312
-
313
- return (alpha, beta)
314
-
315
- def _set_beta_params(self, agent_id: str, alpha: float, beta: float):
316
- """
317
- Update (alpha, beta) in cache and persist to agent_registry.
318
- Also computes and stores the derived trust_score = alpha/(alpha+beta).
319
- """
320
- trust_score = alpha / (alpha + beta) if (alpha + beta) > 0 else 0.0
321
-
322
- with self._beta_lock:
323
- self._beta_params[agent_id] = (alpha, beta)
324
-
325
- try:
326
- from db_connection_manager import DbConnectionManager
327
- mgr = DbConnectionManager.get_instance(self.db_path)
328
-
329
- def _update(conn):
330
- conn.execute(
331
- """UPDATE agent_registry
332
- SET trust_score = ?, trust_alpha = ?, trust_beta = ?
333
- WHERE agent_id = ?""",
334
- (round(trust_score, 4), round(alpha, 4), round(beta, 4), agent_id)
335
- )
336
- conn.commit()
337
-
338
- mgr.execute_write(_update)
339
- except Exception as e:
340
- logger.error("Failed to persist Beta params for %s: %s", agent_id, e)
341
-
342
- def _apply_decay(self, agent_id: str, alpha: float, beta: float) -> tuple:
343
- """
344
- Apply periodic decay to alpha and beta to forget very old signals.
345
-
346
- Called every DECAY_INTERVAL signals per agent.
347
- Multiplies both by DECAY_FACTOR with floor constraints.
348
-
349
- Returns:
350
- (decayed_alpha, decayed_beta)
351
- """
352
- new_alpha = max(ALPHA_FLOOR, alpha * DECAY_FACTOR)
353
- new_beta = max(BETA_FLOOR, beta * DECAY_FACTOR)
354
- return (new_alpha, new_beta)
355
-
356
- # =========================================================================
357
- # Signal Recording (Beta-Binomial Update)
358
- # =========================================================================
359
-
360
- def record_signal(
361
- self,
362
- agent_id: str,
363
- signal_type: str,
364
- context: Optional[dict] = None,
365
- ) -> bool:
366
- """
367
- Record a trust signal for an agent using Beta-Binomial update.
368
-
369
- Positive signals increment alpha (trust evidence).
370
- Negative signals increment beta (distrust evidence).
371
- Neutral signals give a tiny alpha nudge.
372
-
373
- Trust score = alpha / (alpha + beta) — the posterior mean.
374
-
375
- Args:
376
- agent_id: Agent that generated the signal
377
- signal_type: One of SIGNAL_WEIGHTS keys
378
- context: Additional context (memory_id, etc.)
379
-
380
- Returns:
381
- True if signal was recorded successfully
382
- """
383
- if signal_type not in SIGNAL_WEIGHTS:
384
- logger.warning("Unknown trust signal: %s", signal_type)
385
- return False
386
-
387
- direction, weight = SIGNAL_WEIGHTS[signal_type]
388
-
389
- # Get current Beta parameters
390
- alpha, beta = self._get_beta_params(agent_id)
391
- old_score = alpha / (alpha + beta) if (alpha + beta) > 0 else 0.0
392
-
393
- # Apply Beta-Binomial update
394
- if direction == "positive":
395
- alpha += weight
396
- elif direction == "negative":
397
- beta += weight
398
- else: # neutral — tiny alpha nudge
399
- alpha += weight
400
-
401
- # Apply periodic decay
402
- count = self._signal_counts.get(agent_id, 0) + 1
403
- self._signal_counts[agent_id] = count
404
-
405
- if count % DECAY_INTERVAL == 0:
406
- alpha, beta = self._apply_decay(agent_id, alpha, beta)
407
-
408
- # Compute new trust score (posterior mean)
409
- new_score = alpha / (alpha + beta) if (alpha + beta) > 0 else 0.0
410
-
411
- # Compute delta for audit trail (backward compatible with trust_signals table)
412
- delta = new_score - old_score
413
-
414
- # Persist signal to audit trail
415
- self._persist_signal(agent_id, signal_type, delta, old_score, new_score, context)
416
-
417
- # Persist updated Beta parameters and derived trust_score
418
- self._set_beta_params(agent_id, alpha, beta)
419
-
420
- logger.debug(
421
- "Trust signal: agent=%s, type=%s (%s, w=%.2f), "
422
- "alpha=%.2f, beta=%.2f, score=%.4f->%.4f",
423
- agent_id, signal_type, direction, weight,
424
- alpha, beta, old_score, new_score
425
- )
426
-
427
- return True
428
-
429
- def _persist_signal(self, agent_id, signal_type, delta, old_score, new_score, context):
430
- """Save signal to trust_signals table."""
431
- try:
432
- from db_connection_manager import DbConnectionManager
433
- mgr = DbConnectionManager.get_instance(self.db_path)
434
-
435
- def _insert(conn):
436
- conn.execute('''
437
- INSERT INTO trust_signals (agent_id, signal_type, delta, old_score, new_score, context)
438
- VALUES (?, ?, ?, ?, ?, ?)
439
- ''', (agent_id, signal_type, delta, old_score, new_score, json.dumps(context or {})))
440
- conn.commit()
441
-
442
- mgr.execute_write(_insert)
443
- except Exception as e:
444
- logger.error("Failed to persist trust signal: %s", e)
445
-
446
- def _get_agent_trust(self, agent_id: str) -> Optional[float]:
447
- """
448
- Get current trust score from agent_registry.
449
-
450
- This reads the derived trust_score column (which is always kept
451
- in sync with alpha/(alpha+beta) by _set_beta_params).
452
- """
453
- try:
454
- from db_connection_manager import DbConnectionManager
455
- mgr = DbConnectionManager.get_instance(self.db_path)
456
-
457
- with mgr.read_connection() as conn:
458
- cursor = conn.cursor()
459
- cursor.execute(
460
- "SELECT trust_score FROM agent_registry WHERE agent_id = ?",
461
- (agent_id,)
462
- )
463
- row = cursor.fetchone()
464
- return row[0] if row else None
465
- except Exception:
466
- return None
467
-
468
- def _update_agent_trust(self, agent_id: str, new_score: float):
469
- """
470
- Update trust score in agent_registry (legacy compatibility method).
471
-
472
- In Beta-Binomial mode, this is a no-op because _set_beta_params
473
- already updates trust_score alongside alpha and beta. Kept for
474
- backward compatibility if any external code calls it directly.
475
- """
476
- try:
477
- from db_connection_manager import DbConnectionManager
478
- mgr = DbConnectionManager.get_instance(self.db_path)
479
-
480
- def _update(conn):
481
- conn.execute(
482
- "UPDATE agent_registry SET trust_score = ? WHERE agent_id = ?",
483
- (round(new_score, 4), agent_id)
484
- )
485
- conn.commit()
486
-
487
- mgr.execute_write(_update)
488
- except Exception as e:
489
- logger.error("Failed to update agent trust: %s", e)
490
-
491
- # =========================================================================
492
- # High-Level Signal Helpers (called from memory_store_v2 / mcp_server)
493
- # =========================================================================
494
-
495
- def on_memory_created(self, agent_id: str, memory_id: int, importance: int = 5):
496
- """Record signals when a memory is created."""
497
- # Track write timestamp for burst detection
498
- self._track_write(agent_id)
499
-
500
- if importance >= 7:
501
- self.record_signal(agent_id, "high_importance_write",
502
- context={"memory_id": memory_id, "importance": importance})
503
- else:
504
- self.record_signal(agent_id, "normal_write",
505
- context={"memory_id": memory_id})
506
-
507
- # Check for burst pattern
508
- if self._is_burst(agent_id):
509
- self.record_signal(agent_id, "high_volume_burst",
510
- context={"memory_id": memory_id})
511
-
512
- def on_memory_deleted(self, agent_id: str, memory_id: int, created_at: Optional[str] = None):
513
- """Record signals when a memory is deleted."""
514
- if created_at:
515
- try:
516
- created = datetime.fromisoformat(created_at)
517
- age_hours = (datetime.now() - created).total_seconds() / 3600
518
- if age_hours < QUICK_DELETE_HOURS:
519
- self.record_signal(agent_id, "quick_delete",
520
- context={"memory_id": memory_id, "age_hours": round(age_hours, 2)})
521
- return
522
- except (ValueError, TypeError):
523
- pass
524
-
525
- # Normal delete (no negative signal)
526
- self.record_signal(agent_id, "normal_write",
527
- context={"memory_id": memory_id, "action": "delete"})
528
-
529
- def on_memory_recalled(self, agent_id: str, memory_id: int, created_by: Optional[str] = None):
530
- """Record signals when a memory is recalled."""
531
- if created_by and created_by != agent_id:
532
- # Cross-agent validation: another agent found this memory useful
533
- self.record_signal(created_by, "memory_recalled_by_others",
534
- context={"memory_id": memory_id, "recalled_by": agent_id})
535
-
536
- self.record_signal(agent_id, "normal_recall",
537
- context={"memory_id": memory_id})
538
-
539
- # =========================================================================
540
- # Burst Detection
541
- # =========================================================================
542
-
543
- def _track_write(self, agent_id: str):
544
- """Track a write timestamp for burst detection."""
545
- now = datetime.now()
546
- with self._timestamps_lock:
547
- if agent_id not in self._write_timestamps:
548
- self._write_timestamps[agent_id] = []
549
- timestamps = self._write_timestamps[agent_id]
550
- timestamps.append(now)
551
- # Keep only recent timestamps (within burst window)
552
- cutoff = now - timedelta(minutes=BURST_WINDOW_MINUTES)
553
- self._write_timestamps[agent_id] = [t for t in timestamps if t > cutoff]
554
-
555
- def _is_burst(self, agent_id: str) -> bool:
556
- """Check if agent is in a burst write pattern."""
557
- with self._timestamps_lock:
558
- timestamps = self._write_timestamps.get(agent_id, [])
559
- return len(timestamps) > BURST_THRESHOLD
560
-
561
- # =========================================================================
562
- # Query Trust Data
563
- # =========================================================================
564
-
565
- def get_trust_score(self, agent_id: str) -> float:
566
- """
567
- Get current trust score for an agent.
568
-
569
- Computes alpha/(alpha+beta) from cached or stored Beta params.
570
- Returns INITIAL_ALPHA/(INITIAL_ALPHA+INITIAL_BETA) = 0.667 for
571
- unknown agents.
572
- """
573
- alpha, beta = self._get_beta_params(agent_id)
574
- if (alpha + beta) > 0:
575
- return alpha / (alpha + beta)
576
- return INITIAL_ALPHA / (INITIAL_ALPHA + INITIAL_BETA)
577
-
578
- def get_beta_params(self, agent_id: str) -> Dict[str, float]:
579
- """
580
- Get the Beta distribution parameters for an agent.
581
-
582
- Returns:
583
- {"alpha": float, "beta": float, "trust_score": float}
584
- """
585
- alpha, beta = self._get_beta_params(agent_id)
586
- score = alpha / (alpha + beta) if (alpha + beta) > 0 else 0.0
587
- return {
588
- "alpha": round(alpha, 4),
589
- "beta": round(beta, 4),
590
- "trust_score": round(score, 4),
591
- }
592
-
593
- def check_trust(self, agent_id: str, operation: str = "write") -> bool:
594
- """
595
- Check if agent is trusted enough for the given operation.
596
-
597
- v2.6 enforcement: blocks write/delete for agents with trust < 0.3.
598
- New agents start at Beta(2,1) → trust 0.667 — only repeated bad
599
- behavior triggers blocking.
600
-
601
- Args:
602
- agent_id: The agent identifier
603
- operation: One of "read", "write", "delete"
604
-
605
- Returns:
606
- True if operation is allowed, False if blocked
607
- """
608
- if operation == "read":
609
- return True # Reads are always allowed
610
-
611
- score = self.get_trust_score(agent_id)
612
-
613
- threshold = 0.3 # Block write/delete below this
614
- if score < threshold:
615
- logger.warning(
616
- "Trust enforcement: agent '%s' blocked from '%s' (trust=%.4f < %.2f)",
617
- agent_id, operation, score, threshold
618
- )
619
- return False
620
-
621
- return True
622
-
623
- def get_signals(self, agent_id: str, limit: int = 50) -> List[dict]:
624
- """Get recent trust signals for an agent."""
625
- try:
626
- from db_connection_manager import DbConnectionManager
627
- mgr = DbConnectionManager.get_instance(self.db_path)
628
-
629
- with mgr.read_connection() as conn:
630
- cursor = conn.cursor()
631
- cursor.execute("""
632
- SELECT signal_type, delta, old_score, new_score, context, created_at
633
- FROM trust_signals
634
- WHERE agent_id = ?
635
- ORDER BY created_at DESC
636
- LIMIT ?
637
- """, (agent_id, limit))
638
-
639
- signals = []
640
- for row in cursor.fetchall():
641
- ctx = {}
642
- try:
643
- ctx = json.loads(row[4]) if row[4] else {}
644
- except (json.JSONDecodeError, TypeError):
645
- pass
646
- signals.append({
647
- "signal_type": row[0],
648
- "delta": row[1],
649
- "old_score": row[2],
650
- "new_score": row[3],
651
- "context": ctx,
652
- "created_at": row[5],
653
- })
654
- return signals
655
-
656
- except Exception as e:
657
- logger.error("Failed to get trust signals: %s", e)
658
- return []
659
-
660
- def get_trust_stats(self) -> dict:
661
- """Get trust system statistics."""
662
- try:
663
- from db_connection_manager import DbConnectionManager
664
- mgr = DbConnectionManager.get_instance(self.db_path)
665
-
666
- with mgr.read_connection() as conn:
667
- cursor = conn.cursor()
668
-
669
- cursor.execute("SELECT COUNT(*) FROM trust_signals")
670
- total_signals = cursor.fetchone()[0]
671
-
672
- cursor.execute("""
673
- SELECT signal_type, COUNT(*) FROM trust_signals
674
- GROUP BY signal_type ORDER BY COUNT(*) DESC
675
- """)
676
- by_type = dict(cursor.fetchall())
677
-
678
- cursor.execute("""
679
- SELECT agent_id, COUNT(*) FROM trust_signals
680
- GROUP BY agent_id ORDER BY COUNT(*) DESC LIMIT 10
681
- """)
682
- by_agent = dict(cursor.fetchall())
683
-
684
- cursor.execute("""
685
- SELECT AVG(trust_score) FROM agent_registry
686
- WHERE trust_score IS NOT NULL
687
- """)
688
- avg = cursor.fetchone()[0]
689
-
690
- return {
691
- "total_signals": total_signals,
692
- "by_signal_type": by_type,
693
- "by_agent": by_agent,
694
- "avg_trust_score": round(avg, 4) if avg else INITIAL_ALPHA / (INITIAL_ALPHA + INITIAL_BETA),
695
- "scoring_model": "Beta-Binomial",
696
- "prior": f"Beta({INITIAL_ALPHA}, {INITIAL_BETA})",
697
- "enforcement": "enabled (v2.6 — write/delete blocked below 0.3 trust)",
698
- }
699
-
700
- except Exception as e:
701
- logger.error("Failed to get trust stats: %s", e)
702
- return {"total_signals": 0, "error": str(e)}
11
+ # Re-export everything from the trust package
12
+ from trust.constants import ( # noqa: F401
13
+ SIGNAL_WEIGHTS,
14
+ SIGNAL_DELTAS,
15
+ INITIAL_ALPHA,
16
+ INITIAL_BETA,
17
+ DECAY_FACTOR,
18
+ DECAY_INTERVAL,
19
+ ALPHA_FLOOR,
20
+ BETA_FLOOR,
21
+ QUICK_DELETE_HOURS,
22
+ BURST_THRESHOLD,
23
+ BURST_WINDOW_MINUTES,
24
+ )
25
+
26
+ from trust.schema import init_trust_schema # noqa: F401
27
+
28
+ from trust.scorer import TrustScorer # noqa: F401
29
+
30
+ __all__ = [
31
+ "TrustScorer",
32
+ "SIGNAL_WEIGHTS",
33
+ "SIGNAL_DELTAS",
34
+ "INITIAL_ALPHA",
35
+ "INITIAL_BETA",
36
+ "DECAY_FACTOR",
37
+ "DECAY_INTERVAL",
38
+ "ALPHA_FLOOR",
39
+ "BETA_FLOOR",
40
+ "QUICK_DELETE_HOURS",
41
+ "BURST_THRESHOLD",
42
+ "BURST_WINDOW_MINUTES",
43
+ "init_trust_schema",
44
+ ]