nexo-brain 2.2.0 → 2.3.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 (98) hide show
  1. package/README.md +4 -4
  2. package/package.json +1 -1
  3. package/scripts/migrate-v1.7-to-v1.8.py +2 -2
  4. package/scripts/nexo-preflight.sh +236 -0
  5. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  6. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  7. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  8. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  9. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  10. package/src/auto_update.py +25 -0
  11. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  12. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  13. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  14. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  15. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  16. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  17. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  18. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  19. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  20. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  21. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  22. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  23. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  24. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  25. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  26. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  27. package/src/crons/manifest.json +6 -13
  28. package/src/crons/sync.py +151 -6
  29. package/src/db/__init__.py +13 -0
  30. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  31. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  32. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  33. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  34. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  35. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  36. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  37. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  38. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  39. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  40. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  41. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  42. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  43. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  44. package/src/db/_cron_runs.py +74 -0
  45. package/src/db/_episodic.py +40 -6
  46. package/src/db/_schema.py +64 -0
  47. package/src/db/_skills.py +514 -0
  48. package/src/hooks/session-stop.sh +13 -101
  49. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  50. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  51. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  52. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  53. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  54. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  55. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  56. package/src/plugins/episodic_memory.py +5 -3
  57. package/src/plugins/schedule.py +212 -0
  58. package/src/plugins/skills.py +264 -0
  59. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  60. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  61. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  62. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  63. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  64. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  65. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  66. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  67. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  68. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  69. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  70. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  71. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  72. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  73. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  74. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  75. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  76. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  77. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  78. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  79. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  80. package/src/scripts/deep-sleep/apply_findings.py +110 -8
  81. package/src/scripts/deep-sleep/collect.py +33 -11
  82. package/src/scripts/deep-sleep/extract-prompt.md +38 -0
  83. package/src/scripts/deep-sleep/extract.py +80 -8
  84. package/src/scripts/deep-sleep/synthesize-prompt.md +29 -1
  85. package/src/scripts/deep-sleep/synthesize.py +3 -1
  86. package/src/scripts/nexo-catchup.py +65 -29
  87. package/src/scripts/nexo-cron-wrapper.sh +53 -0
  88. package/src/scripts/nexo-daily-self-audit.py +4 -2
  89. package/src/scripts/nexo-deep-sleep.sh +66 -77
  90. package/src/scripts/nexo-evolution-run.py +13 -0
  91. package/src/scripts/nexo-learning-housekeep.py +156 -1
  92. package/src/scripts/nexo-learning-validator.py +19 -0
  93. package/src/scripts/nexo-postmortem-consolidator.py +3 -2
  94. package/src/scripts/nexo-sleep.py +16 -11
  95. package/src/scripts/nexo-synthesis.py +46 -3
  96. package/src/scripts/nexo-watchdog.sh +72 -19
  97. package/src/server.py +5 -1
  98. package/src/scripts/nexo-github-monitor.py +0 -256
