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.
- package/README.md +4 -4
- package/package.json +1 -1
- package/scripts/migrate-v1.7-to-v1.8.py +2 -2
- package/scripts/nexo-preflight.sh +236 -0
- package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
- package/src/auto_update.py +25 -0
- package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
- package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
- package/src/crons/manifest.json +6 -13
- package/src/crons/sync.py +151 -6
- package/src/db/__init__.py +13 -0
- package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
- package/src/db/_cron_runs.py +74 -0
- package/src/db/_episodic.py +40 -6
- package/src/db/_schema.py +64 -0
- package/src/db/_skills.py +514 -0
- package/src/hooks/session-stop.sh +13 -101
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
- package/src/plugins/episodic_memory.py +5 -3
- package/src/plugins/schedule.py +212 -0
- package/src/plugins/skills.py +264 -0
- package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
- package/src/scripts/deep-sleep/apply_findings.py +110 -8
- package/src/scripts/deep-sleep/collect.py +33 -11
- package/src/scripts/deep-sleep/extract-prompt.md +38 -0
- package/src/scripts/deep-sleep/extract.py +80 -8
- package/src/scripts/deep-sleep/synthesize-prompt.md +29 -1
- package/src/scripts/deep-sleep/synthesize.py +3 -1
- package/src/scripts/nexo-catchup.py +65 -29
- package/src/scripts/nexo-cron-wrapper.sh +53 -0
- package/src/scripts/nexo-daily-self-audit.py +4 -2
- package/src/scripts/nexo-deep-sleep.sh +66 -77
- package/src/scripts/nexo-evolution-run.py +13 -0
- package/src/scripts/nexo-learning-housekeep.py +156 -1
- package/src/scripts/nexo-learning-validator.py +19 -0
- package/src/scripts/nexo-postmortem-consolidator.py +3 -2
- package/src/scripts/nexo-sleep.py +16 -11
- package/src/scripts/nexo-synthesis.py +46 -3
- package/src/scripts/nexo-watchdog.sh +72 -19
- package/src/server.py +5 -1
- package/src/scripts/nexo-github-monitor.py +0 -256
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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]
|
package/src/db/_episodic.py
CHANGED
|
@@ -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
|
|