nexo-brain 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/README.md +7 -7
  2. package/bin/nexo-brain.js +53 -26
  3. package/package.json +1 -1
  4. package/scripts/migrate-to-unified 2.sh +813 -0
  5. package/scripts/migrate-v1.5-to-v1.6 2.py +778 -0
  6. package/scripts/migrate-v1.7-to-v1.8 2.py +214 -0
  7. package/scripts/migrate-v1.7-to-v1.8.py +2 -2
  8. package/scripts/nexo-preflight.sh +236 -0
  9. package/scripts/pre-commit-check 2.sh +55 -0
  10. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  11. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  12. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  13. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  14. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  15. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  16. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  17. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  18. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  19. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  20. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  21. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  22. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  23. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  24. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  25. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  26. package/src/auto_close_sessions 2.py +159 -0
  27. package/src/auto_update 2.py +634 -0
  28. package/src/auto_update.py +25 -0
  29. package/src/claim_graph 2.py +323 -0
  30. package/src/cognitive/__init__ 2.py +62 -0
  31. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  32. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  33. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  34. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  35. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  36. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  37. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  38. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  39. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  40. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  41. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  42. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  43. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  44. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  45. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  46. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  47. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  48. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  49. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  50. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  51. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  52. package/src/cognitive/_core 2.py +567 -0
  53. package/src/cognitive/_decay 2.py +382 -0
  54. package/src/cognitive/_ingest 2.py +892 -0
  55. package/src/cognitive/_memory 2.py +912 -0
  56. package/src/cognitive/_search 2.py +949 -0
  57. package/src/cognitive/_trust 2.py +464 -0
  58. package/src/cognitive/_trust.py +10 -36
  59. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  60. package/src/crons/manifest 2.json +106 -0
  61. package/src/crons/manifest.json +6 -13
  62. package/src/crons/sync 2.py +217 -0
  63. package/src/crons/sync.py +151 -6
  64. package/src/dashboard/__init__ 2.py +0 -0
  65. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  66. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  67. package/src/dashboard/app 2.py +789 -0
  68. package/src/db/__init__ 2.py +89 -0
  69. package/src/db/__init__.py +13 -0
  70. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  71. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  72. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  73. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  74. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  75. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  76. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  77. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  78. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  79. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  80. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  81. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  82. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  83. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  84. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  85. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  86. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  87. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  88. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  89. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  90. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  91. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  92. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  93. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  94. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  95. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  96. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  97. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  98. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  99. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  100. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  101. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  102. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  103. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  104. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  105. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  106. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  107. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  108. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  109. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  110. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  111. package/src/db/_core 2.py +417 -0
  112. package/src/db/_credentials 2.py +124 -0
  113. package/src/db/_cron_runs.py +74 -0
  114. package/src/db/_entities 2.py +178 -0
  115. package/src/db/_episodic 2.py +738 -0
  116. package/src/db/_episodic.py +40 -6
  117. package/src/db/_evolution 2.py +54 -0
  118. package/src/db/_fts 2.py +406 -0
  119. package/src/db/_learnings 2.py +168 -0
  120. package/src/db/_reminders 2.py +338 -0
  121. package/src/db/_schema 2.py +364 -0
  122. package/src/db/_schema.py +64 -0
  123. package/src/db/_sessions 2.py +300 -0
  124. package/src/db/_skills.py +514 -0
  125. package/src/db/_tasks 2.py +91 -0
  126. package/src/evolution_cycle 2.py +266 -0
  127. package/src/hnsw_index 2.py +254 -0
  128. package/src/hooks/auto_capture 2.py +208 -0
  129. package/src/hooks/caffeinate-guard 2.sh +8 -0
  130. package/src/hooks/capture-session 2.sh +21 -0
  131. package/src/hooks/capture-session.sh +2 -0
  132. package/src/hooks/capture-tool-logs 2.sh +127 -0
  133. package/src/hooks/capture-tool-logs.sh +3 -2
  134. package/src/hooks/daily-briefing-check 2.sh +33 -0
  135. package/src/hooks/inbox-hook 2.sh +76 -0
  136. package/src/hooks/inbox-hook.sh +3 -2
  137. package/src/hooks/post-compact 2.sh +148 -0
  138. package/src/hooks/post-compact.sh +1 -1
  139. package/src/hooks/pre-compact 2.sh +151 -0
  140. package/src/hooks/pre-compact.sh +1 -1
  141. package/src/hooks/session-start 2.sh +268 -0
  142. package/src/hooks/session-start.sh +6 -3
  143. package/src/hooks/session-stop 2.sh +140 -0
  144. package/src/hooks/session-stop.sh +14 -102
  145. package/src/kg_populate 2.py +290 -0
  146. package/src/knowledge_graph 2.py +257 -0
  147. package/src/maintenance 2.py +59 -0
  148. package/src/migrate_embeddings 2.py +122 -0
  149. package/src/plugin_loader 2.py +202 -0
  150. package/src/plugins/__init__ 2.py +0 -0
  151. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  152. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  153. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  154. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  155. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  156. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  157. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  158. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  159. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  160. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  161. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  162. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  163. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  164. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  165. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  166. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  167. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  168. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  169. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  170. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  171. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  172. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  173. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  174. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  175. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  176. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  177. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  178. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  179. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  180. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  181. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  182. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  183. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  184. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  185. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  186. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  187. package/src/plugins/adaptive_mode 2.py +805 -0
  188. package/src/plugins/agents 2.py +52 -0
  189. package/src/plugins/artifact_registry 2.py +450 -0
  190. package/src/plugins/backup 2.py +104 -0
  191. package/src/plugins/cognitive_memory 2.py +564 -0
  192. package/src/plugins/core_rules 2.py +252 -0
  193. package/src/plugins/cortex 2.py +299 -0
  194. package/src/plugins/entities 2.py +67 -0
  195. package/src/plugins/episodic_memory 2.py +533 -0
  196. package/src/plugins/episodic_memory.py +5 -3
  197. package/src/plugins/evolution 2.py +115 -0
  198. package/src/plugins/guard 2.py +746 -0
  199. package/src/plugins/knowledge_graph_tools 2.py +105 -0
  200. package/src/plugins/preferences 2.py +47 -0
  201. package/src/plugins/schedule.py +212 -0
  202. package/src/plugins/skills.py +264 -0
  203. package/src/plugins/update 2.py +256 -0
  204. package/src/requirements 2.txt +12 -0
  205. package/src/rules/__init__ 2.py +0 -0
  206. package/src/rules/core-rules 2.json +331 -0
  207. package/src/rules/migrate 2.py +207 -0
  208. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  209. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  210. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  211. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  212. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  213. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  214. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  215. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  216. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  217. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  218. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  219. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  220. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  221. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  222. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  223. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  224. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  225. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  226. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  227. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  229. package/src/scripts/check-context 2.py +264 -0
  230. package/src/scripts/deep-sleep/apply_findings.py +168 -8
  231. package/src/scripts/deep-sleep/collect.py +33 -11
  232. package/src/scripts/deep-sleep/extract-prompt.md +38 -0
  233. package/src/scripts/deep-sleep/extract.py +80 -8
  234. package/src/scripts/deep-sleep/synthesize-prompt.md +59 -2
  235. package/src/scripts/deep-sleep/synthesize.py +3 -1
  236. package/src/scripts/nexo-auto-update 2.py +6 -0
  237. package/src/scripts/nexo-backup 2.sh +25 -0
  238. package/src/scripts/nexo-brain-activation 2.sh +140 -0
  239. package/src/scripts/nexo-catchup 2.py +242 -0
  240. package/src/scripts/nexo-catchup.py +65 -29
  241. package/src/scripts/nexo-cognitive-decay 2.py +182 -0
  242. package/src/scripts/nexo-cron-wrapper.sh +53 -0
  243. package/src/scripts/nexo-daily-self-audit 2.py +552 -0
  244. package/src/scripts/nexo-daily-self-audit.py +4 -2
  245. package/src/scripts/nexo-deep-sleep 2.sh +97 -0
  246. package/src/scripts/nexo-deep-sleep.sh +66 -77
  247. package/src/scripts/nexo-evolution-run 2.py +597 -0
  248. package/src/scripts/nexo-evolution-run.py +13 -0
  249. package/src/scripts/nexo-followup-hygiene 2.py +112 -0
  250. package/src/scripts/nexo-immune 2.py +927 -0
  251. package/src/scripts/nexo-inbox-hook 2.sh +74 -0
  252. package/src/scripts/nexo-install 2.py +6 -0
  253. package/src/scripts/nexo-learning-housekeep 2.py +245 -0
  254. package/src/scripts/nexo-learning-housekeep.py +156 -1
  255. package/src/scripts/nexo-learning-validator 2.py +207 -0
  256. package/src/scripts/nexo-learning-validator.py +19 -0
  257. package/src/scripts/nexo-migrate 2.py +232 -0
  258. package/src/scripts/nexo-postmortem-consolidator 2.py +421 -0
  259. package/src/scripts/nexo-postmortem-consolidator.py +3 -2
  260. package/src/scripts/nexo-pre-commit 2.py +120 -0
  261. package/src/scripts/nexo-prevent-sleep 2.sh +29 -0
  262. package/src/scripts/nexo-proactive-dashboard 2.py +345 -0
  263. package/src/scripts/nexo-reflection 2.py +253 -0
  264. package/src/scripts/nexo-runtime-preflight 2.py +274 -0
  265. package/src/scripts/nexo-send-email 2.py +25 -0
  266. package/src/scripts/nexo-send-reply 2.py +178 -0
  267. package/src/scripts/nexo-sleep 2.py +592 -0
  268. package/src/scripts/nexo-sleep.py +16 -11
  269. package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
  270. package/src/scripts/nexo-synthesis 2.py +253 -0
  271. package/src/scripts/nexo-synthesis.py +46 -3
  272. package/src/scripts/nexo-tcc-approve 2.sh +79 -0
  273. package/src/scripts/nexo-update 2.sh +161 -0
  274. package/src/scripts/nexo-watchdog 2.sh +878 -0
  275. package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
  276. package/src/scripts/nexo-watchdog.sh +72 -19
  277. package/src/server 2.py +733 -0
  278. package/src/server.py +11 -2
  279. package/src/storage_router 2.py +32 -0
  280. package/src/tools_coordination 2.py +102 -0
  281. package/src/tools_credentials 2.py +68 -0
  282. package/src/tools_learnings 2.py +220 -0
  283. package/src/tools_menu 2.py +227 -0
  284. package/src/tools_reminders 2.py +86 -0
  285. package/src/tools_reminders_crud 2.py +159 -0
  286. package/src/tools_reminders_crud.py +7 -0
  287. package/src/tools_sessions 2.py +476 -0
  288. package/src/tools_task_history 2.py +57 -0
  289. package/templates/CLAUDE.md 2.template +63 -0
  290. package/templates/openclaw 2.json +13 -0
  291. package/tests/__init__ 2.py +0 -0
  292. package/tests/conftest 2.py +71 -0
  293. package/tests/test_cognitive 2.py +205 -0
  294. package/tests/test_knowledge_graph 2.py +140 -0
  295. package/tests/test_migrations 2.py +137 -0
  296. package/src/scripts/deep-sleep/__pycache__/extract.cpython-314.pyc +0 -0
  297. /package/src/scripts/{nexo-github-monitor.py → nexo-github-monitor 2.py} +0 -0