@@ -0,0 +1,74 @@
1
+ """NEXO DB — Cron execution history."""
2
+ from db._core import get_db
3
+
4
+
5
+ def cron_run_start(cron_id: str) -> int:
6
+ """Record a cron starting. Returns the run ID."""
7
+ conn = get_db()
8
+ cursor = conn.execute(
9
+ "INSERT INTO cron_runs (cron_id) VALUES (?)", (cron_id,)
10
+ )
11
+ conn.commit()
12
+ return cursor.lastrowid
13
+
14
+
15
+ def cron_run_end(run_id: int, exit_code: int, summary: str = '', error: str = ''):
16
+ """Record a cron finishing."""
17
+ conn = get_db()
18
+ conn.execute(
19
+ """UPDATE cron_runs
20
+ SET ended_at = datetime('now'),
21
+ exit_code = ?,
22
+ summary = ?,
23
+ error = ?,
24
+ duration_secs = ROUND((julianday(datetime('now')) - julianday(started_at)) * 86400, 1)
25
+ WHERE id = ?""",
26
+ (exit_code, summary[:500], error[:500], run_id)
27
+ )
28
+ conn.commit()
29
+
30
+
31
+ def cron_runs_recent(hours: int = 24, cron_id: str = '') -> list[dict]:
32
+ """Get recent cron executions."""
33
+ conn = get_db()
34
+ if cron_id:
35
+ rows = conn.execute(
36
+ """SELECT * FROM cron_runs
37
+ WHERE cron_id = ? AND started_at >= datetime('now', ?)
38
+ ORDER BY started_at DESC""",
39
+ (cron_id, f"-{hours} hours")
40
+ ).fetchall()
41
+ else:
42
+ rows = conn.execute(
43
+ """SELECT * FROM cron_runs
44
+ WHERE started_at >= datetime('now', ?)
45
+ ORDER BY started_at DESC""",
46
+ (f"-{hours} hours",)
47
+ ).fetchall()
48
+ return [dict(r) for r in rows]
49
+
50
+
51
+ def cron_runs_summary(hours: int = 24) -> list[dict]:
52
+ """Get summary per cron: last run, success rate, avg duration."""
53
+ conn = get_db()
54
+ rows = conn.execute(
55
+ """SELECT
56
+ cron_id,
57
+ COUNT(*) as total_runs,
58
+ SUM(CASE WHEN exit_code = 0 THEN 1 ELSE 0 END) as succeeded,
59
+ SUM(CASE WHEN exit_code != 0 OR exit_code IS NULL THEN 1 ELSE 0 END) as failed,
60
+ ROUND(AVG(duration_secs), 1) as avg_duration,
61
+ MAX(started_at) as last_run,
62
+ (SELECT exit_code FROM cron_runs cr2
63
+ WHERE cr2.cron_id = cron_runs.cron_id
64
+ ORDER BY started_at DESC LIMIT 1) as last_exit_code,
65
+ (SELECT summary FROM cron_runs cr3
66
+ WHERE cr3.cron_id = cron_runs.cron_id AND cr3.summary != ''
67
+ ORDER BY started_at DESC LIMIT 1) as last_summary
68
+ FROM cron_runs
69
+ WHERE started_at >= datetime('now', ?)
70
+ GROUP BY cron_id
71
+ ORDER BY last_run DESC""",
72
+ (f"-{hours} hours",)
73
+ ).fetchall()
74
+ return [dict(r) for r in rows]
@@ -568,17 +568,35 @@ def get_orphan_sessions(ttl_seconds: int = 900) -> list[dict]:
568
568
 
569
569
 
570
570
  def read_session_diary(session_id: str = '', last_n: int = 3, last_day: bool = False,
571
- domain: str = '') -> list[dict]:
571
+ domain: str = '', include_automated: bool = False) -> list[dict]:
572
572
  """Read session diary entries.
573
573
 
574
574
  - session_id: returns entries for that specific session
575
575
  - last_day: returns ALL entries from the most recent day (multi-terminal aware)
576
576
  - last_n: returns last N entries (default)
577
577
  - domain: filter by project context (nexo, other)
578
+ - include_automated: if False (default), excludes automated sessions (auto-close,
579
+ cron diaries, etc.). Only returns human-interactive sessions.
580
+ Email sessions (user sends email, NEXO responds) ARE included — they're real interactions.
578
581
  """
579
582
  conn = get_db()
580
583
  domain_clause = " AND domain = ?" if domain else ""
581
584
  domain_params = (domain,) if domain else ()
