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.
Files changed (37) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +82 -0
  3. package/community/launch/2026-04-v3-0-2/case-study-outreach.md +36 -0
  4. package/community/launch/2026-04-v3-0-2/devto-v3-0-2.md +91 -0
  5. package/community/launch/2026-04-v3-0-2/github-discussion-v3-0-2.md +58 -0
  6. package/community/launch/2026-04-v3-0-2/x-thread-v3-0-2.md +60 -0
  7. package/hooks/hooks.json +12 -0
  8. package/package.json +1 -1
  9. package/src/auto_update.py +23 -6
  10. package/src/client_sync.py +6 -0
  11. package/src/cognitive/_memory.py +14 -7
  12. package/src/cognitive/_search.py +12 -5
  13. package/src/crons/sync.py +2 -1
  14. package/src/doctor/models.py +25 -0
  15. package/src/doctor/orchestrator.py +32 -2
  16. package/src/doctor/providers/boot.py +7 -7
  17. package/src/doctor/providers/deep.py +24 -21
  18. package/src/doctor/providers/runtime.py +154 -137
  19. package/src/evolution_cycle.py +76 -47
  20. package/src/hook_guardrails.py +182 -0
  21. package/src/hooks/protocol-guardrail.sh +1 -1
  22. package/src/hooks/protocol-pretool-guardrail.sh +9 -0
  23. package/src/kg_populate.py +21 -19
  24. package/src/maintenance.py +3 -3
  25. package/src/migrate_embeddings.py +36 -34
  26. package/src/plugins/backup.py +24 -12
  27. package/src/plugins/protocol.py +15 -0
  28. package/src/plugins/schedule.py +13 -1
  29. package/src/plugins/update.py +18 -4
  30. package/src/protocol_settings.py +59 -0
  31. package/src/public_contribution.py +10 -14
  32. package/src/public_evolution_queue.py +241 -0
  33. package/src/scripts/nexo-catchup.py +15 -15
  34. package/src/scripts/nexo-daily-self-audit.py +677 -28
  35. package/src/scripts/nexo-evolution-run.py +44 -4
  36. package/src/server.py +26 -1
  37. 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
- version = conn.execute("PRAGMA user_version").fetchone()[0]
110
- conn.close()
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
- tables = conn.execute(
254
- "SELECT name FROM sqlite_master WHERE type='table' AND name='learnings'"
255
- ).fetchone()
256
- if not tables:
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
- conn.row_factory = sqlite3.Row
410
- table = conn.execute(
411
- "SELECT name FROM sqlite_master WHERE type='table' AND name='learnings'"
412
- ).fetchone()
413
- if not table:
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
- conn.row_factory = sqlite3.Row
599
- table = conn.execute(
600
- "SELECT name FROM sqlite_master WHERE type='table' AND name='protocol_debt'"
601
- ).fetchone()
602
- if not table:
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
- allowed_claude_projects = {
631
- (src_root / "scripts" / "deep-sleep" / "collect.py").resolve(),
632
- Path(__file__).resolve(),
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
- if ".claude/projects" in text and resolved not in allowed_claude_projects:
652
- offenders.append(f"{path.relative_to(NEXO_CODE)} hardcodes ~/.claude/projects")
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
- conn.row_factory = sqlite3.Row
1176
- cutoff = time.time() - 7200
1177
- day_ago = time.time() - 86400
1178
- rows = conn.execute(
1179
- "SELECT COUNT(*) as cnt FROM sessions WHERE last_update_epoch < ? AND last_update_epoch > ?",
1180
- (cutoff, day_ago),
1181
- ).fetchone()
1182
- conn.close()
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
- # Check if cron_runs table exists
1225
- tables = conn.execute(
1226
- "SELECT name FROM sqlite_master WHERE type='table' AND name='cron_runs'"
1227
- ).fetchone()
1228
- if not tables:
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
- conn.row_factory = sqlite3.Row
2171
- tables = {
2172
- row["name"]
2173
- for row in conn.execute(
2174
- "SELECT name FROM sqlite_master WHERE type='table' AND name IN ('protocol_tasks', 'protocol_debt')"
2175
- ).fetchall()
2176
- }
2177
- if {"protocol_tasks", "protocol_debt"}.issubset(tables):
2178
- window = "-7 days"
2179
- tasks = conn.execute(
2180
- """SELECT * FROM protocol_tasks
2181
- WHERE opened_at >= datetime('now', ?)
2182
- ORDER BY opened_at DESC""",
2183
- (window,),
2184
- ).fetchall()
2185
- debt_rows = conn.execute(
2186
- """SELECT severity, debt_type, COUNT(*) AS total
2187
- FROM protocol_debt
2188
- WHERE status = 'open' AND created_at >= datetime('now', ?)
2189
- GROUP BY severity, debt_type
2190
- ORDER BY total DESC, debt_type ASC""",
2191
- (window,),
2192
- ).fetchall()
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
- if tasks or debt_rows:
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
- row = conn.execute(
2414
- "SELECT COUNT(*) FROM state_watchers WHERE status = 'active'"
2415
- ).fetchone()
2416
- conn.close()
2417
- active_watchers = int(row[0] or 0) if row else 0
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
- conn.row_factory = sqlite3.Row
2522
- table = conn.execute(
2523
- "SELECT name FROM sqlite_master WHERE type='table' AND name='automation_runs'"
2524
- ).fetchone()
2525
- if not table:
2526
- conn.close()
2527
- return DoctorCheck(
2528
- id="runtime.automation_telemetry",
2529
- tier="runtime",
2530
- status="degraded",
2531
- severity="warn",
2532
- summary="Automation telemetry schema is missing",
2533
- evidence=["table automation_runs not found"],
2534
- repair_plan=["Run NEXO migrations before trusting automation cost/parity metrics"],
2535
- escalation_prompt="Shared automation runs are happening without the telemetry table that release metrics depend on.",
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
- row = conn.execute(
2539
- """
2540
- SELECT
2541
- COUNT(*) AS runs,
2542
- SUM(CASE WHEN (input_tokens + cached_input_tokens + output_tokens) > 0 THEN 1 ELSE 0 END) AS usage_runs,
2543
- SUM(CASE WHEN total_cost_usd IS NOT NULL THEN 1 ELSE 0 END) AS cost_runs,
2544
- SUM(CASE WHEN cost_source = 'pricing_unavailable' THEN 1 ELSE 0 END) AS pricing_gaps,
2545
- GROUP_CONCAT(DISTINCT backend) AS backends
2546
- FROM automation_runs
2547
- WHERE created_at >= datetime('now', ?)
2548
- """,
2549
- (f"-{days} days",),
2550
- ).fetchone()
2551
- conn.close()
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(fix=fix),
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(fix=fix),
2642
- check_personal_script_registry(fix=fix),
2643
- check_skill_health(fix=fix),
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
  ]