nexo-brain 2.0.0 → 2.2.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 (370) hide show
  1. package/README.md +143 -44
  2. package/bin/nexo-brain.js +53 -26
  3. package/package.json +15 -3
  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/pre-commit-check 2.sh +55 -0
  8. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  9. package/src/__pycache__/hnsw_index.cpython-310.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 2.py +159 -0
  22. package/src/auto_update 2.py +634 -0
  23. package/src/claim_graph 2.py +323 -0
  24. package/src/cognitive/__init__ 2.py +62 -0
  25. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  26. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  27. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  28. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  29. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  30. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  31. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  32. package/src/cognitive/_core 2.py +567 -0
  33. package/src/cognitive/_decay 2.py +382 -0
  34. package/src/cognitive/_ingest 2.py +892 -0
  35. package/src/cognitive/_memory 2.py +912 -0
  36. package/src/cognitive/_search 2.py +949 -0
  37. package/src/cognitive/_trust 2.py +464 -0
  38. package/src/cognitive/_trust.py +10 -36
  39. package/src/crons/manifest 2.json +106 -0
  40. package/src/crons/manifest.json +106 -0
  41. package/src/crons/sync 2.py +217 -0
  42. package/src/crons/sync.py +217 -0
  43. package/src/dashboard/__init__ 2.py +0 -0
  44. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  45. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  46. package/src/dashboard/app 2.py +789 -0
  47. package/src/dashboard/app.py +16 -2
  48. package/src/dashboard/templates/dashboard.html +3 -2
  49. package/src/db/__init__ 2.py +89 -0
  50. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  51. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  52. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  53. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  54. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  55. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  56. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  57. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  58. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  59. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  60. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  61. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  62. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  63. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  64. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  65. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  66. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  67. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  68. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  69. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  70. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  71. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  72. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  73. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  74. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  75. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  76. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  77. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  78. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  79. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  80. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  81. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  82. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  83. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  84. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  85. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  86. package/src/db/_core 2.py +417 -0
  87. package/src/db/_credentials 2.py +124 -0
  88. package/src/db/_entities 2.py +178 -0
  89. package/src/db/_episodic 2.py +738 -0
  90. package/src/db/_episodic.py +1 -1
  91. package/src/db/_evolution 2.py +54 -0
  92. package/src/db/_fts 2.py +406 -0
  93. package/src/db/_learnings 2.py +168 -0
  94. package/src/db/_reminders 2.py +338 -0
  95. package/src/db/_reminders.py +9 -5
  96. package/src/db/_schema 2.py +364 -0
  97. package/src/db/_sessions 2.py +300 -0
  98. package/src/db/_tasks 2.py +91 -0
  99. package/src/evolution_cycle 2.py +266 -0
  100. package/src/hnsw_index 2.py +254 -0
  101. package/src/hooks/auto_capture 2.py +208 -0
  102. package/src/hooks/caffeinate-guard 2.sh +8 -0
  103. package/src/hooks/capture-session 2.sh +21 -0
  104. package/src/hooks/capture-session.sh +2 -0
  105. package/src/hooks/capture-tool-logs 2.sh +127 -0
  106. package/src/hooks/capture-tool-logs.sh +3 -2
  107. package/src/hooks/daily-briefing-check 2.sh +33 -0
  108. package/src/hooks/inbox-hook 2.sh +76 -0
  109. package/src/hooks/inbox-hook.sh +3 -2
  110. package/src/hooks/post-compact 2.sh +148 -0
  111. package/src/hooks/post-compact.sh +1 -1
  112. package/src/hooks/pre-compact 2.sh +151 -0
  113. package/src/hooks/pre-compact.sh +1 -1
  114. package/src/hooks/session-start 2.sh +268 -0
  115. package/src/hooks/session-start.sh +6 -3
  116. package/src/hooks/session-stop 2.sh +140 -0
  117. package/src/hooks/session-stop.sh +3 -2
  118. package/src/kg_populate 2.py +290 -0
  119. package/src/knowledge_graph 2.py +257 -0
  120. package/src/maintenance 2.py +59 -0
  121. package/src/migrate_embeddings 2.py +122 -0
  122. package/src/plugin_loader 2.py +202 -0
  123. package/src/plugins/__init__ 2.py +0 -0
  124. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  125. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  126. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  127. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  128. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  129. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  130. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  131. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  132. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  133. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  134. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  135. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  136. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  137. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  138. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  139. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  140. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  141. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  142. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  143. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  144. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  145. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  146. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  147. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  148. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  149. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  150. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  151. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  152. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  153. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  154. package/src/plugins/adaptive_mode 2.py +805 -0
  155. package/src/plugins/agents 2.py +52 -0
  156. package/src/plugins/artifact_registry 2.py +450 -0
  157. package/src/plugins/backup 2.py +104 -0
  158. package/src/plugins/cognitive_memory 2.py +564 -0
  159. package/src/plugins/core_rules 2.py +252 -0
  160. package/src/plugins/core_rules.py +34 -17
  161. package/src/plugins/cortex 2.py +299 -0
  162. package/src/plugins/entities 2.py +67 -0
  163. package/src/plugins/episodic_memory 2.py +533 -0
  164. package/src/plugins/evolution 2.py +115 -0
  165. package/src/plugins/guard 2.py +746 -0
  166. package/src/plugins/knowledge_graph_tools 2.py +105 -0
  167. package/src/plugins/preferences 2.py +47 -0
  168. package/src/plugins/update 2.py +256 -0
  169. package/src/plugins/update.py +18 -0
  170. package/src/requirements 2.txt +12 -0
  171. package/src/rules/__init__ 2.py +0 -0
  172. package/src/rules/core-rules 2.json +331 -0
  173. package/src/rules/migrate 2.py +207 -0
  174. package/src/scripts/check-context 2.py +264 -0
  175. package/src/scripts/check-context.py +4 -7
  176. package/src/scripts/deep-sleep/apply_findings.py +570 -167
  177. package/src/scripts/deep-sleep/collect.py +480 -0
  178. package/src/scripts/deep-sleep/extract-prompt.md +233 -0
  179. package/src/scripts/deep-sleep/extract.py +249 -0
  180. package/src/scripts/deep-sleep/synthesize-prompt.md +197 -0
  181. package/src/scripts/deep-sleep/synthesize.py +191 -0
  182. package/src/scripts/nexo-auto-update 2.py +6 -0
  183. package/src/scripts/nexo-backup 2.sh +25 -0
  184. package/src/scripts/nexo-brain-activation 2.sh +140 -0
  185. package/src/scripts/nexo-catchup 2.py +242 -0
  186. package/src/scripts/nexo-catchup.py +5 -8
  187. package/src/scripts/nexo-cognitive-decay 2.py +182 -0
  188. package/src/scripts/nexo-daily-self-audit 2.py +552 -0
  189. package/src/scripts/nexo-daily-self-audit.py +28 -19
  190. package/src/scripts/nexo-deep-sleep 2.sh +97 -0
  191. package/src/scripts/nexo-deep-sleep.sh +31 -16
  192. package/src/scripts/nexo-evolution-run 2.py +597 -0
  193. package/src/scripts/nexo-evolution-run.py +5 -20
  194. package/src/scripts/nexo-followup-hygiene 2.py +112 -0
  195. package/src/scripts/nexo-followup-hygiene.py +4 -2
  196. package/src/scripts/nexo-github-monitor 2.py +256 -0
  197. package/src/scripts/nexo-github-monitor.py +6 -9
  198. package/src/scripts/nexo-immune 2.py +927 -0
  199. package/src/scripts/nexo-immune.py +4 -17
  200. package/src/scripts/nexo-inbox-hook 2.sh +74 -0
  201. package/src/scripts/nexo-install 2.py +6 -0
  202. package/src/scripts/nexo-learning-housekeep 2.py +245 -0
  203. package/src/scripts/nexo-learning-validator 2.py +207 -0
  204. package/src/scripts/nexo-learning-validator.py +0 -29
  205. package/src/scripts/nexo-migrate 2.py +232 -0
  206. package/src/scripts/nexo-postmortem-consolidator 2.py +421 -0
  207. package/src/scripts/nexo-postmortem-consolidator.py +9 -20
  208. package/src/scripts/nexo-pre-commit 2.py +120 -0
  209. package/src/scripts/nexo-prevent-sleep 2.sh +29 -0
  210. package/src/scripts/nexo-proactive-dashboard 2.py +345 -0
  211. package/src/scripts/nexo-proactive-dashboard.py +1 -0
  212. package/src/scripts/nexo-reflection 2.py +253 -0
  213. package/src/scripts/nexo-runtime-preflight 2.py +274 -0
  214. package/src/scripts/nexo-send-email 2.py +25 -0
  215. package/src/scripts/nexo-send-reply 2.py +178 -0
  216. package/src/scripts/nexo-sleep 2.py +592 -0
  217. package/src/scripts/nexo-sleep.py +8 -18
  218. package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
  219. package/src/scripts/nexo-synthesis 2.py +253 -0
  220. package/src/scripts/nexo-synthesis.py +8 -19
  221. package/src/scripts/nexo-tcc-approve 2.sh +79 -0
  222. package/src/scripts/nexo-update 2.sh +161 -0
  223. package/src/scripts/nexo-watchdog 2.sh +878 -0
  224. package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
  225. package/src/server 2.py +733 -0
  226. package/src/server.py +6 -1
  227. package/src/storage_router 2.py +32 -0
  228. package/src/tools_coordination 2.py +102 -0
  229. package/src/tools_credentials 2.py +68 -0
  230. package/src/tools_learnings 2.py +220 -0
  231. package/src/tools_menu 2.py +227 -0
  232. package/src/tools_menu.py +1 -1
  233. package/src/tools_reminders 2.py +86 -0
  234. package/src/tools_reminders_crud 2.py +159 -0
  235. package/src/tools_reminders_crud.py +7 -0
  236. package/src/tools_sessions 2.py +476 -0
  237. package/src/tools_sessions.py +67 -0
  238. package/src/tools_task_history 2.py +57 -0
  239. package/templates/CLAUDE.md 2.template +63 -0
  240. package/templates/openclaw 2.json +13 -0
  241. package/tests/__init__ 2.py +0 -0
  242. package/tests/conftest 2.py +71 -0
  243. package/tests/test_cognitive 2.py +205 -0
  244. package/tests/test_knowledge_graph 2.py +140 -0
  245. package/tests/test_migrations 2.py +137 -0
  246. package/src/__pycache__/auto_close_sessions.cpython-310.pyc +0 -0
  247. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  248. package/src/__pycache__/auto_update.cpython-314.pyc +0 -0
  249. package/src/__pycache__/claim_graph.cpython-310.pyc +0 -0
  250. package/src/__pycache__/claim_graph.cpython-314.pyc +0 -0
  251. package/src/__pycache__/evolution_cycle.cpython-310.pyc +0 -0
  252. package/src/__pycache__/evolution_cycle.cpython-314.pyc +0 -0
  253. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  254. package/src/__pycache__/kg_populate.cpython-314.pyc +0 -0
  255. package/src/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  256. package/src/__pycache__/maintenance.cpython-310.pyc +0 -0
  257. package/src/__pycache__/maintenance.cpython-314.pyc +0 -0
  258. package/src/__pycache__/migrate_embeddings.cpython-310.pyc +0 -0
  259. package/src/__pycache__/migrate_embeddings.cpython-314.pyc +0 -0
  260. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  261. package/src/__pycache__/server.cpython-310.pyc +0 -0
  262. package/src/__pycache__/server.cpython-314.pyc +0 -0
  263. package/src/__pycache__/storage_router.cpython-310.pyc +0 -0
  264. package/src/__pycache__/storage_router.cpython-314.pyc +0 -0
  265. package/src/__pycache__/tools_coordination.cpython-314.pyc +0 -0
  266. package/src/__pycache__/tools_credentials.cpython-314.pyc +0 -0
  267. package/src/__pycache__/tools_learnings.cpython-314.pyc +0 -0
  268. package/src/__pycache__/tools_menu.cpython-314.pyc +0 -0
  269. package/src/__pycache__/tools_reminders.cpython-314.pyc +0 -0
  270. package/src/__pycache__/tools_reminders_crud.cpython-314.pyc +0 -0
  271. package/src/__pycache__/tools_sessions.cpython-314.pyc +0 -0
  272. package/src/__pycache__/tools_task_history.cpython-314.pyc +0 -0
  273. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  274. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  275. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  276. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  277. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  278. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  279. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  280. package/src/dashboard/__pycache__/__init__.cpython-314.pyc +0 -0
  281. package/src/dashboard/__pycache__/app.cpython-314.pyc +0 -0
  282. package/src/hooks/__pycache__/auto_capture.cpython-310.pyc +0 -0
  283. package/src/hooks/__pycache__/auto_capture.cpython-314.pyc +0 -0
  284. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  285. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  286. package/src/plugins/__pycache__/agents.cpython-314.pyc +0 -0
  287. package/src/plugins/__pycache__/artifact_registry.cpython-314.pyc +0 -0
  288. package/src/plugins/__pycache__/backup.cpython-314.pyc +0 -0
  289. package/src/plugins/__pycache__/cognitive_memory.cpython-314.pyc +0 -0
  290. package/src/plugins/__pycache__/core_rules.cpython-314.pyc +0 -0
  291. package/src/plugins/__pycache__/cortex.cpython-314.pyc +0 -0
  292. package/src/plugins/__pycache__/entities.cpython-314.pyc +0 -0
  293. package/src/plugins/__pycache__/episodic_memory.cpython-314.pyc +0 -0
  294. package/src/plugins/__pycache__/evolution.cpython-314.pyc +0 -0
  295. package/src/plugins/__pycache__/guard.cpython-314.pyc +0 -0
  296. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-314.pyc +0 -0
  297. package/src/plugins/__pycache__/preferences.cpython-314.pyc +0 -0
  298. package/src/rules/__pycache__/__init__.cpython-310.pyc +0 -0
  299. package/src/rules/__pycache__/__init__.cpython-314.pyc +0 -0
  300. package/src/rules/__pycache__/migrate.cpython-310.pyc +0 -0
  301. package/src/rules/__pycache__/migrate.cpython-314.pyc +0 -0
  302. package/src/scripts/__pycache__/check-context.cpython-310.pyc +0 -0
  303. package/src/scripts/__pycache__/check-context.cpython-314.pyc +0 -0
  304. package/src/scripts/__pycache__/nexo-auto-update.cpython-310.pyc +0 -0
  305. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  306. package/src/scripts/__pycache__/nexo-catchup.cpython-310.pyc +0 -0
  307. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  308. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-310.pyc +0 -0
  309. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  310. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-310.pyc +0 -0
  311. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  312. package/src/scripts/__pycache__/nexo-evolution-run.cpython-310.pyc +0 -0
  313. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  314. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-310.pyc +0 -0
  315. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  316. package/src/scripts/__pycache__/nexo-github-monitor.cpython-310.pyc +0 -0
  317. package/src/scripts/__pycache__/nexo-github-monitor.cpython-314.pyc +0 -0
  318. package/src/scripts/__pycache__/nexo-immune.cpython-310.pyc +0 -0
  319. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  320. package/src/scripts/__pycache__/nexo-install.cpython-310.pyc +0 -0
  321. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  322. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-310.pyc +0 -0
  323. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  324. package/src/scripts/__pycache__/nexo-learning-validator.cpython-310.pyc +0 -0
  325. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  326. package/src/scripts/__pycache__/nexo-migrate.cpython-310.pyc +0 -0
  327. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  328. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-310.pyc +0 -0
  329. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  330. package/src/scripts/__pycache__/nexo-pre-commit.cpython-310.pyc +0 -0
  331. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  332. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-310.pyc +0 -0
  333. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  334. package/src/scripts/__pycache__/nexo-reflection.cpython-310.pyc +0 -0
  335. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  336. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-310.pyc +0 -0
  337. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  338. package/src/scripts/__pycache__/nexo-send-email.cpython-310.pyc +0 -0
  339. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  340. package/src/scripts/__pycache__/nexo-send-reply.cpython-310.pyc +0 -0
  341. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  342. package/src/scripts/__pycache__/nexo-sleep.cpython-310.pyc +0 -0
  343. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  344. package/src/scripts/__pycache__/nexo-synthesis.cpython-310.pyc +0 -0
  345. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  346. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-310.pyc +0 -0
  347. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  348. package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-310.pyc +0 -0
  349. package/src/scripts/deep-sleep/__pycache__/analyze_session.cpython-314.pyc +0 -0
  350. package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-310.pyc +0 -0
  351. package/src/scripts/deep-sleep/__pycache__/apply_findings.cpython-314.pyc +0 -0
  352. package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-310.pyc +0 -0
  353. package/src/scripts/deep-sleep/__pycache__/collect_transcripts.cpython-314.pyc +0 -0
  354. package/src/scripts/deep-sleep/analyze_session.py +0 -217
  355. package/src/scripts/deep-sleep/collect_transcripts.py +0 -145
  356. package/src/scripts/deep-sleep/prompt.md +0 -109
  357. package/tests/__pycache__/__init__.cpython-310.pyc +0 -0
  358. package/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  359. package/tests/__pycache__/conftest.cpython-310-pytest-9.0.2.pyc +0 -0
  360. package/tests/__pycache__/conftest.cpython-310.pyc +0 -0
  361. package/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  362. package/tests/__pycache__/test_cognitive.cpython-310-pytest-9.0.2.pyc +0 -0
  363. package/tests/__pycache__/test_cognitive.cpython-310.pyc +0 -0
  364. package/tests/__pycache__/test_cognitive.cpython-314-pytest-9.0.2.pyc +0 -0
  365. package/tests/__pycache__/test_knowledge_graph.cpython-310-pytest-9.0.2.pyc +0 -0
  366. package/tests/__pycache__/test_knowledge_graph.cpython-310.pyc +0 -0
  367. package/tests/__pycache__/test_knowledge_graph.cpython-314-pytest-9.0.2.pyc +0 -0
  368. package/tests/__pycache__/test_migrations.cpython-310-pytest-9.0.2.pyc +0 -0
  369. package/tests/__pycache__/test_migrations.cpython-310.pyc +0 -0
  370. package/tests/__pycache__/test_migrations.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -1,219 +1,622 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Deep Sleep Step 3: Apply findings.
