nexo-brain 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/README.md +1 -1
  2. package/bin/nexo-brain.js +92 -9
  3. package/bin/postinstall.js +22 -15
  4. package/package.json +7 -4
  5. package/src/auto_update.py +194 -5
  6. package/src/crons/sync.py +6 -2
  7. package/src/db/_core.py +1 -0
  8. package/src/db/_entities.py +1 -0
  9. package/src/db/_episodic.py +1 -0
  10. package/src/db/_learnings.py +1 -0
  11. package/src/db/_reminders.py +1 -0
  12. package/src/db/_schema.py +11 -1
  13. package/src/db/_sessions.py +1 -0
  14. package/src/db/_skills.py +1 -0
  15. package/src/hooks/capture-tool-logs.sh +23 -6
  16. package/src/hooks/session-start.sh +4 -3
  17. package/src/plugin_loader.py +1 -0
  18. package/src/plugins/update.py +377 -26
  19. package/src/scripts/deep-sleep/apply_findings.py +1 -0
  20. package/src/scripts/deep-sleep/collect.py +1 -0
  21. package/src/scripts/deep-sleep/extract.py +1 -0
  22. package/src/scripts/deep-sleep/synthesize.py +1 -0
  23. package/src/scripts/nexo-catchup.py +29 -4
  24. package/src/scripts/nexo-daily-self-audit.py +21 -1
  25. package/src/scripts/nexo-evolution-run.py +21 -1
  26. package/src/scripts/nexo-learning-housekeep.py +1 -0
  27. package/src/scripts/nexo-postmortem-consolidator.py +34 -9
  28. package/src/scripts/nexo-sleep.py +32 -10
  29. package/src/scripts/nexo-synthesis.py +29 -9
  30. package/src/scripts/nexo-update.sh +109 -7
  31. package/src/scripts/nexo-watchdog.sh +122 -58
  32. package/src/server.py +66 -1
  33. package/src/tools_coordination.py +1 -0
  34. package/src/tools_sessions.py +1 -0
  35. package/scripts/migrate-to-unified 2.sh +0 -813
  36. package/scripts/migrate-to-unified.sh +0 -813
  37. package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
  38. package/scripts/migrate-v1.5-to-v1.6.py +0 -778
  39. package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
  40. package/scripts/migrate-v1.7-to-v1.8.py +0 -214
  41. package/scripts/nexo-preflight.sh +0 -236
  42. package/scripts/pre-commit-check 2.sh +0 -55
  43. package/scripts/pre-commit-check.sh +0 -55
  44. package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
  45. package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
  46. package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
  47. package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
  48. package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
  49. package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
  50. package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
  51. package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
  52. package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
  53. package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
  54. package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
  55. package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
  56. package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
  57. package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
  58. package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
  59. package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
  60. package/src/auto_close_sessions 2.py +0 -159
  61. package/src/auto_update 2.py +0 -634
  62. package/src/claim_graph 2.py +0 -323
  63. package/src/cognitive/__init__ 2.py +0 -62
  64. package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
  65. package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
  66. package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  67. package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
  68. package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
  69. package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
  70. package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
  71. package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
  72. package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
  73. package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
  74. package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
  75. package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
  76. package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
  77. package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
  78. package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
  79. package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
  80. package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
  81. package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
  82. package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
  83. package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
  84. package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
  85. package/src/cognitive/_core 2.py +0 -567
  86. package/src/cognitive/_decay 2.py +0 -382
  87. package/src/cognitive/_ingest 2.py +0 -892
  88. package/src/cognitive/_memory 2.py +0 -912
  89. package/src/cognitive/_search 2.py +0 -949
  90. package/src/cognitive/_trust 2.py +0 -464
  91. package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
  92. package/src/crons/manifest 2.json +0 -106
  93. package/src/crons/sync 2.py +0 -217
  94. package/src/dashboard/__init__ 2.py +0 -0
  95. package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
  96. package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
  97. package/src/dashboard/app 2.py +0 -789
  98. package/src/db/__init__ 2.py +0 -89
  99. package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
  100. package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
  101. package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
  102. package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
  103. package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
  104. package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
  105. package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
  106. package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
  107. package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
  108. package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
  109. package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
  110. package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
  111. package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
  112. package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
  113. package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
  114. package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
  115. package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
  116. package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
  117. package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
  118. package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
  119. package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
  120. package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
  121. package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
  122. package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
  123. package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
  124. package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
  125. package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
  126. package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
  127. package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
  128. package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
  129. package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
  130. package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
  131. package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
  132. package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
  133. package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
  134. package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
  135. package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
  136. package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
  137. package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
  138. package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
  139. package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
  140. package/src/db/_core 2.py +0 -417
  141. package/src/db/_credentials 2.py +0 -124
  142. package/src/db/_entities 2.py +0 -178
  143. package/src/db/_episodic 2.py +0 -738
  144. package/src/db/_evolution 2.py +0 -54
  145. package/src/db/_fts 2.py +0 -406
  146. package/src/db/_learnings 2.py +0 -168
  147. package/src/db/_reminders 2.py +0 -338
  148. package/src/db/_schema 2.py +0 -364
  149. package/src/db/_sessions 2.py +0 -300
  150. package/src/db/_tasks 2.py +0 -91
  151. package/src/evolution_cycle 2.py +0 -266
  152. package/src/hnsw_index 2.py +0 -254
  153. package/src/hooks/auto_capture 2.py +0 -208
  154. package/src/hooks/caffeinate-guard 2.sh +0 -8
  155. package/src/hooks/capture-session 2.sh +0 -21
  156. package/src/hooks/capture-tool-logs 2.sh +0 -127
  157. package/src/hooks/daily-briefing-check 2.sh +0 -33
  158. package/src/hooks/inbox-hook 2.sh +0 -76
  159. package/src/hooks/post-compact 2.sh +0 -148
  160. package/src/hooks/pre-compact 2.sh +0 -151
  161. package/src/hooks/session-start 2.sh +0 -268
  162. package/src/hooks/session-stop 2.sh +0 -140
  163. package/src/kg_populate 2.py +0 -290
  164. package/src/knowledge_graph 2.py +0 -257
  165. package/src/maintenance 2.py +0 -59
  166. package/src/migrate_embeddings 2.py +0 -122
  167. package/src/plugin_loader 2.py +0 -202
  168. package/src/plugins/__init__ 2.py +0 -0
  169. package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
  170. package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
  171. package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
  172. package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
  173. package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
  174. package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
  175. package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
  176. package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
  177. package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
  178. package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
  179. package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
  180. package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
  181. package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
  182. package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
  183. package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
  184. package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
  185. package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
  186. package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
  187. package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
  188. package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
  189. package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
  190. package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
  191. package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
  192. package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
  193. package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
  194. package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
  195. package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
  196. package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
  197. package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
  198. package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
  199. package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
  200. package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
  201. package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
  202. package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
  203. package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
  204. package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
  205. package/src/plugins/adaptive_mode 2.py +0 -805
  206. package/src/plugins/agents 2.py +0 -52
  207. package/src/plugins/artifact_registry 2.py +0 -450
  208. package/src/plugins/backup 2.py +0 -104
  209. package/src/plugins/cognitive_memory 2.py +0 -564
  210. package/src/plugins/core_rules 2.py +0 -252
  211. package/src/plugins/cortex 2.py +0 -299
  212. package/src/plugins/entities 2.py +0 -67
  213. package/src/plugins/episodic_memory 2.py +0 -533
  214. package/src/plugins/evolution 2.py +0 -115
  215. package/src/plugins/guard 2.py +0 -746
  216. package/src/plugins/knowledge_graph_tools 2.py +0 -105
  217. package/src/plugins/preferences 2.py +0 -47
  218. package/src/plugins/update 2.py +0 -256
  219. package/src/requirements 2.txt +0 -12
  220. package/src/rules/__init__ 2.py +0 -0
  221. package/src/rules/core-rules 2.json +0 -331
  222. package/src/rules/migrate 2.py +0 -207
  223. package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
  224. package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
  225. package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
  226. package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
  227. package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
  228. package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
  229. package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
  230. package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
  231. package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
  232. package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
  233. package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
  234. package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
  235. package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
  236. package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
  237. package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
  238. package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
  239. package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
  240. package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
  241. package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
  242. package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
  243. package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
  244. package/src/scripts/check-context 2.py +0 -264
  245. package/src/scripts/nexo-auto-update 2.py +0 -6
  246. package/src/scripts/nexo-backup 2.sh +0 -25
  247. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  248. package/src/scripts/nexo-catchup 2.py +0 -242
  249. package/src/scripts/nexo-cognitive-decay 2.py +0 -182
  250. package/src/scripts/nexo-daily-self-audit 2.py +0 -552
  251. package/src/scripts/nexo-deep-sleep 2.sh +0 -97
  252. package/src/scripts/nexo-evolution-run 2.py +0 -597
  253. package/src/scripts/nexo-followup-hygiene 2.py +0 -112
  254. package/src/scripts/nexo-github-monitor 2.py +0 -256
  255. package/src/scripts/nexo-immune 2.py +0 -927
  256. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  257. package/src/scripts/nexo-install 2.py +0 -6
  258. package/src/scripts/nexo-learning-housekeep 2.py +0 -245
  259. package/src/scripts/nexo-learning-validator 2.py +0 -207
  260. package/src/scripts/nexo-migrate 2.py +0 -232
  261. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
  262. package/src/scripts/nexo-pre-commit 2.py +0 -120
  263. package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
  264. package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
  265. package/src/scripts/nexo-reflection 2.py +0 -253
  266. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  267. package/src/scripts/nexo-send-email 2.py +0 -25
  268. package/src/scripts/nexo-send-email.py +0 -25
  269. package/src/scripts/nexo-send-reply 2.py +0 -178
  270. package/src/scripts/nexo-send-reply.py +0 -178
  271. package/src/scripts/nexo-sleep 2.py +0 -592
  272. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  273. package/src/scripts/nexo-synthesis 2.py +0 -253
  274. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  275. package/src/scripts/nexo-update 2.sh +0 -161
  276. package/src/scripts/nexo-watchdog 2.sh +0 -878
  277. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  278. package/src/server 2.py +0 -733
  279. package/src/storage_router 2.py +0 -32
  280. package/src/tools_coordination 2.py +0 -102
  281. package/src/tools_credentials 2.py +0 -68
  282. package/src/tools_learnings 2.py +0 -220
  283. package/src/tools_menu 2.py +0 -227
  284. package/src/tools_reminders 2.py +0 -86
  285. package/src/tools_reminders_crud 2.py +0 -159
  286. package/src/tools_sessions 2.py +0 -476
  287. package/src/tools_task_history 2.py +0 -57
  288. package/templates/CLAUDE.md 2.template +0 -63
  289. package/templates/openclaw 2.json +0 -13
  290. package/tests/__init__ 2.py +0 -0
  291. package/tests/__init__.py +0 -0
  292. package/tests/conftest 2.py +0 -71
  293. package/tests/conftest.py +0 -71
  294. package/tests/test_cognitive 2.py +0 -205
  295. package/tests/test_cognitive.py +0 -205
  296. package/tests/test_knowledge_graph 2.py +0 -140
  297. package/tests/test_knowledge_graph.py +0 -140
  298. package/tests/test_migrations 2.py +0 -137
  299. package/tests/test_migrations.py +0 -137
