* fix: stop burning Claude Pro tokens on every tool call (#143)
0.8.8 fixed the agentmemory-side Claude API burn (where the engine
called Claude via the user's ANTHROPIC_API_KEY for per-observation
compression). That addressed #138 for users with API keys, but it
missed the second and much larger token-burn path: the PreToolUse
hook writing context to stdout.
Claude Code reads PreToolUse stdout and prepends it to the model's
next turn. src/hooks/pre-tool-use.ts was POSTing /agentmemory/enrich
on every Edit/Write/Read/Glob/Grep tool call and piping up to 4000
chars of response context into stdout. At ~20 tool calls per user
message this silently injected ~20K tokens per message into Claude
Code's input window — all charged against the user's Claude Pro
allocation because Claude Code was the one sending them to Anthropic.
4 messages drained the cap, which matches @adrianricardo's report.
session-start.ts had the same pattern (injected once per session,
smaller blast radius).
Fix: gate both hooks on AGENTMEMORY_INJECT_CONTEXT, default false.
- pre-tool-use.ts: when disabled, exit immediately — no stdin read,
no fetch, no stdout write. The hot path (~20x per message) becomes
a no-op Node startup.
- session-start.ts: when disabled, still POST /agentmemory/session/start
so the session gets registered for observation tracking, but never
write context to stdout. The session registration is cheap and
doesn't touch Claude Code's input window.
- src/config.ts: new isContextInjectionEnabled() helper.
- src/index.ts: startup banner prints 'Context injection: OFF
(default, #143)' on normal startup and a loud WARNING when
opt-in is enabled.
- test/context-injection.test.ts: 5 subprocess tests that spawn the
compiled pre-tool-use.mjs and session-start.mjs hooks, feed real
JSON payloads via stdin, and assert stdout is empty in all the
off/default paths. Also asserts the disabled path exits under 1s
and the opt-in path with an unreachable backend still exits
cleanly.
- README .env section: new AGENTMEMORY_INJECT_CONTEXT entry.
- CHANGELOG [0.8.10] with prominent 'Behavior change' banner.
Observations are still captured via PostToolUse regardless of the
flag — the memory store and MCP search tools are completely
unaffected by this change. The fix only severs the path where
agentmemory silently shoves memory context into the user's Claude
Code conversation.
Bumps to 0.8.10 (main + @agentmemory/mcp shim). Test count:
724 passing (was 719 + 5 new).
* chore: rewrite #143 CHANGELOG entry with corrected diagnosis
PreToolUse stdout is NOT injected into the model context — per
the Claude Code hook docs, only UserPromptSubmit and SessionStart
stdout are injected. My initial #143 PR description and CHANGELOG
claimed PreToolUse was the smoking gun behind 'Pro allocation
burned in 4 messages', which is wrong.
What's actually true:
- SessionStart stdout injection IS real (~1-2K tokens per session)
- PreToolUse stdout goes to debug log only — no tokens
- Claude Pro's Claude Code quotas are tight by design (Anthropic
has publicly acknowledged this); 4 messages to burn is plausible
with or without agentmemory installed
The gate on pre-tool-use.ts is still worth keeping as a resource
cleanup (skips a 20x-per-message Node+HTTP hot path) and as
forward-compat protection in case Claude Code ever changes
PreToolUse hook contract. But the CHANGELOG entry has to stop
claiming it saves tokens when it doesn't.
* fix: mem::retention-evict no longer leaks semantic memories (#124)
The eviction loop was unconditionally calling kv.delete(KV.memories,
id) for every below-threshold candidate, but retention scores are
computed for both episodic (KV.memories) and semantic (KV.semantic)
memories. When a candidate came from KV.semantic, the delete silently
became a no-op (key wasn't in mem:memories to begin with) and the
semantic row stayed alive forever with a sub-threshold score. Semantic
memories could not be evicted by this path at all.
Fix:
- Add a source: "episodic" | "semantic" discriminator to RetentionScore
- Tag it at score creation in both loops of mem::retention-score
- Branch the delete in mem::retention-evict on candidate.source,
routing to KV.memories or KV.semantic accordingly
- Pre-0.8.10 retention rows with no source field are treated as
episodic for backwards-compat so upgraded stores continue to evict
their old rows without re-scoring first
- Response now includes evictedEpisodic and evictedSemantic counts
so callers can see what was removed from each scope
Adds 3 regression tests to test/retention.test.ts:
- Scoring tags rows with the correct source
- Evicting a mixed set of below-threshold episodic + semantic
candidates removes both from their respective scopes
- Legacy-shape score rows with no source field still evict
to mem:memories (backwards-compat)
Full suite: 727 passing (was 724 + 3 new).
* review: probe namespaces for legacy retention rows (#124, round 2)
CodeRabbit caught a real backwards-compat hole in the #124 fix:
pre-0.8.10 stores already contain semantic retention rows with
no source field (because the old mem::retention-score scored
KV.semantic before the discriminator existed). My first fix
defaulted missing source to episodic, which meant those legacy
semantic rows still got delete-routed to KV.memories — the exact
no-op that stranded them in the first place.
Fix: when candidate.source is undefined, probe KV.memories first
for the memoryId; if it's there, route to episodic, otherwise
route to semantic. Count the resolved source in the response.
Adds one new test case: a pre-0.8.10 semantic memory with a
legacy-shape retention row (no source field) gets evicted from
mem:semantic, not silently no-op'd. Existing 'defaults to
episodic' test is kept and retargeted to the genuinely-episodic
legacy case.
Also fixes a README nit: the AGENTMEMORY_INJECT_CONTEXT comment
previously implied SessionStart fires on every tool turn. It's
once per session. Now broken out into two bullets explaining
what each hook does differently, with the note that only
SessionStart actually reaches the model (PreToolUse stdout is
debug-log only per Claude Code docs).
Full suite: 728 passing (was 727 + 1 new).
* review: audit retention evictions + assert persisted source (#124 round 3)
CodeRabbit round 3 findings, both real:
1. retention-evict performs structural deletes (memories / semantic /
retention scores / access logs) but was not calling recordAudit().
Repo learnings say state-changing functions must be auditable except
for read-path bookkeeping. Now emits one batched audit row per
non-zero eviction sweep:
operation: 'delete'
functionId: 'mem::retention-evict'
targetIds: every evicted memoryId
details: { threshold, evicted, evictedEpisodic, evictedSemantic,
reason: 'retention score below threshold' }
Zero-eviction sweeps intentionally do NOT write an audit row (no
state change, no need to flood the audit log during health checks).
2. The #124 scoring test only checked result.scores (transient
response) but not the persisted mem:retention rows. Eviction reads
back from stored rows, so a regression in kv.set/serialization
would have still passed the old assertion. Now also does
kv.get('mem:retention', id) and asserts { source: ... }.
Two new tests:
- Retention evict with a mixed set of 2 episodic + 1 semantic
candidates writes exactly one audit row with all 3 ids in targetIds
and the correct evictedEpisodic/Semantic breakdown in details.
- Retention evict with zero candidates writes zero audit rows.
Full suite: 730 passing (was 728 + 2 new).
* review: audit retention-score + parallelize writes (#124 round 4)
CodeRabbit round 3 outside-diff findings, both addressed:
1. mem::retention-score was persisting schema-relevant writes to
KV.retentionScores (1000+ rows in a mature store) but never called
recordAudit(). Per the repo audit-coverage policy, state-changing
functions need an audit row. Added a single batched audit event
per rescore:
operation: 'retention_score' (new audit op — added to the
AuditEntry union in types.ts)
functionId: 'mem::retention-score'
targetIds: [] (intentionally empty — a mature store can have
1000+ ids per sweep; flooding the audit log
with every memoryId on every cron tick is
worse than recording just the summary counts)
details: { total, episodic, semantic, tiers, config }
Zero-memory stores intentionally skip the audit call.
2. The per-memory kv.set inside the score loop was O(n) sequential
round-trips. Refactored to collect pendingWrites: [id, entry][]
while iterating, then flush with Promise.all at the end. On a
mature store with 1000+ memories this is ~10x faster (depends on
backend pipelining).
Test updates:
- Added 'mem::retention-score emits audit row per rescore' covering
the new audit call, targetIds=[], and details.episodic/semantic.
- Existing '#124 audit evict' and 'zero-evict skip audit' tests now
filter the audit log by functionId === 'mem::retention-evict'
because retention-score also writes one row per sweep now.
Full suite: 731 passing (was 730 + 1 new, existing tests retargeted).
* 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
- author: change from string to object {name, url}
- hooks: remove field (auto-loaded by convention from hooks/hooks.json)
- skills: change from directory string to explicit file path array
marketplace.json was missing required top-level name and owner fields,
and plugins[].source was incorrectly named path. Fixes plugin install
validation error in Claude Code.
Performance:
- auto-forget.ts (#29): concept-bucketed contradiction detection with
pre-tokenized cache, O(n * avg_bucket) instead of O(n^2)
- consolidate.ts (#28): cap LLM calls at 10, process largest concept
groups first
- vector-index.ts (#30): bounded min-heap search replaces full sort,
early skip for low-scoring entries
Scheduling (#36):
- index.ts: setInterval auto-forget (1h default) and consolidation
(2h, opt-in via CONSOLIDATION_ENABLED=true)
- Configurable via AUTO_FORGET_INTERVAL_MS, CONSOLIDATION_INTERVAL_MS
- Timers use .unref() to not prevent process exit
Memory TTL (#63):
- remember.ts: optional ttlDays parameter auto-sets forgetAfter
Plugin (#75):
- plugin.json: version 0.2.0 -> 0.6.0
Closes#28#29#30#36#63#75
- Fix main/bin/start to use .mjs extension (tsdown outputs ESM)
- Remove duplicate shebang banner from standalone build config
- Update integration test: health status "healthy" (v0.4.0), viewer HTML check
- Add POST body to pre-compact hook for claude-bridge sync
* feat: agentmemory v0.4.0 — the memory layer for all AI coding agents
7 new features: Claude Code memory bridge, standalone cross-agent MCP server,
knowledge graph with entity extraction, 4-tier memory consolidation pipeline,
team/shared memory, memory governance with audit trail, git-versioned snapshots.
33 functions, 18 MCP tools, 6 MCP resources, 3 MCP prompts, 49 REST endpoints,
21 KV scopes, 216 tests. All features opt-in via env vars.
* fix: address 28 CodeRabbit findings across v0.4.0 codebase
Critical: Fix bin path mismatch (dist/mcp-standalone.mjs -> dist/standalone.mjs)
Bugs: Fix procedural decay, parseFloat||0.5 for zero values, snapshot restore
missing graphNodes/observations, git catch too broad, N+1 graph query, BFS
duplicate edges, array mutation in audit sort, date validation
Improvements: Lazy readline in transport, JSON-RPC validation, persist error
handling, ID collision prevention, defensive null coalescing for concepts/files,
Windows backslash support in config, direction required for bridge sync tool,
try-catch for audit/governance MCP, team profile fallback teamId, POST body for
bridge sync hook, observations validation for graph-extract endpoint, await in test
* feat: agentmemory v0.4.0 — MCP resources, prompts, enrichment, confidence
Add 4 MCP resources (status, project profile, recent sessions, latest memories),
3 MCP prompts (recall_context, session_handoff, detect_patterns), confidence-scored
memory relations, and a unified enrich endpoint that aggregates file context,
relevant observations, and past bug memories for PreToolUse hook injection.
- Add confidence field to MemoryRelation with auto-scoring from co-occurrence,
recency, and relation type
- Add mem::enrich function aggregating 3 sources with resilient .catch() fallbacks
- Add POST /agentmemory/enrich REST endpoint
- Switch PreToolUse hook from file-context to enrich endpoint with search term extraction
- Add minConfidence filter and confidence-desc sorting to mem::get-related
- 27 new tests (171 total), all passing
* fix: address review findings — input validation, XML escaping, confidence scoring
- enrich.ts: Sort bug memories by recency before slicing top 3; add escapeXml()
for narratives and bug content injected into XML tags
- relations.ts: Use filter+max instead of find() for matchingRelation to pick
highest confidence when multiple edges exist; normalize minConfidence with
Number.isFinite + clamp to [0,1]
- mcp/server.ts: Guard decodeURIComponent with try/catch returning 400 on
malformed percent-encoding; use typeof checks for prompt args instead of
truthy checks; normalize minConfidence/maxHops at MCP layer
- api.ts: Validate files[] and terms[] element types are strings before
calling mem::enrich
- Tests: Add malformed URI and non-string prompt arg test cases (173 total)
New hooks (7):
- PreToolUse: inject file history before edits (Edit/Write/Read/Glob/Grep)
- PostToolUseFailure: capture error patterns for learning
- PreCompact: preserve memory context through compaction
- SubagentStart/SubagentStop: track multi-agent workflows
- Notification: capture permission prompts (tool preferences)
- TaskCompleted: track team task completions
New functions (4):
- mem::file-context: file-centric memory index for PreToolUse
- mem::consolidate: merge observations into long-term memories via LLM
- mem::patterns + mem::generate-rules: detect co-change patterns and recurring errors
- mem::remember + mem::forget: explicit save/delete for long-term memory
MCP server (2 endpoints):
- GET /agentmemory/mcp/tools: list 5 MCP tools (recall, save, file_history, patterns, sessions)
- POST /agentmemory/mcp/call: dispatch tool calls to iii functions
Skills (4):
- /recall [query]: search past observations
- /remember [insight]: save to long-term memory
- /session-history: show past session timeline
- /forget [target]: delete specific memory data
New API endpoints (7):
- POST /agentmemory/file-context
- POST /agentmemory/remember
- POST /agentmemory/forget
- POST /agentmemory/consolidate
- POST /agentmemory/patterns
- POST /agentmemory/generate-rules
- GET/POST /agentmemory/mcp/*
Updated: types (7 new HookTypes, 3 new ObservationTypes), version 0.2.0
1. events.ts: Event triggers were calling api:: functions which
require ApiRequest shape and auth headers. Rewrote to call
core functions (kv.set, sdk.trigger) directly, bypassing auth.
2. All 5 hooks: Missing AGENTMEMORY_SECRET auth header. If secret
was set, every hook would get 401 from the API. Now all hooks
read AGENTMEMORY_SECRET and send Bearer token.
3. observe.ts: stripPrivateData on JSON string could break JSON
structure when replacement text differs in length. Added
try/catch fallback to string coercion.
4. post-tool-use.ts: truncate() for objects did
JSON.parse(str.slice(0, max-1) + '}') which produces invalid
JSON in nearly all cases. Changed to return truncated string.
5. compress.ts: LLM-returned importance was not clamped to 1-10
range. Added Math.max(1, Math.min(10, ...)) with NaN fallback.
6. compress.ts: LLM-returned observation type was not validated
against ObservationType union. Invalid types now fall back to
"other".
7. context.ts: Token estimate for observation blocks only counted
inner content, not the "## Session..." header. Fixed to estimate
the full block text.
8. viewer: WebSocket port now configurable via ?wsPort= query param
for non-default III_STREAMS_PORT configurations.
9. plugin/scripts: Rebuilt with auth header support matching the
updated hook source files.