nexo-brain 2.3.0 → 2.3.2

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 (299) hide show
  1. package/README.md +1 -1
  2. package/bin/nexo-brain.js +92 -9
  3. package/bin/postinstall.js +22 -15
  4. package/package.json +7 -4
  5. package/src/auto_update.py +194 -5
  6. package/src/crons/sync.py +6 -2
  7. package/src/db/_core.py +1 -0
  8. package/src/db/_entities.py +1 -0
  9. package/src/db/_episodic.py +1 -0
  10. package/src/db/_learnings.py +1 -0
  11. package/src/db/_reminders.py +1 -0
  12. package/src/db/_schema.py +11 -1
  13. package/src/db/_sessions.py +1 -0
  14. package/src/db/_skills.py +1 -0
  15. package/src/hooks/capture-tool-logs.sh +23 -6
  16. package/src/hooks/session-start.sh +4 -3
  17. package/src/plugin_loader.py +1 -0
  18. package/src/plugins/update.py +377 -26
  19. package/src/scripts/deep-sleep/apply_findings.py +1 -0
  20. package/src/scripts/deep-sleep/collect.py +1 -0
  21. package/src/scripts/deep-sleep/extract.py +1 -0
  22. package/src/scripts/deep-sleep/synthesize.py +1 -0
  23. package/src/scripts/nexo-catchup.py +29 -4
  24. package/src/scripts/nexo-daily-self-audit.py +21 -1
  25. package/src/scripts/nexo-evolution-run.py +21 -1
  26. package/src/scripts/nexo-learning-housekeep.py +1 -0
  27. package/src/scripts/nexo-postmortem-consolidator.py +34 -9
  28. package/src/scripts/nexo-sleep.py +32 -10
  29. package/src/scripts/nexo-synthesis.py +29 -9
  30. package/src/scripts/nexo-update.sh +109 -7
  31. package/src/scripts/nexo-watchdog.sh +122 -58
  32. package/src/server.py +66 -1
  33. package/src/tools_coordination.py +1 -0
  34. package/src/tools_sessions.py +1 -0
  35. package/scripts/migrate-to-unified 2.sh +0 -813
  36. package/scripts/migrate-to-unified.sh +0 -813
  37. package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
  38. package/scripts/migrate-v1.5-to-v1.6.py +0 -778
  39. package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
  40. package/scripts/migrate-v1.7-to-v1.8.py +0 -214
  41. package/scripts/nexo-preflight.sh +0 -236
  42. package/scripts/pre-commit-check 2.sh +0 -55
  43. package/scripts/pre-commit-check.sh +0 -55
  44. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  45. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  46. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  47. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  48. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  49. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  50. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  51. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  52. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  53. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  54. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  55. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  56. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  57. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  58. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  59. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  60. package/src/auto_close_sessions 2.py +0 -159
  61. package/src/auto_update 2.py +0 -634
  62. package/src/claim_graph 2.py +0 -323
  63. package/src/cognitive/__init__ 2.py +0 -62
  64. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  65. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  66. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  67. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  68. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  69. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  70. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  71. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  72. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  73. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  74. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  75. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  76. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  77. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  78. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  79. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  80. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  81. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  82. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  83. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  84. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  85. package/src/cognitive/_core 2.py +0 -567
  86. package/src/cognitive/_decay 2.py +0 -382
  87. package/src/cognitive/_ingest 2.py +0 -892
  88. package/src/cognitive/_memory 2.py +0 -912
  89. package/src/cognitive/_search 2.py +0 -949
  90. package/src/cognitive/_trust 2.py +0 -464
  91. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  92. package/src/crons/manifest 2.json +0 -106
  93. package/src/crons/sync 2.py +0 -217
  94. package/src/dashboard/__init__ 2.py +0 -0
  95. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  96. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  97. package/src/dashboard/app 2.py +0 -789
  98. package/src/db/__init__ 2.py +0 -89
  99. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  100. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  101. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  102. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  103. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  104. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  105. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  106. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  107. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  108. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  109. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  110. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  111. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  112. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  113. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  114. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  115. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  116. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  117. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  118. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  119. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  120. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  121. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  122. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  123. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  124. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  125. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  126. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  127. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  128. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  129. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  130. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  131. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  132. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  133. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  134. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  135. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  136. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  137. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  138. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  139. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  140. package/src/db/_core 2.py +0 -417
  141. package/src/db/_credentials 2.py +0 -124
  142. package/src/db/_entities 2.py +0 -178
  143. package/src/db/_episodic 2.py +0 -738
  144. package/src/db/_evolution 2.py +0 -54
  145. package/src/db/_fts 2.py +0 -406
  146. package/src/db/_learnings 2.py +0 -168
  147. package/src/db/_reminders 2.py +0 -338
  148. package/src/db/_schema 2.py +0 -364
  149. package/src/db/_sessions 2.py +0 -300
  150. package/src/db/_tasks 2.py +0 -91
  151. package/src/evolution_cycle 2.py +0 -266
  152. package/src/hnsw_index 2.py +0 -254
  153. package/src/hooks/auto_capture 2.py +0 -208
  154. package/src/hooks/caffeinate-guard 2.sh +0 -8
  155. package/src/hooks/capture-session 2.sh +0 -21
  156. package/src/hooks/capture-tool-logs 2.sh +0 -127
  157. package/src/hooks/daily-briefing-check 2.sh +0 -33
  158. package/src/hooks/inbox-hook 2.sh +0 -76
  159. package/src/hooks/post-compact 2.sh +0 -148
  160. package/src/hooks/pre-compact 2.sh +0 -151
  161. package/src/hooks/session-start 2.sh +0 -268
  162. package/src/hooks/session-stop 2.sh +0 -140
  163. package/src/kg_populate 2.py +0 -290
  164. package/src/knowledge_graph 2.py +0 -257
  165. package/src/maintenance 2.py +0 -59
  166. package/src/migrate_embeddings 2.py +0 -122
  167. package/src/plugin_loader 2.py +0 -202
  168. package/src/plugins/__init__ 2.py +0 -0
  169. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  170. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  171. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  172. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  173. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  174. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  175. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  176. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  177. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  178. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  179. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  180. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  181. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  182. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  183. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  184. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  185. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  186. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  187. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  188. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  189. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  190. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  191. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  192. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  193. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  194. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  195. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  196. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  197. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  198. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  199. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  200. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  201. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  202. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  203. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  204. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  205. package/src/plugins/adaptive_mode 2.py +0 -805
  206. package/src/plugins/agents 2.py +0 -52
  207. package/src/plugins/artifact_registry 2.py +0 -450
  208. package/src/plugins/backup 2.py +0 -104
  209. package/src/plugins/cognitive_memory 2.py +0 -564
  210. package/src/plugins/core_rules 2.py +0 -252
  211. package/src/plugins/cortex 2.py +0 -299
  212. package/src/plugins/entities 2.py +0 -67
  213. package/src/plugins/episodic_memory 2.py +0 -533
  214. package/src/plugins/evolution 2.py +0 -115
  215. package/src/plugins/guard 2.py +0 -746
  216. package/src/plugins/knowledge_graph_tools 2.py +0 -105
  217. package/src/plugins/preferences 2.py +0 -47
  218. package/src/plugins/update 2.py +0 -256
  219. package/src/requirements 2.txt +0 -12
  220. package/src/rules/__init__ 2.py +0 -0
  221. package/src/rules/core-rules 2.json +0 -331
  222. package/src/rules/migrate 2.py +0 -207
  223. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  224. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  225. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  226. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  227. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  244. package/src/scripts/check-context 2.py +0 -264
  245. package/src/scripts/nexo-auto-update 2.py +0 -6
  246. package/src/scripts/nexo-backup 2.sh +0 -25
  247. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  248. package/src/scripts/nexo-catchup 2.py +0 -242
  249. package/src/scripts/nexo-cognitive-decay 2.py +0 -182
  250. package/src/scripts/nexo-daily-self-audit 2.py +0 -552
  251. package/src/scripts/nexo-deep-sleep 2.sh +0 -97
  252. package/src/scripts/nexo-evolution-run 2.py +0 -597
  253. package/src/scripts/nexo-followup-hygiene 2.py +0 -112
  254. package/src/scripts/nexo-github-monitor 2.py +0 -256
  255. package/src/scripts/nexo-immune 2.py +0 -927
  256. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  257. package/src/scripts/nexo-install 2.py +0 -6
  258. package/src/scripts/nexo-learning-housekeep 2.py +0 -245
  259. package/src/scripts/nexo-learning-validator 2.py +0 -207
  260. package/src/scripts/nexo-migrate 2.py +0 -232
  261. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
  262. package/src/scripts/nexo-pre-commit 2.py +0 -120
  263. package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
  264. package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
  265. package/src/scripts/nexo-reflection 2.py +0 -253
  266. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  267. package/src/scripts/nexo-send-email 2.py +0 -25
  268. package/src/scripts/nexo-send-email.py +0 -25
  269. package/src/scripts/nexo-send-reply 2.py +0 -178
  270. package/src/scripts/nexo-send-reply.py +0 -178
  271. package/src/scripts/nexo-sleep 2.py +0 -592
  272. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  273. package/src/scripts/nexo-synthesis 2.py +0 -253
  274. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  275. package/src/scripts/nexo-update 2.sh +0 -161
  276. package/src/scripts/nexo-watchdog 2.sh +0 -878
  277. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  278. package/src/server 2.py +0 -733
  279. package/src/storage_router 2.py +0 -32
  280. package/src/tools_coordination 2.py +0 -102
  281. package/src/tools_credentials 2.py +0 -68
  282. package/src/tools_learnings 2.py +0 -220
  283. package/src/tools_menu 2.py +0 -227
  284. package/src/tools_reminders 2.py +0 -86
  285. package/src/tools_reminders_crud 2.py +0 -159
  286. package/src/tools_sessions 2.py +0 -476
  287. package/src/tools_task_history 2.py +0 -57
  288. package/templates/CLAUDE.md 2.template +0 -63
  289. package/templates/openclaw 2.json +0 -13
  290. package/tests/__init__ 2.py +0 -0
  291. package/tests/__init__.py +0 -0
  292. package/tests/conftest 2.py +0 -71
  293. package/tests/conftest.py +0 -71
  294. package/tests/test_cognitive 2.py +0 -205
  295. package/tests/test_cognitive.py +0 -205
  296. package/tests/test_knowledge_graph 2.py +0 -140
  297. package/tests/test_knowledge_graph.py +0 -140
  298. package/tests/test_migrations 2.py +0 -137
  299. package/tests/test_migrations.py +0 -137
