nexo-brain 1.7.0 → 2.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 (261) hide show
  1. package/README.md +160 -60
  2. package/bin/nexo-brain.js +680 -381
  3. package/package.json +18 -3
  4. package/scripts/migrate-to-unified.sh +813 -0
  5. package/scripts/migrate-v1.7-to-v1.8.py +214 -0
  6. package/scripts/pre-commit-check.sh +1 -1
  7. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  8. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  9. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  10. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  11. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  12. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  13. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  14. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  15. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  16. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  17. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  18. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  19. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  20. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  21. package/src/auto_close_sessions.py +1 -1
  22. package/src/auto_update.py +634 -0
  23. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  24. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  25. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  26. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  27. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  28. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  29. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  30. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  31. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  32. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  33. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  34. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  35. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  36. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  37. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  38. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  39. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  40. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  41. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  42. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  43. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  44. package/src/cognitive/_core.py +7 -3
  45. package/src/cognitive/_decay.py +1 -1
  46. package/src/cognitive/_search.py +1 -0
  47. package/src/cognitive/_trust.py +3 -3
  48. package/src/crons/manifest.json +106 -0
  49. package/src/crons/sync.py +217 -0
  50. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  51. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  52. package/src/dashboard/app.py +24 -4
  53. package/src/dashboard/templates/dashboard.html +3 -2
  54. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  55. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  56. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  57. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  58. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  59. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  60. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  61. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  62. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  63. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  64. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  65. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  66. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  67. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  68. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  69. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  70. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  71. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  72. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  73. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  74. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  75. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  76. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  77. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  78. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  79. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  80. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  81. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  82. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  83. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  84. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  85. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  86. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  87. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  88. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  89. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  90. package/src/db/_core.py +5 -1
  91. package/src/db/_episodic.py +2 -4
  92. package/src/db/_reminders.py +45 -6
  93. package/src/db/_schema.py +31 -0
  94. package/src/evolution_cycle.py +33 -11
  95. package/src/hooks/auto_capture.py +1 -1
  96. package/src/hooks/capture-tool-logs.sh +76 -0
  97. package/src/hooks/inbox-hook.sh +2 -1
  98. package/src/hooks/post-compact.sh +2 -1
  99. package/src/hooks/pre-compact.sh +104 -2
  100. package/src/hooks/session-start.sh +6 -2
  101. package/src/hooks/session-stop.sh +4 -2
  102. package/src/kg_populate.py +4 -1
  103. package/src/migrate_embeddings.py +4 -1
  104. package/src/plugin_loader.py +100 -34
  105. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  106. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  107. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  108. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  109. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  110. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  111. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  112. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  113. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  114. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  115. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  116. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  117. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  118. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  119. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  120. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  121. package/src/plugins/agents.py +2 -2
  122. package/src/plugins/backup.py +5 -4
  123. package/src/plugins/core_rules.py +37 -16
  124. package/src/plugins/episodic_memory.py +14 -5
  125. package/src/plugins/evolution.py +6 -2
  126. package/src/plugins/guard.py +20 -11
  127. package/src/plugins/update.py +256 -0
  128. package/src/requirements.txt +12 -0
  129. package/src/scripts/check-context.py +8 -3
  130. package/src/scripts/deep-sleep/__pycache__/extract.cpython-314.pyc +0 -0
  131. package/src/scripts/deep-sleep/apply_findings.py +514 -169
  132. package/src/scripts/deep-sleep/collect.py +480 -0
  133. package/src/scripts/deep-sleep/extract-prompt.md +233 -0
  134. package/src/scripts/deep-sleep/extract.py +249 -0
  135. package/src/scripts/deep-sleep/synthesize-prompt.md +168 -0
  136. package/src/scripts/deep-sleep/synthesize.py +191 -0
  137. package/src/scripts/nexo-auto-update.py +4 -211
  138. package/src/scripts/nexo-backup.sh +5 -13
  139. package/src/scripts/nexo-brain-activation.sh +26 -26
  140. package/src/scripts/nexo-catchup.py +36 -25
  141. package/src/scripts/nexo-cognitive-decay.py +7 -3
  142. package/src/scripts/nexo-daily-self-audit.py +44 -15
  143. package/src/scripts/nexo-deep-sleep.sh +31 -16
  144. package/src/scripts/nexo-evolution-run.py +21 -11
  145. package/src/scripts/nexo-followup-hygiene.py +6 -4
  146. package/src/scripts/nexo-github-monitor.py +12 -8
  147. package/src/scripts/nexo-immune.py +6 -4
  148. package/src/scripts/nexo-inbox-hook.sh +2 -1
  149. package/src/scripts/nexo-install.py +4 -225
  150. package/src/scripts/nexo-learning-housekeep.py +7 -3
  151. package/src/scripts/nexo-learning-validator.py +1 -22
  152. package/src/scripts/nexo-migrate.py +9 -3
  153. package/src/scripts/nexo-postmortem-consolidator.py +17 -10
  154. package/src/scripts/nexo-pre-commit.py +3 -1
  155. package/src/scripts/nexo-prevent-sleep.sh +29 -0
  156. package/src/scripts/nexo-proactive-dashboard.py +5 -4
  157. package/src/scripts/nexo-runtime-preflight.py +59 -55
  158. package/src/scripts/nexo-send-email.py +1 -1
  159. package/src/scripts/nexo-send-reply.py +3 -1
  160. package/src/scripts/nexo-sleep.py +13 -9
  161. package/src/scripts/nexo-snapshot-restore.sh +2 -1
  162. package/src/scripts/nexo-synthesis.py +11 -7
  163. package/src/scripts/nexo-tcc-approve.sh +79 -0
  164. package/src/scripts/nexo-update.sh +161 -0
  165. package/src/scripts/nexo-watchdog-smoke.py +18 -13
  166. package/src/scripts/nexo-watchdog.sh +22 -13
  167. package/src/server.py +77 -28
  168. package/src/storage_router.py +6 -2
  169. package/src/tools_learnings.py +6 -6
  170. package/src/tools_menu.py +1 -1
  171. package/src/tools_reminders_crud.py +10 -8
  172. package/src/tools_sessions.py +76 -4
  173. package/templates/CLAUDE.md.template +14 -80
  174. package/templates/launchagents/README.md +7 -7
  175. package/templates/launchagents/com.nexo.auto-close-sessions.plist +5 -1
  176. package/templates/launchagents/com.nexo.catchup.plist +4 -0
  177. package/templates/launchagents/com.nexo.cognitive-decay.plist +7 -0
  178. package/templates/launchagents/com.nexo.dashboard.plist +5 -1
  179. package/templates/launchagents/com.nexo.deep-sleep.plist +4 -0
  180. package/templates/launchagents/com.nexo.evolution.plist +4 -0
  181. package/templates/launchagents/com.nexo.followup-hygiene.plist +4 -0
  182. package/templates/launchagents/com.nexo.github-monitor.plist +3 -1
  183. package/templates/launchagents/com.nexo.immune.plist +4 -0
  184. package/templates/launchagents/com.nexo.postmortem.plist +4 -0
  185. package/templates/launchagents/com.nexo.self-audit.plist +4 -0
  186. package/templates/launchagents/com.nexo.synthesis.plist +4 -0
  187. package/templates/launchagents/com.nexo.watchdog.plist +4 -0
  188. package/templates/openclaw.json +1 -1
  189. package/tests/conftest.py +2 -2
  190. package/tests/test_cognitive.py +7 -6
  191. package/tests/test_migrations.py +26 -0
  192. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  193. package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
  194. package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
  195. package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
  196. package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  197. package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
  198. package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
  199. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  200. package/src/__pycache__/server.cpython-314.pyc +0 -0
  201. package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
  202. package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
  203. package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
  204. package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
  205. package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
  206. package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
  207. package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
  208. package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
  209. package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
  210. package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
  211. package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
  212. package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
  213. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  214. package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
  215. package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
  216. package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
  217. package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
  218. package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
  219. package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
  220. package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
  221. package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
  222. package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
  223. package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
  224. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
  225. package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
  226. package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
  227. package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  244. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  245. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  246. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  247. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  248. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  249. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  250. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  251. package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
  252. package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
  253. package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-314.pyc +0 -0
  254. package/src/scripts/deep-sleep/analyze_session.py +0 -217
  255. package/src/scripts/deep-sleep/collect_transcripts.py +0 -145
  256. package/src/scripts/deep-sleep/prompt.md +0 -109
  257. package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  258. package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  259. package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
  260. package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
  261. package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -34,6 +34,10 @@
