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
@@ -135,21 +135,147 @@ def update_calibration_mood(synthesis: dict) -> dict:
135
135
  # Keep last 30 days
136
136
  cal["mood_history"] = cal["mood_history"][-30:]
137
137
 
138
- # Apply calibration recommendation if any
138
+ # Apply calibration recommendation automatically
139
139
  rec = emotional_day.get("calibration_recommendation")
140
140
  if rec and rec != "null":
141
- if "calibration_notes" not in cal:
142
- cal["calibration_notes"] = []
143
- cal["calibration_notes"].append({
141
+ applied_changes = []
142
+
143
+ # Parse and apply known calibration adjustments
144
+ rec_lower = rec.lower()
145
+ personality = cal.get("personality", {})
146
+
147
+ # Autonomy adjustments
148
+ if "autonomy" in rec_lower or "autonomía" in rec_lower:
149
+ if any(w in rec_lower for w in ["full", "más autonomía", "subir", "increase"]):
150
+ personality["autonomy"] = "full"
151
+ applied_changes.append("autonomy → full")
152
+ elif any(w in rec_lower for w in ["conservative", "reducir", "bajar"]):
153
+ personality["autonomy"] = "conservative"
154
+ applied_changes.append("autonomy → conservative")
155
+
156
+ # Communication adjustments
157
+ if any(w in rec_lower for w in ["concis", "breve", "shorter", "telegráf"]):
158
+ personality["communication"] = "concise"
159
+ applied_changes.append("communication → concise")
160
+ elif any(w in rec_lower for w in ["detail", "explicar más", "más contexto"]):
161
+ personality["communication"] = "detailed"
162
+ applied_changes.append("communication → detailed")
163
+
164
+ # Proactivity adjustments
165
+ if any(w in rec_lower for w in ["más proactiv", "proactive", "anticipar"]):
166
+ personality["proactivity"] = "proactive"
167
+ applied_changes.append("proactivity → proactive")
168
+
169
+ cal["personality"] = personality
170
+
171
+ # Log the recommendation and what was applied
172
+ if "calibration_log" not in cal:
173
+ cal["calibration_log"] = []
174
+ cal["calibration_log"].append({
144
175
  "date": synthesis.get("date", ""),
145
176
  "recommendation": rec,
146
- "applied": False,
177
+ "applied": applied_changes if applied_changes else ["noted, no auto-applicable changes"],
147
178
  })
148
- # Keep last 10
149
- cal["calibration_notes"] = cal["calibration_notes"][-10:]
179
+ cal["calibration_log"] = cal["calibration_log"][-20:]
150
180
 
151
181
  calibration_file.write_text(json.dumps(cal, indent=2, ensure_ascii=False))
152
- return {"success": True, "mood_score": emotional_day.get("mood_score")}
182
+ changes_str = ", ".join(applied_changes) if rec and applied_changes else "none"
183
+ return {"success": True, "mood_score": emotional_day.get("mood_score"), "calibration_applied": changes_str}
184
+ except Exception as e:
185
+ return {"success": False, "error": str(e)}
186
+
187
+
188
+ def calibrate_trust_score(synthesis: dict, target_date: str) -> dict:
189
+ """Set the daily trust score from Deep Sleep analysis.
190
+
191
+ This is the authoritative score for the day — replaces incremental
192
+ adjustments with a holistic evaluation of the entire day.
193
+ """
194
+ trust_cal = synthesis.get("trust_calibration")
195
+ if not trust_cal or "score" not in trust_cal:
196
+ return {"success": False, "error": "no trust_calibration in synthesis"}
197
+
198
+ score = max(0, min(100, trust_cal["score"]))
199
+ reasoning = trust_cal.get("reasoning", "Deep Sleep calibration")
200
+ trend = trust_cal.get("trend", "stable")
201
+ highlights = trust_cal.get("highlights", [])
202
+ lowlights = trust_cal.get("lowlights", [])
203
+
204
+ context = (
205
+ f"Deep Sleep {target_date} | trend: {trend} | "
206
+ f"highlights: {', '.join(highlights[:3])} | "
207
+ f"lowlights: {', '.join(lowlights[:3])}"
208
+ )
209
+
210
+ try:
211
+ # Get current score for delta calculation
212
+ db = sqlite3.connect(str(COGNITIVE_DB))
213
+ row = db.execute(
214
+ "SELECT score FROM trust_score ORDER BY id DESC LIMIT 1"
215
+ ).fetchone()
216
+ old_score = row[0] if row else 50.0
217
+ delta = score - old_score
218
+
219
+ db.execute(
220
+ "INSERT INTO trust_score (score, event, delta, context) VALUES (?, ?, ?, ?)",
221
+ (score, f"deep_sleep_calibration: {reasoning[:200]}", delta, context[:500])
222
+ )
223
+ db.commit()
224
+ db.close()
225
+
226
+ return {
227
+ "success": True,
228
+ "old_score": old_score,
229
+ "new_score": score,
230
+ "delta": delta,
231
+ "trend": trend,
232
+ }
233
+ except Exception as e:
234
+ return {"success": False, "error": str(e)}
235
+
236
+
237
+ def create_skill(skill_data: dict) -> dict:
238
+ """Create a skill in nexo.db from Deep Sleep extraction."""
239
+ if not NEXO_DB.exists():
240
+ return {"success": False, "error": "nexo.db not found"}
241
+ try:
242
+ import hashlib
243
+ skill_id = skill_data.get("id", "")
244
+ if not skill_id:
245
+ skill_id = "SK-DS-" + hashlib.md5(
246
+ skill_data.get("name", "").encode()
247
+ ).hexdigest()[:8].upper()
248
+
249
+ name = skill_data.get("name", "")
250
+ description = skill_data.get("description", "")
251
+ tags = json.dumps(skill_data.get("tags", []))
252
+ trigger_patterns = json.dumps(skill_data.get("trigger_patterns", []))
253
+ source_sessions = json.dumps(skill_data.get("source_sessions", []))
254
+ steps = skill_data.get("steps", [])
255
+ gotchas = skill_data.get("gotchas", [])
256
+
257
+ # Build file content for the skill .md file
258
+ steps_md = "\n".join(f"{i+1}. {s}" for i, s in enumerate(steps))
259
+ gotchas_md = "\n".join(f"- {g}" for g in gotchas) if gotchas else "None"
260
+
261
+ conn = sqlite3.connect(str(NEXO_DB))
262
+ # Check if skill already exists
263
+ existing = conn.execute("SELECT id FROM skills WHERE id = ?", (skill_id,)).fetchone()
264
+ if existing:
265
+ conn.close()
266
+ return {"success": False, "error": f"Skill {skill_id} already exists", "id": skill_id}
267
+
268
+ now = datetime.now().isoformat(timespec='seconds')
269
+ conn.execute(
270
+ """INSERT INTO skills
271
+ (id, name, description, level, trust_score, tags, trigger_patterns,
272
+ source_sessions, linked_learnings, created_at, updated_at)
273
+ VALUES (?, ?, ?, 'draft', 50, ?, ?, ?, '[]', ?, ?)""",
274
+ (skill_id, name, description, tags, trigger_patterns, source_sessions, now, now),
275
+ )
276
+ conn.commit()
277
+ conn.close()
278
+ return {"success": True, "id": skill_id, "name": name}
153
279
  except Exception as e:
154
280
  return {"success": False, "error": str(e)}
155
281
 
@@ -445,6 +571,11 @@ def apply_action(action: dict, run_id: str) -> dict:
445
571
  log_entry["status"] = "applied" if result.get("success") else "error"
446
572
  log_entry["details"] = result
447
573
 
574
+ elif action_type == "skill_create":
575
+ result = create_skill(content)
576
+ log_entry["status"] = "applied" if result.get("success") else "error"
577
+ log_entry["details"] = result
578
+
448
579
  elif action_type == "morning_briefing_item":
449
580
  # These are included in the briefing file, not applied separately
450
581
  log_entry["status"] = "included_in_briefing"
@@ -527,6 +658,35 @@ def main():
527
658
  else:
528
659
  print(f" Mood skip: {mood_result.get('error', '?')}")
529
660
 
661
+ # Calibrate trust score (authoritative daily score from Deep Sleep)
662
+ print("[apply] Calibrating trust score...")
663
+ trust_result = calibrate_trust_score(synthesis, target_date)
664
+ if trust_result.get("success"):
665
+ stats["applied"] += 1
666
+ print(f" Trust: {trust_result['old_score']:.0f} → {trust_result['new_score']:.0f} (Δ{trust_result['delta']:+.0f}, {trust_result['trend']})")
667
+ else:
668
+ print(f" Trust skip: {trust_result.get('error', '?')}")
669
+
670
+ # Create skills from synthesis
671
+ skills_data = synthesis.get("skills", [])
672
+ if skills_data:
673
+ print(f"[apply] Creating {len(skills_data)} skill(s)...")
674
+ for skill_data in skills_data:
675
+ if skill_data.get("confidence", 0) < 0.7:
676
+ continue
677
+ if skill_data.get("merge_with"):
678
+ print(f" Skip {skill_data.get('id', '?')}: merge candidate (needs runtime merge)")
679
+ continue
680
+ result = create_skill(skill_data)
681
+ if result.get("success"):
682
+ stats["applied"] += 1
683
+ print(f" Skill created: {result['id']} — {result.get('name', '')[:50]}")
684
+ elif "already exists" in result.get("error", ""):
685
+ stats["skipped_dedupe"] += 1
686
+ else:
687
+ stats["errors"] += 1
688
+ print(f" Skill error: {result.get('error', 'unknown')}", file=sys.stderr)
689
+
530
690
  # Create followups for abandoned projects
531
691
  abandoned_results = create_abandoned_followups(synthesis)
532
692
  for r in abandoned_results:
@@ -116,8 +116,15 @@ def extract_session(jsonl_path: Path) -> dict | None:
116
116
  }
117
117
 
118
118
 
119
- def collect_transcripts(target_date: str) -> list[dict]:
120
- """Collect all sessions modified on the target date."""
119
+ def collect_transcripts_since(since_iso: str, until_iso: str = "") -> list[dict]:
120
+ """Collect all sessions modified after `since_iso` (exclusive) up to `until_iso` (inclusive).
121
+
122
+ Uses a watermark approach: deep sleep tracks the last processed timestamp
123
+ so nothing is missed regardless of when sessions happen (day, night, etc.).
124
+ """
125
+ since_dt = datetime.fromisoformat(since_iso)
126
+ until_dt = datetime.fromisoformat(until_iso) if until_iso else datetime.now()
127
+
121
128
  sessions = []
122
129
  for sdir in find_session_dirs():
123
130
  for f in sdir.glob("*.jsonl"):
@@ -125,7 +132,7 @@ def collect_transcripts(target_date: str) -> list[dict]:
125
132
  mtime = datetime.fromtimestamp(f.stat().st_mtime)
126
133
  except OSError:
127
134
  continue
128
- if mtime.strftime("%Y-%m-%d") == target_date:
135
+ if since_dt < mtime <= until_dt:
129
136
  session = extract_session(f)
130
137
  if session:
131
138
  session["modified"] = mtime.isoformat()
@@ -339,25 +346,40 @@ def format_transcripts(sessions: list[dict]) -> str:
339
346
 
340
347
 
341
348
  def main():
342
- target_date = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
349
+ # Watermark-based collection: since_iso and until_iso passed by the wrapper script
350
+ # argv[1] = run_id (date label for output files)
351
+ # argv[2] = since_iso (exclusive lower bound, e.g. "2026-04-01T04:30:00")
352
+ # argv[3] = until_iso (inclusive upper bound, e.g. "2026-04-02T04:30:00") — optional, defaults to now
353
+ run_id = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
354
+ since_iso = sys.argv[2] if len(sys.argv) > 2 else ""
355
+ until_iso = sys.argv[3] if len(sys.argv) > 3 else ""
356
+
343
357
  DEEP_SLEEP_DIR.mkdir(parents=True, exist_ok=True)
344
358
 
345
- print(f"[collect] Phase 1: Collecting context for {target_date}")
359
+ print(f"[collect] Phase 1: Collecting context (run_id={run_id})")
346
360
 
347
- # 1. Transcripts
348
- print("[collect] Gathering transcripts...")
349
- sessions = collect_transcripts(target_date)
361
+ # 1. Transcripts — watermark-based
362
+ if since_iso:
363
+ print(f"[collect] Gathering transcripts since {since_iso}" + (f" until {until_iso}" if until_iso else ""))
364
+ sessions = collect_transcripts_since(since_iso, until_iso)
365
+ else:
366
+ # Fallback: collect everything from last 48h (safe catch-all)
367
+ fallback_since = (datetime.now() - timedelta(hours=48)).isoformat()
368
+ print(f"[collect] No watermark — collecting last 48h since {fallback_since}")
369
+ sessions = collect_transcripts_since(fallback_since)
350
370
  print(f" Found {len(sessions)} sessions")
351
371
 
352
372
  if not sessions:
353
- print(f"[collect] No sessions found for {target_date}. Writing minimal context file.")
354
- output_file = DEEP_SLEEP_DIR / f"{target_date}-context.txt"
373
+ print(f"[collect] No new sessions found. Writing minimal context file.")
374
+ output_file = DEEP_SLEEP_DIR / f"{run_id}-context.txt"
355
375
  output_file.write_text(
356
- f"Deep Sleep Context for {target_date}\n\nNo sessions found for this date.\n"
376
+ f"Deep Sleep Context for {run_id}\n\nNo sessions found.\n"
357
377
  )
358
378
  print(f"[collect] Output: {output_file}")
359
379
  return
360
380
 
381
+ target_date = run_id # Keep variable name for downstream compat
382
+
361
383
  # 2. Core DB data
362
384
  print("[collect] Querying databases...")
363
385
  followups = collect_followups()
@@ -58,6 +58,22 @@ Detect work that was started but not finished in this session:
58
58
  - Investigations started but conclusions never reached
59
59
  Only flag if the work was NOT captured in a followup or reminder.
60
60
 
61
+ ### 9. Skill Candidates (Reusable Procedures)
62
+ Detect multi-step tasks that were completed successfully and could be reused:
63
+ - Tasks that required 3+ distinct steps to complete
64
+ - Tasks where the agent followed a clear sequence of actions
65
+ - Procedures that are likely to be repeated in the future
66
+ - Examples: deploying code, configuring a service, running an audit, setting up infrastructure
67
+
68
+ For each candidate, extract:
69
+ - The full step-by-step procedure (what was actually done, in order)
70
+ - Tags describing the domain (e.g., "shopify", "chrome", "deploy")
71
+ - Trigger phrases that would indicate this procedure is needed (e.g., "deploy extension", "push theme")
72
+ - Any gotchas or warnings discovered during execution
73
+
74
+ Only flag if the procedure was SUCCESSFUL (the task was completed without major failures).
75
+ Do NOT flag trivial tasks (single-step actions, simple file edits, quick lookups).
76
+
61
77
  ### 8. Productivity Patterns
62
78
  Analyze how the session went in terms of efficiency:
63
79
  - How many times did the agent need correction before getting it right?
@@ -195,6 +211,28 @@ Return ONLY valid JSON. No markdown code fences. No explanation text before or a
195
211
  }
