19 Commits

Author SHA1 Message Date
Rohit Ghumare 00df540c87 fix(fs-watcher): redact PEM private-key blocks and standalone JWTs in previews (#448) (#450)
PR #332 covered KEY=value and Bearer-prefixed tokens. This extends the
filesystem watcher's preview pipeline to also collapse multi-line and inline
PEM private-key blocks (RSA / DSA / EC / OPENSSH / ENCRYPTED / generic) into
a single REDACTED line bracketed by the BEGIN/END markers, and to redact
standalone JWT-looking strings outside Bearer context. JWT redaction requires
a three-segment dot pattern AND total length >= 100 chars to avoid matching
incidental base64-shaped words.

Closes #448
2026-05-17 12:41:14 +01:00
이민재 e2f2217f59 fix: redact filesystem watcher previews before transport (#332)
* fix: redact filesystem watcher previews

* fix: redact quoted sensitive preview keys

---------

Co-authored-by: 이민재 <19909783+honor2030@users.noreply.github.com>
2026-05-17 11:19:36 +01:00
Erwan Leboucher 1c8713f978 fix(hermes): is_available() must not do network calls (#377) 2026-05-17 10:29:56 +01:00
Virgile 57ffe91302 fix(hermes): use correct API keys tool_input/tool_output in sync_turn (#384)
The Hermes plugin's sync_turn() method sends conversation observations
to the agentmemory /observe endpoint using keys 'input' and 'output',
but the server's mem::observe handler extracts data using 'tool_input'
and 'tool_output' (see src/functions/observe.ts).

This key mismatch means raw.toolInput and raw.toolOutput are always
undefined. When auto-compress is enabled, the LLM receives a nearly
empty compression prompt (only timestamp + hookType + toolName), which
produces generic, useless observations like:

  title: "Agent initiated conversation"
  narrative: "No specific content was provided"
  importance: 1

With the corrected keys, DeepSeek/Vision models receive the actual
conversation content and produce meaningful, structured observations:

  title: "Verified fix for agentmemory plugin key naming"
  importance: 6
  concepts: ["agentmemory plugin", "tool_input/tool_output key renaming"]
  facts: ["The fix replaces 'input'/'output' keys with..."]
2026-05-17 10:29:50 +01:00
Matt Van Horn 1a706ca2d6 fix(integrations): warn when bearer auth crosses plaintext HTTP to non-loopback (#315)
* fix(integrations): warn when bearer auth crosses plaintext HTTP to non-loopback (#275)

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>

* test(integrations/315): edge cases for IPv6 loopback + LAN IPs + lookalike hostnames + docs

@mvanhorn's PR #315 lands the right plaintext-bearer-auth warn-once
pattern across three runtimes (hermes/python, openclaw/mjs,
pi/typescript). Reviewed cleanly. Adding test cases + docs before
merge so the contract is locked:

Test additions (test/integration-plaintext-http.test.ts pi suite,
+4 cases, 13 total in suite):

- IPv6 loopback (http://[::1]:3111) — URL parser strips brackets,
  hostname comes back as `::1`, the LOOPBACK_HOSTS set already covers
  this. Test pins the parsing semantic so a future refactor can't
  silently break it.

- Private LAN IPs (RFC1918 ranges 192.168.x.y, 10.x.x.x) — NOT
  loopback, guard MUST warn. RFC1918 was a deliberate design choice
  (loopback set stays minimal: localhost / 127.0.0.1 / ::1). LAN
  deployment behind a tunnel still leaks the bearer over the wire.

- No-secret short-circuit (guard with secret="" or undefined) — guard
  never fires when a bearer wouldn't actually be sent. Mirrors the
  usesPlaintextBearerAuth() early-return.

- Lookalike hostname (http://localhost.evil.com:3111) — `parsed.hostname`
  returns the full `localhost.evil.com` string, which is NOT in
  LOOPBACK_HOSTS. Guard warns. This pins the exact-match (vs prefix)
  loopback semantic.

Docs additions:

- integrations/pi/README.md and integrations/hermes/README.md now
  document AGENTMEMORY_REQUIRE_HTTPS in the env-vars table — same row
  shape as AGENTMEMORY_SECRET, names the env value (=1), describes the
  warn-vs-throw semantic, and lists the loopback hostnames the guard
  recognises.

No code changes to the guard itself — it's already correct. Pre-merge
hardening only.

899 / 899 tests pass on rebased branch. Build clean.

---------

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Rohit Ghumare <ghumare64@gmail.com>
2026-05-13 15:59:50 +01:00
Rohit Ghumare 48b11e8351 fix(openclaw): claim plugins.slots.memory via registerMemoryCapability (#310)
* fix(openclaw): claim plugins.slots.memory via registerMemoryCapability

The OpenClaw integration declared kind: "memory" in its manifest and
wired before_agent_start / agent_end hooks, but never called
api.registerMemoryCapability(...). On OpenClaw 2026.4.9+ that
left plugins.slots.memory = "agentmemory" reporting unavailable
even though the hooks worked end-to-end and the REST API was
healthy.

Per OpenClaw's plugin SDK declarations
(plugin-sdk/src/plugins/types.d.ts), api.registerMemoryCapability
takes a MemoryPluginCapability and is the canonical way to occupy
the memory slot. All capability fields are optional. The
promptBuilder field has signature:
  (params: { availableTools: Set<string>; citationsMode? }) => string[]

This commit:

- Calls api.registerMemoryCapability({ promptBuilder }) at register
  time when the host exposes it. promptBuilder returns three lines
  describing agentmemory as the active memory provider, including
  the configured base_url so the agent knows where recall is
  coming from.
- Guards on `typeof api.registerMemoryCapability === "function"`
  so older OpenClaw builds (pre-capability API) still load via
  the existing hook-only path. Net behaviour on older hosts is
  unchanged.
- Does NOT register a runtime adapter. OpenClaw's current
  MemoryRuntimeBackendConfig type is exactly { backend: "builtin" }
  or { backend: "qmd"; qmd?: { command?: string } }; both are
  in-process backends and don't fit an external REST service.
  The hook-driven recall + capture flow is the working
  integration path; runtime registration is documented as a
  follow-up gated on an upstream OpenClaw change to add an
  "external" backend variant.

README troubleshooting block adds a specific entry for the
"unavailable slot" symptom, pointing at the registerMemoryCapability
fix. The README also documents the runtime-adapter scope decision
so users running deep host integrations aren't surprised.

3 new tests in test/openclaw-plugin.test.ts assert:
1. registerMemoryCapability is called with a promptBuilder when
   the host supports it.
2. Plugin still registers hooks and does not throw on older hosts
   that don't expose registerMemoryCapability.
3. promptBuilder respects the configured base_url.

871 / 871 tests pass.

Verified against the openclaw npm package surface:
- api.registerMemoryCapability is declared at
  plugin-sdk/src/plugins/types.d.ts:2028
- MemoryPluginCapability is declared at
  plugin-sdk/src/plugins/memory-state.d.ts
- promptBuilder signature matches MemoryPromptSectionBuilder
- No import from openclaw/extensions/* needed (and that path is
  not in the package's exports map anyway)

This is a fresh implementation. The closed PR #302 attempted the
same end goal but imported memoryRuntime from a non-existent
path (openclaw/extensions/memory-core/runtime-api) and would have
failed with ERR_PACKAGE_PATH_NOT_EXPORTED at load time.

* fix(openclaw): accept params arg in promptBuilder to match MemoryPromptSectionBuilder

Reviewer flagged that promptBuilder was declared as () => [...] which
doesn't match the MemoryPluginCapability contract verified from
openclaw@2026.5.7 (plugin-sdk/src/plugins/memory-state.d.ts):

  type MemoryPromptSectionBuilder = (params: {
    availableTools: Set<string>;
    citationsMode?: MemoryCitationsMode;
  }) => string[];

JavaScript would tolerate the zero-arg form at runtime (extra args
are ignored), but the signature was misleading and would fail a
TypeScript port. Updated to (_params) => [...] with a comment
explaining we accept-but-ignore. Underscore-prefix conveys
intentional non-use to lint/readers.

Validation: 3/3 openclaw tests still pass (they invoke
promptBuilder({ availableTools: new Set() }) — both old and new
signatures accept that call).
2026-05-12 23:39:24 +01:00
Rohit Ghumare eaecc658d3 fix(hermes): serialize handle_tool_call returns to JSON string (closes #254) (#255)
Hermes stores handle_tool_call's return value as the tool result
`content` field in the session history. Anthropic-protocol LLM providers
reject non-string content on the next request with:

  Failed to deserialize the JSON body into the target type:
  messages[N]: content should be a string or a list at line 1 column XXXXX

Once triggered, every subsequent request in that session 400s until the
session JSON is manually cleaned.

The plugin's handle_tool_call returned raw Python dicts in all 4 paths
(memory_recall, memory_save, memory_search, unknown-tool fallback). Wrap
each return in json.dumps so the result is always a string.

This matches the contract that agentmemory's own MCP server already
honors in src/mcp/standalone.ts:

  { type: "text", text: JSON.stringify(payload, null, ...) }

Tightens the return-type annotation on both the abstract base and the
concrete class from Any -> str so mypy / type checkers catch any future
regression.

Reported by @KyoMio with full Anthropic-protocol repro.
2026-05-09 11:50:05 +01:00
Rohit Ghumare c2f2618c74 fix(hermes): preload ~/.agentmemory/.env so status reflects service env (closes #250) (#253)
When agentmemory runs as a systemd user service (or any process manager
that loads ~/.agentmemory/.env directly), the values never reach the
shell that runs `hermes memory status`. Status then reads os.environ in
the Hermes CLI process, finds AGENTMEMORY_URL / AGENTMEMORY_SECRET
unset, and reports the plugin as "Missing" even though the service is
healthy and live sessions can use it.

Preload the documented config file at plugin-import time using
os.environ.setdefault so anything explicitly set in the shell still
wins. Best-effort: silently skip when the file is absent, unreadable,
or malformed — the plugin falls back to its existing defaults.

Both ~/.agentmemory/.env (primary, per agentmemory's own README) and
$XDG_CONFIG_HOME/agentmemory/.env (fallback) are checked.

Reported by @OptionalCoin.

Tested:
  $ printf 'AGENTMEMORY_URL=http://example\nAGENTMEMORY_SECRET=s\n' \
      > ~/.agentmemory/.env
  $ unset AGENTMEMORY_URL AGENTMEMORY_SECRET
  $ python -c "import sys; sys.path.insert(0, 'integrations'); \
                import hermes; \
                import os; \
                print(os.environ['AGENTMEMORY_URL'])"
  http://example
  $ AGENTMEMORY_URL=http://shell-wins python -c "..."
  http://shell-wins         # explicit shell value still wins
2026-05-09 11:41:25 +01:00
Rohit Ghumare 2c24c9077e fix(hermes): accept passthrough kwargs in memory provider hooks (closes #249) (#252)
Hermes calls memory provider hooks with extra context kwargs (e.g.
session_id) that the strict signatures rejected with:

  Memory provider 'agentmemory' sync_turn failed: AgentMemoryProvider.
  sync_turn() got an unexpected keyword argument 'session_id'

Add **kwargs: Any to every hook so extra context fields are tolerated:
sync_turn, on_session_end, on_pre_compress, on_memory_write, prefetch,
queue_prefetch, shutdown. Mirror the same change on the abstract
fallback MemoryProvider used when `agent.memory_provider` is unavailable
at import time.

Where Hermes passes session_id, prefer it over the cached
self._session_id so per-turn observations land on the correct session
when Hermes routes a hook through a different conversation than the one
the provider was initialized with.

Reported and locally verified by @OptionalCoin.
2026-05-09 11:18:49 +01:00
Mandeep Singh 2daeb5afda Add tested Pi/OpenClaw/Hermes integration fixes (#230) 2026-05-07 15:41:52 +01:00
Rohit Ghumare 8f44c3ddde docs(hermes): correct plugin install path
Hermes scans `~/.hermes/plugins/<name>/` flat. Memory category
namespace (`memory/`) is reserved for bundled providers and won't
discover user plugins nested under it.

Closes #197
2026-04-27 00:53:05 +01:00
Rohit Ghumare 02ebc5f1fa fix(fs-watcher): address CodeRabbit review
Three issues flagged by review.

1. CRITICAL - observe endpoint rejects current payload shape. The
   /agentmemory/observe handler requires a full HookPayload (non-empty
   hookType from the fixed HookType union, sessionId, project, cwd,
   timestamp) with observation data under 'data'. The watcher was
   emitting hookType='file_change'/'file_delete' (not valid HookType
   values), possibly-null sessionId and project, no cwd or timestamp,
   and files/content/metadata at the top level. The server would 400
   every observation.

   Now emits hookType='post_tool_use' with changeKind moved into
   data.changeKind. sessionId auto-generates as
   fs-watcher-<ts>-<rand> when not supplied; project defaults to the
   first watched directory's basename. cwd is set to the root that
   triggered the event, timestamp to new Date().toISOString().
   files/content/rootDir/absPath/size/truncated/source all sit under
   data.

2. MAJOR - start() returned successfully even when every fs.watch call
   failed. In CLI/supervisor usage that looked like a healthy start
   while no observations ever fired. Now start() throws if
   this.watchers ends up empty, with the node-version hint in the
   message since Node 18 + Linux is the likely cause.

3. MAJOR - engines.node was >=18, but fs.watch(recursive: true) on
   Linux only landed in Node 19.1.0. Raised to >=20 (next LTS after
   19.x) and documented the requirement in README.

test/fs-watcher.test.ts:
- Updated two shape assertions (hookType, sessionId, project, cwd,
  timestamp at top level; files/content/changeKind under data).
- Added test that start() throws when no roots can be watched.
2026-04-18 18:40:36 +01:00
Rohit Ghumare bfcff26cc1 feat: filesystem connector — emits observations on file changes (#62)
First of the data-source connectors tracked in #62. Ships as
@agentmemory/fs-watcher under integrations/filesystem-watcher/.

Scope (v0.1.0):
- Watches one or more roots via node:fs.watch({recursive: true}).
  No native deps; works on macOS / Linux / Windows 10+.
- Emits file_change / file_delete observations to
  POST /agentmemory/observe on the running server.
- Debounces 500ms per path so a stream of editor saves collapses
  to a single observation.
- Reads the first 4 KB of text files for content preview;
  truncation is flagged in metadata. Binary files are skipped by
  default (AGENTMEMORY_FS_WATCH_ALLOW_BINARY=1 to opt in).
- Default ignore set: .git, node_modules, dist, build, .next,
  .turbo, coverage, .DS_Store, *.log, *.lock. Extensible via
  AGENTMEMORY_FS_WATCH_IGNORE.
- Attaches Bearer AGENTMEMORY_SECRET when set.
- CLI and env-driven config; CLI args win. Can be supervised with
  launchd / systemd / pm2.

Tests (test/fs-watcher.test.ts):
- file_change on write, file_delete on unlink.
- Default ignore set skips node_modules/.
- Bearer auth header attached when secret is configured.
- Debounce collapses four rapid writes to <=2 observations.
- configFromEnv parses dirs and regex ignore patterns.

GitHub and Slack connectors (from #62) will be separate PRs since
they need OAuth / token handling and a shared event-normalizer;
the file-watcher has no such dependency and is useful standalone.
2026-04-18 13:05:13 +01:00
Rohit Ghumare 416fa15af4 chore: release 0.8.6 — scope the MCP shim as @agentmemory/mcp (#134)
0.8.5's publish workflow got blocked on the unscoped `agentmemory-mcp`
name by npm's name-similarity policy (there's an unrelated third-party
package called `agent-memory-mcp`). Publishing the shim under the
`@agentmemory` scope we already own sidesteps the conflict.

Changes:
- Rename packages/agentmemory-mcp → packages/mcp
- Package name: agentmemory-mcp → @agentmemory/mcp (0.8.4 → 0.8.6)
- Keep bin name `agentmemory-mcp` so `npm i -g @agentmemory/mcp`
  still installs a binary at `agentmemory-mcp` for muscle memory
- Log prefix [agentmemory-mcp] → [@agentmemory/mcp] in standalone.ts
  and in-memory-kv.ts
- README / integrations/openclaw / integrations/hermes / src/cli.ts
  all point at `npx -y @agentmemory/mcp`
- .github/workflows/publish.yml working-directory and npm view queries
  target packages/mcp and @agentmemory/mcp
- Shim dependency on @agentmemory/agentmemory bumped to ~0.8.6
- Main package 0.8.5 → 0.8.6 across the 8 version files
- CHANGELOG [0.8.6] entry documenting the scope move
2026-04-14 00:31:10 +01:00
Rohit Ghumare 8a36fe7867 fix: retention scoring (#119) + npx agentmemory-mcp 404 (#120), release 0.8.3 (#123)
* fix: retention scoring (#119) + npx agentmemory-mcp 404 (#120), release 0.8.3

Issue #119 — retention score ignored agent-side reads

`mem::retention-score` previously hardcoded `accessCount=0` and
`accessTimestamps=[]` for episodic memories and used only a single-sample
`lastAccessedAt` for semantic memories, so the time-frequency decay
formula was a dead path. All read endpoints now record access events to
a new `mem:access` KV namespace via a keyed-mutex'd access tracker:

  - mem::search, mem::smart-search (compact + expand), mem::context,
    mem::timeline, mem::file-context, mem::get-related, mem::working-context
  - retention.ts pre-fetches the full access log in one kv.list call,
    normalizes every row defensively, and builds an O(1) lookup Map
  - recordAccessBatch uses Promise.allSettled so a slow keyed-lock on one
    id does not block the rest of the batch
  - Bounded ring buffer of the last 20 access timestamps per memory
  - Backwards-compat fallback: pre-0.8.3 semantic memories with only the
    legacy sem.lastAccessedAt still score correctly
  - Corrupt lastAccessedAt values are NaN-safe via Date.parse + isFinite
  - reinforcementBoost is reported as the raw formula output, not the
    clamped residual

Memory deletion sites (retention-evict, evict, auto-forget,
governance-delete, governance-bulk, forget, import replace) now call
deleteAccessLog to prevent mem:access from accumulating zombie entries.
mem::export / mem::import and mem::snapshot-create / mem::snapshot-restore
round-trip the new namespace so backup/restore cycles no longer zero out
reinforcement signals.

Issue #120 — npx agentmemory-mcp returned npm registry 404

The README instructed users to run `npx agentmemory-mcp`, but
`agentmemory-mcp` only existed as a `bin` entry inside
`@agentmemory/agentmemory`, not a real registry package. Two-part fix:

  - New sibling package `packages/agentmemory-mcp/` — a thin shim that
    depends on `@agentmemory/agentmemory` (~0.8.3, tilde to block 0.9.x
    supply-chain drift) and forwards to dist/standalone.mjs via the new
    `exports` field on the main package
  - Canonical `npx @agentmemory/agentmemory mcp` subcommand on the CLI
    for users who already have the main package installed
  - Removed the dead `agentmemory-mcp` bin from the main package so the
    shim owns the name unambiguously
  - publish.yml publishes both packages in order with idempotent
    guards and npm-view registry polling (no more fixed sleep)
  - All MCP install snippets in README + integrations/openclaw + hermes
    + shim README use `npx -y agentmemory-mcp` to skip install prompts

Release 0.8.3

  - 670 tests passing (665 + 11 new: access tracker, retention,
    smart-search integration, corrupt-input, kv.list failure path,
    Promise.allSettled resilience)
  - Version bumped across package.json, src/version.ts, types.ts,
    export-import supportedVersions, plugin.json
  - package-lock.json regenerated

* fix: address CodeRabbit review feedback on #123

Real issues caught and fixed:

- access-tracker: deleteAccessLog now takes the same mem:access:<id>
  keyed mutex used by recordAccess, eliminating the race where a
  concurrent delete could drop an in-flight RMW update and leave a
  stale access log for an evicted memory
- access-tracker: normalizeAccessLog now coerces count to a
  non-negative integer, drops non-finite recent[] entries, truncates
  recent[] to the last 20 on read (enforcing the ring-buffer invariant),
  and guarantees count >= recent.length so a malformed row can't produce
  NaN downstream. count is NOT capped at 20 — it is the lifetime access
  counter, while recent[] is the 20-entry window
- export-import: import path now rejects accessLogs arrays above
  MAX_ACCESS_LOGS (50_000), normalizes each entry via
  normalizeAccessLog, and drops entries whose memoryId is not in the
  imported memories set, so a crafted export can't blow up memory or
  resurrect zombie access rows for memories that don't exist
- publish.yml: added a post-publish npm view polling loop for
  agentmemory-mcp mirroring the one that already guards the main
  package, so the workflow only succeeds when `npx agentmemory-mcp` is
  actually visible on the registry
- CHANGELOG.md: added the missing [0.8.3] reference-style link target
  at the bottom to match the [0.8.2]/[0.8.1]/[0.8.0] pattern
- README.md: standalone MCP inline examples now use `npx -y` to match
  the MCP config snippets and skip the install confirmation prompt

Tests: 680/680 (+10 new — deleteAccessLog behavior, normalizeAccessLog
invariants, malformed mem:access row handling in retention scoring).

Findings deliberately not applied:

- auto-forget.ts / remember.ts / evict.ts recordAudit calls: existing
  pattern in the codebase only audits governance deletes (user-initiated
  admin actions), not automatic eviction. Adding audit everywhere is a
  policy change, out of scope for this PR
- evict.ts gating deleteAccessLog on memory delete success: current
  order is defensible, deleteAccessLog is already best-effort with its
  own try/catch, and calling it after a failed memory delete is harmless
- snapshot.ts fail-fast on kv.list failure: inconsistent with the
  existing .catch(() => []) pattern used for observations in the same
  function
- retention.ts semantic evict only deletes KV.memories rows: real bug
  but pre-existing on main (RetentionScore has no source/type field).
  Tracked separately — out of scope for this PR
- bin.mjs fallback command: already present in the existing catch
  handler
- Promise.all parallelization of delete+deleteAccessLog: micro
  optimization not worth the readability cost
2026-04-13 12:05:18 +01:00
Rohit Ghumare 79a7a8ec04 feat: real agent logos + paste-this-prompt blocks for integrations (#114)
* feat: flashy README with real agent logos + paste-this-prompt blocks for integrations

## Main README — real logos in Supported Agents grid

Replaced plain text cells with real brand logos for all 16 supported agents:

- simple-icons CDN for: Claude Code (Anthropic), Cursor, Gemini CLI
  (Google Gemini), Codex CLI (OpenAI), Windsurf, Claude Desktop, Claude SDK
- GitHub org avatars for: OpenClaw, OpenCode (opencode-ai), Cline,
  Hermes (NousResearch), Goose (block), Kilo Code (kilo-org), Aider
  (Aider-AI), Roo Code (RooCodeInc)
- Shield badge for the generic "Any agent" cell

Each logo is clickable and links to the upstream project. The OpenClaw and
Hermes cells now link directly to integrations/openclaw and integrations/hermes
so users can find the deeper plugin integration.

Added tagline footer below the grid reinforcing the cross-agent story.

## Integration READMEs — paste-this-prompt blocks

Following the gstack pattern, both integration READMEs now open with a
visual hero (upstream logo + agentmemory logo) and a copy-pasteable text
block the user can drop into their agent. The agent handles the entire
setup — start server, update MCP config, verify health, open viewer.

This matches the "paste this into Claude Code" block we already have in
the main README for Claude Code users.

### openclaw/README.md
- Centered hero: OpenClaw org avatar + agentmemory icon
- Shield badges: 43 MCP tools, 4 lifecycle hooks, 95.2% R@5, self-hosted
- "Install it in 30 seconds" section with the paste-this prompt

### hermes/README.md
- Centered hero: NousResearch avatar + agentmemory icon
- Shield badges: 43 MCP tools, 6 lifecycle hooks, 95.2% R@5, self-hosted
- "Install it in 30 seconds" section with the paste-this prompt

All 654 tests still passing.

* fix: reorder agents, replace broken logos, add OpenClaw/Hermes paste blocks, cleaner hero

## Main README

### Supported Agents grid — 3 fixes

1. **Reorder**: OpenClaw and Hermes now in positions 2 and 3 (right after
   Claude Code), since they have first-class gateway plugins in
   integrations/. Users shouldn't have to scroll to find them.

2. **Fix broken Codex logo**: simple-icons CDN returned 404 for the
   OpenAI slug with black color. Switched ALL logos to GitHub org
   avatars (github.com/<org>.png?size=120) which are bulletproof and
   always return real images. Verified 7/7 resolve to actual PNGs.

3. **Bigger logos**: bumped from 40px to 48px for more visual weight.

### Paste-this-prompt toggles

Added two collapsible <details> blocks after the Claude Code paste
prompt: one for OpenClaw, one for Hermes. Each contains a complete
copy-pasteable setup prompt that the agent can execute, plus a link
to the full integration guide.

## Integration README heroes — redesigned

The old hero had two logos separated by an ugly gray "+" shield badge
that looked cheap. Replaced with:

- agentmemory banner.png as the centered hero image
- H1 title with a small inline upstream logo (28px) next to the title
- Bolded tagline + subtitle with upstream link
- Shield badge row (MCP tools, hooks, R@5, self-hosted, license)

Much cleaner and follows the main README's visual pattern.

Verified: 654/654 tests passing.

* Update README.md

* feat: custom SVG tags for section headers (no emojis)

Replaces emoji section headers with custom SVG badges matching the
agentmemory brand style (orange #FF6B35 accent on dark #1A1A1A).
Inspired by shanraisshan/claude-code-best-practice's custom tag
approach in its !/ directory.

## Custom SVG tags created (30 total in assets/tags/)

### 14 section headers (section-*.svg)
Each is a 44px-tall dark rounded badge with:
- Orange gradient accent strip on the left (brand color #FF6B35 → #FF8F5E)
- Uppercase title in white, 800 weight, 2.2 letter-spacing
- Light gray subtitle (#9CA3AF) with section context
- Subtle #2A2A2A border on dark #1A1A1A → #0F0F0F gradient

Sections: works-with-every-agent, benchmarks, vs-competitors, quick-start,
why, how-it-works, search, mcp-server, real-time-viewer, configuration,
api, architecture, development, license.

### 6 hero stat blocks (stat-*.svg)
Big numbers with small uppercase labels, replacing the shield.io badge
row in two places (hero + benchmarks section):
- stat-recall: 95.2% R@5 (green)
- stat-tokens: 92% fewer tokens (green)
- stat-tools: 43 MCP tools (orange)
- stat-hooks: 12 auto hooks (orange)
- stat-deps: 0 external DBs (purple)
- stat-tests: 654 tests passing (green)

### 8 pill tags (pill-*.svg)
Rounded pills with colored dot indicator for inline status tags:
MCP, AUTO HOOK, SKILL, NEW, BETA, STABLE, PLUGIN, SECURE.

### 1 "New in v0.8.2" version tag
Dark pill with orange v0.8.2 version marker.

### 1 section divider
Horizontal line with orange diamond in the middle (unused for now,
available for future sections).

## README changes

- All <h2> section headers now use <img> tags pointing to the SVG
- Hero stats row replaced with stat-*.svg row (same visual position)
- Benchmarks stats row replaced with the same stat-*.svg row
- Nav links updated to match the new h2 anchor IDs
- "New in v0.8.2" callout uses new-v082.svg tag + note
- Cursor grid cell fixed (was missing <strong>Cursor</strong> label)
- Zero emojis in the entire README

All 30 SVGs validated with xmllint, 654/654 tests passing.

* Remove version 0.8.2 image from README

Removed image tag for version 0.8.2 from README.
2026-04-12 11:46:45 +01:00
Rohit Ghumare ae8f061cd6 feat: demo command, benchmark comparison, OpenClaw integration (#113)
* feat: add demo command, benchmark comparison, OpenClaw integration, token cost in viewer

Product & ecosystem improvements surfaced from the last30days research
and competitive analysis of the AI agent memory space.

## New

### agentmemory demo command
Seeds 3 realistic sessions (JWT auth, N+1 query fix, rate limiting)
and runs smart-search queries against them. Shows token-aware semantic
search finding "N+1 query fix" when you search "database performance
optimization" — proof that keyword matching can't do that.

  npx @agentmemory/agentmemory demo

Surfaces the "show don't tell" moment in 30 seconds without requiring
the user to integrate anything.

### benchmark/COMPARISON.md
Head-to-head comparison vs mem0 (53K stars, 68.5% LoCoMo),
Letta/MemGPT (22K stars, 83.2% LoCoMo), Khoj (34K stars),
claude-mem (46K stars), and Hippo. Feature matrix across 18
dimensions. Token efficiency table. Honest caveats about LongMemEval
vs LoCoMo apples-vs-oranges. Lets users compare without trusting our
README.

### integrations/openclaw/
OpenClaw gateway plugin following the same pattern as the Hermes
integration. README, plugin.yaml with hooks config, and plugin.mjs
implementing the 4 lifecycle hooks (on_session_start, on_pre_llm_call,
on_post_tool_use, on_session_end). Gets agentmemory into the OpenClaw
ecosystem for distribution.

## Improved

### Viewer token savings card
Now shows tokens saved AND dollar cost saved (using $0.30/1K as a
reasonable mid-tier model rate) instead of just raw numbers. Makes
the ROI immediately tangible.

### README additions
- "Try it in 30 seconds" section featuring the new demo command
- Competitor comparison link in benchmarks section
- OpenClaw integration link in supported agents table

* refactor: simplify demo command and OpenClaw plugin

- Extract postJson helper in cli.ts runDemo (4x repeated fetch boilerplate → single call site)
- Break runDemo into buildDemoSessions, seedDemoSession, runDemoSearch for single-responsibility
- Replace three-branch if/else command dispatch with a commands map (single error handler)
- Extract typed DemoSession/DemoObservation/SearchResult interfaces
- Simplify OpenClaw plugin.mjs: collapse headers() + fetchJson() into a single postJson method
  Each hook is now ~5 lines instead of ~10

No behavior change. 654/654 tests passing, CLI help and error paths verified.

* fix: address CodeRabbit review on growth changes

## plugin.mjs
- Honor the enabled config flag: constructor now sets this.enabled and
  all four hooks (onSessionStart, onPreLlmCall, onPostToolUse,
  onSessionEnd) return early when disabled
- Fail-fast on non-OK HTTP: previously !res.ok returned null regardless
  of fallbackOnError. Now throws with status/statusText/body when
  fallbackOnError is false
- Update header JSDoc to use the real hook names instead of the
  fictional prefetch/capture/consolidate

## openclaw README.md
- Replace stale hook names (prefetch/capture/consolidate) with the
  actual onSessionStart/onPreLlmCall/onPostToolUse/onSessionEnd names
  matching plugin.mjs
- Add 'text' language identifier to the Project profile code fence to
  satisfy markdownlint MD040

## cli.ts
- Replace ad-hoc Date.now()-based session IDs with generateId("demo")
  from src/state/schema.ts, matching the rest of the codebase
  (snapshot.ts, migrate.ts, consolidation-pipeline.ts)
- Add postJsonStrict helper that throws on non-OK with status + body
- seedDemoSession now uses postJsonStrict for session/start and
  session/end (critical lifecycle calls that should fail fast)
- The observation POST in the loop now inlines the fetch to surface
  HTTP errors via p.log.warn with status/body, still only incrementing
  the stored counter on success

## viewer/index.html
- Fix token-to-dollar conversion that was 100x underreporting:
  tokensSaved / 1000 * 0.3 returns DOLLARS, not cents. Now computes
  dollars first, then cents = Math.round(dollars * 100)
- 100K tokens saved now correctly displays $30.00, not 30ct

All 654 tests still passing.

* fix: address 2 more CodeRabbit findings on plugin.mjs

- Use nullish coalescing (??) for config defaults so valid falsy
  values like min_confidence: 0 or timeout_ms: 0 aren't overwritten
  by defaults. || would clobber them.

- Fix /agentmemory/context payload to match the server contract at
  src/triggers/api.ts:115. The handler expects { sessionId, project,
  budget? } but the plugin was sending { sessionId, query, tokenBudget,
  minConfidence }. Three issues:
  1. Missing required 'project' field
  2. Wrong field name: 'tokenBudget' should be 'budget'
  3. 'query' and 'minConfidence' are not part of this endpoint's
     contract (probably belong on /smart-search instead)
2026-04-12 11:05:21 +01:00
Rohit Ghumare 9173b090f4 fix: address review findings on Hermes plugin
- SSRF prevention: validate URL scheme (http/https only) before urlopen
- on_pre_compress: inject fetched context into messages list (was no-op)
- Remove unused self._secret; _api reads from env directly
2026-04-09 13:43:34 +01:00
Rohit Ghumare 112e6f4bc0 feat: add Hermes Agent memory provider plugin
Two integration paths:
1. MCP server (zero code) - add to ~/.hermes/config.yaml
2. Memory provider plugin (deeper) - hooks into Hermes agent loop

Plugin implements the full MemoryProvider ABC:
- prefetch() injects relevant memories before each LLM call
- sync_turn() captures conversation turns in background (non-blocking)
- on_session_end() marks sessions for summarization
- on_pre_compress() re-injects context before compaction
- on_memory_write() mirrors MEMORY.md writes
- system_prompt_block() injects project profile
- 3 tools: memory_recall, memory_save, memory_search

Uses only stdlib (urllib, json, threading). No new dependencies.
2026-04-09 13:33:10 +01:00