nexo-brain 3.0.0 → 3.0.2
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/.claude-plugin/plugin.json +1 -1
- package/README.md +21 -1
- package/package.json +1 -1
- package/src/agent_runner.py +5 -1
- package/src/auto_update.py +23 -6
- package/src/client_preferences.py +5 -1
- package/src/client_sync.py +5 -1
- package/src/cognitive/_memory.py +14 -7
- package/src/cognitive/_search.py +12 -5
- package/src/crons/sync.py +2 -1
- package/src/dashboard/app.py +1 -1
- package/src/db/_workflow.py +2 -2
- package/src/doctor/models.py +25 -0
- package/src/doctor/orchestrator.py +32 -2
- package/src/doctor/providers/boot.py +52 -26
- package/src/doctor/providers/deep.py +24 -21
- package/src/doctor/providers/runtime.py +151 -135
- package/src/evolution_cycle.py +48 -46
- package/src/kg_populate.py +21 -19
- package/src/maintenance.py +3 -3
- package/src/migrate_embeddings.py +36 -34
- package/src/plugins/backup.py +24 -12
- package/src/plugins/schedule.py +13 -1
- package/src/plugins/update.py +18 -4
- package/src/public_contribution.py +10 -14
- package/src/requirements.txt +1 -0
- package/src/scripts/nexo-catchup.py +15 -15
- package/src/scripts/nexo-daily-self-audit.py +12 -1
- package/src/scripts/nexo-evolution-run.py +9 -3
- package/src/state_watchers_runtime.py +48 -41
- package/src/tools_sessions.py +2 -2
|
@@ -12,16 +12,20 @@ import sqlite3
|
|
|
12
12
|
import subprocess
|
|
13
13
|
import sys
|
|
14
14
|
import time
|
|
15
|
-
import tomllib
|
|
16
15
|
from pathlib import Path
|
|
17
16
|
|
|
17
|
+
try:
|
|
18
|
+
import tomllib
|
|
19
|
+
except ModuleNotFoundError: # Python < 3.11
|
|
20
|
+
import tomli as tomllib
|
|
21
|
+
|
|
18
22
|
from client_preferences import (
|
|
19
23
|
detect_installed_clients,
|
|
20
24
|
normalize_client_preferences,
|
|
21
25
|
resolve_client_runtime_profile,
|
|
22
26
|
)
|
|
23
27
|
from cron_recovery import should_run_at_load
|
|
24
|
-
from doctor.models import DoctorCheck
|
|
28
|
+
from doctor.models import DoctorCheck, safe_check
|
|
25
29
|
|
|
26
30
|
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
27
31
|
NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parents[2])))
|
|
@@ -402,21 +406,22 @@ def _load_active_conditioned_learnings() -> list[dict]:
|
|
|
402
406
|
import sqlite3
|
|
403
407
|
|
|
404
408
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
409
|
+
try:
|
|
410
|
+
conn.row_factory = sqlite3.Row
|
|
411
|
+
table = conn.execute(
|
|
412
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='learnings'"
|
|
413
|
+
).fetchone()
|
|
414
|
+
if not table:
|
|
415
|
+
return []
|
|
416
|
+
rows = conn.execute(
|
|
417
|
+
"""SELECT id, title, applies_to
|
|
418
|
+
FROM learnings
|
|
419
|
+
WHERE status = 'active' AND COALESCE(applies_to, '') != ''
|
|
420
|
+
ORDER BY updated_at DESC, id DESC"""
|
|
421
|
+
).fetchall()
|
|
422
|
+
return [dict(row) for row in rows]
|
|
423
|
+
finally:
|
|
410
424
|
conn.close()
|
|
411
|
-
return []
|
|
412
|
-
rows = conn.execute(
|
|
413
|
-
"""SELECT id, title, applies_to
|
|
414
|
-
FROM learnings
|
|
415
|
-
WHERE status = 'active' AND COALESCE(applies_to, '') != ''
|
|
416
|
-
ORDER BY updated_at DESC, id DESC"""
|
|
417
|
-
).fetchall()
|
|
418
|
-
conn.close()
|
|
419
|
-
return [dict(row) for row in rows]
|
|
420
425
|
except Exception:
|
|
421
426
|
return []
|
|
422
427
|
|
|
@@ -591,22 +596,23 @@ def _open_protocol_debt_summary(*debt_types: str) -> dict:
|
|
|
591
596
|
|
|
592
597
|
try:
|
|
593
598
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
+
try:
|
|
600
|
+
conn.row_factory = sqlite3.Row
|
|
601
|
+
table = conn.execute(
|
|
602
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='protocol_debt'"
|
|
603
|
+
).fetchone()
|
|
604
|
+
if not table:
|
|
605
|
+
return summary
|
|
606
|
+
placeholders = ",".join("?" for _ in debt_types)
|
|
607
|
+
rows = conn.execute(
|
|
608
|
+
f"""SELECT debt_type, COUNT(*) AS total
|
|
609
|
+
FROM protocol_debt
|
|
610
|
+
WHERE status = 'open' AND debt_type IN ({placeholders})
|
|
611
|
+
GROUP BY debt_type""",
|
|
612
|
+
tuple(debt_types),
|
|
613
|
+
).fetchall()
|
|
614
|
+
finally:
|
|
599
615
|
conn.close()
|
|
600
|
-
return summary
|
|
601
|
-
placeholders = ",".join("?" for _ in debt_types)
|
|
602
|
-
rows = conn.execute(
|
|
603
|
-
f"""SELECT debt_type, COUNT(*) AS total
|
|
604
|
-
FROM protocol_debt
|
|
605
|
-
WHERE status = 'open' AND debt_type IN ({placeholders})
|
|
606
|
-
GROUP BY debt_type""",
|
|
607
|
-
tuple(debt_types),
|
|
608
|
-
).fetchall()
|
|
609
|
-
conn.close()
|
|
610
616
|
except Exception:
|
|
611
617
|
return summary
|
|
612
618
|
|
|
@@ -738,7 +744,7 @@ def _parse_timestamp(value: str) -> dt.datetime | None:
|
|
|
738
744
|
except ValueError:
|
|
739
745
|
return None
|
|
740
746
|
if parsed.tzinfo is None:
|
|
741
|
-
parsed = parsed.replace(tzinfo=dt.
|
|
747
|
+
parsed = parsed.replace(tzinfo=dt.timezone.utc)
|
|
742
748
|
return parsed
|
|
743
749
|
|
|
744
750
|
|
|
@@ -1168,14 +1174,16 @@ def check_stale_sessions() -> DoctorCheck:
|
|
|
1168
1174
|
summary="No DB to check sessions",
|
|
1169
1175
|
)
|
|
1170
1176
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1177
|
+
try:
|
|
1178
|
+
conn.row_factory = sqlite3.Row
|
|
1179
|
+
cutoff = time.time() - 7200
|
|
1180
|
+
day_ago = time.time() - 86400
|
|
1181
|
+
rows = conn.execute(
|
|
1182
|
+
"SELECT COUNT(*) as cnt FROM sessions WHERE last_update_epoch < ? AND last_update_epoch > ?",
|
|
1183
|
+
(cutoff, day_ago),
|
|
1184
|
+
).fetchone()
|
|
1185
|
+
finally:
|
|
1186
|
+
conn.close()
|
|
1179
1187
|
count = rows["cnt"] if rows else 0
|
|
1180
1188
|
if count > 0:
|
|
1181
1189
|
return DoctorCheck(
|
|
@@ -1217,24 +1225,25 @@ def check_cron_freshness() -> DoctorCheck:
|
|
|
1217
1225
|
summary="No DB to check cron runs",
|
|
1218
1226
|
)
|
|
1219
1227
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1228
|
+
try:
|
|
1229
|
+
# Check if cron_runs table exists
|
|
1230
|
+
tables = conn.execute(
|
|
1231
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='cron_runs'"
|
|
1232
|
+
).fetchone()
|
|
1233
|
+
if not tables:
|
|
1234
|
+
return DoctorCheck(
|
|
1235
|
+
id="runtime.cron_freshness",
|
|
1236
|
+
tier="runtime",
|
|
1237
|
+
status="healthy",
|
|
1238
|
+
severity="info",
|
|
1239
|
+
summary="No cron_runs table yet",
|
|
1240
|
+
)
|
|
1241
|
+
# Latest run per cron
|
|
1242
|
+
rows = conn.execute(
|
|
1243
|
+
"SELECT cron_id, MAX(started_at) as last_run FROM cron_runs GROUP BY cron_id"
|
|
1244
|
+
).fetchall()
|
|
1245
|
+
finally:
|
|
1225
1246
|
conn.close()
|
|
1226
|
-
return DoctorCheck(
|
|
1227
|
-
id="runtime.cron_freshness",
|
|
1228
|
-
tier="runtime",
|
|
1229
|
-
status="healthy",
|
|
1230
|
-
severity="info",
|
|
1231
|
-
summary="No cron_runs table yet",
|
|
1232
|
-
)
|
|
1233
|
-
# Latest run per cron
|
|
1234
|
-
rows = conn.execute(
|
|
1235
|
-
"SELECT cron_id, MAX(started_at) as last_run FROM cron_runs GROUP BY cron_id"
|
|
1236
|
-
).fetchall()
|
|
1237
|
-
conn.close()
|
|
1238
1247
|
|
|
1239
1248
|
stale = []
|
|
1240
1249
|
expectations = _cron_expectations()
|
|
@@ -2163,32 +2172,36 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
2163
2172
|
db_path = NEXO_HOME / "data" / "nexo.db"
|
|
2164
2173
|
if db_path.is_file():
|
|
2165
2174
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2175
|
+
try:
|
|
2176
|
+
conn.row_factory = sqlite3.Row
|
|
2177
|
+
tables = {
|
|
2178
|
+
row["name"]
|
|
2179
|
+
for row in conn.execute(
|
|
2180
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('protocol_tasks', 'protocol_debt')"
|
|
2181
|
+
).fetchall()
|
|
2182
|
+
}
|
|
2183
|
+
tasks = None
|
|
2184
|
+
debt_rows = None
|
|
2185
|
+
if {"protocol_tasks", "protocol_debt"}.issubset(tables):
|
|
2186
|
+
window = "-7 days"
|
|
2187
|
+
tasks = conn.execute(
|
|
2188
|
+
"""SELECT * FROM protocol_tasks
|
|
2189
|
+
WHERE opened_at >= datetime('now', ?)
|
|
2190
|
+
ORDER BY opened_at DESC""",
|
|
2191
|
+
(window,),
|
|
2192
|
+
).fetchall()
|
|
2193
|
+
debt_rows = conn.execute(
|
|
2194
|
+
"""SELECT severity, debt_type, COUNT(*) AS total
|
|
2195
|
+
FROM protocol_debt
|
|
2196
|
+
WHERE status = 'open' AND created_at >= datetime('now', ?)
|
|
2197
|
+
GROUP BY severity, debt_type
|
|
2198
|
+
ORDER BY total DESC, debt_type ASC""",
|
|
2199
|
+
(window,),
|
|
2200
|
+
).fetchall()
|
|
2201
|
+
finally:
|
|
2189
2202
|
conn.close()
|
|
2190
2203
|
|
|
2191
|
-
|
|
2204
|
+
if tasks is not None and debt_rows is not None and (tasks or debt_rows):
|
|
2192
2205
|
closed_tasks = [row for row in tasks if row["status"] != "open"]
|
|
2193
2206
|
verify_required = [row for row in closed_tasks if row["must_verify"] and row["status"] == "done"]
|
|
2194
2207
|
verify_ok = [row for row in verify_required if (row["close_evidence"] or "").strip()]
|
|
@@ -2406,11 +2419,13 @@ def check_state_watchers() -> DoctorCheck:
|
|
|
2406
2419
|
if db_path.is_file():
|
|
2407
2420
|
try:
|
|
2408
2421
|
conn = sqlite3.connect(str(db_path))
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2422
|
+
try:
|
|
2423
|
+
row = conn.execute(
|
|
2424
|
+
"SELECT COUNT(*) FROM state_watchers WHERE status = 'active'"
|
|
2425
|
+
).fetchone()
|
|
2426
|
+
active_watchers = int(row[0] or 0) if row else 0
|
|
2427
|
+
finally:
|
|
2428
|
+
conn.close()
|
|
2414
2429
|
except Exception:
|
|
2415
2430
|
active_watchers = 0
|
|
2416
2431
|
|
|
@@ -2470,7 +2485,7 @@ def check_state_watchers() -> DoctorCheck:
|
|
|
2470
2485
|
generated_dt = dt.datetime.fromisoformat(str(generated_at).replace("Z", "+00:00"))
|
|
2471
2486
|
except Exception:
|
|
2472
2487
|
generated_dt = None
|
|
2473
|
-
if not generated_dt or (dt.datetime.now(dt.
|
|
2488
|
+
if not generated_dt or (dt.datetime.now(dt.timezone.utc) - generated_dt).total_seconds() > 36 * 3600:
|
|
2474
2489
|
status = "degraded"
|
|
2475
2490
|
severity = "warn"
|
|
2476
2491
|
repair_plan.append("Refresh state watchers daily so repo/API/expiry drift stays explicit")
|
|
@@ -2514,37 +2529,38 @@ def check_automation_telemetry(days: int = 7) -> DoctorCheck:
|
|
|
2514
2529
|
|
|
2515
2530
|
try:
|
|
2516
2531
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2532
|
+
try:
|
|
2533
|
+
conn.row_factory = sqlite3.Row
|
|
2534
|
+
table = conn.execute(
|
|
2535
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='automation_runs'"
|
|
2536
|
+
).fetchone()
|
|
2537
|
+
if not table:
|
|
2538
|
+
return DoctorCheck(
|
|
2539
|
+
id="runtime.automation_telemetry",
|
|
2540
|
+
tier="runtime",
|
|
2541
|
+
status="degraded",
|
|
2542
|
+
severity="warn",
|
|
2543
|
+
summary="Automation telemetry schema is missing",
|
|
2544
|
+
evidence=["table automation_runs not found"],
|
|
2545
|
+
repair_plan=["Run NEXO migrations before trusting automation cost/parity metrics"],
|
|
2546
|
+
escalation_prompt="Shared automation runs are happening without the telemetry table that release metrics depend on.",
|
|
2547
|
+
)
|
|
2533
2548
|
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2549
|
+
row = conn.execute(
|
|
2550
|
+
"""
|
|
2551
|
+
SELECT
|
|
2552
|
+
COUNT(*) AS runs,
|
|
2553
|
+
SUM(CASE WHEN (input_tokens + cached_input_tokens + output_tokens) > 0 THEN 1 ELSE 0 END) AS usage_runs,
|
|
2554
|
+
SUM(CASE WHEN total_cost_usd IS NOT NULL THEN 1 ELSE 0 END) AS cost_runs,
|
|
2555
|
+
SUM(CASE WHEN cost_source = 'pricing_unavailable' THEN 1 ELSE 0 END) AS pricing_gaps,
|
|
2556
|
+
GROUP_CONCAT(DISTINCT backend) AS backends
|
|
2557
|
+
FROM automation_runs
|
|
2558
|
+
WHERE created_at >= datetime('now', ?)
|
|
2559
|
+
""",
|
|
2560
|
+
(f"-{days} days",),
|
|
2561
|
+
).fetchone()
|
|
2562
|
+
finally:
|
|
2563
|
+
conn.close()
|
|
2548
2564
|
except Exception as exc:
|
|
2549
2565
|
return DoctorCheck(
|
|
2550
2566
|
id="runtime.automation_telemetry",
|
|
@@ -2619,22 +2635,22 @@ def check_automation_telemetry(days: int = 7) -> DoctorCheck:
|
|
|
2619
2635
|
def run_runtime_checks(fix: bool = False) -> list[DoctorCheck]:
|
|
2620
2636
|
"""Run all runtime-tier checks. Read-only by default."""
|
|
2621
2637
|
return [
|
|
2622
|
-
check_immune_status
|
|
2623
|
-
check_watchdog_status
|
|
2624
|
-
check_stale_sessions
|
|
2625
|
-
check_cron_freshness
|
|
2626
|
-
check_client_backend_preferences
|
|
2627
|
-
check_client_bootstrap_parity
|
|
2628
|
-
check_codex_session_parity
|
|
2629
|
-
check_codex_conditioned_file_discipline
|
|
2630
|
-
check_claude_desktop_shared_brain
|
|
2631
|
-
check_transcript_source_parity
|
|
2632
|
-
check_client_assumption_regressions
|
|
2633
|
-
check_protocol_compliance
|
|
2634
|
-
check_automation_telemetry
|
|
2635
|
-
check_state_watchers
|
|
2636
|
-
check_release_artifact_sync
|
|
2637
|
-
check_launchagent_integrity
|
|
2638
|
-
check_personal_script_registry
|
|
2639
|
-
check_skill_health
|
|
2638
|
+
safe_check(check_immune_status),
|
|
2639
|
+
safe_check(check_watchdog_status),
|
|
2640
|
+
safe_check(check_stale_sessions),
|
|
2641
|
+
safe_check(check_cron_freshness),
|
|
2642
|
+
safe_check(check_client_backend_preferences),
|
|
2643
|
+
safe_check(check_client_bootstrap_parity, fix=fix),
|
|
2644
|
+
safe_check(check_codex_session_parity),
|
|
2645
|
+
safe_check(check_codex_conditioned_file_discipline),
|
|
2646
|
+
safe_check(check_claude_desktop_shared_brain),
|
|
2647
|
+
safe_check(check_transcript_source_parity),
|
|
2648
|
+
safe_check(check_client_assumption_regressions),
|
|
2649
|
+
safe_check(check_protocol_compliance),
|
|
2650
|
+
safe_check(check_automation_telemetry),
|
|
2651
|
+
safe_check(check_state_watchers),
|
|
2652
|
+
safe_check(check_release_artifact_sync),
|
|
2653
|
+
safe_check(check_launchagent_integrity, fix=fix),
|
|
2654
|
+
safe_check(check_personal_script_registry, fix=fix),
|
|
2655
|
+
safe_check(check_skill_health, fix=fix),
|
|
2640
2656
|
]
|
package/src/evolution_cycle.py
CHANGED
|
@@ -120,52 +120,54 @@ def save_objective(obj: dict):
|
|
|
120
120
|
def get_week_data(db_path: str) -> dict:
|
|
121
121
|
"""Gather last 7 days of learnings, decisions, changes, diaries."""
|
|
122
122
|
conn = sqlite3.connect(db_path, timeout=10)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
123
|
+
try:
|
|
124
|
+
conn.row_factory = sqlite3.Row
|
|
125
|
+
cutoff_epoch = time.time() - 7 * 86400
|
|
126
|
+
cutoff_date = (date.today() - timedelta(days=7)).isoformat()
|
|
127
|
+
|
|
128
|
+
data = {}
|
|
129
|
+
|
|
130
|
+
rows = conn.execute(
|
|
131
|
+
"SELECT category, title, content FROM learnings WHERE created_at > ? ORDER BY created_at DESC LIMIT 50",
|
|
132
|
+
(cutoff_epoch,)
|
|
133
|
+
).fetchall()
|
|
134
|
+
data["learnings"] = [dict(r) for r in rows]
|
|
135
|
+
|
|
136
|
+
rows = conn.execute(
|
|
137
|
+
"SELECT domain, decision, alternatives, based_on, confidence, outcome FROM decisions "
|
|
138
|
+
"WHERE created_at > ? ORDER BY created_at DESC LIMIT 20",
|
|
139
|
+
(cutoff_date,)
|
|
140
|
+
).fetchall()
|
|
141
|
+
data["decisions"] = [dict(r) for r in rows]
|
|
142
|
+
|
|
143
|
+
rows = conn.execute(
|
|
144
|
+
"SELECT files, what_changed, why, affects, risks FROM change_log "
|
|
145
|
+
"WHERE created_at > ? ORDER BY created_at DESC LIMIT 30",
|
|
146
|
+
(cutoff_date,)
|
|
147
|
+
).fetchall()
|
|
148
|
+
data["changes"] = [dict(r) for r in rows]
|
|
149
|
+
|
|
150
|
+
rows = conn.execute(
|
|
151
|
+
"SELECT summary, decisions as diary_decisions, pending, mental_state, domain, user_signals "
|
|
152
|
+
"FROM session_diary WHERE created_at > ? ORDER BY created_at DESC LIMIT 20",
|
|
153
|
+
(cutoff_date,)
|
|
154
|
+
).fetchall()
|
|
155
|
+
data["diaries"] = [dict(r) for r in rows]
|
|
156
|
+
|
|
157
|
+
rows = conn.execute(
|
|
158
|
+
"SELECT * FROM evolution_log ORDER BY id DESC LIMIT 20"
|
|
159
|
+
).fetchall()
|
|
160
|
+
data["evolution_history"] = [dict(r) for r in rows]
|
|
161
|
+
|
|
162
|
+
rows = conn.execute(
|
|
163
|
+
"SELECT dimension, score, delta, measured_at FROM evolution_metrics "
|
|
164
|
+
"WHERE id IN (SELECT MAX(id) FROM evolution_metrics GROUP BY dimension)"
|
|
165
|
+
).fetchall()
|
|
166
|
+
data["current_metrics"] = {r["dimension"]: dict(r) for r in rows}
|
|
167
|
+
|
|
168
|
+
return data
|
|
169
|
+
finally:
|
|
170
|
+
conn.close()
|
|
169
171
|
|
|
170
172
|
|
|
171
173
|
def create_snapshot(files_to_backup: list) -> str:
|
package/src/kg_populate.py
CHANGED
|
@@ -147,25 +147,27 @@ def backfill_decisions() -> int:
|
|
|
147
147
|
def backfill_somatic() -> int:
|
|
148
148
|
"""Read somatic_markers from cognitive.db → create file/area nodes with risk."""
|
|
149
149
|
cdb = _cognitive_db()
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
150
|
+
try:
|
|
151
|
+
rows = cdb.execute(
|
|
152
|
+
"SELECT target, target_type, risk_score, incident_count FROM somatic_markers"
|
|
153
|
+
).fetchall()
|
|
154
|
+
count = 0
|
|
155
|
+
for row in rows:
|
|
156
|
+
target_type = row["target_type"] or "file"
|
|
157
|
+
node_ref = f"{target_type}:{row['target']}"
|
|
158
|
+
kg.upsert_node(
|
|
159
|
+
node_type=target_type,
|
|
160
|
+
node_ref=node_ref,
|
|
161
|
+
label=os.path.basename(row["target"]) or row["target"],
|
|
162
|
+
properties={
|
|
163
|
+
"risk_score": row["risk_score"],
|
|
164
|
+
"incident_count": row["incident_count"],
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
count += 1
|
|
168
|
+
return count
|
|
169
|
+
finally:
|
|
170
|
+
cdb.close()
|
|
169
171
|
|
|
170
172
|
|
|
171
173
|
def run_full_backfill() -> dict:
|
package/src/maintenance.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Opportunistic maintenance — run overdue tasks on MCP startup."""
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
-
from datetime import datetime
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
5
|
from db import get_db
|
|
6
6
|
|
|
7
7
|
|
|
@@ -16,7 +16,7 @@ def check_and_run_overdue():
|
|
|
16
16
|
if last_run:
|
|
17
17
|
try:
|
|
18
18
|
last_dt = datetime.strptime(last_run, "%Y-%m-%dT%H:%M:%S")
|
|
19
|
-
hours_since = (datetime.now(
|
|
19
|
+
hours_since = (datetime.now(timezone.utc).replace(tzinfo=None) - last_dt).total_seconds() / 3600
|
|
20
20
|
if hours_since < interval:
|
|
21
21
|
continue
|
|
22
22
|
except (ValueError, TypeError):
|
|
@@ -28,7 +28,7 @@ def check_and_run_overdue():
|
|
|
28
28
|
conn.execute(
|
|
29
29
|
"UPDATE maintenance_schedule SET last_run_at = ?, last_duration_ms = ?, "
|
|
30
30
|
"run_count = run_count + 1 WHERE task_name = ?",
|
|
31
|
-
(datetime.now(
|
|
31
|
+
(datetime.now(timezone.utc).replace(tzinfo=None).strftime("%Y-%m-%dT%H:%M:%S"), duration_ms, task))
|
|
32
32
|
conn.commit()
|
|
33
33
|
ran.append({"task": task, "duration_ms": duration_ms})
|
|
34
34
|
except Exception as e:
|
|
@@ -30,15 +30,17 @@ MODELS = {
|
|
|
30
30
|
def verify():
|
|
31
31
|
"""Check current embedding dimensions in the database."""
|
|
32
32
|
conn = sqlite3.connect(DB_PATH)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
try:
|
|
34
|
+
for table in ["stm_memories", "ltm_memories"]:
|
|
35
|
+
count = conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
|
|
36
|
+
if count == 0:
|
|
37
|
+
print(f" {table}: {count} rows (empty)")
|
|
38
|
+
continue
|
|
39
|
+
row = conn.execute(f"SELECT embedding FROM {table} LIMIT 1").fetchone()
|
|
40
|
+
vec = np.frombuffer(row[0], dtype=np.float32)
|
|
41
|
+
print(f" {table}: {count} rows, embedding dim = {len(vec)}")
|
|
42
|
+
finally:
|
|
43
|
+
conn.close()
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
def upgrade():
|
|
@@ -62,31 +64,31 @@ def upgrade():
|
|
|
62
64
|
model = TextEmbedding(model_name)
|
|
63
65
|
|
|
64
66
|
conn = sqlite3.connect(DB_PATH)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
try:
|
|
68
|
+
for table in ["stm_memories", "ltm_memories"]:
|
|
69
|
+
rows = conn.execute(f"SELECT id, content FROM {table}").fetchall()
|
|
70
|
+
if not rows:
|
|
71
|
+
print(f"\n{table}: empty, skipping")
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
print(f"\n{table}: re-embedding {len(rows)} memories...")
|
|
75
|
+
t0 = time.time()
|
|
76
|
+
|
|
77
|
+
# Batch embed for speed
|
|
78
|
+
contents = [r[1] for r in rows]
|
|
79
|
+
ids = [r[0] for r in rows]
|
|
80
|
+
|
|
81
|
+
embeddings = list(model.embed(contents))
|
|
82
|
+
|
|
83
|
+
for mem_id, emb in zip(ids, embeddings):
|
|
84
|
+
blob = np.array(emb, dtype=np.float32).tobytes()
|
|
85
|
+
conn.execute(f"UPDATE {table} SET embedding = ? WHERE id = ?", (blob, mem_id))
|
|
86
|
+
|
|
87
|
+
conn.commit()
|
|
88
|
+
elapsed = time.time() - t0
|
|
89
|
+
print(f" Done: {len(rows)} memories in {elapsed:.1f}s ({elapsed/len(rows)*1000:.0f}ms/memory)")
|
|
90
|
+
finally:
|
|
91
|
+
conn.close()
|
|
90
92
|
|
|
91
93
|
print("\nAfter upgrade:")
|
|
92
94
|
verify()
|