@agentmemory/agentmemory 0.9.29 → 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.
- package/.env.example +102 -35
- package/AGENTS.md +19 -26
- package/README.md +417 -525
- package/dist/.env.example +102 -35
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +2316 -655
- package/dist/cli.mjs.map +1 -1
- package/dist/compression-recovery-CnhLQC6N.mjs +5912 -0
- package/dist/compression-recovery-CnhLQC6N.mjs.map +1 -0
- package/dist/{connect-Qaeq15TQ.mjs → connect-BSLIAMpw.mjs} +900 -302
- package/dist/connect-BSLIAMpw.mjs.map +1 -0
- package/dist/hooks/_capture.d.mts +25 -0
- package/dist/hooks/_capture.d.mts.map +1 -0
- package/dist/hooks/_capture.mjs +944 -0
- package/dist/hooks/_capture.mjs.map +1 -0
- package/dist/hooks/antigravity-bridge.d.mts +1 -1
- package/dist/hooks/antigravity-bridge.d.mts.map +1 -1
- package/dist/hooks/antigravity-bridge.mjs +24 -15
- package/dist/hooks/antigravity-bridge.mjs.map +1 -1
- package/dist/hooks/notification.mjs +35 -29
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-commit.mjs +91 -1
- package/dist/hooks/post-commit.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +71 -29
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +99 -39
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +113 -6
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +91 -1
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +25 -25
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +67 -31
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +110 -6
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +80 -8
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +28 -29
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +33 -29
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +31 -31
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/{image-refs-BKLmp_sD.mjs → image-refs-BaT-9oMU.mjs} +2 -2
- package/dist/image-refs-BaT-9oMU.mjs.map +1 -0
- package/dist/{image-refs-CJgZmb9X.mjs → image-refs-Cyta4Lf9.mjs} +3 -3
- package/dist/image-refs-Cyta4Lf9.mjs.map +1 -0
- package/dist/{keyed-mutex-DTHtcZrt.mjs → image-store-BsN4vyjr.mjs} +2 -14
- package/dist/image-store-BsN4vyjr.mjs.map +1 -0
- package/dist/index.mjs +15180 -4119
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/dsh.d.mts +55 -0
- package/dist/integrations/dsh.d.mts.map +1 -0
- package/dist/integrations/dsh.mjs +1145 -0
- package/dist/integrations/dsh.mjs.map +1 -0
- package/dist/{schema-BFZhNTF7.mjs → keyed-mutex-DCUh7PG-.mjs} +81 -8
- package/dist/keyed-mutex-DCUh7PG-.mjs.map +1 -0
- package/dist/secret-store-D9GwO0xp.mjs +2449 -0
- package/dist/secret-store-D9GwO0xp.mjs.map +1 -0
- package/dist/session-arguments-zxsbbd8y.mjs +113 -0
- package/dist/session-arguments-zxsbbd8y.mjs.map +1 -0
- package/dist/{src-G7yt8gGm.mjs → src-DaMtDzt_.mjs} +8320 -4260
- package/dist/src-DaMtDzt_.mjs.map +1 -0
- package/dist/{standalone-BqNJDJ04.mjs → standalone-BlqshyZ5.mjs} +246 -62
- package/dist/standalone-BlqshyZ5.mjs.map +1 -0
- package/dist/standalone.d.mts +1 -1
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +866 -78
- package/dist/standalone.mjs.map +1 -1
- package/dist/viewer/index.html +7394 -2816
- package/integrations/hermes/README.md +84 -0
- package/integrations/hermes/__init__.py +669 -0
- package/integrations/hermes/plugin.yaml +27 -0
- package/integrations/omp/LICENSE +190 -0
- package/integrations/omp/README.md +76 -0
- package/integrations/omp/capture.ts +1 -0
- package/integrations/omp/dist/index.mjs +1450 -0
- package/integrations/omp/index.ts +488 -0
- package/integrations/omp/package.json +13 -0
- package/integrations/omp/security.ts +67 -0
- package/integrations/omp/skills/agentmemory-agents/REFERENCE.md +32 -0
- package/integrations/omp/skills/agentmemory-agents/SKILL.md +48 -0
- package/integrations/omp/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-architecture/SKILL.md +33 -0
- package/integrations/omp/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-config/REFERENCE.md +90 -0
- package/integrations/omp/skills/agentmemory-config/SKILL.md +78 -0
- package/integrations/omp/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-hooks/REFERENCE.md +20 -0
- package/integrations/omp/skills/agentmemory-hooks/SKILL.md +49 -0
- package/integrations/omp/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
- package/integrations/omp/skills/agentmemory-mcp-tools/SKILL.md +42 -0
- package/integrations/omp/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/agentmemory-rest-api/REFERENCE.md +158 -0
- package/integrations/omp/skills/agentmemory-rest-api/SKILL.md +49 -0
- package/integrations/omp/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/commit-context/EXAMPLES.md +71 -0
- package/integrations/omp/skills/commit-context/SKILL.md +64 -0
- package/integrations/omp/skills/commit-context/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/commit-history/EXAMPLES.md +71 -0
- package/integrations/omp/skills/commit-history/SKILL.md +62 -0
- package/integrations/omp/skills/commit-history/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/forget/EXAMPLES.md +40 -0
- package/integrations/omp/skills/forget/SKILL.md +73 -0
- package/integrations/omp/skills/forget/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/handoff/EXAMPLES.md +57 -0
- package/integrations/omp/skills/handoff/SKILL.md +75 -0
- package/integrations/omp/skills/handoff/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/lesson/SKILL.md +64 -0
- package/integrations/omp/skills/lesson/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/memory-discipline/SKILL.md +65 -0
- package/integrations/omp/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/recall/EXAMPLES.md +74 -0
- package/integrations/omp/skills/recall/SKILL.md +74 -0
- package/integrations/omp/skills/recall/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/recap/EXAMPLES.md +62 -0
- package/integrations/omp/skills/recap/SKILL.md +68 -0
- package/integrations/omp/skills/recap/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/remember/EXAMPLES.md +67 -0
- package/integrations/omp/skills/remember/SKILL.md +66 -0
- package/integrations/omp/skills/remember/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/session-history/EXAMPLES.md +59 -0
- package/integrations/omp/skills/session-history/SKILL.md +67 -0
- package/integrations/omp/skills/session-history/TROUBLESHOOTING.md +70 -0
- package/integrations/omp/skills/write-agentmemory-skill/SKILL.md +41 -0
- package/integrations/omp/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/LICENSE +190 -0
- package/integrations/openclaw/README.md +88 -0
- package/integrations/openclaw/openclaw.plugin.json +71 -0
- package/integrations/openclaw/package.json +38 -0
- package/integrations/openclaw/plugin.mjs +425 -0
- package/integrations/openclaw/plugin.yaml +26 -0
- package/integrations/openclaw/skills/agentmemory-agents/REFERENCE.md +32 -0
- package/integrations/openclaw/skills/agentmemory-agents/SKILL.md +48 -0
- package/integrations/openclaw/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-architecture/SKILL.md +33 -0
- package/integrations/openclaw/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-config/REFERENCE.md +90 -0
- package/integrations/openclaw/skills/agentmemory-config/SKILL.md +78 -0
- package/integrations/openclaw/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-hooks/REFERENCE.md +20 -0
- package/integrations/openclaw/skills/agentmemory-hooks/SKILL.md +49 -0
- package/integrations/openclaw/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
- package/integrations/openclaw/skills/agentmemory-mcp-tools/SKILL.md +42 -0
- package/integrations/openclaw/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/agentmemory-rest-api/REFERENCE.md +158 -0
- package/integrations/openclaw/skills/agentmemory-rest-api/SKILL.md +49 -0
- package/integrations/openclaw/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/commit-context/EXAMPLES.md +71 -0
- package/integrations/openclaw/skills/commit-context/SKILL.md +64 -0
- package/integrations/openclaw/skills/commit-context/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/commit-history/EXAMPLES.md +71 -0
- package/integrations/openclaw/skills/commit-history/SKILL.md +62 -0
- package/integrations/openclaw/skills/commit-history/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/forget/EXAMPLES.md +40 -0
- package/integrations/openclaw/skills/forget/SKILL.md +73 -0
- package/integrations/openclaw/skills/forget/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/handoff/EXAMPLES.md +57 -0
- package/integrations/openclaw/skills/handoff/SKILL.md +75 -0
- package/integrations/openclaw/skills/handoff/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/lesson/SKILL.md +64 -0
- package/integrations/openclaw/skills/lesson/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/memory-discipline/SKILL.md +65 -0
- package/integrations/openclaw/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/recall/EXAMPLES.md +74 -0
- package/integrations/openclaw/skills/recall/SKILL.md +74 -0
- package/integrations/openclaw/skills/recall/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/recap/EXAMPLES.md +62 -0
- package/integrations/openclaw/skills/recap/SKILL.md +68 -0
- package/integrations/openclaw/skills/recap/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/remember/EXAMPLES.md +67 -0
- package/integrations/openclaw/skills/remember/SKILL.md +66 -0
- package/integrations/openclaw/skills/remember/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/session-history/EXAMPLES.md +59 -0
- package/integrations/openclaw/skills/session-history/SKILL.md +67 -0
- package/integrations/openclaw/skills/session-history/TROUBLESHOOTING.md +70 -0
- package/integrations/openclaw/skills/write-agentmemory-skill/SKILL.md +41 -0
- package/integrations/openclaw/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/LICENSE +190 -0
- package/integrations/pi/README.md +45 -51
- package/integrations/pi/dist/index.mjs +1379 -0
- package/integrations/pi/index.ts +139 -173
- package/integrations/pi/package.json +16 -6
- package/integrations/pi/security.ts +54 -22
- package/integrations/pi/skills/agentmemory-agents/REFERENCE.md +32 -0
- package/integrations/pi/skills/agentmemory-agents/SKILL.md +48 -0
- package/integrations/pi/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-architecture/SKILL.md +33 -0
- package/integrations/pi/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-config/REFERENCE.md +90 -0
- package/integrations/pi/skills/agentmemory-config/SKILL.md +78 -0
- package/integrations/pi/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-hooks/REFERENCE.md +20 -0
- package/integrations/pi/skills/agentmemory-hooks/SKILL.md +49 -0
- package/integrations/pi/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
- package/integrations/pi/skills/agentmemory-mcp-tools/SKILL.md +42 -0
- package/integrations/pi/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/agentmemory-rest-api/REFERENCE.md +158 -0
- package/integrations/pi/skills/agentmemory-rest-api/SKILL.md +49 -0
- package/integrations/pi/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/commit-context/EXAMPLES.md +71 -0
- package/integrations/pi/skills/commit-context/SKILL.md +64 -0
- package/integrations/pi/skills/commit-context/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/commit-history/EXAMPLES.md +71 -0
- package/integrations/pi/skills/commit-history/SKILL.md +62 -0
- package/integrations/pi/skills/commit-history/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/forget/EXAMPLES.md +40 -0
- package/integrations/pi/skills/forget/SKILL.md +73 -0
- package/integrations/pi/skills/forget/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/handoff/EXAMPLES.md +57 -0
- package/integrations/pi/skills/handoff/SKILL.md +75 -0
- package/integrations/pi/skills/handoff/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/lesson/SKILL.md +64 -0
- package/integrations/pi/skills/lesson/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/memory-discipline/SKILL.md +65 -0
- package/integrations/pi/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/recall/EXAMPLES.md +74 -0
- package/integrations/pi/skills/recall/SKILL.md +74 -0
- package/integrations/pi/skills/recall/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/recap/EXAMPLES.md +62 -0
- package/integrations/pi/skills/recap/SKILL.md +68 -0
- package/integrations/pi/skills/recap/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/remember/EXAMPLES.md +67 -0
- package/integrations/pi/skills/remember/SKILL.md +66 -0
- package/integrations/pi/skills/remember/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/session-history/EXAMPLES.md +59 -0
- package/integrations/pi/skills/session-history/SKILL.md +67 -0
- package/integrations/pi/skills/session-history/TROUBLESHOOTING.md +70 -0
- package/integrations/pi/skills/write-agentmemory-skill/SKILL.md +41 -0
- package/integrations/pi/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
- package/package.json +42 -12
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +27 -4
- package/plugin/.devin-plugin/plugin.json +5 -3
- package/plugin/.mcp.codex.json +12 -0
- package/plugin/.mcp.copilot.json +2 -7
- package/plugin/.mcp.json +2 -7
- package/plugin/assets/icon.svg +35 -0
- package/plugin/cursor/hooks.json +23 -8
- package/plugin/hooks/hooks.copilot.json +11 -11
- package/plugin/hooks.json +67 -0
- package/plugin/opencode/README.md +267 -31
- package/plugin/opencode/agentmemory-capture.ts +594 -11
- package/plugin/opencode/plugin.json +1 -1
- package/plugin/plugin.json +1 -1
- package/plugin/scripts/_capture.d.mts +25 -0
- package/plugin/scripts/_capture.d.mts.map +1 -0
- package/plugin/scripts/_capture.mjs +944 -0
- package/plugin/scripts/_capture.mjs.map +1 -0
- package/plugin/scripts/antigravity-bridge.d.mts +1 -1
- package/plugin/scripts/antigravity-bridge.d.mts.map +1 -1
- package/plugin/scripts/antigravity-bridge.mjs +24 -15
- package/plugin/scripts/antigravity-bridge.mjs.map +1 -1
- package/plugin/scripts/cursor/run-detached.d.mts +1 -0
- package/plugin/scripts/cursor/run-detached.mjs +786 -0
- package/plugin/scripts/cursor/run-detached.mjs.map +1 -0
- package/plugin/scripts/cursor/run-hook.d.mts +1 -0
- package/plugin/scripts/cursor/run-hook.mjs +704 -0
- package/plugin/scripts/cursor/run-hook.mjs.map +1 -0
- package/plugin/scripts/notification.mjs +35 -29
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/plugin-bridge.mjs +409 -0
- package/plugin/scripts/post-commit.mjs +91 -1
- package/plugin/scripts/post-commit.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +71 -29
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +99 -39
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +113 -6
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +91 -1
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +25 -25
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +67 -31
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +110 -6
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +80 -8
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +28 -29
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +33 -29
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +31 -31
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/plugin/skills/_shared/TROUBLESHOOTING.md +42 -12
- package/plugin/skills/agentmemory-agents/REFERENCE.md +10 -9
- package/plugin/skills/agentmemory-agents/SKILL.md +19 -5
- package/plugin/skills/agentmemory-agents/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-architecture/SKILL.md +4 -4
- package/plugin/skills/agentmemory-architecture/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-config/REFERENCE.md +48 -3
- package/plugin/skills/agentmemory-config/SKILL.md +51 -10
- package/plugin/skills/agentmemory-config/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-hooks/SKILL.md +15 -5
- package/plugin/skills/agentmemory-hooks/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +5 -5
- package/plugin/skills/agentmemory-mcp-tools/SKILL.md +6 -3
- package/plugin/skills/agentmemory-mcp-tools/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/agentmemory-rest-api/REFERENCE.md +18 -2
- package/plugin/skills/agentmemory-rest-api/SKILL.md +12 -6
- package/plugin/skills/agentmemory-rest-api/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/commit-context/SKILL.md +1 -1
- package/plugin/skills/commit-context/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/commit-history/SKILL.md +1 -1
- package/plugin/skills/commit-history/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/forget/EXAMPLES.md +19 -54
- package/plugin/skills/forget/SKILL.md +16 -12
- package/plugin/skills/forget/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/handoff/EXAMPLES.md +5 -4
- package/plugin/skills/handoff/SKILL.md +13 -6
- package/plugin/skills/handoff/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/lesson/SKILL.md +1 -1
- package/plugin/skills/lesson/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/memory-discipline/SKILL.md +14 -6
- package/plugin/skills/memory-discipline/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/recall/SKILL.md +18 -7
- package/plugin/skills/recall/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/recap/EXAMPLES.md +5 -3
- package/plugin/skills/recap/SKILL.md +11 -6
- package/plugin/skills/recap/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/remember/EXAMPLES.md +3 -3
- package/plugin/skills/remember/SKILL.md +1 -1
- package/plugin/skills/remember/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/session-history/EXAMPLES.md +2 -2
- package/plugin/skills/session-history/SKILL.md +10 -4
- package/plugin/skills/session-history/TROUBLESHOOTING.md +70 -0
- package/plugin/skills/write-agentmemory-skill/SKILL.md +2 -1
- package/plugin/skills/write-agentmemory-skill/TROUBLESHOOTING.md +70 -0
- package/dist/connect-Qaeq15TQ.mjs.map +0 -1
- package/dist/docker-compose.yml +0 -47
- package/dist/iii-config.docker.yaml +0 -53
- package/dist/iii-config.yaml +0 -61
- package/dist/image-refs-BKLmp_sD.mjs.map +0 -1
- package/dist/image-refs-CJgZmb9X.mjs.map +0 -1
- package/dist/keyed-mutex-DTHtcZrt.mjs.map +0 -1
- package/dist/logger-BEB7pCI9.mjs +0 -43
- package/dist/logger-BEB7pCI9.mjs.map +0 -1
- package/dist/schema-BFZhNTF7.mjs.map +0 -1
- package/dist/src-G7yt8gGm.mjs.map +0 -1
- package/dist/standalone-BqNJDJ04.mjs.map +0 -1
- package/dist/tools-registry-BV_2bu7z.mjs +0 -1405
- package/dist/tools-registry-BV_2bu7z.mjs.map +0 -1
- package/docker-compose.yml +0 -47
- package/iii-config.docker.yaml +0 -53
- package/iii-config.yaml +0 -61
|
@@ -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())
|