nexo-brain 3.0.1 → 3.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +82 -0
- package/community/launch/2026-04-v3-0-2/case-study-outreach.md +36 -0
- package/community/launch/2026-04-v3-0-2/devto-v3-0-2.md +91 -0
- package/community/launch/2026-04-v3-0-2/github-discussion-v3-0-2.md +58 -0
- package/community/launch/2026-04-v3-0-2/x-thread-v3-0-2.md +60 -0
- package/hooks/hooks.json +12 -0
- package/package.json +1 -1
- package/src/auto_update.py +23 -6
- package/src/client_sync.py +6 -0
- package/src/cognitive/_memory.py +14 -7
- package/src/cognitive/_search.py +12 -5
- package/src/crons/sync.py +2 -1
- package/src/doctor/models.py +25 -0
- package/src/doctor/orchestrator.py +32 -2
- package/src/doctor/providers/boot.py +7 -7
- package/src/doctor/providers/deep.py +24 -21
- package/src/doctor/providers/runtime.py +154 -137
- package/src/evolution_cycle.py +76 -47
- package/src/hook_guardrails.py +182 -0
- package/src/hooks/protocol-guardrail.sh +1 -1
- package/src/hooks/protocol-pretool-guardrail.sh +9 -0
- 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/protocol.py +15 -0
- package/src/plugins/schedule.py +13 -1
- package/src/plugins/update.py +18 -4
- package/src/protocol_settings.py +59 -0
- package/src/public_contribution.py +10 -14
- package/src/public_evolution_queue.py +241 -0
- package/src/scripts/nexo-catchup.py +15 -15
- package/src/scripts/nexo-daily-self-audit.py +677 -28
- package/src/scripts/nexo-evolution-run.py +44 -4
- package/src/server.py +26 -1
- package/src/state_watchers_runtime.py +42 -35
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""Doctor orchestrator — runs providers by tier, aggregates results."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import sys
|
|
4
5
|
import time
|
|
6
|
+
import traceback
|
|
5
7
|
|
|
6
|
-
from doctor.models import DoctorReport
|
|
8
|
+
from doctor.models import DoctorCheck, DoctorReport
|
|
7
9
|
from doctor.providers.boot import run_boot_checks
|
|
8
10
|
from doctor.providers.runtime import run_runtime_checks
|
|
9
11
|
from doctor.providers.deep import run_deep_checks
|
|
@@ -17,6 +19,8 @@ _TIER_RUNNERS = {
|
|
|
17
19
|
|
|
18
20
|
_TIER_ORDER = ["boot", "runtime", "deep"]
|
|
19
21
|
|
|
22
|
+
VALID_TIERS = frozenset(_TIER_ORDER) | {"all"}
|
|
23
|
+
|
|
20
24
|
|
|
21
25
|
def run_doctor(tier: str = "boot", fix: bool = False) -> DoctorReport:
|
|
22
26
|
"""Run diagnostic checks for the specified tier(s).
|
|
@@ -28,14 +32,40 @@ def run_doctor(tier: str = "boot", fix: bool = False) -> DoctorReport:
|
|
|
28
32
|
report = DoctorReport(overall_status="healthy")
|
|
29
33
|
start = time.monotonic()
|
|
30
34
|
|
|
35
|
+
if tier not in VALID_TIERS:
|
|
36
|
+
report.add(DoctorCheck(
|
|
37
|
+
id="orchestrator.invalid_tier",
|
|
38
|
+
tier="orchestrator",
|
|
39
|
+
status="critical",
|
|
40
|
+
severity="error",
|
|
41
|
+
summary=f"Unknown tier '{tier}' — valid options: {', '.join(sorted(VALID_TIERS))}",
|
|
42
|
+
))
|
|
43
|
+
report.compute_status()
|
|
44
|
+
report.duration_ms = int((time.monotonic() - start) * 1000)
|
|
45
|
+
return report
|
|
46
|
+
|
|
31
47
|
tiers = _TIER_ORDER if tier == "all" else [tier]
|
|
32
48
|
|
|
33
49
|
for t in tiers:
|
|
34
50
|
runner = _TIER_RUNNERS.get(t)
|
|
35
|
-
if runner:
|
|
51
|
+
if not runner:
|
|
52
|
+
continue
|
|
53
|
+
try:
|
|
36
54
|
checks = runner(fix=fix)
|
|
37
55
|
for check in checks:
|
|
38
56
|
report.add(check)
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
tb = traceback.format_exception(type(exc), exc, exc.__traceback__)
|
|
59
|
+
last_frame = tb[-1].strip() if tb else str(exc)
|
|
60
|
+
report.add(DoctorCheck(
|
|
61
|
+
id=f"orchestrator.{t}_crashed",
|
|
62
|
+
tier=t,
|
|
63
|
+
status="critical",
|
|
64
|
+
severity="error",
|
|
65
|
+
summary=f"{t} tier checks crashed: {type(exc).__name__}: {exc}",
|
|
66
|
+
evidence=[last_frame],
|
|
67
|
+
repair_plan=[f"Investigate {t} provider — exception during check execution"],
|
|
68
|
+
))
|
|
39
69
|
|
|
40
70
|
report.compute_status()
|
|
41
71
|
report.duration_ms = int((time.monotonic() - start) * 1000)
|
|
@@ -6,7 +6,7 @@ import shutil
|
|
|
6
6
|
import sys
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
-
from doctor.models import DoctorCheck
|
|
9
|
+
from doctor.models import DoctorCheck, safe_check
|
|
10
10
|
|
|
11
11
|
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
12
12
|
|
|
@@ -209,12 +209,12 @@ def check_config_parse() -> DoctorCheck:
|
|
|
209
209
|
def run_boot_checks(fix: bool = False) -> list[DoctorCheck]:
|
|
210
210
|
"""Run all boot-tier checks."""
|
|
211
211
|
checks = [
|
|
212
|
-
check_db_exists
|
|
213
|
-
check_required_dirs
|
|
214
|
-
check_disk_space
|
|
215
|
-
check_wrapper_scripts
|
|
216
|
-
check_python_runtime
|
|
217
|
-
check_config_parse
|
|
212
|
+
safe_check(check_db_exists),
|
|
213
|
+
safe_check(check_required_dirs),
|
|
214
|
+
safe_check(check_disk_space),
|
|
215
|
+
safe_check(check_wrapper_scripts),
|
|
216
|
+
safe_check(check_python_runtime),
|
|
217
|
+
safe_check(check_config_parse),
|
|
218
218
|
]
|
|
219
219
|
|
|
220
220
|
if fix:
|
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import time
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
-
from doctor.models import DoctorCheck
|
|
9
|
+
from doctor.models import DoctorCheck, safe_check
|
|
10
10
|
|
|
11
11
|
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
12
12
|
|
|
@@ -106,8 +106,10 @@ def check_schema_version() -> DoctorCheck:
|
|
|
106
106
|
summary="No database to check schema",
|
|
107
107
|
)
|
|
108
108
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
try:
|
|
110
|
+
version = conn.execute("PRAGMA user_version").fetchone()[0]
|
|
111
|
+
finally:
|
|
112
|
+
conn.close()
|
|
111
113
|
return DoctorCheck(
|
|
112
114
|
id="deep.schema_version",
|
|
113
115
|
tier="deep",
|
|
@@ -250,20 +252,21 @@ def check_learning_count() -> DoctorCheck:
|
|
|
250
252
|
summary="No DB to check learnings",
|
|
251
253
|
)
|
|
252
254
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
try:
|
|
256
|
+
tables = conn.execute(
|
|
257
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='learnings'"
|
|
258
|
+
).fetchone()
|
|
259
|
+
if not tables:
|
|
260
|
+
return DoctorCheck(
|
|
261
|
+
id="deep.learning_count",
|
|
262
|
+
tier="deep",
|
|
263
|
+
status="healthy",
|
|
264
|
+
severity="info",
|
|
265
|
+
summary="No learnings table yet",
|
|
266
|
+
)
|
|
267
|
+
count = conn.execute("SELECT COUNT(*) FROM learnings WHERE archived=0").fetchone()[0]
|
|
268
|
+
finally:
|
|
257
269
|
conn.close()
|
|
258
|
-
return DoctorCheck(
|
|
259
|
-
id="deep.learning_count",
|
|
260
|
-
tier="deep",
|
|
261
|
-
status="healthy",
|
|
262
|
-
severity="info",
|
|
263
|
-
summary="No learnings table yet",
|
|
264
|
-
)
|
|
265
|
-
count = conn.execute("SELECT COUNT(*) FROM learnings WHERE archived=0").fetchone()[0]
|
|
266
|
-
conn.close()
|
|
267
270
|
return DoctorCheck(
|
|
268
271
|
id="deep.learning_count",
|
|
269
272
|
tier="deep",
|
|
@@ -284,9 +287,9 @@ def check_learning_count() -> DoctorCheck:
|
|
|
284
287
|
def run_deep_checks(fix: bool = False) -> list[DoctorCheck]:
|
|
285
288
|
"""Run all deep-tier checks. Read-only."""
|
|
286
289
|
return [
|
|
287
|
-
check_self_audit_summary
|
|
288
|
-
check_schema_version
|
|
289
|
-
check_preflight_summary
|
|
290
|
-
check_watchdog_smoke
|
|
291
|
-
check_learning_count
|
|
290
|
+
safe_check(check_self_audit_summary),
|
|
291
|
+
safe_check(check_schema_version),
|
|
292
|
+
safe_check(check_preflight_summary),
|
|
293
|
+
safe_check(check_watchdog_smoke),
|
|
294
|
+
safe_check(check_learning_count),
|
|
292
295
|
]
|
|
@@ -25,7 +25,7 @@ from client_preferences import (
|
|
|
25
25
|
resolve_client_runtime_profile,
|
|
26
26
|
)
|
|
27
27
|
from cron_recovery import should_run_at_load
|
|
28
|
-
from doctor.models import DoctorCheck
|
|
28
|
+
from doctor.models import DoctorCheck, safe_check
|
|
29
29
|
|
|
30
30
|
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
31
31
|
NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(Path(__file__).resolve().parents[2])))
|
|
@@ -406,21 +406,22 @@ def _load_active_conditioned_learnings() -> list[dict]:
|
|
|
406
406
|
import sqlite3
|
|
407
407
|
|
|
408
408
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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:
|
|
414
424
|
conn.close()
|
|
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
|
-
conn.close()
|
|
423
|
-
return [dict(row) for row in rows]
|
|
424
425
|
except Exception:
|
|
425
426
|
return []
|
|
426
427
|
|
|
@@ -595,22 +596,23 @@ def _open_protocol_debt_summary(*debt_types: str) -> dict:
|
|
|
595
596
|
|
|
596
597
|
try:
|
|
597
598
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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:
|
|
603
615
|
conn.close()
|
|
604
|
-
return summary
|
|
605
|
-
placeholders = ",".join("?" for _ in debt_types)
|
|
606
|
-
rows = conn.execute(
|
|
607
|
-
f"""SELECT debt_type, COUNT(*) AS total
|
|
608
|
-
FROM protocol_debt
|
|
609
|
-
WHERE status = 'open' AND debt_type IN ({placeholders})
|
|
610
|
-
GROUP BY debt_type""",
|
|
611
|
-
tuple(debt_types),
|
|
612
|
-
).fetchall()
|
|
613
|
-
conn.close()
|
|
614
616
|
except Exception:
|
|
615
617
|
return summary
|
|
616
618
|
|
|
@@ -625,11 +627,12 @@ def _client_assumption_regressions() -> list[str]:
|
|
|
625
627
|
src_root = NEXO_CODE if (NEXO_CODE / "server.py").is_file() else (NEXO_CODE / "src")
|
|
626
628
|
if not src_root.is_dir():
|
|
627
629
|
return []
|
|
630
|
+
src_root = src_root.resolve()
|
|
628
631
|
backup_root = (NEXO_HOME / "backups").resolve()
|
|
629
632
|
contrib_root = (NEXO_HOME / "contrib").resolve()
|
|
630
|
-
|
|
631
|
-
(
|
|
632
|
-
Path(
|
|
633
|
+
allowed_relative_paths = {
|
|
634
|
+
Path("scripts") / "deep-sleep" / "collect.py",
|
|
635
|
+
Path("doctor") / "providers" / "runtime.py",
|
|
633
636
|
}
|
|
634
637
|
offenders: list[str] = []
|
|
635
638
|
for path in src_root.rglob("*.py"):
|
|
@@ -648,8 +651,12 @@ def _client_assumption_regressions() -> list[str]:
|
|
|
648
651
|
continue
|
|
649
652
|
except Exception:
|
|
650
653
|
pass
|
|
651
|
-
|
|
652
|
-
|
|
654
|
+
try:
|
|
655
|
+
relative_path = resolved.relative_to(src_root)
|
|
656
|
+
except Exception:
|
|
657
|
+
relative_path = path.relative_to(src_root)
|
|
658
|
+
if ".claude/projects" in text and relative_path not in allowed_relative_paths:
|
|
659
|
+
offenders.append(f"{relative_path} hardcodes ~/.claude/projects")
|
|
653
660
|
collect_path = src_root / "scripts" / "deep-sleep" / "collect.py"
|
|
654
661
|
try:
|
|
655
662
|
collect_text = collect_path.read_text()
|
|
@@ -1172,14 +1179,16 @@ def check_stale_sessions() -> DoctorCheck:
|
|
|
1172
1179
|
summary="No DB to check sessions",
|
|
1173
1180
|
)
|
|
1174
1181
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1182
|
+
try:
|
|
1183
|
+
conn.row_factory = sqlite3.Row
|
|
1184
|
+
cutoff = time.time() - 7200
|
|
1185
|
+
day_ago = time.time() - 86400
|
|
1186
|
+
rows = conn.execute(
|
|
1187
|
+
"SELECT COUNT(*) as cnt FROM sessions WHERE last_update_epoch < ? AND last_update_epoch > ?",
|
|
1188
|
+
(cutoff, day_ago),
|
|
1189
|
+
).fetchone()
|
|
1190
|
+
finally:
|
|
1191
|
+
conn.close()
|
|
1183
1192
|
count = rows["cnt"] if rows else 0
|
|
1184
1193
|
if count > 0:
|
|
1185
1194
|
return DoctorCheck(
|
|
@@ -1221,24 +1230,25 @@ def check_cron_freshness() -> DoctorCheck:
|
|
|
1221
1230
|
summary="No DB to check cron runs",
|
|
1222
1231
|
)
|
|
1223
1232
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1233
|
+
try:
|
|
1234
|
+
# Check if cron_runs table exists
|
|
1235
|
+
tables = conn.execute(
|
|
1236
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='cron_runs'"
|
|
1237
|
+
).fetchone()
|
|
1238
|
+
if not tables:
|
|
1239
|
+
return DoctorCheck(
|
|
1240
|
+
id="runtime.cron_freshness",
|
|
1241
|
+
tier="runtime",
|
|
1242
|
+
status="healthy",
|
|
1243
|
+
severity="info",
|
|
1244
|
+
summary="No cron_runs table yet",
|
|
1245
|
+
)
|
|
1246
|
+
# Latest run per cron
|
|
1247
|
+
rows = conn.execute(
|
|
1248
|
+
"SELECT cron_id, MAX(started_at) as last_run FROM cron_runs GROUP BY cron_id"
|
|
1249
|
+
).fetchall()
|
|
1250
|
+
finally:
|
|
1229
1251
|
conn.close()
|
|
1230
|
-
return DoctorCheck(
|
|
1231
|
-
id="runtime.cron_freshness",
|
|
1232
|
-
tier="runtime",
|
|
1233
|
-
status="healthy",
|
|
1234
|
-
severity="info",
|
|
1235
|
-
summary="No cron_runs table yet",
|
|
1236
|
-
)
|
|
1237
|
-
# Latest run per cron
|
|
1238
|
-
rows = conn.execute(
|
|
1239
|
-
"SELECT cron_id, MAX(started_at) as last_run FROM cron_runs GROUP BY cron_id"
|
|
1240
|
-
).fetchall()
|
|
1241
|
-
conn.close()
|
|
1242
1252
|
|
|
1243
1253
|
stale = []
|
|
1244
1254
|
expectations = _cron_expectations()
|
|
@@ -2167,32 +2177,36 @@ def check_protocol_compliance() -> DoctorCheck:
|
|
|
2167
2177
|
db_path = NEXO_HOME / "data" / "nexo.db"
|
|
2168
2178
|
if db_path.is_file():
|
|
2169
2179
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2180
|
+
try:
|
|
2181
|
+
conn.row_factory = sqlite3.Row
|
|
2182
|
+
tables = {
|
|
2183
|
+
row["name"]
|
|
2184
|
+
for row in conn.execute(
|
|
2185
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('protocol_tasks', 'protocol_debt')"
|
|
2186
|
+
).fetchall()
|
|
2187
|
+
}
|
|
2188
|
+
tasks = None
|
|
2189
|
+
debt_rows = None
|
|
2190
|
+
if {"protocol_tasks", "protocol_debt"}.issubset(tables):
|
|
2191
|
+
window = "-7 days"
|
|
2192
|
+
tasks = conn.execute(
|
|
2193
|
+
"""SELECT * FROM protocol_tasks
|
|
2194
|
+
WHERE opened_at >= datetime('now', ?)
|
|
2195
|
+
ORDER BY opened_at DESC""",
|
|
2196
|
+
(window,),
|
|
2197
|
+
).fetchall()
|
|
2198
|
+
debt_rows = conn.execute(
|
|
2199
|
+
"""SELECT severity, debt_type, COUNT(*) AS total
|
|
2200
|
+
FROM protocol_debt
|
|
2201
|
+
WHERE status = 'open' AND created_at >= datetime('now', ?)
|
|
2202
|
+
GROUP BY severity, debt_type
|
|
2203
|
+
ORDER BY total DESC, debt_type ASC""",
|
|
2204
|
+
(window,),
|
|
2205
|
+
).fetchall()
|
|
2206
|
+
finally:
|
|
2193
2207
|
conn.close()
|
|
2194
2208
|
|
|
2195
|
-
|
|
2209
|
+
if tasks is not None and debt_rows is not None and (tasks or debt_rows):
|
|
2196
2210
|
closed_tasks = [row for row in tasks if row["status"] != "open"]
|
|
2197
2211
|
verify_required = [row for row in closed_tasks if row["must_verify"] and row["status"] == "done"]
|
|
2198
2212
|
verify_ok = [row for row in verify_required if (row["close_evidence"] or "").strip()]
|
|
@@ -2410,11 +2424,13 @@ def check_state_watchers() -> DoctorCheck:
|
|
|
2410
2424
|
if db_path.is_file():
|
|
2411
2425
|
try:
|
|
2412
2426
|
conn = sqlite3.connect(str(db_path))
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2427
|
+
try:
|
|
2428
|
+
row = conn.execute(
|
|
2429
|
+
"SELECT COUNT(*) FROM state_watchers WHERE status = 'active'"
|
|
2430
|
+
).fetchone()
|
|
2431
|
+
active_watchers = int(row[0] or 0) if row else 0
|
|
2432
|
+
finally:
|
|
2433
|
+
conn.close()
|
|
2418
2434
|
except Exception:
|
|
2419
2435
|
active_watchers = 0
|
|
2420
2436
|
|
|
@@ -2518,37 +2534,38 @@ def check_automation_telemetry(days: int = 7) -> DoctorCheck:
|
|
|
2518
2534
|
|
|
2519
2535
|
try:
|
|
2520
2536
|
conn = sqlite3.connect(str(db_path), timeout=2)
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
+
try:
|
|
2538
|
+
conn.row_factory = sqlite3.Row
|
|
2539
|
+
table = conn.execute(
|
|
2540
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='automation_runs'"
|
|
2541
|
+
).fetchone()
|
|
2542
|
+
if not table:
|
|
2543
|
+
return DoctorCheck(
|
|
2544
|
+
id="runtime.automation_telemetry",
|
|
2545
|
+
tier="runtime",
|
|
2546
|
+
status="degraded",
|
|
2547
|
+
severity="warn",
|
|
2548
|
+
summary="Automation telemetry schema is missing",
|
|
2549
|
+
evidence=["table automation_runs not found"],
|
|
2550
|
+
repair_plan=["Run NEXO migrations before trusting automation cost/parity metrics"],
|
|
2551
|
+
escalation_prompt="Shared automation runs are happening without the telemetry table that release metrics depend on.",
|
|
2552
|
+
)
|
|
2537
2553
|
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2554
|
+
row = conn.execute(
|
|
2555
|
+
"""
|
|
2556
|
+
SELECT
|
|
2557
|
+
COUNT(*) AS runs,
|
|
2558
|
+
SUM(CASE WHEN (input_tokens + cached_input_tokens + output_tokens) > 0 THEN 1 ELSE 0 END) AS usage_runs,
|
|
2559
|
+
SUM(CASE WHEN total_cost_usd IS NOT NULL THEN 1 ELSE 0 END) AS cost_runs,
|
|
2560
|
+
SUM(CASE WHEN cost_source = 'pricing_unavailable' THEN 1 ELSE 0 END) AS pricing_gaps,
|
|
2561
|
+
GROUP_CONCAT(DISTINCT backend) AS backends
|
|
2562
|
+
FROM automation_runs
|
|
2563
|
+
WHERE created_at >= datetime('now', ?)
|
|
2564
|
+
""",
|
|
2565
|
+
(f"-{days} days",),
|
|
2566
|
+
).fetchone()
|
|
2567
|
+
finally:
|
|
2568
|
+
conn.close()
|
|
2552
2569
|
except Exception as exc:
|
|
2553
2570
|
return DoctorCheck(
|
|
2554
2571
|
id="runtime.automation_telemetry",
|
|
@@ -2623,22 +2640,22 @@ def check_automation_telemetry(days: int = 7) -> DoctorCheck:
|
|
|
2623
2640
|
def run_runtime_checks(fix: bool = False) -> list[DoctorCheck]:
|
|
2624
2641
|
"""Run all runtime-tier checks. Read-only by default."""
|
|
2625
2642
|
return [
|
|
2626
|
-
check_immune_status
|
|
2627
|
-
check_watchdog_status
|
|
2628
|
-
check_stale_sessions
|
|
2629
|
-
check_cron_freshness
|
|
2630
|
-
check_client_backend_preferences
|
|
2631
|
-
check_client_bootstrap_parity
|
|
2632
|
-
check_codex_session_parity
|
|
2633
|
-
check_codex_conditioned_file_discipline
|
|
2634
|
-
check_claude_desktop_shared_brain
|
|
2635
|
-
check_transcript_source_parity
|
|
2636
|
-
check_client_assumption_regressions
|
|
2637
|
-
check_protocol_compliance
|
|
2638
|
-
check_automation_telemetry
|
|
2639
|
-
check_state_watchers
|
|
2640
|
-
check_release_artifact_sync
|
|
2641
|
-
check_launchagent_integrity
|
|
2642
|
-
check_personal_script_registry
|
|
2643
|
-
check_skill_health
|
|
2643
|
+
safe_check(check_immune_status),
|
|
2644
|
+
safe_check(check_watchdog_status),
|
|
2645
|
+
safe_check(check_stale_sessions),
|
|
2646
|
+
safe_check(check_cron_freshness),
|
|
2647
|
+
safe_check(check_client_backend_preferences),
|
|
2648
|
+
safe_check(check_client_bootstrap_parity, fix=fix),
|
|
2649
|
+
safe_check(check_codex_session_parity),
|
|
2650
|
+
safe_check(check_codex_conditioned_file_discipline),
|
|
2651
|
+
safe_check(check_claude_desktop_shared_brain),
|
|
2652
|
+
safe_check(check_transcript_source_parity),
|
|
2653
|
+
safe_check(check_client_assumption_regressions),
|
|
2654
|
+
safe_check(check_protocol_compliance),
|
|
2655
|
+
safe_check(check_automation_telemetry),
|
|
2656
|
+
safe_check(check_state_watchers),
|
|
2657
|
+
safe_check(check_release_artifact_sync),
|
|
2658
|
+
safe_check(check_launchagent_integrity, fix=fix),
|
|
2659
|
+
safe_check(check_personal_script_registry, fix=fix),
|
|
2660
|
+
safe_check(check_skill_health, fix=fix),
|
|
2644
2661
|
]
|