@agentmemory/agentmemory 0.9.30 → 0.9.32

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