34
34
  <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
35
35
  <key>HOME</key>
36
36
  <string>{{HOME}}</string>
37
+ <key>NEXO_HOME</key>
38
+ <string>{{NEXO_HOME}}</string>
39
+ <key>NEXO_CODE</key>
40
+ <string>{{NEXO_CODE}}</string>
37
41
  </dict>
38
42
  </dict>
39
43
  </plist>
@@ -36,6 +36,10 @@
36
36
  <string>{{HOME}}</string>
37
37
  <key>PATH</key>
38
38
  <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
39
+ <key>NEXO_HOME</key>
40
+ <string>{{NEXO_HOME}}</string>
41
+ <key>NEXO_CODE</key>
42
+ <string>{{NEXO_CODE}}</string>
39
43
  </dict>
40
44
  </dict>
41
45
  </plist>
@@ -36,6 +36,10 @@
36
36
  <string>{{HOME}}</string>
37
37
  <key>PATH</key>
38
38
  <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
39
+ <key>NEXO_HOME</key>
40
+ <string>{{NEXO_HOME}}</string>
41
+ <key>NEXO_CODE</key>
42
+ <string>{{NEXO_CODE}}</string>
39
43
  </dict>
40
44
  </dict>
41
45
  </plist>
@@ -33,7 +33,9 @@
33
33
  <key>HOME</key>