@@ -0,0 +1,421 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ NEXO Post-Mortem Consolidator v2 — The brain consolidates memories.
4
+
5
+ Before: 595 lines of word-overlap at 50% to detect "patterns".
6
+ Now: Collects data, passes them to CLI which UNDERSTANDS what it reads.
7
+
8
+ Runs daily at 23:30 via LaunchAgent. Reads session diaries from today,
9
+ passes them to Claude CLI (opus) which decides what deserves permanent memory.
10
+
11
+ Stage 1 — Data collection (Pure Python):
12
+ Query session diaries, existing feedbacks, history.
13
+
14
+ Stage 2 — Intelligence (Claude CLI opus):
15
+ Read diaries, understand patterns, decide what to promote.
16
+
17
+ Stage 3 — Sensory Register + Force analysis (Pure Python):
18
+ Process cognitive events. Kept from v1 — genuinely mechanical.
19
+ """
20
+
21
+ import json
22
+ import os
23
+ import sqlite3
24
+ import subprocess
25
+ import sys
26
+ from datetime import datetime, date, timedelta
27
+ from pathlib import Path
28
+
29
+ HOME = Path.home()
30
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(HOME / ".nexo")))
31
+
32
+ # Add NEXO_HOME to path for cognitive engine (Stage 3)
33
+ # Auto-detect: if running from repo (src/scripts/), use src/ as NEXO_CODE
34
+ _script_dir = Path(__file__).resolve().parent
35
+ _repo_src = _script_dir.parent # src/scripts/ -> src/
36
+ NEXO_CODE = Path(os.environ.get("NEXO_CODE", str(_repo_src) if (_repo_src / "server.py").exists() else str(NEXO_HOME)))
37
+ sys.path.insert(0, str(NEXO_CODE))
38
+
39
+ NEXO_DB = NEXO_HOME / "data" / "nexo.db"
40
+ # Memory directory — adjust to match your project's memory location
41
+ MEMORY_DIR = NEXO_HOME / "memory"
42
+ MEMORY_INDEX = MEMORY_DIR / "MEMORY.md"
43
+ HISTORY_FILE = NEXO_HOME / "coordination" / "postmortem-history.json"
44
+ CONSOLIDATION_LOG = NEXO_HOME / "logs" / "postmortem-consolidation.log"
45
+ CLAUDE_CLI = HOME / ".local" / "bin" / "claude"
46
+ SESSION_BUFFER = NEXO_HOME / "brain" / "session_buffer.jsonl"
47
+
48
+ TODAY = date.today()
49
+ TODAY_STR = TODAY.isoformat()
50
+
51
+
52
+ def log(msg: str):
53
+ ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
54
+ line = f"[{ts}] {msg}"
55
+ print(line, flush=True)
56
+ CONSOLIDATION_LOG.parent.mkdir(parents=True, exist_ok=True)
57
+ with open(CONSOLIDATION_LOG, "a") as f:
58
+ f.write(line + "\n")
59
+
60
+
61
+ # ─── Stage 1: Data Collection (Pure Python) ─────────────────────────────────
62
+
63
+ def collect_data() -> dict:
64
+ """Collects all data the CLI will need to decide."""
65
+ data = {
66
+ "date": TODAY_STR,
67
+ "diaries": [],
68
+ "existing_feedbacks": [],
69
+ "history_summary": {},
70
+ }
71
+
72
+ if not NEXO_DB.exists():
73
+ return data
74
+
75
+ conn = sqlite3.connect(str(NEXO_DB))
76
+ conn.row_factory = sqlite3.Row
77
+
78
+ # Today's diaries with self-critique
79
+ rows = conn.execute(
80
+ "SELECT id, session_id, summary, self_critique, user_signals, "
81
+ "mental_state, domain, created_at "
82
+ "FROM session_diary WHERE date(created_at) = ? ORDER BY created_at",
83
+ (TODAY_STR,)
84
+ ).fetchall()
85
+ data["diaries"] = [dict(r) for r in rows]
86
+
87
+ conn.close()
88
+
89
+ # Existing postmortem feedbacks (nombres, para no duplicar)
90
+ data["existing_feedbacks"] = [
91
+ f.stem for f in MEMORY_DIR.glob("feedback_postmortem_*.md")
92
+ ]
93
+
94
+ # History summary
95
+ if HISTORY_FILE.exists():
96
+ try:
97
+ history = json.loads(HISTORY_FILE.read_text())
98
+ data["history_summary"] = {
99
+ "total_permanent_rules": len(history.get("permanent_rules", [])),
100
+ "days_tracked": len(history.get("days", {})),
101
+ "recent_rules": history.get("permanent_rules", [])[-10:],
102
+ }
103
+ except Exception:
104
+ pass
105
+
106
+ return data
107
+
108
+
109
+ # ─── Stage 2: Intelligence (Claude CLI opus) ────────────────────────────────
110
+
111
+ def consolidate_with_cli(data: dict) -> bool:
112
+ """The brain consolidates — CLI decides what to promote."""
113
+
114
+ diaries_with_critique = [
115
+ d for d in data["diaries"]
116
+ if d.get("self_critique") and not str(d["self_critique"]).strip().lower().startswith("no self-critique")
117
+ ]
118
+
119
+ if not diaries_with_critique:
120
+ log("All sessions clean or trivial. Nothing to consolidate.")
121
+ return True
122
+
123
+ # Prepare data for CLI (truncate to avoid exceeding context)
124
+ diaries_json = json.dumps(diaries_with_critique, ensure_ascii=False, indent=1)
125
+ if len(diaries_json) > 12000:
126
+ diaries_json = diaries_json[:12000] + "\n... (truncated)"
127
+
128
+ prompt = f"""FIRST: Call nexo_startup(task='nightly postmortem consolidation') to register this session.
129
+
130
+ You are NEXO's nightly consolidator. Your job is to review the self-critiques
131
+ from today and decide which deserve to become permanent rules. Use nexo_learning_add for permanent rules and nexo_followup_create for action items.
132
+
133
+ DATE: {data['date']}
134
+ SESSIONS TODAY: {len(data['diaries'])} total, {len(diaries_with_critique)} with self-critique
135
+
136
+ DIARIES WITH SELF-CRITIQUE:
137
+ {diaries_json}
138
+
139
+ EXISTING POSTMORTEM FEEDBACKS ({len(data['existing_feedbacks'])}):
140
+ {json.dumps(data['existing_feedbacks'][:30], ensure_ascii=False)}
141
+
142
+ RECENT PERMANENT RULES:
143
+ {json.dumps(data['history_summary'].get('recent_rules', []), ensure_ascii=False)}
144
+
145
+ INSTRUCTIONS:
146
+
147
+ 1. Read each self_critique and understand its MEANING (don't count words).
148
+
149
+ 2. PROMOTE to permanent feedback ONLY IF:
150
+ - A pattern appears in 2+ different sessions of the day (by meaning, not literal text)
151
+ - Or the user explicitly corrected (user_signals contains correction)
152
+ - And the self-critique contains a CONCRETE ACTION that prevents a future error
153
+ - And a similar feedback does NOT already exist in the existing ones
154
+
155
+ 3. DO NOT promote if:
156
+ - It's a negative response ("Nothing happened", "clean session")
157
+ - It's generic without concrete action
158
+ - A feedback covering the same topic already exists
159
+
160
+ 4. For each rule to promote, create the file with Write en {MEMORY_DIR}/:
161
+ Nombre: feedback_postmortem_[slug_descriptivo].md
162
+ Formato:
163
+ ---
164
+ name: [descriptive title]
165
+ description: Behavioral rule extracted from self-critique — recurring pattern
166
+ type: feedback
167
+ ---
168
+
169
+ [Clear description of the pattern and rule]
170
+
171
+ **Why:** [Why this matters — with evidence from sessions]
172
+ **How to apply:** [When and how to apply this rule]
173
+
174
+ 5. Write the daily summary en $NEXO_HOME/coordination/postmortem-daily.md:
175
+ # Post-Mortem Daily — {data['date']}
176
+ Sessions: X | Self-critiques: Y | Promoted: Z
177
+
178
+ ## Today's self-critiques (summary)
179
+ [Lista breve]
180
+
181
+ ## Promoted to permanent memory
182
+ [What you promoted and why]
183
+
184
+ ## Discarded (and why)
185
+ [What you did NOT promote and the reason]
186
+
187
+ Execute without asking."""
188
+
189
+ log(f"Stage 2: Invoking Claude CLI (opus) with {len(diaries_with_critique)} critiques...")
190
+ env = os.environ.copy()
191
+ env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
192
+ env.pop("CLAUDECODE", None)
193
+ env.pop("CLAUDE_CODE", None)
194
+
195
+ try:
196
+ result = subprocess.run(
197
+ [str(CLAUDE_CLI), "-p", prompt, "--model", "opus",
198
+ "--output-format", "text",
199
+ "--allowedTools", "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"],
200
+ capture_output=True, text=True, timeout=21600, env=env
201
+ )
202
+
203
+ if result.returncode != 0:
204
+ log(f"Stage 2: CLI error (code {result.returncode}): {(result.stderr or '')[:300]}")
205
+ return False
206
+
207
+ log(f"Stage 2: Completed. Output: {len(result.stdout or '')} chars")
208
+ # Log last 500 chars of output for debugging
209
+ if result.stdout:
210
+ log(f"Stage 2 output tail: {result.stdout[-500:]}")
211
+ return True
212
+
213
+ except subprocess.TimeoutExpired:
214
+ log("Stage 2: CLI timed out (300s)")
215
+ return False
216
+ except Exception as e:
217
+ log(f"Stage 2: Exception: {e}")
218
+ return False
219
+
220
+
221
+ # ─── Stage 3: Sensory Register + Force Analysis (Pure Python) ───────────────
222
+ # Kept from v1 — these are genuinely mechanical (embedding vectors, DB updates)
223
+
224
+ def process_sensory_register():
225
+ """Sensory Register — Atkinson-Shiffrin Layer 1. Embeds events into STM."""
226
+ log("--- Sensory Register processing ---")
227
+
228
+ if not SESSION_BUFFER.exists():
229
+ log(" No session_buffer.jsonl found, skipping")
230
+ return
231
+
232
+ today_events = []
233
+ try:
234
+ with open(SESSION_BUFFER) as f:
235
+ for line in f:
236
+ line = line.strip()
237
+ if not line:
238
+ continue
239
+ try:
240
+ event = json.loads(line)
241
+ if event.get("ts", "").startswith(TODAY_STR):
242
+ today_events.append(event)
243
+ except json.JSONDecodeError:
244
+ continue
245
+ except Exception as e:
246
+ log(f" Error reading session_buffer: {e}")
247
+ return
248
+
249
+ if not today_events:
250
+ log(" No events from today")
251
+ return
252
+
253
+ log(f" Found {len(today_events)} events")
254
+
255
+ try:
256
+ import cognitive
257
+ except ImportError as e:
258
+ log(f" Cannot import cognitive: {e}")
259
+ return
260
+
261
+ ingested = 0
262
+ for event in today_events:
263
+ source = event.get("source", "")
264
+ if source == "hook-fallback":
265
+ task_str = " ".join(event.get("tasks", []))
266
+ if len(task_str) < 50 or "," in task_str:
267
+ continue
268
+
269
+ parts = []
270
+ for key, label in [("tasks", "Tasks"), ("decisions", "Decisions"),
271
+ ("errors_resolved", "Errors"), ("user_patterns", "the user")]:
272
+ val = event.get(key, [])
273
+ if val:
274
+ parts.append(f"{label}: {'; '.join(str(v) for v in val[:3])}")
275
+
276
+ critique = event.get("self_critique", "")
277
+ if critique and "hook-fallback" not in critique:
278
+ parts.append(f"Self-critique: {critique[:200]}")
279
+
280
+ content = " | ".join(parts)
281
+ if not content or len(content) < 20:
282
+ continue
283
+
284
+ try:
285
+ vec = cognitive.embed(content)
286
+ domain = ""
287
+ lower = content.lower()
288
+ # Add your project keywords for domain detection
289
+ for keyword, dom in [("nexo", "nexo")]:
290
+ if keyword in lower:
291
+ domain = dom
292
+ break
293
+
294
+ cognitive.ingest_sensory(
295
+ content=content, source_id=f"buffer#{event.get('ts', '')}",
296
+ domain=domain, created_at=event.get("ts", "")
297
+ )
298
+ ingested += 1
299
+ except Exception as e:
300
+ log(f" Error embedding: {e}")
301
+
302
+ log(f" Ingested {ingested} sensory events into STM")
303
+
304
+
305
+ def analyze_force_events():
306
+ """Analyze --force dissonance resolutions from today."""
307
+ log("--- Force event analysis ---")
308
+
309
+ try:
310
+ import cognitive
311
+ except ImportError:
312
+ log(" Cannot import cognitive, skipping")
313
+ return
314
+
315
+ db = cognitive._get_db()
316
+ today_forces = db.execute(
317
+ """SELECT memory_id, context, created_at FROM memory_corrections
318
+ WHERE correction_type = 'exception' AND context LIKE '%[FORCE]%'
319
+ AND date(created_at) = ? ORDER BY created_at""",
320
+ (TODAY_STR,)
321
+ ).fetchall()
322
+
323
+ if not today_forces:
324
+ log(" No --force events today")
325
+ return
326
+
327
+ log(f" {len(today_forces)} --force events")
328
+
329
+ from collections import Counter
330
+ memory_counts = Counter(r["memory_id"] for r in today_forces)
331
+ for mem_id, count in memory_counts.most_common():
332
+ mem = db.execute(
333
+ "SELECT content, strength FROM ltm_memories WHERE id = ?", (mem_id,)
334
+ ).fetchone()
335
+ if not mem:
336
+ continue
337
+
338
+ total = db.execute(
339
+ "SELECT COUNT(*) FROM memory_corrections WHERE memory_id = ? AND context LIKE '%[FORCE]%'",
340
+ (mem_id,)
341
+ ).fetchone()[0]
342
+
343
+ if total >= 3:
344
+ log(f" PARADIGM SHIFT: LTM #{mem_id} overridden {total}x → decay to 0.3")
345
+ db.execute(
346
+ "UPDATE ltm_memories SET strength = 0.3, "
347
+ "tags = CASE WHEN tags LIKE '%paradigm_candidate%' THEN tags "
348
+ "ELSE tags || ',paradigm_candidate' END WHERE id = ?",
349
+ (mem_id,)
350
+ )
351
+ elif count >= 2:
352
+ log(f" WATCH: LTM #{mem_id} overridden {count}x today")
353
+
354
+ db.commit()
355
+
356
+
357
+ # ─── Main ────────────────────────────────────────────────────────────────────
358
+
359
+ def already_ran_today() -> bool:
360
+ """Prevent running twice on the same day."""
361
+ marker = NEXO_HOME / "coordination" / "postmortem-last-run"
362
+ if marker.exists():
363
+ try:
364
+ return marker.read_text().strip() == TODAY_STR
365
+ except Exception:
366
+ return False
367
+ return False
368
+
369
+
370
+ def mark_done():
371
+ marker = NEXO_HOME / "coordination" / "postmortem-last-run"
372
+ marker.parent.mkdir(parents=True, exist_ok=True)
373
+ marker.write_text(TODAY_STR)
374
+
375
+
376
+ def main():
377
+ if already_ran_today():
378
+ log("Already ran today. Skipping.")
379
+ return
380
+
381
+ log("=== NEXO Post-Mortem Consolidator v2 starting ===")
382
+
383
+ # Stage 1: Collect data
384
+ data = collect_data()
385
+ log(f"Stage 1: {len(data['diaries'])} diaries, {len(data['existing_feedbacks'])} existing feedbacks")
386
+
387
+ if not data["diaries"]:
388
+ log("No session diaries today. Nothing to consolidate.")
389
+ else:
390
+ # Stage 2: CLI intelligence
391
+ success = consolidate_with_cli(data)
392
+ if not success:
393
+ log("Stage 2 failed — falling back to skip (no v1 fallback)")
394
+
395
+ # Stage 3: Sensory Register (mechanical, kept from v1)
396
+ try:
397
+ process_sensory_register()
398
+ except Exception as e:
399
+ log(f"Sensory register failed: {e}")
400
+
401
+ # Stage 3b: Force analysis (mechanical, kept from v1)
402
+ try:
403
+ analyze_force_events()
404
+ except Exception as e:
405
+ log(f"Force analysis failed: {e}")
406
+
407
+ # Register successful run
408
+ try:
409
+ state_file = NEXO_HOME / "operations" / ".catchup-state.json"
410
+ state = json.loads(state_file.read_text()) if state_file.exists() else {}
411
+ state["postmortem"] = datetime.now().isoformat()
412
+ state_file.write_text(json.dumps(state, indent=2))
413
+ except Exception:
414
+ pass
415
+
416
+ mark_done()
417
+ log("=== Consolidation v2 complete ===")
418
+
419
+
420
+ if __name__ == "__main__":
421
+ main()
@@ -387,10 +387,11 @@ def main():
387
387
  if not data["diaries"]:
388
388
  log("No session diaries today. Nothing to consolidate.")
389
389
  else:
390
- # Stage 2: CLI intelligence
390
+ # Stage 2: CLI intelligence (graceful fallback: Stage 3 still runs)
391
391
  success = consolidate_with_cli(data)
392
392
  if not success:
393
- log("Stage 2 failed falling back to skip (no v1 fallback)")
393
+ log("Stage 2 failed (CLI unavailable or error). "
394
+ "Skipping intelligent consolidation. Stage 3 (sensory + force) will still run.")
394
395
 
395
396
  # Stage 3: Sensory Register (mechanical, kept from v1)
396
397
  try:
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env /Library/Frameworks/Python.framework/Versions/3.12/bin/python3
2
+ """
3
+ NEXO Pre-Commit Validator — Dynamic version
4
+ Queries nexo.db learnings to surface relevant warnings/blockers before commit.
5
+ Runs standalone (no MCP dependency, just sqlite3).
6
+ """
7
+ import os
8
+ import sqlite3
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
14
+ NEXO_DB = NEXO_HOME / "data" / "nexo.db"
15
+
16
+
17
+ def get_staged_files():
18
+ """Get list of staged files."""
19
+ result = subprocess.run(
20
+ ['git', 'diff', '--cached', '--name-only'],
21
+ capture_output=True, text=True
22
+ )
23
+ return [f for f in result.stdout.strip().split('\n') if f]
24
+
25
+
26
+ def check_file(filepath, conn):
27
+ """Dynamic checks from learnings DB."""
28
+ errors, warnings = [], []
29
+
30
+ filename = Path(filepath).name
31
+ parent_dir = Path(filepath).parent.name
32
+
33
+ # 1. Find learnings mentioning this file or directory
34
+ file_learnings = conn.execute(
35
+ "SELECT id, title, content FROM learnings WHERE INSTR(content, ?) > 0 OR INSTR(content, ?) > 0",
36
+ (filename, parent_dir)
37
+ ).fetchall()
38
+
39
+ # 2. Check repetition count for each matching learning
40
+ for row in file_learnings:
41
+ lid, title = row[0], row[1]
42
+ rep_count = conn.execute(
43
+ "SELECT COUNT(*) FROM error_repetitions WHERE original_learning_id = ?",
44
+ (lid,)
45
+ ).fetchone()[0]
46
+
47
+ if rep_count >= 5:
48
+ errors.append(f"BLOCKED #{lid} ({rep_count}x repeated): {title[:80]}")
49
+ elif rep_count >= 3:
50
+ warnings.append(f"WARNING #{lid} ({rep_count}x repeated): {title[:80]}")
51
+
52
+ # 3. Universal rules (SIEMPRE/NUNCA/ANTES) matching this file
53
+ universal = conn.execute(
54
+ "SELECT id, title, content FROM learnings WHERE "
55
+ "(content LIKE '%SIEMPRE%' OR content LIKE '%NUNCA%' OR content LIKE '%ANTES%') "
56
+ "AND (INSTR(content, ?) > 0 OR INSTR(content, ?) > 0)",
57
+ (filename, parent_dir)
58
+ ).fetchall()
59
+
60
+ for row in universal:
61
+ lid, title = row[0], row[1]
62
+ # Don't duplicate if already found above
63
+ if not any(f"#{lid}" in e for e in errors + warnings):
64
+ warnings.append(f"RULE #{lid}: {title[:80]}")
65
+
66
+ return errors, warnings
67
+
68
+
69
+ def main():
70
+ """Run pre-commit validation."""
71
+ staged = get_staged_files()
72
+ if not staged:
73
+ print("No staged files to check")
74
+ return 0
75
+
76
+ if not NEXO_DB.exists():
77
+ print("nexo.db not found — skipping dynamic checks")
78
+ return 0
79
+
80
+ conn = sqlite3.connect(str(NEXO_DB), timeout=5)
81
+
82
+ all_errors = {}
83
+ all_warnings = {}
84
+
85
+ for filepath in staged:
86
+ errors, warnings = check_file(filepath, conn)
87
+ if errors:
88
+ all_errors[filepath] = errors
89
+ if warnings:
90
+ all_warnings[filepath] = warnings
91
+
92
+ conn.close()
93
+
94
+ # Print warnings (non-blocking)
95
+ if all_warnings:
96
+ print("\nWARNINGS:")
97
+ for filepath, warns in all_warnings.items():
98
+ print(f"\n {filepath}:")
99
+ for w in warns:
100
+ print(f" - {w}")
101
+
102
+ # Print errors (blocking)
103
+ if all_errors:
104
+ print("\nBLOCKED — Fix these before committing:\n")
105
+ for filepath, errs in all_errors.items():
106
+ print(f" {filepath}:")
107
+ for e in errs:
108
+ print(f" - {e}")
109
+ print()
110
+ return 1
111
+
112
+ if all_warnings:
113
+ print("\nPre-commit passed with warnings\n")
114
+ else:
115
+ print("Pre-commit validation passed")
116
+ return 0
117
+
118
+
119
+ if __name__ == '__main__':
120
+ sys.exit(main())
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+ # NEXO Prevent Sleep — keeps the machine awake so nocturnal processes run.
3
+ #
4
+ # macOS: uses caffeinate -s -i (prevent system + idle sleep)
5
+ # Linux: uses systemd-inhibit or caffeine if available, otherwise no-op
6
+ #
7
+ # Run as LaunchAgent (KeepAlive) or systemd service.
8
+
9
+ case "$(uname -s)" in
10
+ Darwin)
11
+ exec caffeinate -s -i -w $$
12
+ ;;
13
+ Linux)
14
+ if command -v systemd-inhibit &>/dev/null; then
15
+ exec systemd-inhibit --what=idle:sleep --who=nexo --why="NEXO nocturnal processes" sleep infinity
16
+ elif command -v caffeine &>/dev/null; then
17
+ exec caffeine
18
+ else
19
+ echo "[NEXO] No sleep prevention tool found. Install systemd-inhibit or caffeine."
20
+ echo "[NEXO] Nocturnal processes may not run if the system sleeps."
21
+ # Keep running so launchd/systemd doesn't restart loop
22
+ exec sleep infinity
23
+ fi
24
+ ;;
25
+ *)
26
+ echo "[NEXO] Unsupported platform: $(uname -s)"
27
+ exit 1
28
+ ;;
29
+ esac