@@ -1,382 +0,0 @@
1
- """NEXO Cognitive — Decay, promotion, garbage collection, dream consolidation."""
2
- import math
3
- import numpy as np
4
- from datetime import datetime, timedelta
5
- from cognitive._core import _get_db, embed, cosine_similarity, _blob_to_array, _array_to_blob, LAMBDA_STM, LAMBDA_LTM, EMBEDDING_DIM
6
-
7
-
8
- def _hnsw_invalidate():
9
- """Invalidate HNSW indices after bulk operations (best-effort)."""
10
- try:
11
- import hnsw_index
12
- if hnsw_index.is_available():
13
- hnsw_index.invalidate("both")
14
- except Exception:
15
- pass
16
-
17
-
18
- def apply_decay(adaptive: bool = True):
19
- """Apply Ebbinghaus decay to all memories. Mark LTM as dormant if strength < 0.1.
20
-
21
- Args:
22
- adaptive: If True, protect unique memories (no similar neighbors) from aggressive decay.
23
- Unique memories decay at 25% of normal rate. This prevents information loss
24
- in sparse memory stores where there's no redundancy to compensate.
25
- """
26
- db = _get_db()
27
- now = datetime.utcnow()
28
-
29
- # Build redundancy map if adaptive mode — check which memories have similar siblings
30
- _protected_stm = set()
31
- _protected_ltm = set()
32
- if adaptive:
33
- # A memory is "protected" if it has no siblings in memory_siblings table
34
- # (meaning no other memory covers similar content)
35
- sibling_ids = set()
36
- for row in db.execute("SELECT memory_a_id, memory_b_id FROM memory_siblings").fetchall():
37
- sibling_ids.add(row["memory_a_id"])
38
- sibling_ids.add(row["memory_b_id"])
39
-
40
- # STM memories NOT in sibling_ids are unique → protect
41
- for row in db.execute("SELECT id FROM stm_memories WHERE promoted_to_ltm = 0").fetchall():
42
- if row["id"] not in sibling_ids:
43
- _protected_stm.add(row["id"])
44
-
45
- # LTM memories NOT in sibling_ids are unique → protect
46
- for row in db.execute("SELECT id FROM ltm_memories WHERE is_dormant = 0").fetchall():
47
- if row["id"] not in sibling_ids:
48
- _protected_ltm.add(row["id"])
49
-
50
- # STM decay (skip pinned)
51
- rows = db.execute("SELECT id, last_accessed, strength FROM stm_memories WHERE promoted_to_ltm = 0 AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')").fetchall()
52
- for row in rows:
53
- last = datetime.fromisoformat(row["last_accessed"])
54
- hours = (now - last).total_seconds() / 3600.0
55
- decay_rate = LAMBDA_STM * 0.25 if (adaptive and row["id"] in _protected_stm) else LAMBDA_STM
56
- new_strength = row["strength"] * math.exp(-decay_rate * hours)
57
- db.execute("UPDATE stm_memories SET strength = ? WHERE id = ?", (new_strength, row["id"]))
58
-
59
- # LTM decay (skip pinned)
60
- rows = db.execute("SELECT id, last_accessed, strength FROM ltm_memories WHERE is_dormant = 0 AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')").fetchall()
61
- for row in rows:
62
- last = datetime.fromisoformat(row["last_accessed"])
63
- hours = (now - last).total_seconds() / 3600.0
64
- decay_rate = LAMBDA_LTM * 0.25 if (adaptive and row["id"] in _protected_ltm) else LAMBDA_LTM
65
- new_strength = row["strength"] * math.exp(-decay_rate * hours)
66
- if new_strength < 0.1:
67
- db.execute("UPDATE ltm_memories SET strength = ?, is_dormant = 1 WHERE id = ?", (new_strength, row["id"]))
68
- else:
69
- db.execute("UPDATE ltm_memories SET strength = ? WHERE id = ?", (new_strength, row["id"]))
70
-
71
- db.commit()
72
-
73
-
74
- def promote_stm_to_ltm():
75
- """Promote STM memories to LTM based on multiple criteria.
76
-
77
- Promotion rules (any one is sufficient):
78
- 1. access_count >= 3 (actively retrieved = important)
79
- 2. age > 5 days AND strength > 0.4 (survived decay = worth keeping)
80
- 3. source_type in ('learning', 'decision', 'feedback') (high-value by nature)
81
- """
82
- db = _get_db()
83
- now = datetime.utcnow()
84
- age_cutoff = (now - timedelta(days=5)).isoformat()
85
-
86
- # Rule 1: frequently accessed
87
- # Rule 2: old + strong (survived decay)
88
- # Rule 3: high-value source types (always promote if in STM)
89
- rows = db.execute(
90
- """SELECT * FROM stm_memories
91
- WHERE promoted_to_ltm = 0
92
- AND (
93
- access_count >= 3
94
- OR (created_at < ? AND strength > 0.4)
95
- OR source_type IN ('learning', 'decision', 'feedback')
96
- )""",
97
- (age_cutoff,)
98
- ).fetchall()
99
-
100
- promoted = 0
101
- for row in rows:
102
- redacted = row["redaction_applied"] if "redaction_applied" in row.keys() else 0
103
- db.execute(
104
- """INSERT INTO ltm_memories (content, embedding, source_type, source_id, source_title, domain, original_stm_id, redaction_applied)
105
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
106
- (row["content"], row["embedding"], row["source_type"], row["source_id"],
107
- row["source_title"], row["domain"], row["id"], redacted)
108
- )
109
- db.execute("UPDATE stm_memories SET promoted_to_ltm = 1 WHERE id = ?", (row["id"],))
110
- promoted += 1
111
-
112
- db.commit()
113
- if promoted > 0:
114
- _hnsw_invalidate()
115
- return promoted
116
-
117
-
118
- def gc_stm():
119
- """Garbage collect STM: delete weak old memories and anything > 45 days.
120
- Pinned memories are never deleted.
121
- """
122
- db = _get_db()
123
- now = datetime.utcnow()
124
- cutoff_7d = (now - timedelta(days=7)).isoformat()
125
- cutoff_45d = (now - timedelta(days=45)).isoformat()
126
-
127
- # Delete STM with strength < 0.3 and older than 7 days (skip pinned)
128
- cur1 = db.execute(
129
- "DELETE FROM stm_memories WHERE strength < 0.3 AND created_at < ? AND promoted_to_ltm = 0 "
130
- "AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')",
131
- (cutoff_7d,)
132
- )
133
- # Delete any STM older than 45 days (skip pinned)
134
- cur2 = db.execute(
135
- "DELETE FROM stm_memories WHERE created_at < ? AND promoted_to_ltm = 0 "
136
- "AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')",
137
- (cutoff_45d,)
138
- )
139
- db.commit()
140
- deleted = (cur1.rowcount or 0) + (cur2.rowcount or 0)
141
- if deleted > 0:
142
- _hnsw_invalidate()
143
- return deleted
144
-
145
-
146
- def gc_test_memories() -> int:
147
- """Purge STM memories from test/dev sessions that pollute strength metrics.
148
- Removes memories with test domains or known test content patterns.
149
- Returns count of deleted memories.
150
- """
151
- db = _get_db()
152
- test_domains = ("test", "test_session")
153
- deleted = 0
154
-
155
- # 1. Delete by test domain
156
- for domain in test_domains:
157
- cur = db.execute(
158
- "DELETE FROM stm_memories WHERE domain = ? "
159
- "AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')",
160
- (domain,)
161
- )
162
- deleted += cur.rowcount or 0
163
-
164
- # 2. Delete known test content patterns (empty domain, test-like content)
165
- test_patterns = [
166
- "%Secret redact test%",
167
- "%quarantine test fact%",
168
- "%Pin test memory%",
169
- "%API rate limit%AM10%",
170
- "%xyzzy server%",
171
- "%Quantum entanglement enables FTL%",
172
- "%Install Docker%AM10%",
173
- "%normal safe content about coding%",
174
- "%test diary%",
175
- "%test critique%",
176
- "%integration test diary%",
177
- ]
178
- for pattern in test_patterns:
179
- cur = db.execute(
180
- "DELETE FROM stm_memories WHERE content LIKE ? "
181
- "AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')",
182
- (pattern,)
183
- )
184
- deleted += cur.rowcount or 0
185
-
186
- if deleted > 0:
187
- db.commit()
188
- return deleted
189
-
190
- def gc_sensory(max_age_hours: int = 48) -> int:
191
- """Garbage collect sensory memories older than max_age_hours. Returns count deleted."""
192
- db = _get_db()
193
- cutoff = (datetime.utcnow() - timedelta(hours=max_age_hours)).isoformat()
194
- cur = db.execute(
195
- "DELETE FROM stm_memories WHERE source_type = 'sensory' AND created_at < ? AND promoted_to_ltm = 0",
196
- (cutoff,)
197
- )
198
- db.commit()
199
- return cur.rowcount or 0
200
-
201
-
202
- def gc_ltm_dormant(min_age_days: int = 30) -> int:
203
- """Delete dormant LTM memories with strength < 0.1 older than min_age_days."""
204
- db = _get_db()
205
- cutoff = (datetime.utcnow() - timedelta(days=min_age_days)).isoformat()
206
- cur = db.execute(
207
- "DELETE FROM ltm_memories WHERE is_dormant = 1 AND strength < 0.1 AND created_at < ?",
208
- (cutoff,)
209
- )
210
- db.commit()
211
- return cur.rowcount or 0
212
-
213
- def dream_cycle(max_insights: int = 50) -> dict:
214
- """Memory Dreaming — discover hidden connections between recent memories.
215
-
216
- Retrieves memories accessed in the last 24h (STM + LTM), finds pairs with
217
- moderate similarity (0.4-0.7 — related but not duplicates), and creates
218
- 'dream_insight' LTM memories linking them. Skips pairs already dreamed about.
219
-
220
- Uses pure vector math — no LLM calls.
221
-
222
- Returns:
223
- Dict with 'insights_created' count and 'insights' list of details.
224
- """
225
- db = _get_db()
226
- cutoff_24h = (datetime.utcnow() - timedelta(hours=24)).isoformat()
227
-
228
- # 1. Gather all memories accessed in the last 24 hours
229
- recent_memories = []
230
-
231
- stm_rows = db.execute(
232
- """SELECT id, content, embedding, source_type, source_title, domain, 'stm' as store
233
- FROM stm_memories
234
- WHERE last_accessed >= ? AND promoted_to_ltm = 0""",
235
- (cutoff_24h,)
236
- ).fetchall()
237
-
238
- ltm_rows = db.execute(
239
- """SELECT id, content, embedding, source_type, source_title, domain, 'ltm' as store
240
- FROM ltm_memories
241
- WHERE last_accessed >= ? AND is_dormant = 0""",
242
- (cutoff_24h,)
243
- ).fetchall()
244
-
245
- for row in stm_rows + ltm_rows:
246
- recent_memories.append({
247
- "id": row["id"],
248
- "content": row["content"],
249
- "vec": _blob_to_array(row["embedding"]),
250
- "source_type": row["source_type"],
251
- "source_title": row["source_title"] or "",
252
- "domain": row["domain"] or "",
253
- "store": row["store"],
254
- })
255
-
256
- if len(recent_memories) < 2:
257
- return {"insights_created": 0, "insights": [], "memories_scanned": len(recent_memories), "candidates_found": 0}
258
-
259
- # 2. Get already-dreamed pairs to skip
260
- dreamed = set()
261
- for row in db.execute("SELECT memory_a_id, memory_b_id FROM dreamed_pairs").fetchall():
262
- dreamed.add((row["memory_a_id"], row["memory_b_id"]))
263
- dreamed.add((row["memory_b_id"], row["memory_a_id"]))
264
-
265
- # 3. Batch compute all pairwise cosine similarities
266
- # Build matrix for fast numpy dot product
267
- n = len(recent_memories)
268
- vecs = np.array([m["vec"] for m in recent_memories], dtype=np.float32)
269
- norms = np.linalg.norm(vecs, axis=1, keepdims=True)
270
- norms[norms == 0] = 1.0 # avoid division by zero
271
- normalized = vecs / norms
272
- sim_matrix = normalized @ normalized.T # (n x n) cosine similarity matrix
273
-
274
- # 4. Find pairs in the sweet spot (0.4-0.7) — related but not duplicates
275
- candidate_pairs = []
276
- for i in range(n):
277
- for j in range(i + 1, n):
278
- score = float(sim_matrix[i, j])
279
- if 0.4 <= score <= 0.7:
280
- # Use composite key for dreamed check (store:id to disambiguate stm vs ltm)
281
- pair_key = (
282
- f"{recent_memories[i]['store']}:{recent_memories[i]['id']}",
283
- f"{recent_memories[j]['store']}:{recent_memories[j]['id']}",
284
- )
285
- # For DB tracking we use LTM IDs when both are LTM, else skip dreamed check
286
- a_id, b_id = recent_memories[i]["id"], recent_memories[j]["id"]
287
- if (a_id, b_id) in dreamed or (b_id, a_id) in dreamed:
288
- continue
289
- candidate_pairs.append((i, j, score))
290
-
291
- # Sort by similarity descending (strongest connections first)
292
- candidate_pairs.sort(key=lambda x: x[2], reverse=True)
293
-
294
- # 5. Generate insights (capped at max_insights)
295
- insights = []
296
- for i, j, score in candidate_pairs[:max_insights]:
297
- mem_a = recent_memories[i]
298
- mem_b = recent_memories[j]
299
-
300
- # Build titles — use source_title if available, else first 60 chars of content
301
- title_a = mem_a["source_title"] or mem_a["content"][:60].replace("\n", " ").strip()
302
- title_b = mem_b["source_title"] or mem_b["content"][:60].replace("\n", " ").strip()
303
-
304
- # Build domain context
305
- domains = set(filter(None, [mem_a["domain"], mem_b["domain"]]))
306
- domain_str = ", ".join(domains) if domains else "general"
307
-
308
- # Create insight content
309
- insight_content = (
310
- f"[Dream Insight] Connection found between:\n"
311
- f" A: {title_a}\n"
312
- f" B: {title_b}\n"
313
- f"Similarity: {score:.3f} | Domains: {domain_str}\n"
314
- f"These memories appeared together in the same 24h window and share moderate semantic overlap, "
315
- f"suggesting a potential relationship worth investigating."
316
- )
317
-
318
- # Create embedding as average of the two source vectors (midpoint in vector space)
319
- insight_vec = (mem_a["vec"] + mem_b["vec"]) / 2.0
320
- insight_vec = insight_vec / (np.linalg.norm(insight_vec) or 1.0) # re-normalize
321
- blob = _array_to_blob(insight_vec)
322
-
323
- # Store as LTM with dream_insight tag
324
- cur = db.execute(
325
- """INSERT INTO ltm_memories (content, embedding, source_type, source_id, source_title, domain, tags, strength)
326
- VALUES (?, ?, 'dream_insight', ?, ?, ?, 'dream_insight', 0.5)""",
327
- (insight_content, blob,
328
- f"{mem_a['store']}:{mem_a['id']},{mem_b['store']}:{mem_b['id']}",
329
- f"Dream: {title_a[:30]} <-> {title_b[:30]}",
330
- domain_str)
331
- )
332
- insight_id = cur.lastrowid
333
-
334
- # Track the dreamed pair
335
- a_id, b_id = mem_a["id"], mem_b["id"]
336
- try:
337
- db.execute(
338
- "INSERT OR IGNORE INTO dreamed_pairs (memory_a_id, memory_b_id, insight_id) VALUES (?, ?, ?)",
339
- (min(a_id, b_id), max(a_id, b_id), insight_id)
340
- )
341
- except Exception:
342
- pass
343
-
344
- insights.append({
345
- "insight_id": insight_id,
346
- "title_a": title_a[:80],
347
- "title_b": title_b[:80],
348
- "similarity": round(score, 4),
349
- "domain": domain_str,
350
- })
351
-
352
- db.commit()
353
-
354
- # Dream cap: archive oldest dream_insights if total exceeds MAX_DREAM_INSIGHTS
355
- MAX_DREAM_INSIGHTS = 50
356
- dream_count = db.execute(
357
- "SELECT COUNT(*) FROM ltm_memories WHERE source_type = 'dream_insight' AND is_dormant = 0"
358
- ).fetchone()[0]
359
- archived_dreams = 0
360
- if dream_count > MAX_DREAM_INSIGHTS:
361
- excess = dream_count - MAX_DREAM_INSIGHTS
362
- oldest = db.execute(
363
- "SELECT id FROM ltm_memories WHERE source_type = 'dream_insight' AND is_dormant = 0 "
364
- "ORDER BY strength ASC, created_at ASC LIMIT ?", (excess,)
365
- ).fetchall()
366
- for row in oldest:
367
- db.execute(
368
- "UPDATE ltm_memories SET lifecycle_state = 'archived' WHERE id = ?", (row["id"],)
369
- )
370
- archived_dreams += 1
371
- db.commit()
372
-
373
- result = {
374
- "insights_created": len(insights),
375
- "insights": insights,
376
- "memories_scanned": len(recent_memories),
377
- "candidates_found": len(candidate_pairs),
378
- }
379
- if archived_dreams > 0:
380
- result["dreams_archived"] = archived_dreams
381
- result["dreams_total"] = dream_count
382
- return result