34
34
  <string>{{HOME}}</string>
35
35
  <key>NEXO_HOME</key>
36
- <string>{{HOME}}/.nexo</string>
36
+ <string>{{NEXO_HOME}}</string>
37
+ <key>NEXO_CODE</key>
38
+ <string>{{NEXO_CODE}}</string>
37
39
  <key>PATH</key>
38
40
  <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
39
41
  </dict>
@@ -32,6 +32,10 @@
32
32
  <string>{{HOME}}</string>
33
33
  <key>PATH</key>
34
34
  <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
35
+ <key>NEXO_HOME</key>
36
+ <string>{{NEXO_HOME}}</string>
37
+ <key>NEXO_CODE</key>
38
+ <string>{{NEXO_CODE}}</string>
35
39
  </dict>
36
40
  </dict>
37
41
  </plist>
@@ -36,6 +36,10 @@
36
36
  <string>{{HOME}}</string>
37
37
  <key>PATH</key>
38
38
  <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
39
+ <key>NEXO_HOME</key>
40
+ <string>{{NEXO_HOME}}</string>
41
+ <key>NEXO_CODE</key>
42
+ <string>{{NEXO_CODE}}</string>
39
43
  </dict>
40
44
  </dict>
41
45
  </plist>
@@ -38,6 +38,10 @@
38
38
  <string>{{HOME}}</string>
39
39
  <key>PATH</key>
40
40
  <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
41
+ <key>NEXO_HOME</key>
42
+ <string>{{NEXO_HOME}}</string>
43
+ <key>NEXO_CODE</key>
44
+ <string>{{NEXO_CODE}}</string>
41
45
  </dict>
42
46
  </dict>
43
47
  </plist>
@@ -36,6 +36,10 @@
36
36
  <string>{{HOME}}</string>
37
37
  <key>PATH</key>
38
38
  <string>/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin</string>
39
+ <key>NEXO_HOME</key>
40
+ <string>{{NEXO_HOME}}</string>
41
+ <key>NEXO_CODE</key>
42
+ <string>{{NEXO_CODE}}</string>
39
43
  </dict>
40
44
  </dict>
41
45
  </plist>
@@ -28,6 +28,10 @@
28
28
  <dict>
29
29
  <key>PATH</key>
30
30
  <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
31
+ <key>NEXO_HOME</key>
32
+ <string>{{NEXO_HOME}}</string>
33
+ <key>NEXO_CODE</key>
34
+ <string>{{NEXO_CODE}}</string>
31
35
  </dict>
32
36
  </dict>
33
37
  </plist>
