* fix: Claude Code plugin auto-wires MCP + sandbox-safe skills (#139)
Two fixes reported in #139:
1. The Claude Code plugin (rohitg00/agentmemory) only shipped hooks
and skills; it did not auto-register any MCP server. Users had to
configure the @agentmemory/mcp stdio server separately, which the
README didn't make clear. Add plugin/.mcp.json so
/plugin install agentmemory@agentmemory wires up the MCP server
automatically when the plugin is enabled.
2. The `recall` and `session-history` skills used inline bash
pre-execution blocks (`!$(curl -s ... ${VAR:-default})`) with
shell expansion, which Claude Code's sandbox rejects by pattern
match with "Contains expansion" — no way to bypass except for
Claude to reconstruct the curl call manually, as the reporter
had to work around.
Rewrite all four plugin skills (recall / remember / forget /
session-history) as pure prompts that tell Claude to use the MCP
tools directly: memory_smart_search, memory_save, memory_sessions,
memory_governance_delete. No bash, no sandbox issues, no shell
escaping. Skills also run faster — no curl subprocess fork on
every invocation.
Also tighten the README Claude Code install snippet to note that
the plugin now registers hooks + skills AND auto-wires the MCP
server in one step.
Bumps to 0.8.9. 707 tests still pass (no behavioral changes to
server code; only plugin assets).
* review: implement the MCP tools the new skills call (#139)
CodeRabbit flagged that the rewritten plugin skills reference
memory_smart_search and memory_governance_delete, but the
standalone MCP shim (@agentmemory/mcp) only exposed 5 tools:
memory_save, memory_recall, memory_sessions, memory_export,
memory_audit. At runtime the skills would have thrown
"Unknown tool" the first time they ran.
Add the missing handlers to src/mcp/standalone.ts:
- memory_smart_search — aliases memory_recall, falling back to
substring search over the in-memory KV store. The standalone
shim has no engine behind it, so BM25/vector/graph aren't
available, but the substring match is good enough for plugin
usage on small personal memory stores.
- memory_governance_delete — deletes memories by id. Accepts
memoryIds as an array or a comma-separated string, returns
{deleted, requested, reason}. Throws on missing/empty input,
silently skips unknown ids.
Also normalize memory_save concepts/files: they now accept both
arrays (what the new skills send) and CSV strings (what the old
skills and legacy clients send). New normalizeList() helper
centralizes the logic.
Fix memory_sessions to honour a limit arg (default 20) — it was
previously returning every session in the store.
Update the forget skill wording per CodeRabbit's nit: make it
explicit that memory_governance_delete only takes memoryIds, and
that deleting a whole session means collecting all memory IDs
from that session via search first.
Adds 8 regression tests to test/mcp-standalone.test.ts covering:
- memory_save with array concepts/files
- memory_save with CSV concepts/files (legacy path)
- memory_smart_search substring fallback
- memory_sessions limit
- memory_governance_delete array path
- memory_governance_delete CSV path
- memory_governance_delete missing/empty validation
- memory_governance_delete unknown-id skip
Full suite: 715 passing (was 707 + 8 new).
* review: reject empty search query + fix skill troubleshooting paths (#139)
CodeRabbit round 2:
1. src/mcp/standalone.ts memory_smart_search accepted '' as a
query and used .includes('') which matches every memory. In
the forget flow this would surface unrelated memory IDs for
deletion. Now throws 'query is required' on missing/empty/
whitespace-only input. Also broadens the search corpus from
just title+content to include files, concepts, sessionIds,
and id — so users can /forget by file path, concept name, or
session ID, as the skill docs already promise.
2. All four skill files (recall, remember, forget, session-history)
said 'check the agentmemory engine is running on localhost:3111'
when tools are missing. Wrong troubleshooting path: the stdio
MCP shim spawned by Claude Code via .mcp.json has no REST API,
no port, no localhost. The correct checks are /plugin list,
/mcp, and restarting Claude Code after install. Updated all
four skills.
Adds 2 new regression tests to test/mcp-standalone.test.ts:
- memory_smart_search rejects empty/whitespace/missing query
- memory_smart_search finds memories by file, concept, or id
Full suite: 717 passing.
* review: tighten content/limit validation in standalone MCP (#139)
CodeRabbit round 3:
1. memory_save cast args.content as string and called .trim(),
which throws a runtime TypeError if content is a number, object,
null, etc. Replace the cast with a proper typeof guard that
returns the clean 'content is required' error for any non-string
input.
2. memory_smart_search trimmed rawQuery for emptiness but then
lowercased the *untrimmed* value, so a query like ' bcrypt '
would fail substring matches with trailing whitespace. Trim
first, then lowercase.
3. (args.limit as number) || 10 is fragile — it treats 0 and NaN
the same, silently accepts negative/Infinity values, and has no
upper bound so a malicious or buggy caller could request a
million memories in one slice(). Add parseLimit() helper that:
- rejects non-number/non-string types (booleans, objects, arrays)
- rejects NaN, Infinity, and non-positive values
- clamps to MAX_LIMIT=100 via Math.min
- Math.floor()s to force integer
- falls back to a per-call default (10 for search, 20 for
sessions, 50 for audit)
Applied to all three limit sites: memory_smart_search,
memory_sessions, memory_audit.
Adds 2 regression tests:
- memory_save rejects 42 / {} / [] / null / undefined / true without
crashing on .trim()
- parseLimit falls back on bogus values and clamps 99999 -> 100
Full suite: 719 passing (was 717 + 2 new).
* fix: stop burning Claude tokens on every PostToolUse hook (#138)
mem::observe was firing mem::compress unconditionally on every
PostToolUse hook, which called Claude via the user's ANTHROPIC_API_KEY
to compress each raw observation into a structured summary. On an
active coding session (50-200 tool calls/hour) this silently burned
hundreds of thousands of Claude API tokens within minutes. Reported
by @olcor1 on a fresh Docker install — his token allocation was gone
in 20 minutes.
Make per-observation LLM compression opt-in.
- New env var AGENTMEMORY_AUTO_COMPRESS, default `false`. When false,
observe.ts builds a zero-LLM synthetic CompressedObservation from
raw tool I/O (title from toolName, narrative from truncated
input/output, files extracted from file_path/pattern/etc., type
inferred from camelCase-normalized tool name) and stores + indexes
it. Recall and BM25 search still work.
- New src/functions/compress-synthetic.ts with buildSyntheticCompression().
- src/functions/observe.ts gates the compress trigger on the flag and
writes the synthetic observation + stream events when disabled.
- Startup banner now logs "Auto-compress: OFF (default, #138)" or a
loud warning when opt-in is on, so the mode is never silent.
- Regression test: test/auto-compress.test.ts covers the default path
(no mem::compress fired), explicit opt-in, tool-name-to-type
mapping, file-path extraction, narrative truncation, and the
post_tool_failure→error mapping.
Bumps to 0.8.8 (main package + shim). 707 tests pass.
**Migration**: users who were relying on LLM-generated summaries set
AGENTMEMORY_AUTO_COMPRESS=true in ~/.agentmemory/.env and restart.
Existing compressed observations on disk are untouched.
* review: address CodeRabbit findings on #140
- compress-synthetic.ts:92 — use the fallback toolName for inferType
so both title and type derive from the same resolved value
- test/auto-compress.test.ts — vi.resetModules() in beforeEach to
prevent cached observe.js from seeing stale env var state between
the default, opt-in, and explicit-off test cases
docker-compose.yml mounts `./iii-config.docker.yaml:/app/config.yaml:ro`
but the `files` array in package.json only shipped `iii-config.yaml`.
Docker resolves missing bind sources by silently creating them as empty
directories, so `npx @agentmemory/agentmemory` mounted an empty dir at
/app/config.yaml and iii-engine crashed with:
Error: Failed to read config file '/app/config.yaml':
Is a directory (os error 21)
Fix: add iii-config.docker.yaml to package.json `files`.
Also adds a regression test in test/consistency.test.ts that parses
every `./<path>:<container>` bind mount in docker-compose.yml and
asserts the source file is shipped via the `files` array, so this
class of bug can't reach a release again.
Bumps main package + shim to 0.8.7 and adds CHANGELOG [0.8.7].
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
Two community contributions on top of 0.8.3 land as a patch release so
they reach users immediately and so `npx agentmemory-mcp` ships to the
npm registry for the first time (closing out #120 on the live release).
- #122 (Jason Landbridge): memory_save persists to disk immediately
after every call, not just on SIGINT/SIGTERM, so memories survive
SIGKILL from agent sessions ending forcefully
- #121 (Jason Landbridge): OpenCode MCP config format corrected —
opencode.json with mcp/type:local/command array instead of the
wrong .opencode/config.json with mcpServers
Version bumped across package.json, src/version.ts, types.ts,
export-import supportedVersions, plugin.json, shim package.json and
its dependency on the main package (~0.8.4). package-lock.json
regenerated. 684 tests passing, clean build.
* 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
- Update zod from ^3.23.0 to ^4.0.0 (claude-agent-sdk peer dep conflict)
- Mount iii-config.yaml in docker-compose.yml (missing /app/config.yaml)
- Change host binding from 127.0.0.1 to 0.0.0.0 for Docker compatibility
- Switch npm start from index.mjs to cli.mjs (auto-starts iii-engine)
- Fix CLI health check to detect engine via any HTTP response, not just worker route
- Handle EADDRINUSE on viewer port gracefully instead of crashing
- Fix README step numbering and version in health example