superlocalmemory 2.7.2 → 2.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -1
- package/README.md +1 -1
- package/docs/ARCHITECTURE.md +8 -8
- package/docs/COMPRESSION-README.md +1 -1
- package/docs/SEARCH-ENGINE-V2.2.0.md +1 -0
- package/hooks/post-recall-hook.js +53 -0
- package/mcp_server.py +425 -17
- package/package.json +1 -1
- package/skills/slm-recall/SKILL.md +1 -0
- package/src/agent_registry.py +3 -3
- package/src/auto_backup.py +64 -31
- package/src/graph_engine.py +15 -11
- package/src/learning/adaptive_ranker.py +70 -1
- package/src/learning/feature_extractor.py +131 -16
- package/src/learning/feedback_collector.py +114 -0
- package/src/learning/learning_db.py +158 -34
- package/src/learning/tests/test_adaptive_ranker.py +5 -4
- package/src/learning/tests/test_aggregator.py +4 -3
- package/src/learning/tests/test_feedback_collector.py +7 -4
- package/src/learning/tests/test_signal_inference.py +399 -0
- package/src/learning/tests/test_synthetic_bootstrap.py +1 -1
- package/src/trust_scorer.py +288 -74
- package/ui/app.js +4 -4
- package/ui/index.html +38 -0
- package/ui/js/agents.js +4 -4
- package/ui/js/feedback.js +333 -0
- package/ui/js/learning.js +117 -0
- package/ui/js/modal.js +22 -1
- package/ui/js/profiles.js +8 -0
- package/ui/js/settings.js +58 -1
package/src/trust_scorer.py
CHANGED
|
@@ -12,10 +12,23 @@ Attribution must be preserved in all copies or derivatives.
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
"""
|
|
15
|
-
TrustScorer —
|
|
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.
|
|
16
29
|
|
|
17
30
|
v2.5 BEHAVIOR (this version):
|
|
18
|
-
- All agents start at
|
|
31
|
+
- All agents start at Beta(2.0, 1.0) → trust 0.667
|
|
19
32
|
- Signals are collected silently (no enforcement, no ranking, no blocking)
|
|
20
33
|
- Trust scores are updated in agent_registry.trust_score
|
|
21
34
|
- Dashboard shows scores but they don't affect recall ordering yet
|
|
@@ -30,31 +43,26 @@ v3.0 BEHAVIOR (future):
|
|
|
30
43
|
- Admin approval workflow for untrusted agents
|
|
31
44
|
|
|
32
45
|
Trust Signals (all silently collected):
|
|
33
|
-
POSITIVE (increase trust):
|
|
46
|
+
POSITIVE (increase alpha — build trust):
|
|
34
47
|
- Memory recalled by other agents (cross-agent validation)
|
|
35
48
|
- Memory updated (shows ongoing relevance)
|
|
36
49
|
- High importance memories (agent writes valuable content)
|
|
37
50
|
- Consistent write patterns (not spam-like)
|
|
38
51
|
|
|
39
|
-
NEGATIVE (
|
|
52
|
+
NEGATIVE (increase beta — erode trust):
|
|
40
53
|
- Memory deleted shortly after creation (low quality)
|
|
41
54
|
- Very high write volume in short time (potential spam/poisoning)
|
|
42
55
|
- Content flagged or overwritten by user
|
|
43
56
|
|
|
44
57
|
NEUTRAL:
|
|
45
|
-
- Normal read/write patterns
|
|
58
|
+
- Normal read/write patterns (tiny alpha nudge to reward activity)
|
|
46
59
|
- Agent disconnects/reconnects
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
by
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
decay_factor = 1 / (1 + signal_count * 0.01) # Stabilizes over time
|
|
54
|
-
|
|
55
|
-
This means early signals have more impact, and the score converges
|
|
56
|
-
as more data is collected. Similar to MACLA Beta-Binomial approach
|
|
57
|
-
(arXiv:2512.18950) but simplified for local computation.
|
|
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.
|
|
58
66
|
|
|
59
67
|
Security (OWASP for Agentic AI):
|
|
60
68
|
- Memory poisoning (#1 threat): Trust scoring is the first defense layer
|
|
@@ -64,7 +72,6 @@ Security (OWASP for Agentic AI):
|
|
|
64
72
|
|
|
65
73
|
import json
|
|
66
74
|
import logging
|
|
67
|
-
import math
|
|
68
75
|
import threading
|
|
69
76
|
from datetime import datetime, timedelta
|
|
70
77
|
from pathlib import Path
|
|
@@ -72,24 +79,59 @@ from typing import Optional, Dict, List
|
|
|
72
79
|
|
|
73
80
|
logger = logging.getLogger("superlocalmemory.trust")
|
|
74
81
|
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"
|
|
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),
|
|
91
110
|
}
|
|
92
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
|
+
|
|
93
135
|
# Thresholds
|
|
94
136
|
QUICK_DELETE_HOURS = 1 # Delete within 1 hour = negative signal
|
|
95
137
|
BURST_THRESHOLD = 20 # >20 writes in burst window = negative
|
|
@@ -98,9 +140,12 @@ BURST_WINDOW_MINUTES = 5 # Burst detection window
|
|
|
98
140
|
|
|
99
141
|
class TrustScorer:
|
|
100
142
|
"""
|
|
101
|
-
|
|
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).
|
|
102
148
|
|
|
103
|
-
v2.5: Collection only, no enforcement. All agents start at 1.0.
|
|
104
149
|
Thread-safe singleton per database path.
|
|
105
150
|
"""
|
|
106
151
|
|
|
@@ -136,19 +181,26 @@ class TrustScorer:
|
|
|
136
181
|
self._write_timestamps: Dict[str, list] = {}
|
|
137
182
|
self._timestamps_lock = threading.Lock()
|
|
138
183
|
|
|
139
|
-
# Signal count per agent (for decay
|
|
184
|
+
# Signal count per agent (for decay interval tracking)
|
|
140
185
|
self._signal_counts: Dict[str, int] = {}
|
|
141
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
|
+
|
|
142
192
|
self._init_schema()
|
|
143
|
-
logger.info("TrustScorer initialized (
|
|
193
|
+
logger.info("TrustScorer initialized (Beta-Binomial — alpha=%.1f, beta=%.1f prior)",
|
|
194
|
+
INITIAL_ALPHA, INITIAL_BETA)
|
|
144
195
|
|
|
145
196
|
def _init_schema(self):
|
|
146
|
-
"""Create trust_signals table
|
|
197
|
+
"""Create trust_signals table and add alpha/beta columns to agent_registry."""
|
|
147
198
|
try:
|
|
148
199
|
from db_connection_manager import DbConnectionManager
|
|
149
200
|
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
150
201
|
|
|
151
202
|
def _create(conn):
|
|
203
|
+
# Trust signals audit trail
|
|
152
204
|
conn.execute('''
|
|
153
205
|
CREATE TABLE IF NOT EXISTS trust_signals (
|
|
154
206
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -169,6 +221,18 @@ class TrustScorer:
|
|
|
169
221
|
CREATE INDEX IF NOT EXISTS idx_trust_created
|
|
170
222
|
ON trust_signals(created_at)
|
|
171
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
|
+
|
|
172
236
|
conn.commit()
|
|
173
237
|
|
|
174
238
|
mgr.execute_write(_create)
|
|
@@ -189,11 +253,108 @@ class TrustScorer:
|
|
|
189
253
|
''')
|
|
190
254
|
conn.execute('CREATE INDEX IF NOT EXISTS idx_trust_agent ON trust_signals(agent_id)')
|
|
191
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
|
+
|
|
192
267
|
conn.commit()
|
|
193
268
|
conn.close()
|
|
194
269
|
|
|
195
270
|
# =========================================================================
|
|
196
|
-
#
|
|
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)
|
|
197
358
|
# =========================================================================
|
|
198
359
|
|
|
199
360
|
def record_signal(
|
|
@@ -203,50 +364,68 @@ class TrustScorer:
|
|
|
203
364
|
context: Optional[dict] = None,
|
|
204
365
|
) -> bool:
|
|
205
366
|
"""
|
|
206
|
-
Record a trust signal for an agent.
|
|
367
|
+
Record a trust signal for an agent using Beta-Binomial update.
|
|
207
368
|
|
|
208
|
-
|
|
209
|
-
|
|
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.
|
|
210
374
|
|
|
211
375
|
Args:
|
|
212
376
|
agent_id: Agent that generated the signal
|
|
213
|
-
signal_type: One of
|
|
377
|
+
signal_type: One of SIGNAL_WEIGHTS keys
|
|
214
378
|
context: Additional context (memory_id, etc.)
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
True if signal was recorded successfully
|
|
215
382
|
"""
|
|
216
|
-
if signal_type not in
|
|
383
|
+
if signal_type not in SIGNAL_WEIGHTS:
|
|
217
384
|
logger.warning("Unknown trust signal: %s", signal_type)
|
|
218
|
-
return
|
|
385
|
+
return False
|
|
386
|
+
|
|
387
|
+
direction, weight = SIGNAL_WEIGHTS[signal_type]
|
|
219
388
|
|
|
220
|
-
|
|
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
|
|
221
392
|
|
|
222
|
-
#
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
226
400
|
|
|
227
|
-
# Apply decay
|
|
228
|
-
count = self._signal_counts.get(agent_id, 0)
|
|
229
|
-
|
|
230
|
-
adjusted_delta = delta * decay
|
|
401
|
+
# Apply periodic decay
|
|
402
|
+
count = self._signal_counts.get(agent_id, 0) + 1
|
|
403
|
+
self._signal_counts[agent_id] = count
|
|
231
404
|
|
|
232
|
-
|
|
233
|
-
|
|
405
|
+
if count % DECAY_INTERVAL == 0:
|
|
406
|
+
alpha, beta = self._apply_decay(agent_id, alpha, beta)
|
|
234
407
|
|
|
235
|
-
#
|
|
236
|
-
|
|
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
|
|
237
413
|
|
|
238
414
|
# Persist signal to audit trail
|
|
239
|
-
self._persist_signal(agent_id, signal_type,
|
|
415
|
+
self._persist_signal(agent_id, signal_type, delta, old_score, new_score, context)
|
|
240
416
|
|
|
241
|
-
#
|
|
242
|
-
|
|
243
|
-
self._update_agent_trust(agent_id, new_score)
|
|
417
|
+
# Persist updated Beta parameters and derived trust_score
|
|
418
|
+
self._set_beta_params(agent_id, alpha, beta)
|
|
244
419
|
|
|
245
420
|
logger.debug(
|
|
246
|
-
"Trust signal: agent=%s, type=%s,
|
|
247
|
-
|
|
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
|
|
248
425
|
)
|
|
249
426
|
|
|
427
|
+
return True
|
|
428
|
+
|
|
250
429
|
def _persist_signal(self, agent_id, signal_type, delta, old_score, new_score, context):
|
|
251
430
|
"""Save signal to trust_signals table."""
|
|
252
431
|
try:
|
|
@@ -265,7 +444,12 @@ class TrustScorer:
|
|
|
265
444
|
logger.error("Failed to persist trust signal: %s", e)
|
|
266
445
|
|
|
267
446
|
def _get_agent_trust(self, agent_id: str) -> Optional[float]:
|
|
268
|
-
"""
|
|
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
|
+
"""
|
|
269
453
|
try:
|
|
270
454
|
from db_connection_manager import DbConnectionManager
|
|
271
455
|
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
@@ -282,7 +466,13 @@ class TrustScorer:
|
|
|
282
466
|
return None
|
|
283
467
|
|
|
284
468
|
def _update_agent_trust(self, agent_id: str, new_score: float):
|
|
285
|
-
"""
|
|
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
|
+
"""
|
|
286
476
|
try:
|
|
287
477
|
from db_connection_manager import DbConnectionManager
|
|
288
478
|
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
@@ -373,16 +563,40 @@ class TrustScorer:
|
|
|
373
563
|
# =========================================================================
|
|
374
564
|
|
|
375
565
|
def get_trust_score(self, agent_id: str) -> float:
|
|
376
|
-
"""
|
|
377
|
-
score
|
|
378
|
-
|
|
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
|
+
}
|
|
379
592
|
|
|
380
593
|
def check_trust(self, agent_id: str, operation: str = "write") -> bool:
|
|
381
594
|
"""
|
|
382
595
|
Check if agent is trusted enough for the given operation.
|
|
383
596
|
|
|
384
597
|
v2.6 enforcement: blocks write/delete for agents with trust < 0.3.
|
|
385
|
-
New agents start at 1.
|
|
598
|
+
New agents start at Beta(2,1) → trust 0.667 — only repeated bad
|
|
599
|
+
behavior triggers blocking.
|
|
386
600
|
|
|
387
601
|
Args:
|
|
388
602
|
agent_id: The agent identifier
|
|
@@ -394,14 +608,12 @@ class TrustScorer:
|
|
|
394
608
|
if operation == "read":
|
|
395
609
|
return True # Reads are always allowed
|
|
396
610
|
|
|
397
|
-
score = self.
|
|
398
|
-
if score is None:
|
|
399
|
-
return True # Unknown agent = first-time = allowed (starts at 1.0)
|
|
611
|
+
score = self.get_trust_score(agent_id)
|
|
400
612
|
|
|
401
613
|
threshold = 0.3 # Block write/delete below this
|
|
402
614
|
if score < threshold:
|
|
403
615
|
logger.warning(
|
|
404
|
-
"Trust enforcement: agent '%s' blocked from '%s' (trust=%.
|
|
616
|
+
"Trust enforcement: agent '%s' blocked from '%s' (trust=%.4f < %.2f)",
|
|
405
617
|
agent_id, operation, score, threshold
|
|
406
618
|
)
|
|
407
619
|
return False
|
|
@@ -479,7 +691,9 @@ class TrustScorer:
|
|
|
479
691
|
"total_signals": total_signals,
|
|
480
692
|
"by_signal_type": by_type,
|
|
481
693
|
"by_agent": by_agent,
|
|
482
|
-
"avg_trust_score": round(avg, 4) if avg else
|
|
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})",
|
|
483
697
|
"enforcement": "enabled (v2.6 — write/delete blocked below 0.3 trust)",
|
|
484
698
|
}
|
|
485
699
|
|
package/ui/app.js
CHANGED
|
@@ -1445,9 +1445,9 @@ async function loadAgents() {
|
|
|
1445
1445
|
|
|
1446
1446
|
// Trust score
|
|
1447
1447
|
var tdTrust = document.createElement('td');
|
|
1448
|
-
var trustScore = agent.trust_score != null ? agent.trust_score :
|
|
1449
|
-
tdTrust.className = trustScore < 0.
|
|
1450
|
-
: trustScore < 0.
|
|
1448
|
+
var trustScore = agent.trust_score != null ? agent.trust_score : 0.667;
|
|
1449
|
+
tdTrust.className = trustScore < 0.3 ? 'text-danger fw-bold'
|
|
1450
|
+
: trustScore < 0.5 ? 'text-warning fw-bold' : 'text-success fw-bold';
|
|
1451
1451
|
tdTrust.textContent = trustScore.toFixed(2);
|
|
1452
1452
|
tr.appendChild(tdTrust);
|
|
1453
1453
|
|
|
@@ -1524,7 +1524,7 @@ async function loadTrustOverview() {
|
|
|
1524
1524
|
card2.className = 'border rounded p-3 text-center';
|
|
1525
1525
|
var val2 = document.createElement('div');
|
|
1526
1526
|
val2.className = 'fs-4 fw-bold';
|
|
1527
|
-
val2.textContent = (stats.avg_trust_score ||
|
|
1527
|
+
val2.textContent = (stats.avg_trust_score || 0.667).toFixed(3);
|
|
1528
1528
|
card2.appendChild(val2);
|
|
1529
1529
|
var lbl2 = document.createElement('small');
|
|
1530
1530
|
lbl2.className = 'text-muted';
|
package/ui/index.html
CHANGED
|
@@ -742,6 +742,10 @@
|
|
|
742
742
|
</li>
|
|
743
743
|
</ul>
|
|
744
744
|
|
|
745
|
+
<!-- Privacy Notice & Feedback Progress (v2.7.4) -->
|
|
746
|
+
<div id="privacy-notice"></div>
|
|
747
|
+
<div id="feedback-progress" class="mb-3"></div>
|
|
748
|
+
|
|
745
749
|
<div class="tab-content">
|
|
746
750
|
<!-- Graph Visualization -->
|
|
747
751
|
<div class="tab-pane fade show active" id="graph-pane">
|
|
@@ -956,6 +960,20 @@
|
|
|
956
960
|
</div>
|
|
957
961
|
</div>
|
|
958
962
|
|
|
963
|
+
<!-- What We Learned (v2.7.4 — Summary Card) -->
|
|
964
|
+
<div class="card p-3 mb-3" id="what-we-learned-card">
|
|
965
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
966
|
+
<h6 class="mb-0"><i class="bi bi-lightbulb"></i> What SuperLocalMemory Learned About You</h6>
|
|
967
|
+
<span class="badge bg-success" id="learned-profile-badge">default</span>
|
|
968
|
+
</div>
|
|
969
|
+
<div id="what-we-learned-content">
|
|
970
|
+
<div class="text-center text-muted py-3">
|
|
971
|
+
<i class="bi bi-hourglass-split" style="font-size: 1.5rem;"></i>
|
|
972
|
+
<p class="mt-2 mb-0 small">Loading learned insights...</p>
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
</div>
|
|
976
|
+
|
|
959
977
|
<div class="row g-3">
|
|
960
978
|
<!-- Tech Preferences (Layer 1) -->
|
|
961
979
|
<div class="col-md-6">
|
|
@@ -1239,6 +1257,25 @@
|
|
|
1239
1257
|
</div>
|
|
1240
1258
|
</div>
|
|
1241
1259
|
</div>
|
|
1260
|
+
<!-- Learning Data Management (v2.7.4) -->
|
|
1261
|
+
<div class="card p-3 mb-3">
|
|
1262
|
+
<h5 class="mb-3"><i class="bi bi-brain"></i> Learning Data</h5>
|
|
1263
|
+
<p class="text-muted small mb-3">
|
|
1264
|
+
SuperLocalMemory learns from your usage patterns to improve recall results.
|
|
1265
|
+
All learning data is stored locally in <code>~/.claude-memory/learning.db</code>.
|
|
1266
|
+
</p>
|
|
1267
|
+
<div id="learning-data-stats" class="mb-3"></div>
|
|
1268
|
+
<div class="d-flex gap-2">
|
|
1269
|
+
<button class="btn btn-outline-danger" onclick="resetLearningData()">
|
|
1270
|
+
<i class="bi bi-arrow-counterclockwise"></i> Reset Learning Data
|
|
1271
|
+
</button>
|
|
1272
|
+
<button class="btn btn-outline-info" onclick="backupLearningDb()">
|
|
1273
|
+
<i class="bi bi-download"></i> Backup Learning DB
|
|
1274
|
+
</button>
|
|
1275
|
+
</div>
|
|
1276
|
+
<small class="text-muted mt-2 d-block">Reset clears all learned preferences, feedback signals, and patterns. Your memories are preserved.</small>
|
|
1277
|
+
</div>
|
|
1278
|
+
|
|
1242
1279
|
<!-- Backup History -->
|
|
1243
1280
|
<div class="card p-3">
|
|
1244
1281
|
<h5 class="mb-3"><i class="bi bi-clock-history"></i> Backup History</h5>
|
|
@@ -1319,6 +1356,7 @@
|
|
|
1319
1356
|
<script src="static/js/events.js"></script>
|
|
1320
1357
|
<script src="static/js/agents.js"></script>
|
|
1321
1358
|
<script src="static/js/learning.js"></script>
|
|
1359
|
+
<script src="static/js/feedback.js"></script>
|
|
1322
1360
|
<script src="static/js/init.js"></script>
|
|
1323
1361
|
|
|
1324
1362
|
<footer>
|
package/ui/js/agents.js
CHANGED
|
@@ -78,9 +78,9 @@ async function loadAgents() {
|
|
|
78
78
|
tr.appendChild(tdProto);
|
|
79
79
|
|
|
80
80
|
var tdTrust = document.createElement('td');
|
|
81
|
-
var trustScore = agent.trust_score != null ? agent.trust_score :
|
|
82
|
-
tdTrust.className = trustScore < 0.
|
|
83
|
-
: trustScore < 0.
|
|
81
|
+
var trustScore = agent.trust_score != null ? agent.trust_score : 0.667;
|
|
82
|
+
tdTrust.className = trustScore < 0.3 ? 'text-danger fw-bold'
|
|
83
|
+
: trustScore < 0.5 ? 'text-warning fw-bold' : 'text-success fw-bold';
|
|
84
84
|
tdTrust.textContent = trustScore.toFixed(2);
|
|
85
85
|
tr.appendChild(tdTrust);
|
|
86
86
|
|
|
@@ -133,7 +133,7 @@ async function loadTrustOverview() {
|
|
|
133
133
|
|
|
134
134
|
var cardData = [
|
|
135
135
|
{ value: (stats.total_signals || 0).toLocaleString(), label: 'Total Signals Collected', cls: '' },
|
|
136
|
-
{ value: (stats.avg_trust_score ||
|
|
136
|
+
{ value: (stats.avg_trust_score || 0.667).toFixed(3), label: 'Average Trust Score', cls: '' },
|
|
137
137
|
{ value: stats.enforcement || 'disabled', label: 'Enforcement Status', cls: 'text-info' }
|
|
138
138
|
];
|
|
139
139
|
|