@agentmemory/agentmemory 0.9.30 → 0.9.31

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 (331) hide show
  1. package/.env.example +39 -33
  2. package/AGENTS.md +8 -8
  3. package/README.md +173 -558
  4. package/dist/.env.example +39 -33
  5. package/dist/cli.d.mts.map +1 -1
  6. package/dist/cli.mjs +1741 -966
  7. package/dist/cli.mjs.map +1 -1
  8. package/dist/compression-recovery-CnhLQC6N.mjs +5912 -0
  9. package/dist/compression-recovery-CnhLQC6N.mjs.map +1 -0
  10. package/dist/{connect-B4_ca6BD.mjs → connect-BSLIAMpw.mjs} +874 -267
  11. package/dist/connect-BSLIAMpw.mjs.map +1 -0
  12. package/dist/hooks/_capture.d.mts +5 -2
  13. package/dist/hooks/_capture.d.mts.map +1 -1
  14. package/dist/hooks/_capture.mjs +55 -19
  15. package/dist/hooks/_capture.mjs.map +1 -1
  16. package/dist/hooks/notification.mjs +20 -6
  17. package/dist/hooks/notification.mjs.map +1 -1
  18. package/dist/hooks/post-commit.mjs +25 -5
  19. package/dist/hooks/post-commit.mjs.map +1 -1
  20. package/dist/hooks/post-tool-failure.mjs +12 -4
  21. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  22. package/dist/hooks/post-tool-use.mjs +12 -4
  23. package/dist/hooks/post-tool-use.mjs.map +1 -1
  24. package/dist/hooks/pre-compact.mjs +37 -10
  25. package/dist/hooks/pre-compact.mjs.map +1 -1
  26. package/dist/hooks/pre-tool-use.mjs +25 -5
  27. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  28. package/dist/hooks/prompt-submit.mjs +12 -4
  29. package/dist/hooks/prompt-submit.mjs.map +1 -1
  30. package/dist/hooks/session-end.mjs +47 -16
  31. package/dist/hooks/session-end.mjs.map +1 -1
  32. package/dist/hooks/session-start.mjs +37 -10
  33. package/dist/hooks/session-start.mjs.map +1 -1
  34. package/dist/hooks/stop.mjs +74 -72
  35. package/dist/hooks/stop.mjs.map +1 -1
  36. package/dist/hooks/subagent-start.mjs +13 -5
  37. package/dist/hooks/subagent-start.mjs.map +1 -1
  38. package/dist/hooks/subagent-stop.mjs +18 -6
  39. package/dist/hooks/subagent-stop.mjs.map +1 -1
  40. package/dist/hooks/task-completed.mjs +13 -5
  41. package/dist/hooks/task-completed.mjs.map +1 -1
  42. package/dist/{image-refs-T_urbwHM.mjs → image-refs-Cyta4Lf9.mjs} +2 -2
  43. package/dist/{image-refs-T_urbwHM.mjs.map → image-refs-Cyta4Lf9.mjs.map} +1 -1
  44. package/dist/index.mjs +8539 -4203
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/integrations/dsh.d.mts +55 -0
  47. package/dist/integrations/dsh.d.mts.map +1 -0
  48. package/dist/integrations/dsh.mjs +1145 -0
  49. package/dist/integrations/dsh.mjs.map +1 -0
  50. package/dist/{keyed-mutex-DchXsyfP.mjs → keyed-mutex-DCUh7PG-.mjs} +58 -9
  51. package/dist/keyed-mutex-DCUh7PG-.mjs.map +1 -0
  52. package/dist/{secret-store-BnY3A50C.mjs → secret-store-D9GwO0xp.mjs} +535 -434
  53. package/dist/secret-store-D9GwO0xp.mjs.map +1 -0
  54. package/dist/session-arguments-zxsbbd8y.mjs +113 -0
  55. package/dist/session-arguments-zxsbbd8y.mjs.map +1 -0
  56. package/dist/{src-Jsq9LCeH.mjs → src-DaMtDzt_.mjs} +4087 -3788
  57. package/dist/src-DaMtDzt_.mjs.map +1 -0
  58. package/dist/{standalone-DKCdrf1d.mjs → standalone-BlqshyZ5.mjs} +213 -56
  59. package/dist/standalone-BlqshyZ5.mjs.map +1 -0
  60. package/dist/standalone.d.mts +1 -1
  61. package/dist/standalone.d.mts.map +1 -1
  62. package/dist/standalone.mjs +626 -73
  63. package/dist/standalone.mjs.map +1 -1
  64. package/dist/viewer/index.html +288 -140
  65. package/integrations/hermes/README.md +84 -0
  66. package/integrations/hermes/__init__.py +669 -0
  67. package/integrations/hermes/plugin.yaml +27 -0
  68. package/integrations/omp/LICENSE +190 -0
  69. package/integrations/omp/README.md +76 -0
  70. package/integrations/omp/capture.ts +1 -0
  71. package/integrations/omp/dist/index.mjs +1450 -0
  72. package/integrations/omp/index.ts +488 -0
  73. package/integrations/omp/package.json +13 -0
  74. package/integrations/omp/security.ts +67 -0
  75. package/integrations/omp/skills/agentmemory-agents/REFERENCE.md +32 -0
  76. package/integrations/omp/skills/agentmemory-agents/SKILL.md +48 -0
  77. package/integrations/omp/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
  78. package/integrations/omp/skills/agentmemory-architecture/SKILL.md +33 -0
  79. package/integrations/omp/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
  80. package/integrations/omp/skills/agentmemory-config/REFERENCE.md +90 -0
  81. package/integrations/omp/skills/agentmemory-config/SKILL.md +78 -0
  82. package/integrations/omp/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
  83. package/integrations/omp/skills/agentmemory-hooks/REFERENCE.md +20 -0
  84. package/integrations/omp/skills/agentmemory-hooks/SKILL.md +49 -0
  85. package/integrations/omp/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
  86. package/integrations/omp/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
  87. package/integrations/omp/skills/agentmemory-mcp-tools/SKILL.md +42 -0
  88. package/integrations/omp/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
  89. package/integrations/omp/skills/agentmemory-rest-api/REFERENCE.md +158 -0
  90. package/integrations/omp/skills/agentmemory-rest-api/SKILL.md +49 -0
  91. package/integrations/omp/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
  92. package/integrations/omp/skills/commit-context/EXAMPLES.md +71 -0
  93. package/integrations/omp/skills/commit-context/SKILL.md +64 -0
  94. package/integrations/omp/skills/commit-context/TROUBLESHOOTING.md +70 -0
  95. package/integrations/omp/skills/commit-history/EXAMPLES.md +71 -0
  96. package/integrations/omp/skills/commit-history/SKILL.md +62 -0
  97. package/integrations/omp/skills/commit-history/TROUBLESHOOTING.md +70 -0
  98. package/integrations/omp/skills/forget/EXAMPLES.md +40 -0
  99. package/integrations/omp/skills/forget/SKILL.md +73 -0
  100. package/integrations/omp/skills/forget/TROUBLESHOOTING.md +70 -0
  101. package/integrations/omp/skills/handoff/EXAMPLES.md +57 -0
  102. package/integrations/omp/skills/handoff/SKILL.md +75 -0
  103. package/integrations/omp/skills/handoff/TROUBLESHOOTING.md +70 -0
  104. package/integrations/omp/skills/lesson/SKILL.md +64 -0
  105. package/integrations/omp/skills/lesson/TROUBLESHOOTING.md +70 -0
  106. package/integrations/omp/skills/memory-discipline/SKILL.md +65 -0
  107. package/integrations/omp/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
  108. package/integrations/omp/skills/recall/EXAMPLES.md +74 -0
  109. package/integrations/omp/skills/recall/SKILL.md +74 -0
  110. package/integrations/omp/skills/recall/TROUBLESHOOTING.md +70 -0
  111. package/integrations/omp/skills/recap/EXAMPLES.md +62 -0
  112. package/integrations/omp/skills/recap/SKILL.md +68 -0
  113. package/integrations/omp/skills/recap/TROUBLESHOOTING.md +70 -0
  114. package/integrations/omp/skills/remember/EXAMPLES.md +67 -0
  115. package/integrations/omp/skills/remember/SKILL.md +66 -0
  116. package/integrations/omp/skills/remember/TROUBLESHOOTING.md +70 -0
  117. package/integrations/omp/skills/session-history/EXAMPLES.md +59 -0
  118. package/integrations/omp/skills/session-history/SKILL.md +67 -0
  119. package/integrations/omp/skills/session-history/TROUBLESHOOTING.md +70 -0
  120. package/integrations/omp/skills/write-agentmemory-skill/SKILL.md +41 -0
  121. package/integrations/omp/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
  122. package/integrations/openclaw/LICENSE +190 -0
  123. package/integrations/openclaw/README.md +88 -0
  124. package/integrations/openclaw/openclaw.plugin.json +71 -0
  125. package/integrations/openclaw/package.json +38 -0
  126. package/integrations/openclaw/plugin.mjs +425 -0
  127. package/integrations/openclaw/plugin.yaml +26 -0
  128. package/integrations/openclaw/skills/agentmemory-agents/REFERENCE.md +32 -0
  129. package/integrations/openclaw/skills/agentmemory-agents/SKILL.md +48 -0
  130. package/integrations/openclaw/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
  131. package/integrations/openclaw/skills/agentmemory-architecture/SKILL.md +33 -0
  132. package/integrations/openclaw/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
  133. package/integrations/openclaw/skills/agentmemory-config/REFERENCE.md +90 -0
  134. package/integrations/openclaw/skills/agentmemory-config/SKILL.md +78 -0
  135. package/integrations/openclaw/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
  136. package/integrations/openclaw/skills/agentmemory-hooks/REFERENCE.md +20 -0
  137. package/integrations/openclaw/skills/agentmemory-hooks/SKILL.md +49 -0
  138. package/integrations/openclaw/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
  139. package/integrations/openclaw/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
  140. package/integrations/openclaw/skills/agentmemory-mcp-tools/SKILL.md +42 -0
  141. package/integrations/openclaw/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
  142. package/integrations/openclaw/skills/agentmemory-rest-api/REFERENCE.md +158 -0
  143. package/integrations/openclaw/skills/agentmemory-rest-api/SKILL.md +49 -0
  144. package/integrations/openclaw/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
  145. package/integrations/openclaw/skills/commit-context/EXAMPLES.md +71 -0
  146. package/integrations/openclaw/skills/commit-context/SKILL.md +64 -0
  147. package/integrations/openclaw/skills/commit-context/TROUBLESHOOTING.md +70 -0
  148. package/integrations/openclaw/skills/commit-history/EXAMPLES.md +71 -0
  149. package/integrations/openclaw/skills/commit-history/SKILL.md +62 -0
  150. package/integrations/openclaw/skills/commit-history/TROUBLESHOOTING.md +70 -0
  151. package/integrations/openclaw/skills/forget/EXAMPLES.md +40 -0
  152. package/integrations/openclaw/skills/forget/SKILL.md +73 -0
  153. package/integrations/openclaw/skills/forget/TROUBLESHOOTING.md +70 -0
  154. package/integrations/openclaw/skills/handoff/EXAMPLES.md +57 -0
  155. package/integrations/openclaw/skills/handoff/SKILL.md +75 -0
  156. package/integrations/openclaw/skills/handoff/TROUBLESHOOTING.md +70 -0
  157. package/integrations/openclaw/skills/lesson/SKILL.md +64 -0
  158. package/integrations/openclaw/skills/lesson/TROUBLESHOOTING.md +70 -0
  159. package/integrations/openclaw/skills/memory-discipline/SKILL.md +65 -0
  160. package/integrations/openclaw/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
  161. package/integrations/openclaw/skills/recall/EXAMPLES.md +74 -0
  162. package/integrations/openclaw/skills/recall/SKILL.md +74 -0
  163. package/integrations/openclaw/skills/recall/TROUBLESHOOTING.md +70 -0
  164. package/integrations/openclaw/skills/recap/EXAMPLES.md +62 -0
  165. package/integrations/openclaw/skills/recap/SKILL.md +68 -0
  166. package/integrations/openclaw/skills/recap/TROUBLESHOOTING.md +70 -0
  167. package/integrations/openclaw/skills/remember/EXAMPLES.md +67 -0
  168. package/integrations/openclaw/skills/remember/SKILL.md +66 -0
  169. package/integrations/openclaw/skills/remember/TROUBLESHOOTING.md +70 -0
  170. package/integrations/openclaw/skills/session-history/EXAMPLES.md +59 -0
  171. package/integrations/openclaw/skills/session-history/SKILL.md +67 -0
  172. package/integrations/openclaw/skills/session-history/TROUBLESHOOTING.md +70 -0
  173. package/integrations/openclaw/skills/write-agentmemory-skill/SKILL.md +41 -0
  174. package/integrations/openclaw/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
  175. package/integrations/pi/LICENSE +190 -0
  176. package/integrations/pi/README.md +45 -51
  177. package/integrations/pi/dist/index.mjs +1379 -0
  178. package/integrations/pi/index.ts +138 -171
  179. package/integrations/pi/package.json +16 -6
  180. package/integrations/pi/security.ts +6 -26
  181. package/integrations/pi/skills/agentmemory-agents/REFERENCE.md +32 -0
  182. package/integrations/pi/skills/agentmemory-agents/SKILL.md +48 -0
  183. package/integrations/pi/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
  184. package/integrations/pi/skills/agentmemory-architecture/SKILL.md +33 -0
  185. package/integrations/pi/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
  186. package/integrations/pi/skills/agentmemory-config/REFERENCE.md +90 -0
  187. package/integrations/pi/skills/agentmemory-config/SKILL.md +78 -0
  188. package/integrations/pi/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
  189. package/integrations/pi/skills/agentmemory-hooks/REFERENCE.md +20 -0
  190. package/integrations/pi/skills/agentmemory-hooks/SKILL.md +49 -0
  191. package/integrations/pi/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
  192. package/integrations/pi/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
  193. package/integrations/pi/skills/agentmemory-mcp-tools/SKILL.md +42 -0
  194. package/integrations/pi/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
  195. package/integrations/pi/skills/agentmemory-rest-api/REFERENCE.md +158 -0
  196. package/integrations/pi/skills/agentmemory-rest-api/SKILL.md +49 -0
  197. package/integrations/pi/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
  198. package/integrations/pi/skills/commit-context/EXAMPLES.md +71 -0
  199. package/integrations/pi/skills/commit-context/SKILL.md +64 -0
  200. package/integrations/pi/skills/commit-context/TROUBLESHOOTING.md +70 -0
  201. package/integrations/pi/skills/commit-history/EXAMPLES.md +71 -0
  202. package/integrations/pi/skills/commit-history/SKILL.md +62 -0
  203. package/integrations/pi/skills/commit-history/TROUBLESHOOTING.md +70 -0
  204. package/integrations/pi/skills/forget/EXAMPLES.md +40 -0
  205. package/integrations/pi/skills/forget/SKILL.md +73 -0
  206. package/integrations/pi/skills/forget/TROUBLESHOOTING.md +70 -0
  207. package/integrations/pi/skills/handoff/EXAMPLES.md +57 -0
  208. package/integrations/pi/skills/handoff/SKILL.md +75 -0
  209. package/integrations/pi/skills/handoff/TROUBLESHOOTING.md +70 -0
  210. package/integrations/pi/skills/lesson/SKILL.md +64 -0
  211. package/integrations/pi/skills/lesson/TROUBLESHOOTING.md +70 -0
  212. package/integrations/pi/skills/memory-discipline/SKILL.md +65 -0
  213. package/integrations/pi/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
  214. package/integrations/pi/skills/recall/EXAMPLES.md +74 -0
  215. package/integrations/pi/skills/recall/SKILL.md +74 -0
  216. package/integrations/pi/skills/recall/TROUBLESHOOTING.md +70 -0
  217. package/integrations/pi/skills/recap/EXAMPLES.md +62 -0
  218. package/integrations/pi/skills/recap/SKILL.md +68 -0
  219. package/integrations/pi/skills/recap/TROUBLESHOOTING.md +70 -0
  220. package/integrations/pi/skills/remember/EXAMPLES.md +67 -0
  221. package/integrations/pi/skills/remember/SKILL.md +66 -0
  222. package/integrations/pi/skills/remember/TROUBLESHOOTING.md +70 -0
  223. package/integrations/pi/skills/session-history/EXAMPLES.md +59 -0
  224. package/integrations/pi/skills/session-history/SKILL.md +67 -0
  225. package/integrations/pi/skills/session-history/TROUBLESHOOTING.md +70 -0
  226. package/integrations/pi/skills/write-agentmemory-skill/SKILL.md +41 -0
  227. package/integrations/pi/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
  228. package/package.json +32 -11
  229. package/plugin/.claude-plugin/plugin.json +1 -1
  230. package/plugin/.codex-plugin/plugin.json +4 -3
  231. package/plugin/.devin-plugin/plugin.json +5 -3
  232. package/plugin/cursor/hooks.json +23 -8
  233. package/plugin/hooks.json +67 -0
  234. package/plugin/opencode/README.md +1 -1
  235. package/plugin/opencode/agentmemory-capture.ts +25 -11
  236. package/plugin/opencode/plugin.json +1 -1
  237. package/plugin/plugin.json +1 -1
  238. package/plugin/scripts/_capture.d.mts +5 -2
  239. package/plugin/scripts/_capture.d.mts.map +1 -1
  240. package/plugin/scripts/_capture.mjs +55 -19
  241. package/plugin/scripts/_capture.mjs.map +1 -1
  242. package/plugin/scripts/cursor/run-detached.d.mts +1 -0
  243. package/plugin/scripts/cursor/run-detached.mjs +786 -0
  244. package/plugin/scripts/cursor/run-detached.mjs.map +1 -0
  245. package/plugin/scripts/cursor/run-hook.d.mts +1 -0
  246. package/plugin/scripts/cursor/run-hook.mjs +704 -0
  247. package/plugin/scripts/cursor/run-hook.mjs.map +1 -0
  248. package/plugin/scripts/notification.mjs +20 -6
  249. package/plugin/scripts/notification.mjs.map +1 -1
  250. package/plugin/scripts/plugin-bridge.mjs +37 -6
  251. package/plugin/scripts/post-commit.mjs +25 -5
  252. package/plugin/scripts/post-commit.mjs.map +1 -1
  253. package/plugin/scripts/post-tool-failure.mjs +12 -4
  254. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  255. package/plugin/scripts/post-tool-use.mjs +12 -4
  256. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  257. package/plugin/scripts/pre-compact.mjs +37 -10
  258. package/plugin/scripts/pre-compact.mjs.map +1 -1
  259. package/plugin/scripts/pre-tool-use.mjs +25 -5
  260. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  261. package/plugin/scripts/prompt-submit.mjs +12 -4
  262. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  263. package/plugin/scripts/session-end.mjs +47 -16
  264. package/plugin/scripts/session-end.mjs.map +1 -1
  265. package/plugin/scripts/session-start.mjs +37 -10
  266. package/plugin/scripts/session-start.mjs.map +1 -1
  267. package/plugin/scripts/stop.mjs +74 -72
  268. package/plugin/scripts/stop.mjs.map +1 -1
  269. package/plugin/scripts/subagent-start.mjs +13 -5
  270. package/plugin/scripts/subagent-start.mjs.map +1 -1
  271. package/plugin/scripts/subagent-stop.mjs +18 -6
  272. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  273. package/plugin/scripts/task-completed.mjs +13 -5
  274. package/plugin/scripts/task-completed.mjs.map +1 -1
  275. package/plugin/skills/_shared/TROUBLESHOOTING.md +21 -7
  276. package/plugin/skills/agentmemory-agents/REFERENCE.md +7 -6
  277. package/plugin/skills/agentmemory-agents/SKILL.md +19 -5
  278. package/plugin/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
  279. package/plugin/skills/agentmemory-architecture/SKILL.md +4 -4
  280. package/plugin/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
  281. package/plugin/skills/agentmemory-config/REFERENCE.md +12 -15
  282. package/plugin/skills/agentmemory-config/SKILL.md +7 -3
  283. package/plugin/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
  284. package/plugin/skills/agentmemory-hooks/SKILL.md +15 -5
  285. package/plugin/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
  286. package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +4 -4
  287. package/plugin/skills/agentmemory-mcp-tools/SKILL.md +6 -3
  288. package/plugin/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
  289. package/plugin/skills/agentmemory-rest-api/REFERENCE.md +9 -1
  290. package/plugin/skills/agentmemory-rest-api/SKILL.md +12 -6
  291. package/plugin/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
  292. package/plugin/skills/commit-context/SKILL.md +1 -1
  293. package/plugin/skills/commit-context/TROUBLESHOOTING.md +70 -0
  294. package/plugin/skills/commit-history/SKILL.md +1 -1
  295. package/plugin/skills/commit-history/TROUBLESHOOTING.md +70 -0
  296. package/plugin/skills/forget/EXAMPLES.md +19 -54
  297. package/plugin/skills/forget/SKILL.md +16 -12
  298. package/plugin/skills/forget/TROUBLESHOOTING.md +70 -0
  299. package/plugin/skills/handoff/EXAMPLES.md +5 -4
  300. package/plugin/skills/handoff/SKILL.md +10 -5
  301. package/plugin/skills/handoff/TROUBLESHOOTING.md +70 -0
  302. package/plugin/skills/lesson/SKILL.md +1 -1
  303. package/plugin/skills/lesson/TROUBLESHOOTING.md +70 -0
  304. package/plugin/skills/memory-discipline/SKILL.md +1 -1
  305. package/plugin/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
  306. package/plugin/skills/recall/SKILL.md +18 -7
  307. package/plugin/skills/recall/TROUBLESHOOTING.md +70 -0
  308. package/plugin/skills/recap/EXAMPLES.md +5 -3
  309. package/plugin/skills/recap/SKILL.md +11 -6
  310. package/plugin/skills/recap/TROUBLESHOOTING.md +70 -0
  311. package/plugin/skills/remember/EXAMPLES.md +3 -3
  312. package/plugin/skills/remember/SKILL.md +1 -1
  313. package/plugin/skills/remember/TROUBLESHOOTING.md +70 -0
  314. package/plugin/skills/session-history/EXAMPLES.md +2 -2
  315. package/plugin/skills/session-history/SKILL.md +10 -4
  316. package/plugin/skills/session-history/TROUBLESHOOTING.md +70 -0
  317. package/plugin/skills/write-agentmemory-skill/SKILL.md +2 -1
  318. package/plugin/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
  319. package/dist/connect-B4_ca6BD.mjs.map +0 -1
  320. package/dist/docker-compose.yml +0 -73
  321. package/dist/iii-config.docker.yaml +0 -53
  322. package/dist/iii-config.yaml +0 -63
  323. package/dist/keyed-mutex-DchXsyfP.mjs.map +0 -1
  324. package/dist/secret-store-BnY3A50C.mjs.map +0 -1
  325. package/dist/src-Jsq9LCeH.mjs.map +0 -1
  326. package/dist/standalone-DKCdrf1d.mjs.map +0 -1
  327. package/dist/status-IRz7ta8-.mjs +0 -2344
  328. package/dist/status-IRz7ta8-.mjs.map +0 -1
  329. package/docker-compose.yml +0 -73
  330. package/iii-config.docker.yaml +0 -53
  331. package/iii-config.yaml +0 -63