585
+ # By default, filter out automated sessions so startup shows human sessions only.
586
+ # Keeps: interactive sessions + auto-closed sessions that had real user interaction.
587
+ # An auto-close is human if it has heartbeats > 0 (heartbeat only fires on user messages).
588
+ # Excludes: cron jobs, auto-closed crons (0 heartbeats or "Minimal diary").
589
+ if include_automated:
590
+ source_clause = ""
591
+ else:
592
+ source_clause = (
593
+ " AND ("
594
+ " (source = 'claude' AND summary NOT LIKE '[AUTO-%')"
595
+ " OR (source = 'auto-close'"
596
+ " AND mental_state NOT LIKE '%0 heartbeats%'"
597
+ " AND mental_state NOT LIKE '%Minimal diary%')"
598
+ ")"
599
+ )
582
600
 
583
601
  if session_id:
584
602
  rows = conn.execute(
@@ -586,25 +604,25 @@ def read_session_diary(session_id: str = '', last_n: int = 3, last_day: bool = F
586
604
  (session_id,) + domain_params
587
605
  ).fetchall()
588
606
  elif last_day:
589
- # Get all entries from the most recent calendar day
607
+ # Get all entries from the most recent calendar day (human sessions only)
590
608
  if domain:
591
609
  latest = conn.execute(
592
- "SELECT date(created_at) as day FROM session_diary WHERE domain = ? ORDER BY created_at DESC LIMIT 1",
610
+ f"SELECT date(created_at) as day FROM session_diary WHERE domain = ?{source_clause} ORDER BY created_at DESC LIMIT 1",
593
611
  (domain,)
594
612
  ).fetchone()
595
613
  else:
596
614
  latest = conn.execute(
597
- "SELECT date(created_at) as day FROM session_diary ORDER BY created_at DESC LIMIT 1"
615
+ f"SELECT date(created_at) as day FROM session_diary WHERE 1=1{source_clause} ORDER BY created_at DESC LIMIT 1"
598
616
  ).fetchone()
599
617
  if not latest:
600
618
  return []
601
619
  rows = conn.execute(
602
- f"SELECT * FROM session_diary WHERE date(created_at) = ?{domain_clause} ORDER BY created_at DESC",
620
+ f"SELECT * FROM session_diary WHERE date(created_at) = ?{domain_clause}{source_clause} ORDER BY created_at DESC",
603
621
  (latest['day'],) + domain_params
604
622
  ).fetchall()
605
623
  else:
606
624
  rows = conn.execute(
607
- f"SELECT * FROM session_diary WHERE 1=1{domain_clause} ORDER BY created_at DESC LIMIT ?",
625
+ f"SELECT * FROM session_diary WHERE 1=1{domain_clause}{source_clause} ORDER BY created_at DESC LIMIT ?",
608
626
  domain_params + (last_n,)
609
627
  ).fetchall()
610
628
  return [dict(r) for r in rows]
@@ -732,6 +750,22 @@ def recall(query: str, days: int = 30) -> list[dict]:
732
750
  """, [cutoff_str] + params).fetchall()
733
751
  results.extend([dict(r) for r in rows])
734
752
 
753
+ # Skills
754
+ try:
755
+ frag, params = _multi_word_like(query, ["name", "description", "tags", "trigger_patterns"])
756
+ rows = conn.execute(f"""
757
+ SELECT id, created_at, 'skill' AS source,
758
+ name AS title,
759
+ (COALESCE(description,'') || ' | ' || COALESCE(tags,'') || ' | ' || COALESCE(trigger_patterns,'')) AS snippet,
760
+ level AS category, 0 AS rank
761
+ FROM skills
762
+ WHERE created_at >= ? AND ({frag})
763
+ ORDER BY trust_score DESC LIMIT 10
764
+ """, [cutoff_str] + params).fetchall()
765
+ results.extend([dict(r) for r in rows])
766
+ except Exception:
767
+ pass # Table may not exist yet during migration
768
+
735
769
  results.sort(key=lambda r: r.get('created_at', ''), reverse=True)
736
770
  return results[:20]
737
771
 
package/src/db/_schema.py CHANGED
@@ -295,7 +295,69 @@ def _m15_core_rules_tables(conn):
295
295
  conn.execute("CREATE INDEX IF NOT EXISTS idx_core_rules_active ON core_rules(is_active)")
296
296
 
297
297
 
298
+ def _m16_skills_tables(conn):
299
+ """Skill Auto-Creation system — reusable procedures extracted from complex tasks.
300
+
301
+ Skills are procedural knowledge (step-by-step how-tos) vs learnings which are
302
+ declarative (don't do X). Pipeline: trace → draft → published, fully autonomous.
303
+ Trust score with decay controls quality without human approval gates.
304
+ """
305
+ conn.execute("""
306
+ CREATE TABLE IF NOT EXISTS skills (
307
+ id TEXT PRIMARY KEY,
308
+ name TEXT NOT NULL,
309
+ description TEXT DEFAULT '',
310
+ level TEXT NOT NULL DEFAULT 'trace',
311
+ trust_score INTEGER NOT NULL DEFAULT 50,
312
+ file_path TEXT DEFAULT '',
313
+ tags TEXT DEFAULT '[]',
314
+ trigger_patterns TEXT DEFAULT '[]',
315
+ source_sessions TEXT DEFAULT '[]',
316
+ linked_learnings TEXT DEFAULT '[]',
317
+ use_count INTEGER DEFAULT 0,
318
+ success_count INTEGER DEFAULT 0,
319
+ fail_count INTEGER DEFAULT 0,
320
+ created_at TEXT DEFAULT (datetime('now')),
321
+ last_used_at TEXT DEFAULT NULL,
322
+ updated_at TEXT DEFAULT (datetime('now'))
323
+ )
324
+ """)
325
+ conn.execute("""
326
+ CREATE TABLE IF NOT EXISTS skill_usage (
327
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
328
+ skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
329
+ session_id TEXT DEFAULT '',
330
+ success INTEGER NOT NULL DEFAULT 1,
331
+ context TEXT DEFAULT '',
332
+ notes TEXT DEFAULT '',
333
+ created_at TEXT DEFAULT (datetime('now'))
334
+ )
335
+ """)
336
+ _migrate_add_index(conn, "idx_skills_level", "skills", "level")
337
+ _migrate_add_index(conn, "idx_skills_trust", "skills", "trust_score")
338
+ _migrate_add_index(conn, "idx_skills_last_used", "skills", "last_used_at")
339
+ _migrate_add_index(conn, "idx_skill_usage_skill_id", "skill_usage", "skill_id")
340
+ _migrate_add_index(conn, "idx_skill_usage_created", "skill_usage", "created_at")
341
+
342
+
298
343
  # Migration registry — APPEND ONLY, never reorder or delete
344
+ def _m17_cron_runs(conn):
345
+ conn.execute("""
346
+ CREATE TABLE IF NOT EXISTS cron_runs (
347
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
348
+ cron_id TEXT NOT NULL,
349
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
350
+ ended_at TEXT,
351
+ exit_code INTEGER,
352
+ summary TEXT DEFAULT '',
353
+ error TEXT DEFAULT '',
354
+ duration_secs REAL
355
+ )
356
+ """)
357
+ _migrate_add_index(conn, "idx_cron_runs_cron_id", "cron_runs", "cron_id")
358
+ _migrate_add_index(conn, "idx_cron_runs_started", "cron_runs", "started_at")
359
+
360
+
299
361
  MIGRATIONS = [
300
362
  (1, "learnings_columns", _m1_learnings_columns),
301
363
  (2, "followups_reasoning", _m2_followups_reasoning),
@@ -312,6 +374,8 @@ MIGRATIONS = [
312
374
  (13, "claude_session_id", _m13_claude_session_id),
313
375
  (14, "learnings_priority_weight", _m14_learnings_priority_weight),
314
376
  (15, "core_rules_tables", _m15_core_rules_tables),
377
+ (16, "skills_tables", _m16_skills_tables),
378
+ (17, "cron_runs", _m17_cron_runs),
315
379
  ]
316
380
 
317
381