@@ -1,91 +0,0 @@
1
- """NEXO DB — Tasks module."""
2
- from db._core import get_db, now_epoch
3
-
4
- # ── Task History & Frequencies ─────────────────────────────────────
5
-
6
- def log_task(task_num: str, task_name: str, notes: str = '', reasoning: str = '') -> dict:
7
- """Log a task execution with optional reasoning."""
8
- conn = get_db()
9
- now = now_epoch()
10
- cursor = conn.execute(
11
- "INSERT INTO task_history (task_num, task_name, executed_at, notes, reasoning) "
12
- "VALUES (?, ?, ?, ?, ?)",
13
- (task_num, task_name, now, notes, reasoning)
14
- )
15
- conn.commit()
16
- row = conn.execute(
17
- "SELECT * FROM task_history WHERE id = ?", (cursor.lastrowid,)
18
- ).fetchone()
19
- return dict(row)
20
-
21
-
22
- def list_task_history(task_num: str = None, days: int = 30) -> list[dict]:
23
- """List task execution history, optionally filtered by task_num."""
24
- conn = get_db()
25
- cutoff = now_epoch() - (days * 86400)
26
- if task_num:
27
- rows = conn.execute(
28
- "SELECT * FROM task_history WHERE task_num = ? AND executed_at >= ? "
29
- "ORDER BY executed_at DESC",
30
- (task_num, cutoff)
31
- ).fetchall()
32
- else:
33
- rows = conn.execute(
34
- "SELECT * FROM task_history WHERE executed_at >= ? "
35
- "ORDER BY executed_at DESC",
36
- (cutoff,)
37
- ).fetchall()
38
- return [dict(r) for r in rows]
39
-
40
-
41
- def set_task_frequency(task_num: str, task_name: str,
42
- frequency_days: int, description: str = '') -> dict:
43
- """Set or update the expected frequency for a task."""
44
- conn = get_db()
45
- conn.execute(
46
- "INSERT OR REPLACE INTO task_frequencies (task_num, task_name, frequency_days, description) "
47
- "VALUES (?, ?, ?, ?)",
48
- (task_num, task_name, frequency_days, description)
49
- )
50
- conn.commit()
51
- row = conn.execute(
52
- "SELECT * FROM task_frequencies WHERE task_num = ?", (task_num,)
53
- ).fetchone()
54
- return dict(row)
55
-
56
-
57
- def get_overdue_tasks() -> list[dict]:
58
- """Get tasks where last execution exceeds the configured frequency."""
59
- conn = get_db()
60
- freqs = conn.execute("SELECT * FROM task_frequencies").fetchall()
61
- now = now_epoch()
62
- overdue = []
63
- for f in freqs:
64
- last = conn.execute(
65
- "SELECT MAX(executed_at) as last_exec FROM task_history WHERE task_num = ?",
66
- (f["task_num"],)
67
- ).fetchone()
68
- last_exec = last["last_exec"] if last and last["last_exec"] else None
69
- threshold = f["frequency_days"] * 86400
70
- if last_exec is None or (now - last_exec) > threshold:
71
- days_ago = round((now - last_exec) / 86400, 1) if last_exec else None
72
- overdue.append({
73
- "task_num": f["task_num"],
74
- "task_name": f["task_name"],
75
- "frequency_days": f["frequency_days"],
76
- "last_executed": last_exec,
77
- "days_since_last": days_ago,
78
- "description": f["description"]
79
- })
80
- return overdue
81
-
82
-
83
- def get_task_frequencies() -> list[dict]:
84
- """Get all configured task frequencies."""
85
- conn = get_db()
86
- rows = conn.execute(
87
- "SELECT * FROM task_frequencies ORDER BY task_num ASC"
88
- ).fetchall()
89
- return [dict(r) for r in rows]
90
-
91
-
@@ -1,266 +0,0 @@
1
- """NEXO Evolution Cycle — Self-improvement via Opus API.
2
-
3
- Runs weekly after DMN. Analyzes patterns, proposes improvements.
4
- v1: observe-only (all proposals logged as 'proposed' for the user to review).
5
- v1.1 (future): sandbox execution of auto-approved changes.
6
- """
7
-
8
- import json
9
- import os
10
- import shutil
11
- import subprocess
12
- import sqlite3
13
- import time
14
- from datetime import datetime, date, timedelta
15
- from pathlib import Path
16
-
17
- NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
18
- NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(NEXO_HOME)))
19
- NEXO_DB = NEXO_HOME / "data" / "nexo.db"
20
- SANDBOX_DIR = NEXO_HOME / "sandbox" / "workspace"
21
- SNAPSHOTS_DIR = NEXO_HOME / "snapshots"
22
- RESTORE_LOG = NEXO_HOME / "logs" / "snapshot-restores.log"
23
-
24
- # Evolution config: brain/ (canonical) > cortex/ (legacy) > NEXO_CODE (dev)
25
- def _resolve_evolution_file(name: str) -> Path:
26
- for candidate in [NEXO_HOME / "brain" / name, NEXO_HOME / "cortex" / name, NEXO_CODE / name]:
27
- if candidate.exists():
28
- return candidate
29
- return NEXO_HOME / "brain" / name # default canonical path
30
-
31
- OBJECTIVE_FILE = _resolve_evolution_file("evolution-objective.json")
32
- PROMPT_FILE = _resolve_evolution_file("evolution-prompt.md")
33
-
34
- MAX_SNAPSHOTS = 8
35
-
36
-
37
- def load_objective() -> dict:
38
- if OBJECTIVE_FILE.exists():
39
- return json.loads(OBJECTIVE_FILE.read_text())
40
- return {}
41
-
42
-
43
- def save_objective(obj: dict):
44
- OBJECTIVE_FILE.write_text(json.dumps(obj, indent=2, ensure_ascii=False))
45
-
46
-
47
- def get_week_data(db_path: str) -> dict:
48
- """Gather last 7 days of learnings, decisions, changes, diaries."""
49
- conn = sqlite3.connect(db_path, timeout=10)
50
- conn.row_factory = sqlite3.Row
51
- cutoff_epoch = time.time() - 7 * 86400
52
- cutoff_date = (date.today() - timedelta(days=7)).isoformat()
53
-
54
- data = {}
55
-
56
- rows = conn.execute(
57
- "SELECT category, title, content FROM learnings WHERE created_at > ? ORDER BY created_at DESC LIMIT 50",
58
- (cutoff_epoch,)
59
- ).fetchall()
60
- data["learnings"] = [dict(r) for r in rows]
61
-
62
- rows = conn.execute(
63
- "SELECT domain, decision, alternatives, based_on, confidence, outcome FROM decisions "
64
- "WHERE created_at > ? ORDER BY created_at DESC LIMIT 20",
65
- (cutoff_date,)
66
- ).fetchall()
67
- data["decisions"] = [dict(r) for r in rows]
68
-
69
- rows = conn.execute(
70
- "SELECT files, what_changed, why, affects, risks FROM change_log "
71
- "WHERE created_at > ? ORDER BY created_at DESC LIMIT 30",
72
- (cutoff_date,)
73
- ).fetchall()
74
- data["changes"] = [dict(r) for r in rows]
75
-
76
- rows = conn.execute(
77
- "SELECT summary, decisions as diary_decisions, pending, mental_state, domain, user_signals "
78
- "FROM session_diary WHERE created_at > ? ORDER BY created_at DESC LIMIT 20",
79
- (cutoff_date,)
80
- ).fetchall()
81
- data["diaries"] = [dict(r) for r in rows]
82
-
83
- rows = conn.execute(
84
- "SELECT * FROM evolution_log ORDER BY id DESC LIMIT 20"
85
- ).fetchall()
86
- data["evolution_history"] = [dict(r) for r in rows]
87
-
88
- rows = conn.execute(
89
- "SELECT dimension, score, delta, measured_at FROM evolution_metrics "
90
- "WHERE id IN (SELECT MAX(id) FROM evolution_metrics GROUP BY dimension)"
91
- ).fetchall()
92
- data["current_metrics"] = {r["dimension"]: dict(r) for r in rows}
93
-
94
- conn.close()
95
- return data
96
-
97
-
98
- def create_snapshot(files_to_backup: list) -> str:
99
- """Create a snapshot of specific files before modification."""
100
- ts = datetime.now().strftime("%Y-%m-%dT%H:%M")
101
- snap_dir = SNAPSHOTS_DIR / ts
102
- files_dir = snap_dir / "files"
103
-
104
- manifest = {
105
- "created_at": datetime.now().isoformat(),
106
- "files": [],
107
- "reason": "evolution_cycle"
108
- }
109
-
110
- for filepath in files_to_backup:
111
- fp = Path(filepath).expanduser()
112
- if fp.exists():
113
- rel = str(fp).replace(str(Path.home()) + "/", "")
114
- dest = files_dir / rel
115
- dest.parent.mkdir(parents=True, exist_ok=True)
116
- if os.path.abspath(str(fp)) == os.path.abspath(str(dest)):
117
- continue # Skip: source and destination are the same file
118
- shutil.copy2(fp, dest)
119
- manifest["files"].append(rel)
120
-
121
- snap_dir.mkdir(parents=True, exist_ok=True)
122
- (snap_dir / "manifest.json").write_text(json.dumps(manifest, indent=2))
123
-
124
- latest = SNAPSHOTS_DIR / "latest"
125
- if latest.is_symlink():
126
- latest.unlink()
127
- latest.symlink_to(snap_dir)
128
-
129
- _cleanup_snapshots()
130
- return str(snap_dir)
131
-
132
-
133
- def _cleanup_snapshots():
134
- """Remove old snapshots, keeping MAX_SNAPSHOTS most recent + golden."""
135
- if not SNAPSHOTS_DIR.exists():
136
- return
137
- snaps = sorted(
138
- [d for d in SNAPSHOTS_DIR.iterdir()
139
- if d.is_dir() and d.name not in ("latest", "golden")],
140
- key=lambda d: d.stat().st_mtime,
141
- reverse=True
142
- )
143
- for old in snaps[MAX_SNAPSHOTS:]:
144
- shutil.rmtree(old)
145
-
146
-
147
- def dry_run_restore_test() -> bool:
148
- """Test that snapshot+restore works before making real changes."""
149
- test_file = SANDBOX_DIR / "restore-test.txt"
150
- test_file.parent.mkdir(parents=True, exist_ok=True)
151
- test_file.write_text("original_content")
152
-
153
- snap_dir = create_snapshot([str(test_file)])
154
-
155
- test_file.write_text("modified_content")
156
-
157
- # Find restore script: NEXO_CODE/scripts/ first, then NEXO_HOME/scripts/
158
- _nexo_code = Path(os.environ.get("NEXO_CODE", ""))
159
- restore_script = None
160
- for candidate in [_nexo_code / "scripts" / "nexo-snapshot-restore.sh",
161
- NEXO_HOME / "scripts" / "nexo-snapshot-restore.sh"]:
162
- if candidate.exists():
163
- restore_script = candidate
164
- break
165
- if not restore_script:
166
- test_file.unlink(missing_ok=True)
167
- return False # No restore script available
168
-
169
- try:
170
- subprocess.run(
171
- [str(restore_script), snap_dir],
172
- capture_output=True, timeout=10, check=True
173
- )
174
- content = test_file.read_text()
175
- test_file.unlink(missing_ok=True)
176
- # Clean up test snapshot
177
- snap_path = Path(snap_dir)
178
- if snap_path.exists():
179
- shutil.rmtree(snap_path)
180
- return content == "original_content"
181
- except Exception:
182
- test_file.unlink(missing_ok=True)
183
- return False
184
-
185
-
186
- def build_evolution_prompt(week_data: dict, objective: dict) -> str:
187
- """Build a SHORT prompt — CLI investigates on its own using tools."""
188
-
189
- # Summary stats only — CLI will dig deeper with tools
190
- stats = {
191
- "learnings_this_week": len(week_data.get("learnings", [])),
192
- "decisions_this_week": len(week_data.get("decisions", [])),
193
- "changes_this_week": len(week_data.get("changes", [])),
194
- "diaries_this_week": len(week_data.get("diaries", [])),
195
- "evolution_history": len(week_data.get("evolution_history", [])),
196
- "current_scores": {dim: m["score"] for dim, m in week_data.get("current_metrics", {}).items()},
197
- }
198
-
199
- mode = objective.get("evolution_mode", "auto")
200
- total = objective.get("total_evolutions", 0)
201
- max_auto = max_auto_changes(total)
202
-
203
- prompt = f"""You are NEXO Evolution — the weekly self-improvement cycle.
204
-
205
- YOUR JOB: Analyze the past week and propose concrete improvements to NEXO's codebase.
206
-
207
- WEEK SUMMARY:
208
- - {stats['learnings_this_week']} new learnings
209
- - {stats['decisions_this_week']} decisions made
210
- - {stats['changes_this_week']} code changes deployed
211
- - {stats['diaries_this_week']} session diaries
212
- - {stats['evolution_history']} past evolution proposals
213
- - Current scores: {json.dumps(stats['current_scores'])}
214
-
215
- MODE: {mode} ({"proposals only, owner reviews" if mode == "review" else f"max {max_auto} auto-applied changes"})
216
- CYCLE: #{total + 1}
217
-
218
- INVESTIGATE using these tools:
219
- 1. Bash: sqlite3 {NEXO_DB} "SELECT category, title FROM learnings WHERE created_at > {time.time() - 7*86400} ORDER BY created_at DESC LIMIT 30"
220
- 2. Bash: sqlite3 {NEXO_DB} "SELECT area, COUNT(*) as cnt FROM error_repetitions GROUP BY area ORDER BY cnt DESC LIMIT 10"
221
- 3. Read ~/.nexo/coordination/daily-synthesis.md — today's context
222
- 4. Read ~/.nexo/coordination/postmortem-daily.md — self-critique patterns
223
- 5. Read ~/.nexo/logs/self-audit-summary.json — system health
224
- 6. Glob ~/.nexo/scripts/*.py — existing scripts
225
- 7. Glob ~/.nexo/plugins/*.py — existing plugins
226
-
227
- LOOK FOR:
228
- - Repeated errors that guard isn't preventing
229
- - Scripts or processes that are failing or underperforming
230
- - Missing functionality that session diaries keep asking for
231
- - Redundant code or config that could be simplified
232
- - Patterns in self-critique that suggest systemic issues
233
-
234
- SAFETY:
235
- - Safe zones for auto changes: ~/.nexo/scripts/, ~/.nexo/plugins/, ~/.nexo/brain/
236
- - IMMUTABLE files (never touch): db.py, server.py, plugin_loader.py, cognitive.py, CLAUDE.md
237
- - Every change needs: what file, what to change, why, risk, how to verify
238
-
239
- OUTPUT FORMAT (JSON):
240
- {{
241
- "analysis": "one paragraph summary of what you found",
242
- "patterns": [{{"type": "...", "description": "...", "frequency": "..."}}],
243
- "proposals": [
244
- {{
245
- "classification": "auto" or "propose",
246
- "dimension": "reliability|proactivity|efficiency|safety|learning",
247
- "action": "what to do",
248
- "reasoning": "why",
249
- "scope": "local",
250
- "changes": [{{"file": "path", "operation": "create|replace|append", "search": "text to find", "content": "new text"}}]
251
- }}
252
- ]
253
- }}
254
-
255
- Max 3 proposals. Quality over quantity. If nothing needs improving, say so."""
256
-
257
- return prompt
258
-
259
-
260
- def max_auto_changes(total_evolutions: int) -> int:
261
- """Progressive trust: 1 for first 4 cycles, 2 for next 4, then 3."""
262
- if total_evolutions < 4:
263
- return 1
264
- elif total_evolutions < 8:
265
- return 2
266
- return 3
@@ -1,254 +0,0 @@
1
- """NEXO HNSW Vector Index — Optional acceleration for cognitive search.
2
-
3
- When memory count exceeds THRESHOLD (default 10_000), this module builds and
4
- maintains an HNSW index for approximate nearest neighbor search. Falls back
5
- gracefully to brute-force when hnswlib is not available or index is cold.
6
-
7
- Usage in cognitive.search():
8
- from hnsw_index import hnsw_search
9
- candidates = hnsw_search(query_vec, store="stm", top_k=50)
10
- # candidates is a list of (memory_id, distance) or None if not available
11
- """
12
-
13
- import os
14
- import sqlite3
15
- import threading
16
- import numpy as np
17
- from pathlib import Path
18
- from typing import Optional
19
-
20
- try:
21
- import hnswlib
22
- HNSWLIB_AVAILABLE = True
23
- except ImportError:
24
- HNSWLIB_AVAILABLE = False
25
-
26
- # When to activate HNSW (below this, brute force is fine)
27
- ACTIVATION_THRESHOLD = int(os.environ.get("NEXO_HNSW_THRESHOLD", "10000"))
28
-
29
- # Index params
30
- EMBEDDING_DIM = 768
31
- EF_CONSTRUCTION = 200 # Higher = better recall during build, slower
32
- M = 16 # Connections per node (16 is good for 768-dim)
33
- EF_SEARCH = 50 # Higher = better recall during search
34
-
35
- # Index file paths
36
- _INDEX_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "hnsw_indices")
37
-
38
- # In-memory indices (one per store)
39
- _indices: dict = {} # {"stm": hnswlib.Index, "ltm": hnswlib.Index}
40
- _index_lock = threading.Lock()
41
- _id_maps: dict = {} # {"stm": {internal_id: db_id}, "ltm": {internal_id: db_id}}
42
-
43
-
44
- def is_available() -> bool:
45
- """Check if HNSW is available and should be used."""
46
- return HNSWLIB_AVAILABLE
47
-
48
-
49
- def _index_path(store: str) -> str:
50
- return os.path.join(_INDEX_DIR, f"{store}.bin")
51
-
52
-
53
- def _id_map_path(store: str) -> str:
54
- return os.path.join(_INDEX_DIR, f"{store}_ids.npy")
55
-
56
-
57
- def should_activate(store: str = "both") -> bool:
58
- """Check if memory count exceeds threshold, making HNSW worthwhile."""
59
- if not HNSWLIB_AVAILABLE:
60
- return False
61
- try:
62
- import cognitive
63
- db = cognitive._get_db()
64
- total = 0
65
- if store in ("both", "stm"):
66
- total += db.execute("SELECT COUNT(*) FROM stm_memories WHERE promoted_to_ltm = 0").fetchone()[0]
67
- if store in ("both", "ltm"):
68
- total += db.execute("SELECT COUNT(*) FROM ltm_memories WHERE is_dormant = 0").fetchone()[0]
69
- return total >= ACTIVATION_THRESHOLD
70
- except Exception:
71
- return False
72
-
73
-
74
- def build_index(store: str) -> dict:
75
- """Build HNSW index from all active memories in the given store.
76
-
77
- Args:
78
- store: "stm" or "ltm"
79
-
80
- Returns:
81
- {"count": N, "store": store, "status": "built"} or error dict
82
- """
83
- if not HNSWLIB_AVAILABLE:
84
- return {"error": "hnswlib not installed"}
85
-
86
- try:
87
- import cognitive
88
- db = cognitive._get_db()
89
- except Exception as e:
90
- return {"error": str(e)}
91
-
92
- table = "stm_memories" if store == "stm" else "ltm_memories"
93
- where = "promoted_to_ltm = 0" if store == "stm" else "is_dormant = 0"
94
-
95
- rows = db.execute(f"SELECT id, embedding FROM {table} WHERE {where}").fetchall()
96
- if not rows:
97
- return {"count": 0, "store": store, "status": "empty"}
98
-
99
- count = len(rows)
100
- index = hnswlib.Index(space='cosine', dim=EMBEDDING_DIM)
101
- index.init_index(max_elements=max(count * 2, 1000), ef_construction=EF_CONSTRUCTION, M=M)
102
- index.set_ef(EF_SEARCH)
103
-
104
- id_map = {}
105
- vectors = []
106
- internal_ids = []
107
-
108
- for i, row in enumerate(rows):
109
- vec = np.frombuffer(row["embedding"], dtype=np.float32)
110
- if len(vec) != EMBEDDING_DIM:
111
- continue
112
- vectors.append(vec)
113
- internal_ids.append(i)
114
- id_map[i] = row["id"]
115
-
116
- if not vectors:
117
- return {"count": 0, "store": store, "status": "no_valid_vectors"}
118
-
119
- data = np.array(vectors, dtype=np.float32)
120
- ids = np.array(internal_ids, dtype=np.int64)
121
- index.add_items(data, ids)
122
-
123
- # Save to disk
124
- Path(_INDEX_DIR).mkdir(exist_ok=True)
125
- index.save_index(_index_path(store))
126
- np.save(_id_map_path(store), id_map)
127
-
128
- with _index_lock:
129
- _indices[store] = index
130
- _id_maps[store] = id_map
131
-
132
- return {"count": count, "store": store, "status": "built"}
133
-
134
-
135
- def load_index(store: str) -> bool:
136
- """Load a previously built index from disk."""
137
- if not HNSWLIB_AVAILABLE:
138
- return False
139
-
140
- idx_path = _index_path(store)
141
- map_path = _id_map_path(store) + ".npy" if not _id_map_path(store).endswith(".npy") else _id_map_path(store)
142
-
143
- if not os.path.exists(idx_path):
144
- return False
145
-
146
- try:
147
- index = hnswlib.Index(space='cosine', dim=EMBEDDING_DIM)
148
- index.load_index(idx_path)
149
- index.set_ef(EF_SEARCH)
150
-
151
- id_map = np.load(map_path, allow_pickle=True).item()
152
-
153
- with _index_lock:
154
- _indices[store] = index
155
- _id_maps[store] = id_map
156
- return True
157
- except Exception:
158
- return False
159
-
160
-
161
- def search(query_vec: np.ndarray, store: str = "stm", top_k: int = 50) -> Optional[list[tuple[int, float]]]:
162
- """Search the HNSW index for approximate nearest neighbors.
163
-
164
- Args:
165
- query_vec: Query embedding (768-dim float32)
166
- store: "stm" or "ltm"
167
- top_k: Number of results
168
-
169
- Returns:
170
- List of (db_memory_id, cosine_distance) or None if index not available.
171
- Note: hnswlib with cosine space returns 1 - cosine_similarity as distance.
172
- """
173
- with _index_lock:
174
- index = _indices.get(store)
175
- id_map = _id_maps.get(store)
176
-
177
- if index is None or id_map is None:
178
- # Try loading from disk
179
- if load_index(store):
180
- with _index_lock:
181
- index = _indices.get(store)
182
- id_map = _id_maps.get(store)
183
- if index is None:
184
- return None
185
-
186
- try:
187
- query = query_vec.reshape(1, -1).astype(np.float32)
188
- labels, distances = index.knn_query(query, k=min(top_k, index.get_current_count()))
189
- results = []
190
- for label, dist in zip(labels[0], distances[0]):
191
- db_id = id_map.get(int(label))
192
- if db_id is not None:
193
- # Convert cosine distance to similarity: sim = 1 - dist
194
- results.append((db_id, float(1.0 - dist)))
195
- return results
196
- except Exception:
197
- return None
198
-
199
-
200
- def add_item(store: str, db_id: int, embedding: np.ndarray) -> bool:
201
- """Incrementally add a single item to the index (for new ingestions)."""
202
- with _index_lock:
203
- index = _indices.get(store)
204
- id_map = _id_maps.get(store)
205
-
206
- if index is None or id_map is None:
207
- return False
208
-
209
- try:
210
- internal_id = max(id_map.keys()) + 1 if id_map else 0
211
- # Resize if needed
212
- if index.get_current_count() >= index.get_max_elements() - 1:
213
- index.resize_index(index.get_max_elements() * 2)
214
-
215
- vec = embedding.reshape(1, -1).astype(np.float32)
216
- index.add_items(vec, np.array([internal_id], dtype=np.int64))
217
-
218
- with _index_lock:
219
- id_map[internal_id] = db_id
220
- return True
221
- except Exception:
222
- return False
223
-
224
-
225
- def invalidate(store: str = "both"):
226
- """Remove indices from memory (forces rebuild on next use)."""
227
- with _index_lock:
228
- if store in ("both", "stm"):
229
- _indices.pop("stm", None)
230
- _id_maps.pop("stm", None)
231
- if store in ("both", "ltm"):
232
- _indices.pop("ltm", None)
233
- _id_maps.pop("ltm", None)
234
-
235
-
236
- def stats() -> dict:
237
- """Return HNSW index statistics."""
238
- result = {
239
- "hnswlib_available": HNSWLIB_AVAILABLE,
240
- "activation_threshold": ACTIVATION_THRESHOLD,
241
- "indices": {},
242
- }
243
- with _index_lock:
244
- for store in ("stm", "ltm"):
245
- idx = _indices.get(store)
246
- if idx:
247
- result["indices"][store] = {
248
- "count": idx.get_current_count(),
249
- "max_elements": idx.get_max_elements(),
250
- "ef_search": EF_SEARCH,
251
- }
252
- else:
253
- result["indices"][store] = {"status": "not_loaded"}
254
- return result