CodeRabbit flagged that processLine only checked `id != null` in the
invalid-shape path, which meant (a) malformed messages with non-primitive
ids could be echoed back with that id, and (b) valid-shape requests with
id: {} / [] / true would fall through to the handler and be responded to
with the bogus id as the response id — both spec violations.
Add isValidId() (string | number | null | undefined), extract rawId once
as unknown, and:
- In the invalid-shape path, only echo if rawId is string or number.
- After shape validation, reject bad-id requests with -32600 / id: null
before invoking the handler.
Adds 5 test cases covering id: object, array, boolean, plus the malformed
+ non-primitive-id drop path and a positive string-id case.
Codex CLI v0.120.0 fails to start the standalone MCP server with
"MCP startup failed: Transport closed". Other MCP clients (Claude
Desktop, Cursor, OpenClaw) work fine with the same binary. Reported
in #129 by @GuillaumeOuint.
Root cause: src/mcp/transport.ts unconditionally writes a JSON-RPC
response for every incoming message, including notifications. Per
JSON-RPC 2.0 §4.1 a notification is "a Request object without an id
member" and "the Server MUST NOT reply to a Notification". Strict
clients close the transport when they receive an unsolicited
response — exactly the symptom Guillaume reported.
The trigger sequence on Codex CLI:
1. Codex sends initialize (request, has id) → server responds ✓
2. Codex sends notifications/initialized (notification, no id)
3. Our handler in standalone.ts returns {}
4. Transport writes {"jsonrpc":"2.0","result":{}} to stdout
(no id field because request.id was undefined)
5. Codex parses an unsolicited response → closes transport
6. Guillaume sees "Transport closed"
This worked accidentally for lenient clients (Claude Desktop,
Cursor, etc.) which silently ignored the spurious responses.
Fix:
- transport.ts: detect notifications via missing/null id field,
run the handler for side effects, then drop the response. Errors
inside notification handlers are logged to stderr (not silently
swallowed) so production debugging still works
- transport.ts: malformed messages that have no id are also dropped
silently (consistent with notification semantics) instead of being
responded to with id: null
- transport.ts: extracted processLine() so the line-handling logic
is testable independently of process.stdin/stdout
- new test/mcp-transport.test.ts with 9 tests covering: request
responses (success + error), notification suppression (no id +
null id + handler-throws), malformed input (parse error, invalid
request shape with id, invalid request shape without id, empty
lines)
Tests: 693/693 (684 baseline + 9 new). Build clean.
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: persist memories to disk immediately after memory_save
Previously, kv.persist() was only called on SIGINT/SIGTERM. If the
process was killed with SIGKILL, crashed, or the host terminated it
forcefully (e.g. OpenCode session end), all saved memories were lost.
Fix: call kv.persist() immediately after kv.set() in the memory_save
handler so data is written to standalone.json on every save.
Also export handleToolCall with an injectable kv parameter to allow
unit testing, and add tests verifying that persist is called on save.
* refactor: use generateId() and single isoNow in memory_save handler
Replace ad-hoc ID/timestamp logic with project-standard helpers:
- generateId("mem") instead of manual Date.now()/Math.random() concatenation
- Single const isoNow = new Date().toISOString() shared by createdAt and updatedAt
fingerprintId is intentionally not used: memory_save always creates a new
unique entry (no dedup), so a content-addressable key would silently overwrite
duplicate content rather than append — matching existing project behaviour.
* 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
Fixes 6 security issues in agentmemory's default deployment, viewer, mesh sync, export path, and startup flow.
## Findings Addressed
1. **Default REST/stream exposure on 0.0.0.0** — bind default REST and stream services to 127.0.0.1; restrict Docker port publishing to localhost; add dedicated iii-config.docker.yaml for Docker usage.
2. **Stored XSS via inline viewer handlers** — remove inline event handlers from the viewer; replace with delegated data-action event handling; switch to nonce-based CSP for viewer HTML; add script-src-attr 'none'.
3. **Arbitrary path writes in Obsidian export** — confine exports to AGENTMEMORY_EXPORT_ROOT (default ~/.agentmemory); reject vaultDir values outside the allowed root.
4. **Mesh sync without peer auth** — require AGENTMEMORY_SECRET for mesh endpoints and sync operations; send Authorization: Bearer <secret> on mesh push/pull requests.
5. **Secret redaction misses modern token formats** — add coverage for Bearer tokens, sk-proj-*, broader sk/pk/rk/ak variants, GitHub ghs_/ghu_ tokens.
6. **CLI executes remote shell script directly** — remove curl|sh installation pattern; use existing local iii-engine binary if available, otherwise fall back to Docker.
## Validation
- 654 tests passing (8 new security tests)
- npm run build succeeds
- Merged with main and resolved README conflicts to preserve the new benchmark/competitor table layout
Thanks @eng-pf for catching these and submitting the fixes.
* feat: v0.6.1 — citations, cascading updates, expanded mesh (#53#56#59)
Add JIT verification with citation chains, self-organizing memory via
cascading staleness propagation, and expand mesh sync to 7 scopes
(semantic, procedural, relations, graph nodes/edges).
- mem::verify traces memory/observation provenance back to source
- mem::cascade-update flags stale graph items and sibling memories
- Mesh default scopes expanded from 2 to 7 with project-based filtering
- Memory.sourceObservationIds, CompressedObservation.confidence fields
- GraphNode.stale, GraphEdge.stale for staleness tracking
- MeshPeer.syncFilter for project-scoped action sync
- 573 tests passing across 49 files
* fix: address code review — stale filtering, LWW clock, validation, scoping
- Filter stale nodes/edges in graph-retrieval.ts and graph.ts traversals
- Use updatedAt (not createdAt) as LWW clock for graph node mesh sync
- Set updatedAt in cascade.ts when flagging nodes as stale
- Skip non-project scopes (semantic, procedural, relations, graph) when
syncFilter.project is set to prevent leaking unscoped data
- Validate sourceObservationIds as array of strings in remember.ts
- Deduplicate observation IDs in consolidate.ts sourceObservationIds
- mesh-export endpoint uses updatedAt for graph nodes, accepts project param
- Extract shared test helpers into test/helpers/mocks.ts
- Assert memory_verify tool name in mcp-standalone.test.ts
- Timing-safe HMAC auth in api.ts, mcp/server.ts (eliminates length leak)
- Keyed mutexes for race conditions in observe, remember, relations
- Write ordering: supersede before save to prevent dual-latest
- Single VERSION constant replacing 11 hardcoded strings
- Input validation: governance filter guard, team itemType, maxObs, commitHash regex
- Future-proof import version validation with VERSION in supported set
- Circuit breaker configurable thresholds with positiveFinite validation
- Health monitor interval.unref(), telemetry dead exports removed
- 3 new secret patterns (npm_, glpat-, dop_v1_), shared CSP constant
- 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)
- Add integration test suite covering all 11 API endpoints
- Rewrite README for clarity and user-friendliness
- Extract VIEWER_CSP constant, remove dead IndexEntry.terms field
- Consolidate SIGINT/SIGTERM handlers
- XML tag validation: reject invalid tag names before regex construction
- API auth: optional bearer token (AGENTMEMORY_SECRET) on POST endpoints
- Conservative token estimation: length/3 for code-heavy content
- XML-escape project name in context output
- Prefix-only search matching (startsWith vs includes)
- Dynamic viewer URLs from window.location
- CSP header on viewer endpoint
- Importance class sanitization in viewer
- 45 tests: privacy, XML parsing, search index, schema