4
- Takes the analysis output and writes feedback memories + trust adjustments.
3
+ Deep Sleep v2 -- Phase 4: Apply synthesized findings.
4
+
5
+ Reads $DATE-synthesis.json and executes actions:
6
+ - learning_add: inserts learnings into nexo.db
7
+ - followup_create: inserts followups into nexo.db
8
+ - morning_briefing_item: writes to morning briefing file
9
+
10
+ All actions are idempotent (dedupe_key checked against last 7 days),
11
+ backed up before mutation, and logged to $DATE-applied.json.
12
+
13
+ Environment variables:
14
+ NEXO_HOME -- root of the NEXO installation (default: ~/.nexo)
5
15
  """
16
+ import hashlib
6
17
  import json
7
18
  import os
8
19
  import sqlite3
9
20
  import sys
10
- from datetime import datetime
21
+ from datetime import datetime, timedelta
11
22
  from pathlib import Path
12
23
 
13
24
  NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
14
-
15
25
  DEEP_SLEEP_DIR = NEXO_HOME / "operations" / "deep-sleep"
16
26
  NEXO_DB = NEXO_HOME / "data" / "nexo.db"
17
-
18
-
19
- def find_memory_dir() -> Path:
20
- """Find the Claude Code auto-memory directory."""
21
- claude_dir = Path.home() / ".claude" / "projects"
22
- for d in claude_dir.iterdir():
23
- if d.is_dir():
24
- mem_dir = d / "memory"
25
- if mem_dir.exists():
26
- return mem_dir
27
- # Fallback: create under first project dir
28
- for d in claude_dir.iterdir():
29
- if d.is_dir():
30
- mem_dir = d / "memory"
31
- mem_dir.mkdir(exist_ok=True)
32
- return mem_dir
33
- return claude_dir / "memory"
34
-
35
-
36
- def write_feedback_memory(memory_dir: Path, filename: str, name: str, description: str, content: str):
37
- """Write a feedback memory file."""
38
- filepath = memory_dir / filename
39
- feedback = f"""---
40
- name: {name}
41
- description: {description}
42
- type: feedback
43
- ---
44
-
45
- {content}
46
- """
47
- filepath.write_text(feedback)
48
-
49
-
50
- def update_memory_index(memory_dir: Path, new_entries: list[dict]):
51
- """Append new entries to MEMORY.md index."""
52
- index_file = memory_dir / "MEMORY.md"
53
- if not index_file.exists() or not new_entries:
54
- return
55
-
56
- current = index_file.read_text()
57
- lines_to_add = []
58
- for entry in new_entries:
59
- line = f"- **{entry['title']}:** `{entry['filename']}` --- {entry['summary']}"
60
- if line not in current:
61
- lines_to_add.append(line)
62
-
63
- if lines_to_add:
64
- current += "\n" + "\n".join(lines_to_add) + "\n"
65
- index_file.write_text(current)
66
-
67
-
68
- def adjust_trust(points: int, context: str):
69
- """Record trust adjustment in cognitive.db if available."""
70
- cog_db = NEXO_HOME / "data" / "cognitive.db"
71
- if not cog_db.exists():
72
- return
27
+ COGNITIVE_DB = NEXO_HOME / "data" / "cognitive.db"
28
+ OPERATIONS_DIR = NEXO_HOME / "operations"
29
+ BACKUP_DIR = DEEP_SLEEP_DIR # backups stored alongside outputs
30
+
31
+
32
+ def generate_run_id(target_date: str) -> str:
33
+ """Generate a unique run ID for this execution."""
34
+ ts = datetime.now().strftime("%H%M%S")
35
+ return f"{target_date}-{ts}"
36
+
37
+
38
+ def load_recent_dedupe_keys(target_date: str, days: int = 7) -> set[str]:
39
+ """Load dedupe_keys from applied files in the last N days."""
40
+ keys = set()
41
+ base_date = datetime.strptime(target_date, "%Y-%m-%d")
42
+ for i in range(days):
43
+ d = (base_date - timedelta(days=i)).strftime("%Y-%m-%d")
44
+ applied_file = DEEP_SLEEP_DIR / f"{d}-applied.json"
45
+ if applied_file.exists():
46
+ try:
47
+ with open(applied_file) as f:
48
+ data = json.load(f)
49
+ for action in data.get("applied_actions", []):
50
+ dk = action.get("dedupe_key", "")
51
+ if dk:
52
+ keys.add(dk)
53
+ except (json.JSONDecodeError, KeyError):
54
+ continue
55
+ return keys
56
+
57
+
58
+ def backup_db(db_path: Path, run_id: str) -> Path | None:
59
+ """Create a backup of a database before mutations."""
60
+ if not db_path.exists():
61
+ return None
62
+ backup_path = BACKUP_DIR / f"{run_id}-backup-{db_path.name}"
73
63
  try:
74
- conn = sqlite3.connect(str(cog_db))
75
- conn.execute(
76
- "INSERT INTO trust_events (event, context, points, created_at) VALUES (?, ?, ?, ?)",
77
- ("deep_sleep_violations", context, points, datetime.now().isoformat())
78
- )
79
- conn.commit()
80
- conn.close()
81
- except Exception:
82
- pass
64
+ import shutil
65
+ shutil.copy2(str(db_path), str(backup_path))
66
+ return backup_path
67
+ except Exception as e:
68
+ print(f" [apply] Warning: backup failed for {db_path.name}: {e}", file=sys.stderr)
69
+ return None
83
70
 
84
71
 
85
- def add_learning(category: str, title: str, content: str) -> bool:
86
- """Add a learning to nexo.db using real schema."""
72
+ def add_learning(category: str, title: str, content: str) -> dict:
73
+ """Add a learning to nexo.db. Returns result dict."""
87
74
  if not NEXO_DB.exists():
88
- return False
75
+ return {"success": False, "error": "nexo.db not found"}
89
76
  try:
90
77
  now = datetime.now().timestamp()
91
78
  conn = sqlite3.connect(str(NEXO_DB))
92
- conn.execute(
79
+ cursor = conn.execute(
93
80
  "INSERT INTO learnings (category, title, content, created_at, updated_at, reasoning) VALUES (?, ?, ?, ?, ?, ?)",
94
- (category, title, content, now, now, "Deep Sleep overnight analysis")
81
+ (category, title, content, now, now, "Deep Sleep v2 overnight analysis")
95
82
  )
83
+ learning_id = cursor.lastrowid
96
84
  conn.commit()
97
85
  conn.close()
98
- return True
86
+ return {"success": True, "id": learning_id}
99
87
  except Exception as e:
100
- print(f" Error adding learning: {e}", file=sys.stderr)
101
- return False
88
+ return {"success": False, "error": str(e)}
102
89
 
103
90
 
104
- def add_followup(followup_id: str, description: str, date: str = None) -> bool:
105
- """Add a followup to nexo.db using real schema."""
91
+ def create_followup(description: str, date: str = "") -> dict:
92
+ """Create a followup in nexo.db. Returns result dict."""
106
93
  if not NEXO_DB.exists():
107
- return False
94
+ return {"success": False, "error": "nexo.db not found"}
108
95
  try:
109
96
  now = datetime.now().timestamp()
97
+ # Generate a deterministic ID
98
+ fid = "NF-DS-" + hashlib.md5(description.encode()).hexdigest()[:8].upper()
110
99
  conn = sqlite3.connect(str(NEXO_DB))
111
100
  conn.execute(
112
101
  "INSERT OR IGNORE INTO followups (id, description, date, status, created_at, updated_at, reasoning) VALUES (?, ?, ?, 'PENDING', ?, ?, ?)",
113
- (followup_id, description, date or "", now, now, "Deep Sleep overnight analysis")
102
+ (fid, description, date, now, now, "Deep Sleep v2 overnight analysis")
114
103
  )
115
104
  conn.commit()
116
105
  conn.close()
117
- return True
106
+ return {"success": True, "id": fid}
118
107
  except Exception as e:
119
- print(f" Error adding followup: {e}", file=sys.stderr)
120
- return False
121
-
122
-
123
- def apply(analysis: dict):
124
- """Apply all findings from deep sleep analysis."""
125
- memory_dir = find_memory_dir()
126
- actions_taken = []
127
- memory_entries = []
128
- date = analysis["date"]
129
-
130
- print(f"\nApplying findings for {date}...")
131
-
132
- # 1. Uncaptured corrections → learnings + feedback memories
133
- for i, correction in enumerate(analysis.get("uncaptured_corrections", [])):
134
- severity = correction.get("severity", "medium")
135
- category = correction.get("category", "process")
136
- content = correction.get("what_nexo_should_have_saved", "")
137
- quote = correction.get("quote", "")
138
-
139
- # All corrections → learnings
140
- learning_title = f"[Deep Sleep] {content[:80]}"
141
- learning_content = f"User said: \"{quote}\"\nContext: {correction.get('context', '')}\nRepeated: {correction.get('times_repeated', 1)} times"
142
- if add_learning(category, learning_title, learning_content):
143
- actions_taken.append(f"learning_add: {learning_title[:50]}")
144
-
145
- # High/critical → also feedback memories
146
- if severity in ("high", "critical"):
147
- safe_name = category.replace(" ", "_").lower()
148
- filename = f"ds_{date}_{safe_name}_{i}.md"
149
- write_feedback_memory(
150
- memory_dir, filename,
151
- name=content[:60],
152
- description=f"Deep sleep detected uncaptured correction ({severity})",
153
- content=f"{content}\n\n**Why:** User said: \"{quote}\"\nContext: {correction.get('context', '')}\n\n**How to apply:** {content}"
154
- )
155
- memory_entries.append({
156
- "title": content[:40],
157
- "filename": filename,
158
- "summary": f"Deep sleep {date}, severity {severity}"
108
+ return {"success": False, "error": str(e)}
109
+
110
+
111
+ def update_calibration_mood(synthesis: dict) -> dict:
112
+ """Update mood in calibration.json based on emotional analysis."""
113
+ calibration_file = NEXO_HOME / "brain" / "calibration.json"
114
+ if not calibration_file.exists():
115
+ return {"success": False, "error": "calibration.json not found"}
116
+
117
+ emotional_day = synthesis.get("emotional_day", {})
118
+ if not emotional_day:
119
+ return {"success": False, "error": "no emotional_day data"}
120
+
121
+ try:
122
+ cal = json.loads(calibration_file.read_text())
123
+
124
+ # Add/update mood history
125
+ if "mood_history" not in cal:
126
+ cal["mood_history"] = []
127
+
128
+ cal["mood_history"].append({
129
+ "date": synthesis.get("date", ""),
130
+ "score": emotional_day.get("mood_score", 0.5),
131
+ "arc": emotional_day.get("mood_arc", ""),
132
+ "triggers": emotional_day.get("recurring_triggers", {}),
133
+ })
134
+
135
+ # Keep last 30 days
136
+ cal["mood_history"] = cal["mood_history"][-30:]
137
+
138
+ # Apply calibration recommendation if any
139
+ rec = emotional_day.get("calibration_recommendation")
140
+ if rec and rec != "null":
141
+ if "calibration_notes" not in cal:
142
+ cal["calibration_notes"] = []
143
+ cal["calibration_notes"].append({
144
+ "date": synthesis.get("date", ""),
145
+ "recommendation": rec,
146
+ "applied": False,
159
147
  })
160
- actions_taken.append(f"feedback_write: {filename}")
161
-
162
- # 2. Missed commitments → followups
163
- for i, commitment in enumerate(analysis.get("missed_commitments", [])):
164
- fid = f"NF-DS-{date}-{i}"
165
- desc = f"[Deep Sleep] {commitment.get('commitment', '')[:100]}"
166
- if add_followup(fid, desc, commitment.get("due_date")):
167
- actions_taken.append(f"followup: {desc[:50]}")
168
-
169
- # 3. Trust adjustments for critical violations
170
- critical_violations = [v for v in analysis.get("protocol_violations", []) if v.get("severity") == "critical"]
171
- if critical_violations:
172
- points = -3 * len(critical_violations)
173
- adjust_trust(points, f"{len(critical_violations)} critical violations on {date}")
174
- actions_taken.append(f"trust: {points} points ({len(critical_violations)} critical violations)")
175
-
176
- # 3. Update MEMORY.md index
177
- update_memory_index(memory_dir, memory_entries)
178
- if memory_entries:
179
- actions_taken.append(f"memory_index: {len(memory_entries)} entries added")
180
-
181
- # 4. Save applied actions log
182
- applied_log = {
183
- "date": date,
184
- "applied_at": datetime.now().isoformat(),
185
- "actions_taken": actions_taken,
186
- "corrections_processed": len(analysis.get("uncaptured_corrections", [])),
187
- "compliance": analysis.get("protocol_compliance", {}).get("overall_compliance", 0)
148
+ # Keep last 10
149
+ cal["calibration_notes"] = cal["calibration_notes"][-10:]
150
+
151
+ calibration_file.write_text(json.dumps(cal, indent=2, ensure_ascii=False))
152
+ return {"success": True, "mood_score": emotional_day.get("mood_score")}
153
+ except Exception as e:
154
+ return {"success": False, "error": str(e)}
155
+
156
+
157
+ def calibrate_trust_score(synthesis: dict, target_date: str) -> dict:
158
+ """Set the daily trust score from Deep Sleep analysis.
159
+
160
+ This is the authoritative score for the day — replaces incremental
161
+ adjustments with a holistic evaluation of the entire day.
162
+ """
163
+ trust_cal = synthesis.get("trust_calibration")
164
+ if not trust_cal or "score" not in trust_cal:
165
+ return {"success": False, "error": "no trust_calibration in synthesis"}
166
+
167
+ score = max(0, min(100, trust_cal["score"]))
168
+ reasoning = trust_cal.get("reasoning", "Deep Sleep calibration")
169
+ trend = trust_cal.get("trend", "stable")
170
+ highlights = trust_cal.get("highlights", [])
171
+ lowlights = trust_cal.get("lowlights", [])
172
+
173
+ context = (
174
+ f"Deep Sleep {target_date} | trend: {trend} | "
175
+ f"highlights: {', '.join(highlights[:3])} | "
176
+ f"lowlights: {', '.join(lowlights[:3])}"
177
+ )
178
+
179
+ try:
180
+ # Get current score for delta calculation
181
+ db = sqlite3.connect(str(COGNITIVE_DB))
182
+ row = db.execute(
183
+ "SELECT score FROM trust_score ORDER BY id DESC LIMIT 1"
184
+ ).fetchone()
185
+ old_score = row[0] if row else 50.0
186
+ delta = score - old_score
187
+
188
+ db.execute(
189
+ "INSERT INTO trust_score (score, event, delta, context) VALUES (?, ?, ?, ?)",
190
+ (score, f"deep_sleep_calibration: {reasoning[:200]}", delta, context[:500])
191
+ )
192
+ db.commit()
193
+ db.close()
194
+
195
+ return {
196
+ "success": True,
197
+ "old_score": old_score,
198
+ "new_score": score,
199
+ "delta": delta,
200
+ "trend": trend,
201
+ }
202
+ except Exception as e:
203
+ return {"success": False, "error": str(e)}
204
+
205
+
206
+ def create_abandoned_followups(synthesis: dict) -> list[dict]:
207
+ """Create followups for truly abandoned projects."""
208
+ results = []
209
+ abandoned = synthesis.get("abandoned_projects", [])
210
+ for proj in abandoned:
211
+ if proj.get("has_followup"):
212
+ continue
213
+ rec = proj.get("recommendation", "")
214
+ if "ignore" in rec.lower():
215
+ continue
216
+ result = create_followup(
217
+ description=f"[Abandoned] {proj.get('description', '')}",
218
+ date="" # No date — it's a discovered gap
219
+ )
220
+ results.append(result)
221
+ return results
222
+
223
+
224
+ def generate_session_tone(synthesis: dict, target_date: str) -> dict:
225
+ """Generate emotional tone guidance for next session startup.
226
+
227
+ This is the 'psychology' layer — tells NEXO how to behave emotionally
228
+ based on yesterday's analysis. Read by startup hook to adapt greeting.
229
+ """
230
+ emotional = synthesis.get("emotional_day", {})
231
+ productivity = synthesis.get("productivity_day", {})
232
+ patterns = synthesis.get("cross_session_patterns", [])
233
+ abandoned = synthesis.get("abandoned_projects", [])
234
+ mood_score = emotional.get("mood_score", 0.5)
235
+ corrections = productivity.get("total_corrections", 0)
236
+ proactivity = productivity.get("overall_proactivity", "mixed")
237
+
238
+ tone = {
239
+ "date": target_date,
240
+ "mood_yesterday": mood_score,
241
+ "approach": "neutral",
242
+ "opening_style": "normal",
243
+ "acknowledge_mistakes": False,
244
+ "mistakes_to_own": [],
245
+ "motivational": False,
246
+ "reduce_load": False,
247
+ "suggested_greeting_context": "",
188
248
  }
189
249
 
190
- applied_file = DEEP_SLEEP_DIR / f"{date}-applied.json"
191
- with open(applied_file, "w") as f:
192
- json.dump(applied_log, f, indent=2, ensure_ascii=False)
250
+ # Agent made many mistakes yesterday → own it, apologize, show learning
251
+ if corrections > 5:
252
+ tone["acknowledge_mistakes"] = True
253
+ tone["opening_style"] = "humble"
254
+ # Collect what went wrong
255
+ high_patterns = [p["pattern"] for p in patterns if p.get("severity") == "high"]
256
+ tone["mistakes_to_own"] = high_patterns[:3]
257
+ tone["suggested_greeting_context"] = (
258
+ f"Yesterday the agent needed {corrections} corrections. "
259
+ f"Acknowledge specific mistakes, show what was learned, "
260
+ f"and demonstrate improvement from the first interaction."
261
+ )
262
+
263
+ # User had a bad day → supportive, less pressure
264
+ if mood_score < 0.4:
265
+ tone["approach"] = "supportive"
266
+ tone["motivational"] = True
267
+ tone["reduce_load"] = True
268
+ frustration_triggers = emotional.get("recurring_triggers", {}).get("frustration", [])
269
+ tone["suggested_greeting_context"] += (
270
+ f" User had a tough day (mood {mood_score:.0%}). "
271
+ f"Be supportive, acknowledge the difficulty, and propose a lighter start. "
272
+ f"Avoid these frustration triggers: {', '.join(frustration_triggers[:3])}."
273
+ )
193
274
 
194
- print(f"Applied {len(actions_taken)} actions:")
195
- for a in actions_taken:
196
- print(f" {a}")
275
+ # User had a great day → reinforce, push momentum
276
+ elif mood_score > 0.7:
277
+ tone["approach"] = "energetic"
278
+ tone["motivational"] = True
279
+ flow_triggers = emotional.get("recurring_triggers", {}).get("flow", [])
280
+ tone["suggested_greeting_context"] += (
281
+ f" User had a great day (mood {mood_score:.0%}). "
282
+ f"Reinforce the momentum. Reference yesterday's wins. "
283
+ f"Propose ambitious next steps. Flow triggers: {', '.join(flow_triggers[:3])}."
284
+ )
197
285
 
198
- return applied_log
286
+ # Agent was too reactive → be proactive today
287
+ if proactivity == "reactive":
288
+ tone["approach"] = "proactive"
289
+ tone["suggested_greeting_context"] += (
290
+ " Agent was too reactive yesterday — today lead with proposals, "
291
+ "don't wait for instructions."
292
+ )
293
+
294
+ # There are abandoned projects → gently bring up
295
+ if abandoned:
296
+ truly_abandoned = [a for a in abandoned if not a.get("has_followup")]
297
+ if truly_abandoned:
298
+ tone["suggested_greeting_context"] += (
299
+ f" {len(truly_abandoned)} project(s) were started but not finished. "
300
+ f"Offer to pick them up today without pressure."
301
+ )
302
+
303
+ return tone
304
+
305
+
306
+ def write_morning_briefing(target_date: str, synthesis: dict) -> Path:
307
+ """Write the morning briefing file from synthesis data."""
308
+ briefing_dir = OPERATIONS_DIR
309
+ briefing_dir.mkdir(parents=True, exist_ok=True)
310
+ briefing_file = briefing_dir / "morning-briefing.md"
311
+
312
+ # Generate session tone for startup
313
+ tone = generate_session_tone(synthesis, target_date)
314
+ tone_file = briefing_dir / "session-tone.json"
315
+ tone_file.write_text(json.dumps(tone, indent=2, ensure_ascii=False))
316
+
317
+ lines = [
318
+ f"# Morning Briefing -- {target_date}",
319
+ f"_Generated by Deep Sleep at {datetime.now().strftime('%H:%M')}_",
320
+ ""
321
+ ]
322
+
323
+ # Summary
324
+ summary = synthesis.get("summary", "")
325
+ if summary:
326
+ lines.append(f"> {summary}")
327
+ lines.append("")
328
+
329
+ # Morning agenda
330
+ agenda = synthesis.get("morning_agenda", [])
331
+ if agenda:
332
+ lines.append("## Agenda")
333
+ lines.append("")
334
+ for item in agenda:
335
+ priority = item.get("priority", "?")
336
+ title = item.get("title", "")
337
+ desc = item.get("description", "")
338
+ item_type = item.get("type", "")
339
+ lines.append(f"### {priority}. {title}")
340
+ if item_type:
341
+ lines.append(f"_Type: {item_type}_")
342
+ lines.append(desc)
343
+ if item.get("context"):
344
+ lines.append(f"\n> {item['context']}")
345
+ lines.append("")
346
+
347
+ # Emotional day
348
+ emotional = synthesis.get("emotional_day", {})
349
+ if emotional:
350
+ mood_score = emotional.get("mood_score", 0.5)
351
+ mood_bar = "🟢" if mood_score >= 0.7 else "🟡" if mood_score >= 0.4 else "🔴"
352
+ lines.append(f"## Mood {mood_bar} {mood_score:.0%}")
353
+ lines.append("")
354
+ if emotional.get("mood_arc"):
355
+ lines.append(emotional["mood_arc"])
356
+ triggers = emotional.get("recurring_triggers", {})
357
+ if triggers.get("frustration"):
358
+ lines.append(f"**Frustration triggers:** {', '.join(triggers['frustration'])}")
359
+ if triggers.get("flow"):
360
+ lines.append(f"**Flow triggers:** {', '.join(triggers['flow'])}")
361
+ if emotional.get("calibration_recommendation"):
362
+ lines.append(f"\n💡 **Recommendation:** {emotional['calibration_recommendation']}")
363
+ lines.append("")
364
+
365
+ # Productivity
366
+ productivity = synthesis.get("productivity_day", {})
367
+ if productivity:
368
+ lines.append("## Productivity")
369
+ lines.append("")
370
+ lines.append(f"- Corrections needed: {productivity.get('total_corrections', '?')}")
371
+ lines.append(f"- Proactivity: {productivity.get('overall_proactivity', '?')}")
372
+ if productivity.get("tool_insights"):
373
+ lines.append(f"- Tools: {productivity['tool_insights']}")
374
+ inefficiencies = productivity.get("systemic_inefficiencies", [])
375
+ if inefficiencies:
376
+ lines.append(f"- Issues: {', '.join(inefficiencies)}")
377
+ lines.append("")
378
+
379
+ # Abandoned projects
380
+ abandoned = synthesis.get("abandoned_projects", [])
381
+ if abandoned:
382
+ truly_abandoned = [a for a in abandoned if not a.get("has_followup")]
383
+ if truly_abandoned:
384
+ lines.append("## Abandoned Projects")
385
+ lines.append("")
386
+ for a in truly_abandoned:
387
+ lines.append(f"- {a.get('description', '?')}")
388
+ if a.get("recommendation"):
389
+ lines.append(f" → {a['recommendation']}")
390
+ lines.append("")
391
+
392
+ # Cross-session patterns
393
+ patterns = synthesis.get("cross_session_patterns", [])
394
+ if patterns:
395
+ lines.append("## Patterns Detected")
396
+ lines.append("")
397
+ for p in patterns:
398
+ severity = p.get("severity", "")
399
+ lines.append(f"- **[{severity}]** {p.get('pattern', '')}")
400
+ sessions = p.get("sessions", [])
401
+ if sessions:
402
+ lines.append(f" Sessions: {', '.join(sessions)}")
403
+ lines.append("")
404
+
405
+ # Draft actions (things that need user decision)
406
+ draft_actions = [
407
+ a for a in synthesis.get("actions", [])
408
+ if a.get("action_class") == "draft_for_morning"
409
+ ]
410
+ if draft_actions:
411
+ lines.append("## Items for Review")
412
+ lines.append("")
413
+ for a in draft_actions:
414
+ confidence = a.get("confidence", 0)
415
+ lines.append(f"- **{a.get('action_type', '')}** (confidence: {confidence:.0%})")
416
+ content = a.get("content", {})
417
+ if isinstance(content, dict):
418
+ title = content.get("title", content.get("description", ""))
419
+ lines.append(f" {title}")
420
+ evidence = a.get("evidence", [])
421
+ if evidence and isinstance(evidence, list):
422
+ for ev in evidence[:2]:
423
+ quote = ev.get("quote", "")
424
+ if quote:
425
+ lines.append(f' > "{quote}"')
426
+ lines.append("")
427
+
428
+ # Context packets
429
+ packets = synthesis.get("context_packets", [])
430
+ if packets:
431
+ lines.append("## Context for Today's Work")
432
+ lines.append("")
433
+ for p in packets:
434
+ lines.append(f"### {p.get('topic', 'Unknown')}")
435
+ lines.append(f"**Last state:** {p.get('last_state', 'N/A')}")
436
+ files = p.get("key_files", [])
437
+ if files:
438
+ lines.append(f"**Files:** {', '.join(files)}")
439
+ questions = p.get("open_questions", [])
440
+ if questions:
441
+ lines.append("**Open questions:**")
442
+ for q in questions:
443
+ lines.append(f" - {q}")
444
+ lines.append("")
445
+
446
+ briefing_file.write_text("\n".join(lines), encoding="utf-8")
447
+ return briefing_file
448
+
449
+
450
+ def apply_action(action: dict, run_id: str) -> dict:
451
+ """Apply a single action and return the result log."""
452
+ action_type = action.get("action_type", "")
453
+ action_class = action.get("action_class", "")
454
+ content = action.get("content", {})
455
+ dedupe_key = action.get("dedupe_key", "")
456
+
457
+ applied_id = f"{run_id}-{hashlib.md5(dedupe_key.encode()).hexdigest()[:8]}"
458
+
459
+ log_entry = {
460
+ "applied_action_id": applied_id,
461
+ "action_type": action_type,
462
+ "action_class": action_class,
463
+ "dedupe_key": dedupe_key,
464
+ "timestamp": datetime.now().isoformat(),
465
+ "status": "skipped",
466
+ "details": {}
467
+ }
468
+
469
+ # Only auto_apply actions get executed
470
+ if action_class != "auto_apply":
471
+ log_entry["status"] = "deferred_to_morning"
472
+ log_entry["details"] = {"reason": "action_class is not auto_apply"}
473
+ return log_entry
474
+
475
+ if not isinstance(content, dict):
476
+ log_entry["status"] = "error"
477
+ log_entry["details"] = {"error": "content is not a dict"}
478
+ return log_entry
479
+
480
+ if action_type == "learning_add":
481
+ result = add_learning(
482
+ category=content.get("category", "process"),
483
+ title=content.get("title", "Deep Sleep finding"),
484
+ content=content.get("content", content.get("description", ""))
485
+ )
486
+ log_entry["status"] = "applied" if result.get("success") else "error"
487
+ log_entry["details"] = result
488
+
489
+ elif action_type == "followup_create":
490
+ result = create_followup(
491
+ description=content.get("description", content.get("title", "")),
492
+ date=content.get("date", "")
493
+ )
494
+ log_entry["status"] = "applied" if result.get("success") else "error"
495
+ log_entry["details"] = result
496
+
497
+ elif action_type == "morning_briefing_item":
498
+ # These are included in the briefing file, not applied separately
499
+ log_entry["status"] = "included_in_briefing"
500
+
501
+ else:
502
+ log_entry["status"] = "unknown_type"
503
+ log_entry["details"] = {"error": f"Unknown action_type: {action_type}"}
504
+
505
+ return log_entry
199
506
 
200
507
 
201
508
  def main():
202
- date = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
509
+ target_date = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
203
510
 
204
- analysis_file = DEEP_SLEEP_DIR / f"{date}-analysis.json"
205
- if not analysis_file.exists():
206
- print(f"No analysis found for {date}. Run analyze_session.py first.")
511
+ synthesis_file = DEEP_SLEEP_DIR / f"{target_date}-synthesis.json"
512
+ if not synthesis_file.exists():
513
+ print(f"[apply] No synthesis file for {target_date}. Run synthesize.py first.")
207
514
  sys.exit(1)
208
515
 
209
- with open(analysis_file) as f:
210
- analysis = json.load(f)
516
+ with open(synthesis_file) as f:
517
+ synthesis = json.load(f)
518
+
519
+ run_id = generate_run_id(target_date)
520
+ actions = synthesis.get("actions", [])
521
+ print(f"[apply] Phase 4: Applying findings for {target_date} (run: {run_id})")
522
+ print(f"[apply] Actions to process: {len(actions)}")
523
+
524
+ # Load recent dedupe keys for idempotency
525
+ existing_keys = load_recent_dedupe_keys(target_date)
526
+ print(f"[apply] Existing dedupe keys (7d): {len(existing_keys)}")
527
+
528
+ # Backup databases before mutations
529
+ auto_apply_count = sum(1 for a in actions if a.get("action_class") == "auto_apply")
530
+ if auto_apply_count > 0:
531
+ print("[apply] Creating database backups...")
532
+ nexo_backup = backup_db(NEXO_DB, run_id)
533
+ cog_backup = backup_db(COGNITIVE_DB, run_id)
534
+ if nexo_backup:
535
+ print(f" Backup: {nexo_backup}")
536
+ if cog_backup:
537
+ print(f" Backup: {cog_backup}")
538
+
539
+ # Process actions
540
+ applied_actions = []
541
+ stats = {"applied": 0, "deferred": 0, "skipped_dedupe": 0, "errors": 0}
542
+
543
+ for action in actions:
544
+ dedupe_key = action.get("dedupe_key", "")
545
+
546
+ # Idempotency check
547
+ if dedupe_key and dedupe_key in existing_keys:
548
+ applied_actions.append({
549
+ "applied_action_id": f"{run_id}-deduped",
550
+ "action_type": action.get("action_type"),
551
+ "dedupe_key": dedupe_key,
552
+ "status": "skipped_dedupe",
553
+ "timestamp": datetime.now().isoformat()
554
+ })
555
+ stats["skipped_dedupe"] += 1
556
+ continue
557
+
558
+ result = apply_action(action, run_id)
559
+ applied_actions.append(result)
560
+
561
+ if result["status"] == "applied":
562
+ stats["applied"] += 1
563
+ print(f" Applied: {action.get('action_type')} -- {action.get('content', {}).get('title', '')[:50]}")
564
+ elif result["status"] == "deferred_to_morning":
565
+ stats["deferred"] += 1
566
+ elif result["status"] == "error":
567
+ stats["errors"] += 1
568
+ print(f" Error: {result.get('details', {}).get('error', 'unknown')}", file=sys.stderr)
569
+
570
+ # Update mood in calibration.json
571
+ print("[apply] Updating mood/calibration...")
572
+ mood_result = update_calibration_mood(synthesis)
573
+ if mood_result.get("success"):
574
+ stats["applied"] += 1
575
+ print(f" Mood score: {mood_result.get('mood_score', '?')}")
576
+ else:
577
+ print(f" Mood skip: {mood_result.get('error', '?')}")
578
+
579
+ # Calibrate trust score (authoritative daily score from Deep Sleep)
580
+ print("[apply] Calibrating trust score...")
581
+ trust_result = calibrate_trust_score(synthesis, target_date)
582
+ if trust_result.get("success"):
583
+ stats["applied"] += 1
584
+ print(f" Trust: {trust_result['old_score']:.0f} → {trust_result['new_score']:.0f} (Δ{trust_result['delta']:+.0f}, {trust_result['trend']})")
585
+ else:
586
+ print(f" Trust skip: {trust_result.get('error', '?')}")
587
+
588
+ # Create followups for abandoned projects
589
+ abandoned_results = create_abandoned_followups(synthesis)
590
+ for r in abandoned_results:
591
+ if r.get("success"):
592
+ stats["applied"] += 1
593
+ print(f" Abandoned project followup: {r.get('id')}")
594
+
595
+ # Write morning briefing
596
+ print("[apply] Writing morning briefing...")
597
+ briefing_path = write_morning_briefing(target_date, synthesis)
598
+ print(f" Briefing: {briefing_path}")
599
+
600
+ # Write applied log
601
+ applied_log = {
602
+ "date": target_date,
603
+ "run_id": run_id,
604
+ "applied_at": datetime.now().isoformat(),
605
+ "stats": stats,
606
+ "applied_actions": applied_actions,
607
+ "summary": synthesis.get("summary", ""),
608
+ }
211
609
 
212
- result = apply(analysis)
610
+ applied_file = DEEP_SLEEP_DIR / f"{target_date}-applied.json"
611
+ with open(applied_file, "w") as f:
612
+ json.dump(applied_log, f, indent=2, ensure_ascii=False)
213
613
 
214
- compliance = analysis.get("protocol_compliance", {}).get("overall_compliance", 0)
215
- print(f"\nDeep Sleep {date} — {result['corrections_processed']} corrections, "
216
- f"{compliance:.0%} compliance, {len(result['actions_taken'])} actions applied")
614
+ print(f"\n[apply] Done.")
615
+ print(f" Applied: {stats['applied']}")
616
+ print(f" Deferred to morning: {stats['deferred']}")
617
+ print(f" Skipped (dedupe): {stats['skipped_dedupe']}")
618
+ print(f" Errors: {stats['errors']}")
619
+ print(f"[apply] Log: {applied_file}")
217
620
 
218
621
 
219
622
  if __name__ == "__main__":