nexo-brain 2.7.0 → 3.0.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 +66 -12
- package/hooks/hooks.json +79 -0
- package/package.json +1 -1
- package/src/agent_runner.py +290 -6
- package/src/cli.py +111 -0
- package/src/client_preferences.py +94 -0
- package/src/client_sync.py +202 -2
- package/src/cognitive/__init__.py +1 -1
- package/src/cognitive/_search.py +39 -19
- package/src/dashboard/app.py +140 -0
- package/src/dashboard/templates/base.html +4 -0
- package/src/dashboard/templates/protocol.html +199 -0
- package/src/db/__init__.py +23 -1
- package/src/db/_learnings.py +31 -4
- package/src/db/_personal_scripts.py +12 -0
- package/src/db/_protocol.py +303 -0
- package/src/db/_schema.py +248 -0
- package/src/db/_watchers.py +173 -0
- package/src/db/_workflow.py +952 -0
- package/src/doctor/providers/runtime.py +918 -7
- package/src/evolution_cycle.py +62 -0
- package/src/hook_guardrails.py +308 -0
- package/src/hooks/protocol-guardrail.sh +10 -0
- package/src/nexo_sdk.py +103 -0
- package/src/plugins/cognitive_memory.py +18 -0
- package/src/plugins/cortex.py +55 -35
- package/src/plugins/guard.py +132 -16
- package/src/plugins/protocol.py +911 -0
- package/src/plugins/schedule.py +40 -6
- package/src/plugins/simple_api.py +103 -0
- package/src/plugins/skills.py +67 -0
- package/src/plugins/state_watchers.py +79 -0
- package/src/plugins/workflow.py +588 -0
- package/src/public_contribution.py +86 -12
- package/src/script_registry.py +142 -0
- package/src/scripts/deep-sleep/apply_findings.py +204 -0
- package/src/scripts/deep-sleep/collect.py +49 -4
- package/src/scripts/nexo-agent-run.py +2 -0
- package/src/scripts/nexo-daily-self-audit.py +843 -5
- package/src/scripts/nexo-evolution-run.py +343 -1
- package/src/server.py +92 -6
- package/src/skills_runtime.py +151 -0
- package/src/state_watchers_runtime.py +334 -0
- package/src/tools_learnings.py +345 -7
- package/src/tools_sessions.py +183 -0
- package/templates/CLAUDE.md.template +9 -1
- package/templates/CODEX.AGENTS.md.template +10 -2
|
@@ -25,6 +25,7 @@ NEXO_CODE = Path(os.environ.get("NEXO_CODE", ""))
|
|
|
25
25
|
DEEP_SLEEP_DIR = NEXO_HOME / "operations" / "deep-sleep"
|
|
26
26
|
NEXO_DB = NEXO_HOME / "data" / "nexo.db"
|
|
27
27
|
COGNITIVE_DB = NEXO_HOME / "data" / "cognitive.db"
|
|
28
|
+
_TABLE_COLUMNS_CACHE: dict[tuple[str, str], set[str]] = {}
|
|
28
29
|
|
|
29
30
|
MIN_USER_MESSAGES = 3 # Skip trivial sessions
|
|
30
31
|
|
|
@@ -304,6 +305,33 @@ def safe_query(db_path: Path, query: str, params: tuple = ()) -> list[dict]:
|
|
|
304
305
|
return []
|
|
305
306
|
|
|
306
307
|
|
|
308
|
+
def _table_columns(db_path: Path, table_name: str) -> set[str]:
|
|
309
|
+
cache_key = (str(db_path), table_name)
|
|
310
|
+
cached = _TABLE_COLUMNS_CACHE.get(cache_key)
|
|
311
|
+
if cached is not None:
|
|
312
|
+
return cached
|
|
313
|
+
if not db_path.exists():
|
|
314
|
+
_TABLE_COLUMNS_CACHE[cache_key] = set()
|
|
315
|
+
return set()
|
|
316
|
+
try:
|
|
317
|
+
conn = sqlite3.connect(str(db_path))
|
|
318
|
+
conn.row_factory = sqlite3.Row
|
|
319
|
+
rows = conn.execute(f"PRAGMA table_info({table_name})").fetchall()
|
|
320
|
+
conn.close()
|
|
321
|
+
except Exception:
|
|
322
|
+
_TABLE_COLUMNS_CACHE[cache_key] = set()
|
|
323
|
+
return set()
|
|
324
|
+
columns = {str(row["name"]) for row in rows}
|
|
325
|
+
_TABLE_COLUMNS_CACHE[cache_key] = columns
|
|
326
|
+
return columns
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _optional_column_sql(db_path: Path, table_name: str, column_name: str, default_sql: str = "''") -> str:
|
|
330
|
+
if column_name in _table_columns(db_path, table_name):
|
|
331
|
+
return column_name
|
|
332
|
+
return f"{default_sql} AS {column_name}"
|
|
333
|
+
|
|
334
|
+
|
|
307
335
|
def collect_followups() -> list[dict]:
|
|
308
336
|
"""Active followups from nexo.db."""
|
|
309
337
|
return safe_query(
|
|
@@ -513,9 +541,13 @@ def _project_priority_signals(target_day: datetime, compact_diaries: list[dict])
|
|
|
513
541
|
for project in candidates:
|
|
514
542
|
bump(project, 3.0 * recency_bonus, "diary_sessions", "recent session diary activity")
|
|
515
543
|
|
|
544
|
+
learning_priority_sql = _optional_column_sql(NEXO_DB, "learnings", "priority", "'medium'")
|
|
545
|
+
learning_weight_sql = _optional_column_sql(NEXO_DB, "learnings", "weight", "0")
|
|
546
|
+
learning_applies_sql = _optional_column_sql(NEXO_DB, "learnings", "applies_to", "''")
|
|
516
547
|
learning_rows = safe_query(
|
|
517
548
|
NEXO_DB,
|
|
518
|
-
"SELECT category, title, content, created_at, updated_at,
|
|
549
|
+
f"SELECT category, title, content, created_at, updated_at, {learning_priority_sql}, "
|
|
550
|
+
f"{learning_weight_sql}, {learning_applies_sql} FROM learnings "
|
|
519
551
|
"ORDER BY COALESCE(updated_at, created_at) DESC LIMIT 160",
|
|
520
552
|
)
|
|
521
553
|
for row in learning_rows:
|
|
@@ -535,9 +567,12 @@ def _project_priority_signals(target_day: datetime, compact_diaries: list[dict])
|
|
|
535
567
|
for project in matched:
|
|
536
568
|
bump(project, score, "learnings", "recent leverage-bearing learning")
|
|
537
569
|
|
|
570
|
+
followup_priority_sql = _optional_column_sql(NEXO_DB, "followups", "priority", "'medium'")
|
|
571
|
+
followup_reasoning_sql = _optional_column_sql(NEXO_DB, "followups", "reasoning", "''")
|
|
538
572
|
followup_rows = safe_query(
|
|
539
573
|
NEXO_DB,
|
|
540
|
-
"SELECT id, description, date, status,
|
|
574
|
+
f"SELECT id, description, date, status, {followup_priority_sql}, created_at, updated_at, "
|
|
575
|
+
f"{followup_reasoning_sql} FROM followups "
|
|
541
576
|
"WHERE status NOT IN ('COMPLETED', 'CANCELLED') ORDER BY date ASC, created_at ASC LIMIT 120",
|
|
542
577
|
)
|
|
543
578
|
for row in followup_rows:
|
|
@@ -565,9 +600,13 @@ def _project_priority_signals(target_day: datetime, compact_diaries: list[dict])
|
|
|
565
600
|
for project in matched:
|
|
566
601
|
bump(project, score, "followups", "open followup pressure")
|
|
567
602
|
|
|
603
|
+
decision_status_sql = _optional_column_sql(NEXO_DB, "decisions", "status", "''")
|
|
604
|
+
decision_reasoning_sql = _optional_column_sql(NEXO_DB, "decisions", "reasoning", "''")
|
|
605
|
+
decision_review_due_sql = _optional_column_sql(NEXO_DB, "decisions", "review_due_at", "NULL")
|
|
568
606
|
decision_rows = safe_query(
|
|
569
607
|
NEXO_DB,
|
|
570
|
-
"SELECT domain, outcome,
|
|
608
|
+
f"SELECT domain, outcome, {decision_status_sql}, {decision_reasoning_sql}, decision, based_on, created_at, "
|
|
609
|
+
f"{decision_review_due_sql} FROM decisions "
|
|
571
610
|
"ORDER BY COALESCE(created_at, review_due_at) DESC LIMIT 120",
|
|
572
611
|
)
|
|
573
612
|
for row in decision_rows:
|
|
@@ -579,6 +618,8 @@ def _project_priority_signals(target_day: datetime, compact_diaries: list[dict])
|
|
|
579
618
|
" ".join(
|
|
580
619
|
[
|
|
581
620
|
str(row.get("reasoning", "") or ""),
|
|
621
|
+
str(row.get("decision", "") or ""),
|
|
622
|
+
str(row.get("based_on", "") or ""),
|
|
582
623
|
str(row.get("outcome", "") or ""),
|
|
583
624
|
str(row.get("status", "") or ""),
|
|
584
625
|
]
|
|
@@ -647,9 +688,13 @@ def collect_long_horizon_context(
|
|
|
647
688
|
recurring_states = Counter(row["mental_state"] for row in compact_diaries if row.get("mental_state"))
|
|
648
689
|
recurring_critiques = Counter(row["self_critique"] for row in compact_diaries if row.get("self_critique"))
|
|
649
690
|
|
|
691
|
+
learning_reasoning_sql = _optional_column_sql(NEXO_DB, "learnings", "reasoning", "''")
|
|
692
|
+
learning_prevention_sql = _optional_column_sql(NEXO_DB, "learnings", "prevention", "''")
|
|
693
|
+
learning_applies_sql = _optional_column_sql(NEXO_DB, "learnings", "applies_to", "''")
|
|
650
694
|
learning_rows = safe_query(
|
|
651
695
|
NEXO_DB,
|
|
652
|
-
"SELECT category, title, content, created_at, updated_at,
|
|
696
|
+
f"SELECT category, title, content, created_at, updated_at, {learning_reasoning_sql}, "
|
|
697
|
+
f"{learning_prevention_sql}, {learning_applies_sql} "
|
|
653
698
|
"FROM learnings ORDER BY COALESCE(updated_at, created_at) DESC LIMIT 120"
|
|
654
699
|
)
|
|
655
700
|
long_horizon_learnings = []
|
|
@@ -29,6 +29,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
29
29
|
parser.add_argument("--prompt", default="", help="Prompt text")
|
|
30
30
|
parser.add_argument("--prompt-file", default="", help="Read prompt text from a file")
|
|
31
31
|
parser.add_argument("--cwd", default="", help="Working directory for the backend")
|
|
32
|
+
parser.add_argument("--task-profile", default="", help="Automation task profile: default|fast|balanced|deep")
|
|
32
33
|
parser.add_argument("--model", default="", help="Backend model hint")
|
|
33
34
|
parser.add_argument("--reasoning-effort", default="", help="Backend reasoning effort/profile")
|
|
34
35
|
parser.add_argument("--timeout", type=int, default=21600, help="Timeout in seconds")
|
|
@@ -51,6 +52,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
51
52
|
result = run_automation_prompt(
|
|
52
53
|
prompt,
|
|
53
54
|
cwd=args.cwd or None,
|
|
55
|
+
task_profile=args.task_profile,
|
|
54
56
|
model=args.model,
|
|
55
57
|
reasoning_effort=args.reasoning_effort,
|
|
56
58
|
timeout=args.timeout,
|