@@ -0,0 +1,669 @@
1
+ """
2
+ agentmemory memory provider for Hermes Agent.
3
+
4
+ Install the packaged provider with agentmemory connect hermes.
5
+
6
+ Requires a running AgentMemory server.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ from contextvars import ContextVar
13
+ import ipaddress
14
+ import tempfile
15
+ import json
16
+ import os
17
+ import sys
18
+ import threading
19
+ import subprocess
20
+ import time
21
+ from pathlib import Path, PurePath
22
+ from typing import Any, Callable
23
+ from urllib.parse import urlparse
24
+ from urllib.request import HTTPRedirectHandler, Request, build_opener
25
+ from urllib.error import HTTPError, URLError
26
+
27
+
28
+ def _resolve_project(cwd: str) -> str:
29
+ """Canonical project scope, matching the hooks' resolveProject order:
30
+ AGENTMEMORY_PROJECT_NAME env override, git repository basename, cwd basename.
31
+ Keeps Hermes sessions in the same project bucket as every other agent."""
32
+ explicit = (_environment_value("AGENTMEMORY_PROJECT_NAME") or "").strip()
33
+ if explicit:
34
+ return explicit
35
+ try:
36
+ out = subprocess.run(
37
+ ["git", "rev-parse", "--show-toplevel", "--git-dir", "--git-common-dir"],
38
+ cwd=cwd,
39
+ capture_output=True,
40
+ text=True,
41
+ timeout=5,
42
+ ).stdout.strip()
43
+ top, git_dir, common_dir = (out.splitlines() + ["", "", ""])[:3]
44
+ if top:
45
+ return PurePath(_repository_root(cwd, top, git_dir, common_dir)).name
46
+ except Exception:
47
+ pass
48
+ return PurePath(cwd).name or cwd
49
+
50
+
51
+ def _repository_root(cwd: str, top: str, git_dir: str, common_dir: str) -> str:
52
+ """A linked worktree is named by the main checkout owning the shared .git;
53
+ bare repos and submodules keep the toplevel."""
54
+ if not git_dir or not common_dir:
55
+ return top
56
+ physical = os.path.realpath(cwd)
57
+ common = os.path.normpath(os.path.join(physical, common_dir))
58
+ if os.path.normpath(os.path.join(physical, git_dir)) == common:
59
+ return top
60
+ if os.path.basename(common) != ".git":
61
+ return top
62
+ return os.path.dirname(common)
63
+
64
+
65
+ try:
66
+ from agent.memory_provider import MemoryProvider
67
+ except ImportError:
68
+ from abc import ABC, abstractmethod
69
+
70
+ class MemoryProvider(ABC):
71
+ @property
72
+ @abstractmethod
73
+ def name(self) -> str: ...
74
+ @abstractmethod
75
+ def is_available(self) -> bool: ...
76
+ @abstractmethod
77
+ def initialize(self, session_id: str, **kwargs: Any) -> None: ...
78
+ @abstractmethod
79
+ def get_tool_schemas(self) -> list[dict]: ...
80
+ @abstractmethod
81
+ def handle_tool_call(self, name: str, args: dict) -> str: ...
82
+ def get_config_schema(self) -> list[dict]: return []
83
+ def save_config(self, values: dict, hermes_home: str) -> None: pass
84
+ def system_prompt_block(self) -> str: return ""
85
+ def prefetch(self, query: str, **kwargs: Any) -> str: return ""
86
+ def queue_prefetch(self, query: str, **kwargs: Any) -> None: pass
87
+ def sync_turn(self, user: str, assistant: str, **kwargs: Any) -> None: pass
88
+ def on_session_end(self, messages: list, **kwargs: Any) -> None: pass
89
+ def on_pre_compress(self, messages: list, **kwargs: Any) -> None: pass
90
+ def on_memory_write(self, action: str, target: str, content: str, **kwargs: Any) -> None: pass
91
+ def shutdown(self, **kwargs: Any) -> None: pass
92
+
93
+
94
+ DEFAULT_BASE_URL = "http://localhost:3111"
95
+ TIMEOUT = 5
96
+ _plaintext_bearer_warned = False
97
+
98
+ def _is_loopback(base: str) -> bool:
99
+ if not _validate_url(base):
100
+ return False
101
+ host = (urlparse(base).hostname or "").lower()
102
+ if host == "localhost":
103
+ return True
104
+ try:
105
+ return ipaddress.ip_address(host).is_loopback
106
+ except ValueError:
107
+ return False
108
+
109
+
110
+ def _validate_url(base: str) -> bool:
111
+ if not isinstance(base, str) or not base or any(char.isspace() for char in base) or "?" in base or "#" in base:
112
+ return False
113
+ try:
114
+ parsed = urlparse(base)
115
+ # .port raises ValueError on a non-numeric or out-of-range port
116
+ _ = parsed.port
117
+ except ValueError:
118
+ return False
119
+ if parsed.scheme not in ("http", "https"):
120
+ return False
121
+ return bool(parsed.hostname) and not (parsed.username or parsed.password or parsed.query or parsed.fragment)
122
+
123
+
124
+ def _uses_plaintext_bearer_auth(base: str, secret: str = "") -> bool:
125
+ if not secret:
126
+ return False
127
+ parsed = urlparse(base)
128
+ return parsed.scheme == "http" and not _is_loopback(base)
129
+
130
+
131
+ def _plaintext_bearer_auth_message(base: str) -> str:
132
+ return f"agentmemory: AGENTMEMORY_SECRET is configured for plaintext HTTP to {base}. Bearer tokens and memory payloads can be observed on the network; use HTTPS or an SSH tunnel."
133
+
134
+
135
+ def _warn_plaintext_bearer_auth(message: str) -> None:
136
+ print(message, file=sys.stderr)
137
+
138
+
139
+ def _check_plaintext_bearer_guard(
140
+ base: str,
141
+ secret: str = "",
142
+ warn: Callable[[str], None] | None = None,
143
+ ) -> None:
144
+ global _plaintext_bearer_warned
145
+ if not _uses_plaintext_bearer_auth(base, secret):
146
+ return
147
+ message = _plaintext_bearer_auth_message(base)
148
+ if os.environ.get("AGENTMEMORY_REQUIRE_HTTPS") == "1":
149
+ raise RuntimeError(message)
150
+ if not _plaintext_bearer_warned:
151
+ _plaintext_bearer_warned = True
152
+ (warn or _warn_plaintext_bearer_auth)(message)
153
+
154
+
155
+ def _reset_plaintext_bearer_guard_for_tests() -> None:
156
+ global _plaintext_bearer_warned
157
+ _plaintext_bearer_warned = False
158
+
159
+
160
+ def _usable_secret(value: str | None) -> str:
161
+ trimmed = (value or "").strip()
162
+ if not trimmed or (trimmed.startswith("${") and trimmed.endswith("}")):
163
+ return ""
164
+ return trimmed
165
+
166
+
167
+ def _environment_value(name: str) -> str | None:
168
+ try:
169
+ from agent.secret_scope import get_secret
170
+ except ImportError as error:
171
+ raise RuntimeError("Hermes scoped credential access is unavailable. Update Hermes before enabling AgentMemory.") from error
172
+ return get_secret(name)
173
+
174
+
175
+ def _active_home() -> Path:
176
+ try:
177
+ from hermes_constants import get_hermes_home
178
+ except ImportError:
179
+ return Path(os.environ.get("HERMES_HOME") or Path.home() / ".hermes")
180
+ return Path(get_hermes_home())
181
+
182
+
183
+ def _profile_home() -> Path:
184
+ home = _active_home().expanduser().resolve()
185
+ try:
186
+ from agent.secret_scope import current_secret_scope_home, current_secret_scope, serves_routed_profile
187
+ except ImportError:
188
+ return home
189
+ if serves_routed_profile() and current_secret_scope() is None:
190
+ raise RuntimeError("AgentMemory requires the active Hermes profile secret scope.")
191
+ scoped_home = current_secret_scope_home()
192
+ if scoped_home and Path(scoped_home).expanduser().resolve() != home:
193
+ raise RuntimeError("AgentMemory profile home does not match the active secret scope.")
194
+ return home
195
+
196
+
197
+ def _configuration(hermes_home: str | None = None) -> tuple[str, str]:
198
+ home = _profile_home()
199
+ if hermes_home and Path(hermes_home).expanduser().resolve() != home:
200
+ raise RuntimeError("AgentMemory profile changed. Restart this Hermes session before sending memory requests.")
201
+ try:
202
+ saved = json.loads((home / "agentmemory.json").read_text(encoding="utf-8"))
203
+ if not isinstance(saved, dict):
204
+ saved = {}
205
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError):
206
+ saved = {}
207
+ saved_url = saved.get("url")
208
+ if not isinstance(saved_url, str):
209
+ saved_url = ""
210
+ base = (_environment_value("AGENTMEMORY_URL") or saved_url or DEFAULT_BASE_URL).rstrip("/")
211
+ return base, _usable_secret(_environment_value("AGENTMEMORY_SECRET"))
212
+
213
+
214
+ class _NoRedirect(HTTPRedirectHandler):
215
+ def redirect_request(self, req, fp, code, msg, headers, newurl):
216
+ raise HTTPError(req.full_url, code, "redirect refused", headers, fp)
217
+
218
+
219
+ urlopen = build_opener(_NoRedirect()).open
220
+
221
+
222
+ def _api(base: str, path: str, body: dict | None = None, method: str = "POST", secret: str | None = None) -> dict:
223
+ if not _validate_url(base):
224
+ return {"success": False, "errorCode": "invalid_url", "error": "Configure AGENTMEMORY_URL with an HTTP or HTTPS endpoint without credentials, query or fragment."}
225
+ headers = {"Content-Type": "application/json"}
226
+ auth = _usable_secret(_environment_value("AGENTMEMORY_SECRET")) if secret is None else _usable_secret(secret)
227
+ _check_plaintext_bearer_guard(base, auth)
228
+ if auth:
229
+ headers["Authorization"] = f"Bearer {auth}"
230
+ data = json.dumps(body).encode() if body is not None else None
231
+ req = Request(f"{base.rstrip('/')}/agentmemory/{path}", data=data, headers=headers, method=method)
232
+ try:
233
+ with urlopen(req, timeout=TIMEOUT) as resp:
234
+ result = json.loads(resp.read().decode())
235
+ if not isinstance(result, dict):
236
+ raise ValueError("expected an object")
237
+ return result
238
+ except HTTPError as error:
239
+ error.close()
240
+ if error.code in (401, 403):
241
+ return {"success": False, "errorCode": "authentication", "error": "AgentMemory rejected authentication. Check AGENTMEMORY_SECRET for the configured endpoint."}
242
+ return {"success": False, "errorCode": "http_error", "error": f"AgentMemory returned HTTP {error.code}. Check the configured endpoint; redirects are not followed."}
243
+ except (URLError, TimeoutError, OSError):
244
+ return {"success": False, "errorCode": "unavailable", "error": "AgentMemory is unavailable. Start the server and check AGENTMEMORY_URL."}
245
+ except (ValueError, UnicodeDecodeError):
246
+ return {"success": False, "errorCode": "invalid_response", "error": "AgentMemory returned an invalid JSON object."}
247
+
248
+
249
+ class AgentMemoryProvider(MemoryProvider):
250
+
251
+ @property
252
+ def name(self) -> str:
253
+ return "agentmemory"
254
+
255
+ def is_available(self) -> bool:
256
+ try:
257
+ return _validate_url(_configuration()[0])
258
+ except RuntimeError:
259
+ return False
260
+
261
+ def unavailable_reason(self) -> str:
262
+ try:
263
+ _configuration()
264
+ except RuntimeError as error:
265
+ return str(error)
266
+ return "Set a valid AGENTMEMORY_URL or run hermes memory setup."
267
+
268
+ def initialize(self, session_id: str, **kwargs: Any) -> None:
269
+ if not isinstance(session_id, str) or not session_id.strip():
270
+ raise ValueError("AgentMemory requires a non-empty Hermes session ID.")
271
+ self._hermes_home = _profile_home()
272
+ self._base, secret = _configuration(kwargs.get("hermes_home"))
273
+ self._session_id = session_id.strip()
274
+ self._platform = kwargs.get("platform") or "cli"
275
+ self._local = self._platform in ("cli", "oneshot", "tui", "desktop", "acp")
276
+ self._cwd = kwargs.get("cwd")
277
+ if not self._cwd:
278
+ try:
279
+ from agent.runtime_cwd import scope_terminal_cwd
280
+ self._cwd = scope_terminal_cwd()
281
+ except ImportError:
282
+ pass
283
+ self._cwd = self._cwd or (os.getcwd() if self._local else str(self._hermes_home))
284
+ self._project = _resolve_project(self._cwd)
285
+ self._explicit_agent = (_environment_value("AGENTMEMORY_AGENT_ID") or "").strip()
286
+ if self._explicit_agent == "*" or len(self._explicit_agent) > 128:
287
+ raise ValueError("AGENTMEMORY_AGENT_ID must be at most 128 characters and cannot be a wildcard.")
288
+ author = kwargs.get("user_id")
289
+ self._author = ContextVar("agentmemory_author", default=author.strip() if isinstance(author, str) else "")
290
+ self._writes_enabled = kwargs.get("agent_context", "primary") == "primary"
291
+ self._write_lock = threading.RLock()
292
+ self._sessions: dict[str, str] = {}
293
+ self._ended: set[str] = set()
294
+ self._stopped = False
295
+ self._prefetched: tuple[str, str, str, str] | None = None
296
+ _check_plaintext_bearer_guard(self._base, secret)
297
+ if self._agent_id and self._writes_enabled:
298
+ self._start_session()
299
+
300
+ @property
301
+ def _agent_id(self) -> str:
302
+ if self._explicit_agent:
303
+ return self._explicit_agent
304
+ author = self._author.get()
305
+ if not author and not self._local:
306
+ return ""
307
+ identity = [str(self._hermes_home), self._platform if author else "local", author]
308
+ return "hermes-" + hashlib.sha256(json.dumps(identity).encode()).hexdigest()[:32]
309
+
310
+ def _scoped_session_id(self, session_id: str | None = None) -> str:
311
+ identity = [str(self._hermes_home), self._agent_id, session_id or self._session_id]
312
+ return "hermes-" + hashlib.sha256(json.dumps(identity).encode()).hexdigest()
313
+
314
+ def _session_payload(self, session_id: str | None = None) -> dict:
315
+ return {"sessionId": self._scoped_session_id(session_id), "project": self._project, "cwd": self._cwd, "agentId": self._agent_id}
316
+
317
+ def _start_session(self, session_id: str | None = None) -> None:
318
+ raw_id = session_id or self._session_id
319
+ scoped_id = self._scoped_session_id(raw_id)
320
+ if scoped_id in self._sessions and scoped_id not in self._ended:
321
+ return
322
+ result = self._request("session/start", self._session_payload(raw_id))
323
+ session = result.get("session")
324
+ if isinstance(session, dict) and session.get("id") == scoped_id:
325
+ self._sessions[scoped_id] = raw_id
326
+ self._ended.discard(scoped_id)
327
+
328
+ def _bound_configuration(self) -> tuple[str, str]:
329
+ base, secret = _configuration(str(self._hermes_home))
330
+ explicit_agent = (_environment_value("AGENTMEMORY_AGENT_ID") or "").strip()
331
+ if base != self._base or explicit_agent != self._explicit_agent:
332
+ raise RuntimeError("AgentMemory endpoint or identity changed. Restart this Hermes session before sending memory requests.")
333
+ if not self._agent_id:
334
+ raise RuntimeError("AgentMemory requires a message author for this gateway turn.")
335
+ return base, secret
336
+
337
+ def _request(self, path: str, body: dict) -> dict:
338
+ writing = path in ("remember", "evolve", "observe", "session/start", "session/end")
339
+ if writing:
340
+ self._prefetched = None
341
+ try:
342
+ _, secret = self._bound_configuration()
343
+ if writing and (not self._writes_enabled or self._stopped):
344
+ return {"success": False, "errorCode": "read_only_context", "error": "AgentMemory writes are disabled outside the primary active session."}
345
+ result = _api(self._base, path, body, secret=secret)
346
+ except RuntimeError as error:
347
+ result = {"success": False, "errorCode": "configuration_changed", "error": str(error)}
348
+ if result.get("error"):
349
+ print(f"agentmemory: {result['error']}", file=sys.stderr)
350
+ return result
351
+
352
+ def on_turn_start(self, turn_number: int, message: str, **kwargs: Any) -> None:
353
+ author = kwargs.get("author_id")
354
+ self._author.set(author.strip() if isinstance(author, str) else "")
355
+ self._prefetched = None
356
+
357
+ def get_config_schema(self) -> list[dict]:
358
+ return [
359
+ {
360
+ "key": "url",
361
+ "description": "agentmemory server URL",
362
+ "default": DEFAULT_BASE_URL,
363
+ "env_var": "AGENTMEMORY_URL",
364
+ },
365
+ {
366
+ "key": "project",
367
+ "description": "Project name (optional; defaults to the active workspace)",
368
+ "required": False,
369
+ "env_var": "AGENTMEMORY_PROJECT_NAME",
370
+ },
371
+ {
372
+ "key": "secret",
373
+ "description": "agentmemory auth secret (optional)",
374
+ "secret": True,
375
+ "required": False,
376
+ "env_var": "AGENTMEMORY_SECRET",
377
+ },
378
+ ]
379
+
380
+ def save_config(self, values: dict, hermes_home: str) -> None:
381
+ saved = {"url": values["url"].rstrip("/")} if isinstance(values.get("url"), str) else {}
382
+ if "url" in saved and not _validate_url(saved["url"]):
383
+ raise ValueError("AgentMemory requires an HTTP or HTTPS URL without credentials, query or fragment.")
384
+ config_path = Path(hermes_home) / "agentmemory.json"
385
+ config_path.parent.mkdir(parents=True, exist_ok=True)
386
+ config_path.write_text(json.dumps(saved, indent=2))
387
+
388
+ def system_prompt_block(self) -> str:
389
+ return "Use memory_recall to retrieve relevant saved context and memory_save to retain durable facts. Memory is scoped to this Hermes profile and author by default."
390
+
391
+ def prefetch(self, query: str, **kwargs: Any) -> str:
392
+ session_id = kwargs.get("session_id") or self._session_id
393
+ cached = self._prefetched
394
+ self._prefetched = None
395
+ if cached and cached[:3] == (session_id, self._agent_id, query):
396
+ try:
397
+ self._bound_configuration()
398
+ return cached[3]
399
+ except RuntimeError:
400
+ return ""
401
+ result = self._request("smart-search", {"query": query, "limit": 5, "project": self._project, "agentId": self._agent_id, "includeLessons": False})
402
+ rows = result.get("results", [])
403
+ if not isinstance(rows, list):
404
+ return ""
405
+ ids = []
406
+ for item in rows[:5]:
407
+ if not isinstance(item, dict) or not isinstance(item.get("obsId"), str) or not item["obsId"]:
408
+ continue
409
+ reference = {"obsId": item["obsId"]}
410
+ if isinstance(item.get("sessionId"), str) and item["sessionId"]:
411
+ reference["sessionId"] = item["sessionId"]
412
+ if reference not in ids:
413
+ ids.append(reference)
414
+ if ids:
415
+ expanded = self._request("smart-search", {"expandIds": ids, "project": self._project, "agentId": self._agent_id})
416
+ rows = expanded.get("results", [])
417
+ if not isinstance(rows, list):
418
+ return ""
419
+ lines = []
420
+ for item in rows[:5]:
421
+ if not isinstance(item, dict):
422
+ continue
423
+ obs = item.get("observation")
424
+ if not isinstance(obs, dict):
425
+ continue
426
+ title, narrative = obs.get("title"), obs.get("narrative")
427
+ if isinstance(title, str) and isinstance(narrative, str):
428
+ lines.append(f"- {title[:200]}: {narrative[:1000]}")
429
+ return "\n".join(lines)[:6000]
430
+
431
+ def queue_prefetch(self, query: str, **kwargs: Any) -> None:
432
+ session_id = kwargs.get("session_id") or self._session_id
433
+ context = self.prefetch(query, session_id=session_id)
434
+ self._prefetched = (session_id, self._agent_id, query, context)
435
+
436
+ def get_tool_schemas(self) -> list[dict]:
437
+ return [
438
+ {
439
+ "name": "memory_recall",
440
+ "description": "Search agentmemory for past observations by keyword",
441
+ "parameters": {
442
+ "type": "object",
443
+ "properties": {
444
+ "query": {"type": "string", "description": "Search query"},
445
+ "project": {"type": "string", "description": "Project scope; defaults to the current project"},
446
+ "agentId": {"type": "string", "description": "Agent identity filter"},
447
+ "targetLayer": {"type": "string", "enum": ["all", "memory", "observation"]},
448
+ "limit": {"type": "integer", "description": "Max results", "default": 10},
449
+ },
450
+ "required": ["query"],
451
+ },
452
+ },
453
+ {
454
+ "name": "memory_save",
455
+ "description": "Save an insight, decision, or pattern to long-term memory",
456
+ "parameters": {
457
+ "type": "object",
458
+ "properties": {
459
+ "content": {"type": "string", "description": "What to remember"},
460
+ "concepts": {"type": "string", "description": "Comma-separated concepts"},
461
+ "files": {"type": "string", "description": "Comma-separated relevant file paths"},
462
+ "project": {"type": "string", "description": "Project scope; defaults to the current project"},
463
+ "agentId": {"type": "string", "description": "Agent identity owning the memory"},
464
+ "type": {
465
+ "type": "string",
466
+ "enum": ["pattern", "preference", "architecture", "bug", "workflow", "fact"],
467
+ "description": "Memory type",
468
+ },
469
+ },
470
+ "required": ["content"],
471
+ },
472
+ },
473
+ {
474
+ "name": "memory_search",
475
+ "description": "Hybrid semantic + keyword search across all memories",
476
+ "parameters": {
477
+ "type": "object",
478
+ "properties": {
479
+ "query": {"type": "string"},
480
+ "project": {"type": "string", "description": "Project scope; defaults to the current project"},
481
+ "agentId": {"type": "string", "description": "Agent identity filter"},
482
+ "targetLayer": {"type": "string", "enum": ["all", "memory", "observation"]},
483
+ "limit": {"type": "integer", "default": 5},
484
+ },
485
+ "required": ["query"],
486
+ },
487
+ },
488
+ ]
489
+
490
+ def handle_tool_call(self, name: str, args: dict, **kwargs: Any) -> str:
491
+ if name not in ("memory_recall", "memory_search", "memory_save"):
492
+ return json.dumps({"success": False, "error": f"Unknown tool: {name}"})
493
+ if not isinstance(args, dict):
494
+ return json.dumps({"success": False, "error": "Tool arguments must be an object."})
495
+ key = "content" if name == "memory_save" else "query"
496
+ if not isinstance(args.get(key), str) or not args[key].strip():
497
+ return json.dumps({"success": False, "error": f"{key} must be a non-empty string."})
498
+ payload = {key: args[key], "project": self._project, "agentId": self._agent_id}
499
+ for field in ("project", "agentId"):
500
+ if field in args:
501
+ if not isinstance(args[field], str) or not args[field].strip():
502
+ return json.dumps({"success": False, "error": f"{field} must be a non-empty string."})
503
+ payload[field] = args[field].strip()
504
+ if field == "agentId" and (payload[field] == "*" or len(payload[field]) > 128):
505
+ return json.dumps({"success": False, "error": "agentId must be at most 128 characters and cannot be a wildcard."})
506
+ if name == "memory_save":
507
+ kind = args.get("type", "fact")
508
+ if kind not in ("pattern", "preference", "architecture", "bug", "workflow", "fact"):
509
+ return json.dumps({"success": False, "error": "Invalid memory type."})
510
+ payload["type"] = kind
511
+ for field in ("concepts", "files"):
512
+ value = args.get(field, [])
513
+ if isinstance(value, str):
514
+ value = value.split(",")
515
+ if not isinstance(value, list) or not all(isinstance(item, str) for item in value):
516
+ return json.dumps({"success": False, "error": f"{field} must be CSV text or an array of strings."})
517
+ payload[field] = list(dict.fromkeys(item.strip() for item in value if item.strip()))
518
+ path = "remember"
519
+ else:
520
+ limit = args.get("limit", 10 if name == "memory_recall" else 5)
521
+ if type(limit) is not int or not 1 <= limit <= 100:
522
+ return json.dumps({"success": False, "error": "limit must be an integer between 1 and 100."})
523
+ payload["limit"] = limit
524
+ if "targetLayer" in args:
525
+ if args["targetLayer"] not in ("all", "memory", "observation"):
526
+ return json.dumps({"success": False, "error": "Invalid targetLayer."})
527
+ payload["targetLayer"] = args["targetLayer"]
528
+ payload["includeLessons"] = False
529
+ path = "search" if name == "memory_recall" else "smart-search"
530
+ return json.dumps(self._request(path, payload))
531
+
532
+ def sync_turn(self, user: str, assistant: str, **kwargs: Any) -> None:
533
+ # Hermes owns the serialized background worker and its context propagation.
534
+ with self._write_lock:
535
+ if self._stopped or not self._writes_enabled:
536
+ return
537
+ author = kwargs.get("turn_author")
538
+ token = None
539
+ if isinstance(author, dict):
540
+ author_id = author.get("id")
541
+ token = self._author.set(author_id.strip() if isinstance(author_id, str) else "")
542
+ try:
543
+ raw_id = kwargs.get("session_id") or self._session_id
544
+ self._start_session(raw_id)
545
+ self._request("observe", {
546
+ **self._session_payload(raw_id),
547
+ "hookType": "post_tool_use",
548
+ "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
549
+ "data": {"tool_name": "conversation", "tool_input": user[:500], "tool_output": assistant[:2000]},
550
+ })
551
+ finally:
552
+ if token is not None:
553
+ self._author.reset(token)
554
+
555
+ def on_session_end(self, messages: list, **kwargs: Any) -> None:
556
+ with self._write_lock:
557
+ raw_id = kwargs.get("session_id") or self._session_id
558
+ for scoped_id, session_id in list(self._sessions.items()):
559
+ if session_id != raw_id or scoped_id in self._ended:
560
+ continue
561
+ result = self._request("session/end", {"sessionId": scoped_id})
562
+ if result.get("success"):
563
+ self._ended.add(scoped_id)
564
+
565
+ def on_session_switch(self, new_session_id: str, **kwargs: Any) -> None:
566
+ with self._write_lock:
567
+ self._prefetched = None
568
+ if not isinstance(new_session_id, str) or not new_session_id.strip() or new_session_id.strip() == self._session_id:
569
+ return
570
+ self.on_session_end([])
571
+ self._session_id = new_session_id.strip()
572
+ if kwargs.get("cwd"):
573
+ self._cwd = kwargs["cwd"]
574
+ self._project = _resolve_project(self._cwd)
575
+ if self._agent_id and self._writes_enabled:
576
+ self._start_session()
577
+
578
+ def on_pre_compress(self, messages: list, **kwargs: Any) -> str:
579
+ query = " ".join(message["content"] for message in messages[-5:] if isinstance(message, dict) and isinstance(message.get("content"), str))
580
+ return self.prefetch(query[-2000:], **kwargs) if query.strip() else ""
581
+
582
+ def _mirror_key(self, target: str, content: str) -> str:
583
+ scope = [self._base, self._project, self._agent_id, str(self._hermes_home), target, content]
584
+ return hashlib.sha256(json.dumps(scope).encode()).hexdigest()
585
+
586
+ def _write_mirrors(self, path: Path, records: dict[str, str]) -> None:
587
+ fd, temporary = tempfile.mkstemp(prefix=".agentmemory-mirrors-", dir=path.parent)
588
+ try:
589
+ with os.fdopen(fd, "w") as handle:
590
+ json.dump(records, handle)
591
+ handle.flush()
592
+ os.fsync(handle.fileno())
593
+ os.replace(temporary, path)
594
+ finally:
595
+ if os.path.exists(temporary):
596
+ os.unlink(temporary)
597
+
598
+ def on_memory_write(self, action: str, target: str, content: str, metadata: dict | None = None, **kwargs: Any) -> None:
599
+ if action not in ("add", "update", "replace") or not isinstance(content, str) or not content.strip():
600
+ return
601
+ if self._stopped or not self._writes_enabled:
602
+ return
603
+ # Only IDs returned by this provider may be cleaned up after native replacement.
604
+ try:
605
+ self._bound_configuration()
606
+ import fcntl
607
+ self._hermes_home.mkdir(parents=True, exist_ok=True)
608
+ path = self._hermes_home / "agentmemory-mirrors.json"
609
+ with self._write_lock, (self._hermes_home / "agentmemory-mirrors.lock").open("a") as lock:
610
+ if self._stopped or not self._writes_enabled:
611
+ return
612
+ deadline = time.monotonic() + TIMEOUT
613
+ while True:
614
+ try:
615
+ fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
616
+ break
617
+ except BlockingIOError:
618
+ if time.monotonic() >= deadline:
619
+ raise TimeoutError("native mirror lock deadline reached")
620
+ time.sleep(0.01)
621
+ try:
622
+ try:
623
+ records = json.loads(path.read_text())
624
+ except FileNotFoundError:
625
+ records = {}
626
+ if not isinstance(records, dict) or not all(isinstance(key, str) and isinstance(value, str) for key, value in records.items()):
627
+ raise ValueError("invalid native mirror ownership file")
628
+ previous = (metadata or {}).get("previous_content")
629
+ old_key = self._mirror_key(target, previous) if isinstance(previous, str) and previous else None
630
+ old_id = records.get(old_key) if old_key else None
631
+ replacing = action in ("replace", "update")
632
+ if replacing and old_id:
633
+ if previous == content:
634
+ return
635
+ result = self._request("evolve", {"memoryId": old_id, "newContent": content})
636
+ if not result.get("success"):
637
+ print("agentmemory: native replacement could not evolve its previous record; inspect the reported error before retrying.", file=sys.stderr)
638
+ return
639
+ else:
640
+ result = self._request("remember", {"content": content, "type": "fact", "project": self._project, "agentId": self._agent_id})
641
+ memory = result.get("memory", {})
642
+ if not result.get("success") or not isinstance(memory, dict):
643
+ return
644
+ new_id = memory.get("id")
645
+ if not isinstance(new_id, str) or not new_id.startswith("mem_") or memory.get("content") != content or memory.get("project") != self._project or memory.get("agentId") != self._agent_id:
646
+ print("agentmemory: native mirror write did not establish record ownership; replacement tracking was not updated.", file=sys.stderr)
647
+ return
648
+ new_key = self._mirror_key(target, content)
649
+ records[new_key] = new_id
650
+ if replacing and old_key and old_key != new_key and old_id:
651
+ records.pop(old_key, None)
652
+ self._write_mirrors(path, records)
653
+ if replacing and not old_id:
654
+ print("agentmemory: native replacement saved; previous record ownership is unavailable, so the previous record could not be superseded.", file=sys.stderr)
655
+ finally:
656
+ fcntl.flock(lock, fcntl.LOCK_UN)
657
+ except (ImportError, OSError, ValueError, RuntimeError) as error:
658
+ print(f"agentmemory: native memory mirroring could not complete ({type(error).__name__}); check the active Hermes home and retry.", file=sys.stderr)
659
+
660
+ def shutdown(self, **kwargs: Any) -> None:
661
+ self._stopped = True
662
+ if self._write_lock.acquire(timeout=TIMEOUT):
663
+ self._write_lock.release()
664
+ else:
665
+ print("agentmemory: shutdown deadline reached while a write is still pending.", file=sys.stderr)
666
+
667
+
668
+ def register(ctx: Any) -> None:
669
+ ctx.register_memory_provider(AgentMemoryProvider())