@@ -3,7 +3,7 @@
3
3
  "servers": {
4
4
  "nexo-brain": {
5
5
  "command": "python3",
6
- "args": ["~/.nexo/src/server.py"],
6
+ "args": ["~/.nexo/server.py"],
7
7
  "env": {
8
8
  "NEXO_HOME": "~/.nexo"
9
9
  }
package/tests/conftest.py CHANGED
@@ -9,8 +9,8 @@ import sqlite3
9
9
 
10
10
  import pytest
11
11
 
12
- # Add parent dir to path so we can import nexo modules
13
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
12
+ # Add src/ to path so we can import nexo modules
13
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
14
14
 
15
15
 
16
16
  @pytest.fixture(autouse=True)
@@ -165,16 +165,17 @@ def test_kg_boost_relevance_gate():
165
165
 
166
166
 
167
167
  def test_decay_formula():
168
- """Ebbinghaus decay should reduce strength over time."""
168
+ """Ebbinghaus decay should reduce strength over time (lambda operates on HOURS)."""
169
169
  import cognitive
170
- # STM decay: lambda=0.1, after 7 days strength should be ~0.5
170
+ # STM decay: lambda=0.004126, after 7 days (168h) strength should be ~0.5
171
171
  initial = 1.0
172
- days = 7
173
- decayed = initial * math.exp(-cognitive.LAMBDA_STM * days)
172
+ hours_7d = 7 * 24
173
+ decayed = initial * math.exp(-cognitive.LAMBDA_STM * hours_7d)
174
174
  assert 0.45 < decayed < 0.55
175
175
 
176
- # LTM decay: lambda=0.012, after 60 days strength should be ~0.5
177
- decayed_ltm = initial * math.exp(-cognitive.LAMBDA_LTM * 60)
176
+ # LTM decay: lambda=0.000481, after 60 days (1440h) strength should be ~0.5
177
+ hours_60d = 60 * 24
178
+ decayed_ltm = initial * math.exp(-cognitive.LAMBDA_LTM * hours_60d)
178
179
  assert 0.45 < decayed_ltm < 0.55
179
180
 
180
181
 
@@ -87,6 +87,32 @@ def test_reminder_followup_crud():
87
87
  assert followup2["status"] == "COMPLETED"
88
88
 
89
89
 
90
+ def test_recurring_followup():
91
+ """Recurring followup: complete archives with date suffix, creates new pending, returns correct IDs."""
92
+ db_mod.create_followup("NF-REC1", "Recurring test", date="2026-03-31", recurrence="weekly:monday")
93
+ followup = db_mod.get_followup("NF-REC1")
94
+ assert followup is not None
95
+ assert followup["recurrence"] == "weekly:monday"
96
+
97
+ result = db_mod.complete_followup("NF-REC1", result="done weekly")
98
+
99
+ # Result should reference the archived ID, not the recycled NF-REC1
100
+ assert result["status"] == "COMPLETED"
101
+ assert result["id"].startswith("NF-REC1-") # archived with date suffix
102
+ assert result["next_id"] == "NF-REC1"
103
+ assert result["next_date"] is not None
104
+
105
+ # The new NF-REC1 should be PENDING (not the completed one)
106
+ new_followup = db_mod.get_followup("NF-REC1")
107
+ assert new_followup is not None
108
+ assert new_followup["status"] == "PENDING"
109
+
110
+ # The archived one should exist with date suffix
111
+ archived = db_mod.get_followup(result["id"])
112
+ assert archived is not None
113
+ assert archived["status"] == "COMPLETED"
114
+
115
+
90
116
  def test_credential_crud():
91
117
  """Create, get, and delete credentials."""
92
118
  db_mod.create_credential("test-service", "api_key", "secret123", notes="test")
@@ -1,217 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Deep Sleep — Step 2: Analyze transcripts with Claude CLI (bare mode).
4
- Sends each session to Claude opus for analysis, then consolidates findings.
5
- """
6
- import json
7
- import os
8
- import subprocess
9
- import sys
10
- from datetime import datetime
11
- from pathlib import Path
12
-
13
- NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
14
-
15
- PROMPT_FILE = Path(__file__).parent / "prompt.md"
16
- DEEP_SLEEP_DIR = NEXO_HOME / "operations" / "deep-sleep"
17
- MAX_TRANSCRIPT_CHARS = 150_000
18
-
19
-
20
- def build_transcript_text(session: dict) -> str:
21
- """Build a readable transcript from a session."""
22
- lines = [
23
- f"## Session: {session['session_file']}",
24
- f"Modified: {session['modified']}",
25
- f"Messages: {session['message_count']}, Tool uses: {session['tool_use_count']}",
26
- "",
27
- "### Conversation"
28
- ]
29
- for msg in session["messages"]:
30
- role = "USER" if msg["role"] == "user" else "NEXO"
31
- lines.append(f"\n**{role}:**")
32
- lines.append(msg["text"])
33
-
34
- if session["tool_uses"]:
35
- lines.append("\n### Tool Usage Log")
36
- for tu in session["tool_uses"]:
37
- file_info = f" [{tu['file'][:80]}]" if tu.get("file") else ""
38
- lines.append(f"- {tu['tool']}{file_info}")
39
-
40
- return "\n".join(lines)
41
-
42
-
43
- def find_api_key() -> str | None:
44
- """Find Anthropic API key from common locations."""
45
- # Environment variable
46
- key = os.environ.get("ANTHROPIC_API_KEY", "")
47
- if key:
48
- return key
49
-
50
- # Common file locations
51
- for path in [
52
- Path.home() / ".claude" / "anthropic-api-key.txt",
53
- Path.home() / ".anthropic" / "api_key",
54
- Path.home() / ".config" / "anthropic" / "api_key",
55
- ]:
56
- if path.exists():
57
- return path.read_text().strip()
58
-
59
- return None
60
-
61
-
62
- def analyze_with_claude(transcript: str, prompt: str) -> dict | None:
63
- """Send transcript to Claude CLI for analysis."""
64
- full_prompt = (
65
- f"{prompt}\n\n---\n\n# TODAY'S TRANSCRIPT\n\n{transcript}\n\n---\n\n"
66
- "Analyze this transcript and return the JSON output as specified. "
67
- "Return ONLY the JSON, no markdown code fences."
68
- )
69
-
70
- api_key = find_api_key()
71
- env = os.environ.copy()
72
- if api_key:
73
- env["ANTHROPIC_API_KEY"] = api_key
74
-
75
- try:
76
- result = subprocess.run(
77
- ["claude", "-p", full_prompt, "--model", "opus", "--output-format", "text", "--bare"],
78
- capture_output=True, text=True, timeout=300, env=env
79
- )
80
-
81
- if result.returncode != 0:
82
- print(f"Claude CLI error: {result.stderr[:500]}", file=sys.stderr)
83
- return None
84
-
85
- response_text = result.stdout.strip()
86
-
87
- # Strip markdown code fences if present
88
- if response_text.startswith("```"):
89
- lines = response_text.split("\n")
90
- response_text = "\n".join(lines[1:-1] if lines[-1].strip() == "```" else lines[1:])
91
- response_text = response_text.strip()
92
-
93
- # Find JSON object in response
94
- json_start = response_text.find("{")
95
- json_end = response_text.rfind("}") + 1
96
- if json_start >= 0 and json_end > json_start:
97
- response_text = response_text[json_start:json_end]
98
-
99
- return json.loads(response_text)
100
-
101
- except subprocess.TimeoutExpired:
102
- print("Claude CLI timeout (300s)", file=sys.stderr)
103
- return None
104
- except json.JSONDecodeError as e:
105
- print(f"Failed to parse Claude response: {e}", file=sys.stderr)
106
- return None
107
- except FileNotFoundError:
108
- print("Claude CLI not found. Install: npm install -g @anthropic-ai/claude-code", file=sys.stderr)
109
- return None
110
-
111
-
112
- def consolidate_findings(results: list[dict]) -> dict:
113
- """Merge findings from multiple sessions into one report."""
114
- consolidated = {
115
- "uncaptured_corrections": [],
116
- "uncaptured_ideas": [],
117
- "missed_commitments": [],
118
- "protocol_compliance": {
119
- "guard_check": {"required": 0, "executed": 0},
120
- "heartbeat_quality": {"total": 0, "with_good_context": 0},
121
- "trust_adjustments": {"corrections_detected": 0, "adjusted": 0},
122
- "learning_capture": {"errors_resolved": 0, "captured": 0},
123
- "change_log": {"production_edits": 0, "logged": 0},
124
- "feedback_capture": {"corrections": 0, "captured": 0},
125
- },
126
- "protocol_violations": [],
127
- "quality_issues": [],
128
- "auto_reinforcements": [],
129
- }
130
-
131
- for r in results:
132
- if not r:
133
- continue
134
- for key in ["uncaptured_corrections", "uncaptured_ideas", "missed_commitments",
135
- "protocol_violations", "quality_issues", "auto_reinforcements"]:
136
- consolidated[key].extend(r.get(key, []))
137
-
138
- pc = r.get("protocol_compliance", {})
139
- for key in consolidated["protocol_compliance"]:
140
- if key in pc and isinstance(pc[key], dict):
141
- for subkey in consolidated["protocol_compliance"][key]:
142
- consolidated["protocol_compliance"][key][subkey] += pc[key].get(subkey, 0)
143
-
144
- # Calculate rates
145
- for key, vals in consolidated["protocol_compliance"].items():
146
- keys = list(vals.keys())
147
- if len(keys) == 2:
148
- denominator = vals[keys[0]]
149
- numerator = vals[keys[1]]
150
- vals["rate"] = round(numerator / denominator, 2) if denominator > 0 else 1.0
151
-
152
- rates = [v.get("rate", 1.0) for v in consolidated["protocol_compliance"].values()]
153
- consolidated["protocol_compliance"]["overall_compliance"] = round(sum(rates) / len(rates), 2) if rates else 1.0
154
- consolidated["auto_reinforcements"] = list(set(consolidated["auto_reinforcements"]))
155
-
156
- return consolidated
157
-
158
-
159
- def main():
160
- date = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
161
-
162
- transcripts_file = DEEP_SLEEP_DIR / f"{date}-transcripts.json"
163
- if not transcripts_file.exists():
164
- print(f"No transcripts found for {date}. Run collect_transcripts.py first.")
165
- sys.exit(1)
166
-
167
- with open(transcripts_file) as f:
168
- data = json.load(f)
169
-
170
- sessions = data["sessions"]
171
- print(f"Analyzing {len(sessions)} sessions from {date}...")
172
-
173
- prompt = PROMPT_FILE.read_text()
174
-
175
- results = []
176
- for i, session in enumerate(sessions):
177
- transcript = build_transcript_text(session)
178
-
179
- if len(transcript) < 500:
180
- print(f" Session {i+1}/{len(sessions)}: skipped (too short)")
181
- continue
182
-
183
- if len(transcript) > MAX_TRANSCRIPT_CHARS:
184
- transcript = transcript[:MAX_TRANSCRIPT_CHARS] + "\n\n[TRUNCATED]"
185
-
186
- print(f" Session {i+1}/{len(sessions)}: {session['session_file'][:12]}... ({len(transcript)} chars)")
187
- result = analyze_with_claude(transcript, prompt)
188
- if result:
189
- results.append(result)
190
- print(f" → {len(result.get('uncaptured_corrections', []))} corrections, "
191
- f"{len(result.get('protocol_violations', []))} violations")
192
- else:
193
- print(f" → Analysis failed")
194
-
195
- consolidated = consolidate_findings(results)
196
- consolidated["date"] = date
197
- consolidated["sessions_analyzed"] = len(results)
198
-
199
- n_corrections = len(consolidated["uncaptured_corrections"])
200
- n_violations = len(consolidated["protocol_violations"])
201
- compliance = consolidated["protocol_compliance"]["overall_compliance"]
202
- consolidated["summary"] = (
203
- f"Analyzed {len(results)} sessions. "
204
- f"Found {n_corrections} uncaptured corrections, {n_violations} protocol violations. "
205
- f"Overall compliance: {compliance:.0%}."
206
- )
207
-
208
- output_file = DEEP_SLEEP_DIR / f"{date}-analysis.json"
209
- with open(output_file, "w") as f:
210
- json.dump(consolidated, f, indent=2, ensure_ascii=False)
211
-
212
- print(f"\nResults: {output_file}")
213
- print(consolidated["summary"])
214
-
215
-
216
- if __name__ == "__main__":
217
- main()