196
212
  ],
197
213
 
214
+ "skill_candidates": [
215
+ {
216
+ "name": "Short name for the procedure (e.g., Deploy Chrome Extension)",
217
+ "description": "What this procedure accomplishes (1-2 sentences)",
218
+ "steps": [
219
+ "Step 1: What was done first",
220
+ "Step 2: What was done next",
221
+ "Step 3: etc."
222
+ ],
223
+ "tags": ["domain1", "domain2"],
224
+ "trigger_phrases": ["phrase that would trigger this", "another trigger"],
225
+ "gotchas": ["Warning or caveat discovered during execution"],
226
+ "evidence": {
227
+ "type": "transcript",
228
+ "session_id": "filename.jsonl",
229
+ "message_index": 10,
230
+ "quote": "Start of the multi-step task"
231
+ },
232
+ "confidence": 0.85
233
+ }
234
+ ],
235
+
198
236
  "productivity_score": {
199
237
  "corrections_needed": 0,
200
238
  "proactivity": "reactive|mixed|proactive",
@@ -113,6 +113,14 @@ def analyze_session(session_id: str, date_dir: Path, shared_context_file: Path |
113
113
  try:
114
114
  env = os.environ.copy()
115
115
  env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
116
+ env.pop("CLAUDECODE", None)
117
+ env.pop("CLAUDE_CODE", None)
118
+
119
+ JSON_SYSTEM_PROMPT = (
120
+ "You are a JSON-only analyst. Your ENTIRE response must be a single valid JSON object. "
121
+ "No text before it. No text after it. No markdown fences. No explanations. "
122
+ "If you want to summarize, put it inside the JSON fields. Start with { and end with }."
123
+ )
116
124
 
117
125
  result = subprocess.run(
118
126
  [
@@ -120,8 +128,9 @@ def analyze_session(session_id: str, date_dir: Path, shared_context_file: Path |
120
128
  "-p", prompt,
121
129
  "--model", "opus",
122
130
  "--output-format", "text",
131
+ "--append-system-prompt", JSON_SYSTEM_PROMPT,
123
132
  "--allowedTools",
124
- "Read,Grep,Bash,mcp__nexo__nexo_startup,mcp__nexo__nexo_learning_search,mcp__nexo__nexo_recall"
133
+ "Read,Grep,Bash"
125
134
  ],
126
135
  capture_output=True,
127
136
  text=True,
@@ -139,6 +148,28 @@ def analyze_session(session_id: str, date_dir: Path, shared_context_file: Path |
139
148
  if not line.strip().startswith("Post-mortem") and line.strip()
140
149
  )
141
150
  parsed = extract_json_from_response(output)
151
+
152
+ # Fallback: if Claude returned text instead of JSON, ask a short conversion call
153
+ if not parsed and len(output.strip()) > 50:
154
+ print(f" Got text instead of JSON ({len(output)} chars). Converting...")
155
+ convert_prompt = (
156
+ f"Convert the following analysis into the exact JSON schema required. "
157
+ f"Return ONLY the JSON object, nothing else.\n\n"
158
+ f"Analysis:\n{output[:8000]}\n\n"
159
+ f"Required schema: session_id, findings[], emotional_timeline[], "
160
+ f"abandoned_projects[], skill_candidates[], productivity_score, protocol_summary"
161
+ )
162
+ convert_result = subprocess.run(
163
+ [claude_bin, "-p", convert_prompt, "--model", "sonnet",
164
+ "--output-format", "text",
165
+ "--append-system-prompt", JSON_SYSTEM_PROMPT],
166
+ capture_output=True, text=True, timeout=120, env=env
167
+ )
168
+ if convert_result.returncode == 0:
169
+ parsed = extract_json_from_response(convert_result.stdout)
170
+ if parsed:
171
+ print(f" Conversion succeeded")
172
+
142
173
  if not parsed:
143
174
  # Save raw output for debugging
144
175
  debug_file = DEEP_SLEEP_DIR / f"debug-extract-{session_id[:20]}.txt"
@@ -207,32 +238,70 @@ def main():
207
238
  print(f"[extract] Phase 2: Analyzing {len(session_files)} sessions for {target_date}")
208
239
  print(f"[extract] Claude CLI: {claude_bin}")
209
240
 
241
+ # Checkpoint directory: one JSON per session, survives crashes
242
+ checkpoint_dir = date_dir / "checkpoints"
243
+ checkpoint_dir.mkdir(parents=True, exist_ok=True)
244
+
210
245
  all_extractions = []
211
246
  total_findings = 0
247
+ skipped = 0
248
+ MAX_RETRIES = 3
212
249
 
213
250
  for i, session_id in enumerate(session_files):
251
+ sid_safe = session_id.replace(".jsonl", "")[:30]
252
+ checkpoint_file = checkpoint_dir / f"{sid_safe}.json"
253
+
254
+ # Resume: skip already-processed sessions
255
+ if checkpoint_file.exists():
256
+ try:
257
+ with open(checkpoint_file) as f:
258
+ cached = json.load(f)
259
+ findings_count = len(cached.get("findings", []))
260
+ total_findings += findings_count
261
+ all_extractions.append(cached)
262
+ skipped += 1
263
+ print(f"[extract] Session {i + 1}/{len(session_files)}: {session_id} (cached, {findings_count} findings)")
264
+ continue
265
+ except (json.JSONDecodeError, KeyError):
266
+ pass # Corrupted checkpoint, re-process
267
+
214
268
  print(f"[extract] Session {i + 1}/{len(session_files)}: {session_id}")
215
269
 
216
- result = analyze_session(session_id, date_dir, shared_context_file, claude_bin)
270
+ # Retry loop
271
+ result = None
272
+ for attempt in range(1, MAX_RETRIES + 1):
273
+ result = analyze_session(session_id, date_dir, shared_context_file, claude_bin)
274
+ if result:
275
+ break
276
+ if attempt < MAX_RETRIES:
277
+ print(f" -> Attempt {attempt}/{MAX_RETRIES} failed, retrying...")
217
278
 
218
279
  if result:
219
280
  findings_count = len(result.get("findings", []))
220
281
  total_findings += findings_count
221
282
  all_extractions.append(result)
222
- print(f" -> {findings_count} findings extracted")
283
+ # Save checkpoint
284
+ with open(checkpoint_file, "w") as f:
285
+ json.dump(result, f, indent=2, ensure_ascii=False)
286
+ print(f" -> {findings_count} findings extracted (checkpointed)")
223
287
  else:
224
- print(f" -> Extraction failed, continuing with next session")
225
- all_extractions.append({
288
+ print(f" -> Failed after {MAX_RETRIES} attempts, marking as failed")
289
+ failed_entry = {
226
290
  "session_id": session_id,
227
291
  "findings": [],
228
- "error": "Extraction failed"
229
- })
292
+ "error": f"Extraction failed after {MAX_RETRIES} attempts"
293
+ }
294
+ all_extractions.append(failed_entry)
295
+ # Save failed checkpoint too (so we don't retry forever)
296
+ with open(checkpoint_file, "w") as f:
297
+ json.dump(failed_entry, f, indent=2, ensure_ascii=False)
230
298
 
231
299
  # Merge into output
232
300
  output = {
233
301
  "date": target_date,
234
302
  "sessions_analyzed": len(session_files),
235
303
  "sessions_succeeded": len([e for e in all_extractions if "error" not in e]),
304
+ "sessions_cached": skipped,
236
305
  "total_findings": total_findings,
237
306
  "extractions": all_extractions
238
307
  }
@@ -241,7 +310,10 @@ def main():
241
310
  with open(output_file, "w") as f:
242
311
  json.dump(output, f, indent=2, ensure_ascii=False)
243
312
 
244
- print(f"\n[extract] Done. {total_findings} total findings from {len(session_files)} sessions.")
313
+ if skipped:
314
+ print(f"\n[extract] Done. {total_findings} findings from {len(session_files)} sessions ({skipped} cached, {len(session_files) - skipped} new).")
315
+ else:
316
+ print(f"\n[extract] Done. {total_findings} findings from {len(session_files)} sessions.")
245
317
  print(f"[extract] Output: {output_file}")
246
318
 
247
319
 
@@ -52,7 +52,41 @@ Consolidate `abandoned_projects` from all sessions:
52
52
  - Cross-reference across sessions — was the abandoned work picked up later in another session?
53
53
  - Only flag projects that are truly abandoned (no followup AND not resumed)
54
54
 
55
- ### 7. Consolidated Actions
55
+ ### 7. Trust Calibration (CRITICAL)
56
+ Score the agent's performance for the day on a scale of 0-100. This score becomes the agent's trust score and directly affects its autonomy level the next day. Be fair but honest.
57
+
58
+ Scoring guide:
59
+ - **90-100**: Flawless day. Zero corrections needed. Proactive. Anticipated user needs. Deployed code without issues.
60
+ - **70-89**: Good day. Minor corrections, quickly resolved. Mostly proactive. User satisfied.
61
+ - **50-69**: Average day. Some corrections, some reactive behavior. Mixed results.
62
+ - **30-49**: Below average. Multiple corrections. Repeated mistakes. User had to push.
63
+ - **0-29**: Bad day. Many corrections, repeated errors, user frustrated. Broke things.
64
+
65
+ Consider ALL of these:
66
+ - Number and severity of corrections (most important signal)
67
+ - Tasks completed successfully vs failed
68
+ - Did the agent act autonomously or wait to be told?
69
+ - Did the agent catch its own mistakes or did the user?
70
+ - Did the agent repeat known errors (worst offense)?
71
+ - User emotional signals throughout the day
72
+ - Code deployed: did it work first try?
73
+
74
+ The score should feel fair. A day with 2 minor corrections and 10 tasks completed is still a good day (75+). A day with 1 catastrophic error might be a 40 even if everything else was fine.
75
+
76
+ ### 9. Skill Extraction
77
+ Consolidate `skill_candidates` from all session extractions into publishable skills:
78
+ - Merge similar procedures from different sessions into a single skill
79
+ - Generalize: replace session-specific IDs, paths, or names with placeholders or descriptions
80
+ - Only include skills with confidence >= 0.7
81
+ - Check if a similar skill already exists (use `nexo_skill_match` if available) — if so, note it for merging instead of creating new
82
+
83
+ For each skill, generate:
84
+ - A unique ID starting with `SK-` (e.g., `SK-DEPLOY-CHROME-EXT`)
85
+ - Name, description, tags, trigger_patterns
86
+ - The full step-by-step procedure as the skill content
87
+ - Source session IDs for traceability
88
+
89
+ ### 8. Consolidated Actions
56
90
  Merge and deduplicate all findings into a final action list. Each action should have:
57
91
  - `action_type`: `learning_add`, `followup_create`, `morning_briefing_item`
58
92
  - `action_class`: `auto_apply` (confidence >= 0.8, reversible) or `draft_for_morning` (confidence < 0.8 or high impact)
@@ -101,9 +135,24 @@ Return ONLY valid JSON. No markdown code fences. No explanation text.
101
135
  }
102
136
  ],
103
137
 
138
+ "skills": [
139
+ {
140
+ "id": "SK-SHORT-ID",
141
+ "name": "Human readable name",
142
+ "description": "What this procedure does (1-2 sentences)",
143
+ "steps": ["Step 1", "Step 2", "Step 3"],
144
+ "tags": ["tag1", "tag2"],
145
+ "trigger_patterns": ["trigger phrase 1", "trigger phrase 2"],
146
+ "gotchas": ["Warning or caveat"],
147
+ "source_sessions": ["session1.jsonl"],
148
+ "confidence": 0.85,
149
+ "merge_with": null
150
+ }
151
+ ],
152
+
104
153
  "actions": [
105
154
  {
106
- "action_type": "learning_add|followup_create|morning_briefing_item",
155
+ "action_type": "learning_add|followup_create|skill_create|morning_briefing_item",
107
156
  "action_class": "auto_apply|draft_for_morning",
108
157
  "confidence": 0.9,
109
158
  "impact": "low|medium|high",
@@ -147,6 +196,14 @@ Return ONLY valid JSON. No markdown code fences. No explanation text.
147
196
  }
148
197
  ],
149
198
 
199
+ "trust_calibration": {
200
+ "score": 72,
201
+ "reasoning": "Why this score -- based on corrections, completions, autonomy, proactivity, and user satisfaction signals across ALL sessions",
202
+ "highlights": ["What went well"],
203
+ "lowlights": ["What went poorly"],
204
+ "trend": "improving|stable|declining"
205
+ },
206
+
150
207
  "summary": "2-3 sentence overall assessment of the day"
151
208
  }
152
209
  ```
@@ -115,6 +115,8 @@ def main():
115
115
  try:
116
116
  env = os.environ.copy()
117
117
  env["NEXO_HEADLESS"] = "1" # Skip stop hook post-mortem
118
+ env.pop("CLAUDECODE", None)
119
+ env.pop("CLAUDE_CODE", None)
118
120
 
119
121
  result = subprocess.run(
120
122
  [
@@ -123,7 +125,7 @@ def main():
123
125
  "--model", "opus",
124
126
  "--output-format", "text",
125
127
  "--allowedTools",
126
- "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__nexo_startup,mcp__nexo__nexo_learning_search,mcp__nexo__nexo_recall,mcp__nexo__nexo_reminders"
128
+ "Read,Grep,Bash"
127
129
  ],
128
130
  capture_output=True,
129
131
  text=True,
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env python3
2
+ """DEPRECATED: Updates are handled automatically by NEXO on startup."""
3
+ import sys
4
+ print("This script is deprecated. NEXO auto-updates on startup.")
5
+ print("To update manually, use the nexo_update MCP tool.")
6
+ sys.exit(0)
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+ # NEXO DB hourly backup — crontab: 0 * * * * $NEXO_HOME/scripts/nexo-backup.sh
3
+ NEXO_HOME="${NEXO_HOME:-$HOME/.nexo}"
4
+ NEXO_DIR="$NEXO_HOME"
5
+ BACKUP_DIR="$NEXO_HOME/backups"
6
+ WEEKLY_DIR="$BACKUP_DIR/weekly"
7
+ DB="$NEXO_HOME/data/nexo.db"
8
+ RETENTION_HOURS=48
9
+
10
+ mkdir -p "$BACKUP_DIR" "$WEEKLY_DIR"
11
+
12
+ # Hourly backup
13
+ TIMESTAMP=$(date +%Y-%m-%d-%H%M)
14
+ sqlite3 "$DB" ".backup '$BACKUP_DIR/nexo-$TIMESTAMP.db'"
15
+
16
+ # Weekly backup — save one per week (Sundays)
17
+ WEEK=$(date +%Y-W%V)
18
+ WEEKLY_FILE="$WEEKLY_DIR/weekly-$WEEK.db"
19
+ if [ ! -f "$WEEKLY_FILE" ] && [ "$(date +%u)" = "7" ]; then
20
+ cp "$BACKUP_DIR/nexo-$TIMESTAMP.db" "$WEEKLY_FILE"
21
+ fi
22
+
23
+ # Cleanup: hourly >48h, weekly >90 days
24
+ find "$BACKUP_DIR" -maxdepth 1 -name "nexo-*.db" -mmin +$((RETENTION_HOURS * 60)) -delete
25
+ find "$WEEKLY_DIR" -name "weekly-*.db" -mtime +90 -delete