Commit Graph

26 Commits

Author SHA1 Message Date
Rohit Ghumare 37ea1b99ad feat: cursor marketplace plugin with hooks, mcp, and skills (#1213)
* feat: cursor marketplace plugin with native hooks and mcp config

* fix: cursor payload compat and transcript prompt backfill in hooks

* fix: plugin-root hook paths, backfill ordering, session-id fallbacks

* docs: cursor plugin rows in readme, translations, and changelog

* fix: cursor native-plugin card, broken agent logos

* chore: sync openclaw and hermes plugin manifest versions

* docs: openclaw hook permission and hermes tool count

* chore: clawhub compat metadata for openclaw plugin

* docs: tested openclaw and hermes install rows in readme
2026-08-16 13:30:40 +01:00
Rohit Ghumare 696cf7abb8 feat: recall quality, provenance, keyless graph, and connector parity (#1205)
* fix: prompt dedup, double summarize, docker-mode stop, hermetic tests

- observe: hash the hook payload when tool_input is absent so prompt_submit,
  notification, and lifecycle events dedup on content instead of collapsing
  onto one shared key that silently dropped every prompt after the first in
  a TTL window (#1173)
- stop hook: drop the direct /agentmemory/summarize POST; /session/end
  already fans out event::session::stopped which runs mem::summarize, so
  every Stop dispatched two full summarizes (#1203)
- cli: refuse to adopt or signal Docker/VM port holders (com.docker.backend,
  vpnkit, colima, ...) as the native engine unless --force; scope Docker-mode
  teardown to agentmemory's own compose services via rm -s -f instead of an
  unscoped down; reap the native worker before Docker teardown instead of
  deleting worker.pid with the process still running (#1151)
- tests: isolate HOME/USERPROFILE for the whole vitest run so suites stop
  reading the developer's real ~/.agentmemory/.env (#1178)

* fix(viewer): live stream port discovery, fresh tab data, honest states

- resolve the stream WebSocket target from /agentmemory/livez (new
  streamsPort field) instead of viewerPort-1 arithmetic, which pointed at
  the wrong server whenever the viewer bound a fallback port and silently
  degraded live updates to 10s polling — verified reaching 'live' on the
  fallback-port case
- refetch tab data on every tab entry; the loaded-once cache meant a
  memory saved by the agent never appeared until a hard browser reload
  (loading placeholders now render only on first load, so background
  refreshes don't flash)
- memories: rows expand on click/Enter to the full stored record —
  content, id, project, created, supersedes, files — plus a collapsible
  raw JSON view
- graph: a 503 with the structured disabled body renders 'Knowledge
  graph is off' with the enableHow text and docs link instead of a
  'query failed / Retry' error that sends users to server logs
- sessions: cards get role=button, tabindex, Enter/Space activation, and
  the detail panel scrolls into view on select; session ids truncate
  head…tail so the distinguishing suffix stays visible
- style search inputs and toolbar buttons on lessons/actions/crystals/
  replay (previously bare native controls); horizontal scroll containment
  for narrow viewports
- demo: only print the semantic-recall success notice when the search
  actually hit; on 0 hits explain the missing embedding key instead

* fix: thread agentId/project through save paths, per-session OpenCode scope

- REST /agentmemory/remember accepts and forwards agentId to mem::remember;
  it previously dropped the field so per-request multi-agent scoping was
  impossible over REST (#1159)
- memoryToObservation() carries the memory's agentId into the search-index
  shape; dropping it made every memory invisible to agent-scoped search
  (#1160)
- MCP memory_save path: the tool schema now exposes agentId, the in-worker
  MCP server forwards it, and the standalone stdio package parses and
  forwards both agentId and project — the stdio pipeline previously dropped
  project even though its schema advertised it (#1197)
- opencode plugin: project/cwd attribution is per-session (resolved from
  the session's own directory at session.created, pruned on session end)
  instead of module-level state that recorded every session in a
  multi-directory OpenCode process under whichever repo loaded the plugin
  first (#1188)

Live-verified: memory saved with agentId=agent-alpha is returned by
smart-search for agent-alpha and hidden from agent-beta.

* feat: hybrid recall everywhere, indexed lessons, provenance, recall hygiene

- mem::search ranks through the full BM25+vector+graph fusion when the
  vector index is populated (injected post-boot via setHybridRanker);
  the primary recall surface was keyword-only while only smart-search
  got hybrid ranking
- fusion weights normalize per item over the streams that actually
  ranked it, with a small explicit cross-stream agreement bonus; the
  old every-enabled-stream denominator permanently penalized
  single-stream hits (the graph stream is empty on default installs).
  Result order is now deterministic (score, best rank, id)
- lessons get a dedicated in-memory BM25 index built lazily from one KV
  list and maintained incrementally on save/delete/decay; recall
  previously listed and substring-scanned the whole corpus per query.
  Confidence x recency composite scoring is unchanged
- mem::remember finds supersession candidates through the search index
  (top-50) instead of walking every memory per save, with a full-scan
  fallback while the index is cold; near-miss similarity (0.4-0.7)
  is reported back as an advisory similarTo hint
- superseded memory versions leave the BM25 and vector indexes; the
  version chain stays in KV for history, but recall no longer returns
  an outdated fact as if current
- every observation and memory now carries an immutable origin block
  (channel: user|agent|tool|import|shared, detail, capturedAt) stamped
  at capture, save, and import, and inherited through both compression
  paths — the base for trust-aware retrieval and ingest screening
- regression tests: supersede index removal, similarTo hint,
  index-backed candidate discovery, lesson index recall/lazy
  rebuild/delete

* feat(viewer): two-pane sessions, navigable dashboard, motion and copy polish

- sessions: list + sticky detail panel side by side above 1100px (the
  detail previously rendered below the whole list, off-screen on any
  real corpus); selected/hover/active states with reserved left border
  so selection doesn't shift layout
- dashboard stat cards for sessions/memories/lessons/crystals/graph
  navigate to their tabs (click or Enter), with hover affordance
- observation subtitles that are raw serialized tool input now display
  the meaningful field (file path, command, pattern, url) instead of a
  JSON blob
- expanded memory rows show the new origin provenance (channel + detail)
- motion: 160ms view entrance, live-badge pulse, both gated behind
  prefers-reduced-motion; tabular numerals in tables
- mobile: header stops wrapping the dateline into the badge row
- lessons/crystals empty-state copy aligned with the header definitions
  (each concept was described two conflicting ways)

* refactor: cleanup pass over the branch diff

- shared test mocks: the three new test files use test/helpers/mocks
  (extended with update, store access, and an opt-in loose trigger)
  instead of three diverging inline copies
- lessons: record cache beside the index takes recall to zero KV
  round-trips (was up to 50 gets per call); the observation adapter
  moved next to memoryToObservation so both record kinds thread new
  fields in one place; dead reset export removed
- mem::search hybrid path carries the observations the ranker already
  loaded instead of refetching every result (halves KV I/O on the
  primary recall path); remember's candidate lookup skips ids that
  cannot resolve as memories and fails open to a full scan
- fusion: derived tiebreak field no longer rides along past the sort;
  comment trimmed to the non-obvious history
- cli: engine identity is a positive check (only the iii binary may be
  adopted or signaled; unknown port holders are refused, not just known
  VM names); worker reap extracted to one helper; demo notice picks its
  branch from a hoisted count
- api: livez and health share one instanceInfo source (health now
  reports streamsPort too) computed once at boot instead of rebuilding
  the merged env per request
- provenance: one importOrigin factory encodes the keep-or-mark rule at
  all three import sites
- opencode plugin: project resolution memoized per directory (was a
  blocking git subprocess per session event); session.created uses the
  entry it just built
- observe: origin channel derived from a named hook set, no nested
  ternary
- viewer: toolbar buttons merged into the .btn rules, one 720px media
  block, generic keyboard activation for role-carrying cards,
  scroll-into-view only on the stacked layout, 5s freshness gate on tab
  refetch (replay stays fetch-once, reason documented), subtitle
  humanizer covers the capture-side key variants

* feat(viewer): clarity pass and ambient refresh

- health notes/alerts translate their machine slugs into sentences
  (memory_heap_tight_93%_rss111mb reads as heap usage with context)
- lessons rows expand to full detail: rule, why-learned context, tags,
  learned/last-confirmed times, source sessions, raw record; column
  headers carry title hints for confidence and uses
- actions tab gets the same intro card as the other tabs (status flow
  and frontier explained on the populated view, not just when empty)
- timeline defaults to the session with the most observations instead
  of the newest, which was often a sparse just-started session
- consolidation status and top-concepts zero states explain what fills
  them and which flags gate it
- ambient background: the static dot grid becomes a slowly drifting
  ordered-dither field (quarter-res canvas, ~12fps, static frame under
  prefers-reduced-motion, theme-aware)
- dark theme: layered near-black surfaces, hairline borders, softened
  accent — replaces the flat gray borders

* feat(website): reskin on the near-black token system

- neutral token foundation in globals.css: canvas/canvas-soft/card
  surfaces, hairline borders, ink/body/mute text scale, one warm accent
  used sparingly, 8px card radius + pill buttons, focus-visible rings
- Inter display at weight 400 with tight tracking; mono uppercase
  eyebrows and captions; sentence-case body (case normalization only,
  no copy changes)
- hero: two-tone lowercase wordmark, install command as a soft input
  card, ambient drifting dot field capped at 0.12 alpha with a static
  frame under prefers-reduced-motion
- sections rebuilt on the card recipe: quiet background-shift hovers,
  hairline data table for the comparison, segmented tabs with polarity
  flip, per-vendor accent colors stripped from agent cards
- fixed two latent token misuses that resolved to nothing
- build green: 5/5 static pages, TypeScript clean

* fix(viewer): make the graph tab legible without edges

- nodes anchor to per-type cluster centers (captioned on the canvas)
  whenever relations are sparse; a pure force layout with no edges was
  an unlabeled scatter. Edge springs take over as real relations arrive
- labels always render on graphs of 30 or fewer visible nodes instead
  of only past a zoom threshold
- sidebar explains the entities-without-relations state and what
  produces edges; static legend removed (the type filter already
  carries color and shape)

* fix(viewer): graph readability on sparse data

- hover focus-fade only engages when the graph has edges; with none it
  faded every other node and suppressed all labels
- cluster anchor pull reduced and initial scatter widened so type
  groups spread instead of collapsing into blobs
- minimum node radius raised for degree-zero nodes; cluster captions
  offset above their groups

* fix(viewer): graph fits the view; site copy grounded in the repo

viewer graph:
- container height leaves room for the footer instead of running under it
- one-shot auto-fit zooms and pans to the node bounds once the layout
  settles, so first paint is framed instead of adrift
- cluster captions and small-graph labels hide below readable zoom

website copy (full pass, technical register):
- every unverifiable number removed: benchmark percentages, latency
  claims, press strip, testimonials, invented terminal output; the
  comparison table usage dropped rather than shipping stale competitor
  figures
- remaining stats are build-derived (54 MCP tools, 130 REST endpoints,
  12 hooks, 1619 tests) or live from the GitHub API (stars)
- feature copy corrected against src behavior: consolidation, graph
  extraction, and LLM compression activate with a provider key;
  provider list completed; install step numbering fixed
- release-branch capabilities surfaced: agent-scoped save and recall,
  write-time provenance channels, hybrid ranking on the primary recall
  path, indexed lessons, near-duplicate save hints, superseded-version
  recall hygiene, JSONL import deriving crystals and lessons
- em-dashes and slop phrasing removed throughout

* fix: restore featured strip, label collision avoidance, optional no-think

- website: FeaturedIn strip returns to the hero (its claims are the
  project's own credentials); rest of the grounded-copy pass unchanged
- viewer graph: canvas cluster captions removed and labels place
  greedily into free space (selected/hovered always win), so zoomed-out
  views degrade to fewer labels instead of overlapping pills
- graph extraction: AGENTMEMORY_LLM_NOTHINK=1 opt-in asks local
  reasoning models to skip their hidden thinking pass (several times
  faster, slight quality tradeoff); documented in .env.example, default
  behavior unchanged

* feat(website): testimonials return, OpenCode joins the featured connectors

- Testimonials section restored after LiveTerminal (launch-thread
  quotes are the project's own record)
- OpenCode promoted from the marquee to the featured grid: it ships a
  native capture plugin with per-session project attribution; fills the
  empty eighth slot
- full connector roster verified against src/cli/connect (18 dedicated
  adapters all present: featured grid + marquee)

* fix(viewer): official icon as the favicon (was a text placeholder)

* test(viewer): favicon assertion checks the served SVG, not a hex value

* test(viewer): favicon checks assert served SVG shape, not old artwork

* docs: readme grounded in source, changelog entry, env example consistency

* revert(website): restore measured benchmark claims and comparison table

The retrieval recall and token reduction figures are the project's own
measurements and its adoption story; earlier scrubbing was over-strict.
Backing them with a published run of the eval harness stays on the
roadmap.

* fix(website): drop the orphaned pause control on the hero field

The old animated constellation earned a pause button; the subtle dither
field does not, and prefers-reduced-motion already renders it static.

* fix(website): official OpenCode brand mark on the featured card

* chore: bump provider default models to current generations

OpenAI gpt-4o-mini to gpt-5.6-luna, Anthropic claude-sonnet-4-20250514 to claude-sonnet-5, Gemini gemini-2.5-flash to gemini-3.7-flash, MiniMax M2.7 to M3, OpenRouter default to anthropic/claude-sonnet-5. Premium cost warning matches the Sol tier; cheap-model hints lead with deepseek/deepseek-v4-flash-0731. README local picks move to qwen3 / gpt-oss / deepseek-r1 with a NOTHINK callout; cost table refreshed with verified OpenRouter list prices. Embedding defaults unchanged.

* feat: keyless heuristic graph extraction with LLM enrichment optional

Entities and co-occurrence do not need a language model: files and concepts on compressed observations already name the nodes, and appearing in the same observation is an edge. mem::graph-extract now always runs this deterministic pass, so the graph populates for keyless installs; the LLM pass layers typed relations on top only when GRAPH_EXTRACTION_ENABLED is set and a real provider exists. Session end fires extraction unconditionally.

* feat: DeepSeek Harness connector via home-level cordis patch layer

agentmemory connect dsh appends an @deepseek-ai/dsh-mcp-client row to DSH_HOME/cordis.patch.yml (default ~/.dsh), the machine-local patch layer every Harness profile loads, so the MCP tools register as mcp__agentmemory__* before the first turn. Idempotent, --force replaces the row, dry-run supported. Config shape verified against the mcp-client README and publish docs in deepseek-ai/deepseek-harness. Website agents grid and README connector table updated.

* feat: dsh --with-hooks auto-capture via Harness Claude Code bridge

DeepSeek Harness ships a first-party @deepseek-ai/dsh-hooks-claude-code plugin that runs Claude Code shaped command hooks on the harness's own interception points. connect dsh --with-hooks writes the bundled hook manifest (absolute script paths, reusing the codex-hooks merge engine) to DSH_HOME/agentmemory.hooks.json and appends a second patch row pointing the bridge at it. Auto-capture on SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop; PreCompact is outside the bridge subset and skipped. Adapter recategorized native. MCP-only installs never touch the hooks row; --force replaces both.

* feat(pi): automated connect install into pi's auto-discovery dir

connect pi was a stub printing manual copy steps because integrations/ never shipped in the npm package. The extension source now ships (integrations/pi/ in files), and the adapter copies index.ts + security.ts into ~/.pi/agent/extensions/agentmemory/, which pi auto-discovers with no settings.json edit; /reload picks it up live. Idempotent by content compare, stale copies refresh with a backup, dry-run supported. integrations/pi is also a private local pi package (pi-package keyword, pi.extensions manifest limited to index.ts so security.ts is not loaded as its own extension, peer deps on the pi core packages) so pi install ./integrations/pi works from a checkout; never published to npm. Type import moved to @earendil-works/pi-coding-agent (upstream rename).

* fix(codex): warn that hooks need one-time TUI trust approval

Codex executes only hooks with a recorded trusted_hash in config.toml, and the Hooks-need-review approval prompt appears only in the interactive TUI. A codex exec-only workflow therefore never runs freshly installed hooks and gets no signal why. connect codex --with-hooks now warns to launch codex once and choose Trust all, and to re-approve after upgrades since the refreshed absolute paths change the hash. Verified live on codex 0.147.0: before trust, exec dispatched nothing for agentmemory hooks; after TUI approval, SessionStart and UserPromptSubmit fired and the observation landed in the daemon.

* feat(pi): capture parity for the pi extension

Session registration on session_start (ordered after the health check so it fires on the first session of a fresh process), prompt capture with client-side dedup and user-channel provenance, per-tool observations from tool_result with AGENTMEMORY_TOOL_OBSERVE=0 opt-out, turn slices raised to 8000, memory_save scoped to the current project, session end plus one consolidate run on real quit only (no client summarize: session/end already fans out the summary), health accepts status ok, and refreshStatus binds the status setter before awaiting so a session replacement mid-check cannot throw a stale-context error. Live-verified on pi 0.84.2 with a local model: prompt and turn observations landed and the session closed as completed on quit.

* chore: regenerate skill reference docs

npm run skills:gen after the adapter, env, and tool changes: 20 adapters including dsh, refreshed env defaults, tool listing.

* refactor: trim oversized comments to constraint one-liners

Connector and extension comments compressed to the constraints the code cannot show; narrative headers, source citations, and restated behavior removed.

* docs(changelog): fold unreleased into the 0.9.29 release section

This branch ships as 0.9.29 (npm latest is 0.9.28; the previous 0.9.29 section was prepped but never published). One section, dated 2026-08-15, upgrade notes preserved, all 44 bullets intact.

* docs(readme): interactive-first install, dedupe, refresh stale counts

Install leads with npx and the first-run wizard (agent multi-select, provider pick, global-install offer) instead of six manual commands; Windows, EACCES, npx-cache, and iii-pin notes collapse into details blocks. Quick Start drops the duplicated install prose for an everyday-commands list. Nav drops the redundant iii Console link. Gist badge updated to the live 1.6k stars / 230 forks; test count pill and alt text updated to 1,648.

* fix: apply review round — ranking, indexes, lifecycle, connectors

Hybrid scoring normalizes once per query by the best attainable weighted score over streams that produced results, so configured stream weights survive single-stream hits; expansion merge gets the same deterministic tie-break. Graph functions register unconditionally (keyless installs previously fired mem::graph-extract at an unregistered function every session end) and the trigger goes through fireVoid; heuristic edges accumulate observation provenance for repeated pairs instead of dropping it. Supersession candidate search waits for the memory index walk (new isMemoryIndexReady signal) instead of trusting idx.size, and mem::search falls back to keyword search when the hybrid ranker throws. Lesson recall over-fetches under project/confidence filters, refreshes the index entry when reinforcement changes indexed text, and resetLessonIndex clears the cache after import and replay write lessons directly. Observe preserves primitive payloads in the dedup key so distinct prompts never collapse (regression test added). pi extension dedups prompts per session and passes project on both smart-search calls. dsh reads a corrupt hooks manifest as absent via readJsonSafe; pi install returns skipped instead of throwing when the bundled source is missing; both use the shared writeTextAtomic. Docker-mode stop clears each pidfile/state only after its shutdown succeeded and matches compose services at any indentation. Viewer livez fetch gets a 5s timeout. opencode session.deleted prunes through pruneSessionMaps (was leaking sessionProjects). MiniMax MAX_TOKENS doc says the real 4096 default. README MCP catalog: base-tools table completed to the registry's 14, the 8-tool core mode and 7-tool standalone fallback distinguished, two missing resources listed. Tests restore env vars without writing the string undefined, similarTo assertions are unconditional, vitest test home is unique per run, website meta regenerated, deprecated word-break replaced.

* docs: competitors refreshed — TencentDB Agent Memory column, entrants

TencentDB Agent Memory (TencentCloud OSS, May 2026, 22K stars) gets a full column: team memory hub captured through an LLM proxy, four asset types, PersonaMem 76% self-reported, Docker Core+Hub+Proxy stack. Stale star counts refreshed against the live API (mem0 58K to 63K, Letta 24K, Khoj 36K, supermemory 29K). A newer-entrants table covers Zep/Graphiti, Cognee, LangMem, Cloudflare Agent Memory, and Memobase, with matching choose-if sections in benchmark/COMPARISON.md. Section badge subtitle updated.

* fix: lesson index build races, rebuild ready flag, pi file backups

* docs: drop competitor links from README

* Update README.md
2026-08-15 20:37:12 +01:00
Rohit Ghumare 9c82d2aa7d chore(release): v0.9.29 with project-scope parity across capture surfaces (#1141)
* chore(release): v0.9.29 with project-scope parity across surfaces

Version trio + plugin manifests + supportedVersions + ExportData union
bumped to 0.9.29; CHANGELOG entry covering everything since v0.9.28 with
upgrade notes for the four visible behavior changes.

Fixes the endpoint-count drift on main (130 registered routes vs docs
saying 129 after #1132 landed in parallel with #1136).

Project-scope parity: OpenCode plugin, Hermes plugin, Pi extension, and
JSONL replay now resolve project the same way the hooks do (env
override, git toplevel basename, cwd basename) instead of sending raw
filesystem paths, closing #903 and #1135 and pre-empting the same bug
in pi. The filesystem watcher accepts AGENTMEMORY_PROJECT_NAME with the
old AGENTMEMORY_PROJECT kept as a deprecated alias, replay handles
Windows-recorded paths, and OpenCode file enrichment matches the
agent's lowercase tool names (the capitalized set never matched).

Tests: opencode fallback expectations updated to basenames per the
canonicalization, git-toplevel resolution covered with a fixture repo,
new project-scope-parity suite for replay and fs-watcher.

* fix(release): review findings, git-toplevel parity, doc counts

- skills generator dedupes routes on method plus path, so the REST
  reference lists all 130 registered routes instead of hiding the second
  method on ten dual-method paths (header said 119)
- fs-watcher trims AGENTMEMORY_PROJECT_NAME and the deprecated alias,
  treating whitespace as unset, and derives the git toplevel basename
  when watching a subdirectory
- replay resolves the git toplevel basename when the recorded cwd still
  exists locally (memoized per cwd), keeping the basename fallback for
  historical or cross-platform paths; no env override here since a bulk
  import spans many projects
- parity tests for replay git-root resolution, watcher git-root and
  trim behavior
- stat-tests badge updated from 1428+ to 1550+ passing

* fix(cli): refuse second-instance boot over a live daemon

Closes the class behind issue 1140: agentmemory consolidate (or any
unrecognized word) fell through the command table into the full server
boot, registering a duplicate worker on the running engine; on iii
0.11.2 the second instance's shutdown tears down the daemon's HTTP
trigger routing until a full engine restart. Unknown subcommands now
error with the supported list, and main() probes livez on the resolved
port and refuses to boot over a live daemon, so multi-instance setups
on other ports are unaffected. Verified behaviorally against the built
CLI: both paths refuse with exit 1.

Also from review: the watcher stamps each event with its own root's
project via a per-root map (an explicit config.project still overrides
for every root), and replay only accepts a non-empty string cwd from
parsed JSONL so malformed entries cannot reach the filesystem probe.

* test(watcher): two-repository flush events scope to their own project

* chore(release): bump packages/mcp, guard it, refresh CONTRIBUTING

packages/mcp was still 0.9.28 after the release bump because nothing
guarded it; a consistency test now pins it to package.json. CONTRIBUTING
release list corrected to the files a bump actually touches (no tracked
lockfile, the two extra plugin manifests, the export test derives from
VERSION now), and the subsystems table gains src/cli, integrations/pi,
and the generated-manifest note.

* fix(export): refuse over-frame export instead of dropping the worker

Closes the availability bug in issue 1142: GET /agentmemory/export
assembles the full store and returns it through sdk.trigger, so a store
whose serialized export passes the engine's 16 MiB WebSocket frame
(tungstenite max_frame_size, not raisable under the 0.11.2 pin) dies on
the worker->engine hop, drops the worker, and 404s every endpoint for
~1s. The session collections page on maxSessions/offset but ~18 others
do not, so a large store hits this at any parameter combination.

A shared frame-guard measures the serialized size before returning:
mem::export returns a small oversized error instead of the giant
object, and api::mesh-export returns 413 (same dead-end as #890). Either
way the over-frame payload never crosses the boundary, so the daemon
stays up and the failure is one clean request with a hint to narrow the
range. Full pagination of the non-session collections is a follow-up.

Layer 1 of the fix; verified with a synthetic oversized export returning
the error object (tiny) rather than the payload.

* ci: collapse to a single npm install to fix Node 24/26 CI

The two-step install (npm install --package-lock-only then npm ci) failed
only on the Node 24/26 matrix rows: their stricter npm rejects rolldown's
optional platform bindings (@rolldown/binding-android-arm64) that a
--package-lock-only pass does not fully enumerate. Lockfiles are gitignored,
so npm ci re-validation buys no reproducibility here. A single lenient
npm install resolves and installs in one pass.

* fix(mesh): scope exported memories by project like actions

api::mesh-export filtered actions by ?project but returned every project's
memories. On a mesh instance federating one project to a peer, the peer
pulled other projects' memories (cross-project leak), and those extras could
push the payload past the 16 MiB transport frame into a 413 even when the
requested project's own slice fit. Memories carry the same optional project
field as actions, so filter both before the frame-size guard runs.

Adds a regression test asserting a project-scoped export excludes other
projects' memories and that an oversized memory in another project no longer
413s the scoped request.

* chore(release): credit the Antigravity native hooks adapter in 0.9.29 notes

* chore(release): sweep stale 0.9.28 refs for 0.9.29

Deploy Dockerfiles/compose/render pins, AGENTS.md stats header, opencode
plugin manifest, website meta snapshot, test-count claims (1,428 -> 1,596)
in README/AGENTS/stat SVGs, and the missing 0.9.29 CHANGELOG compare link.

* chore(release): sync stat-tests badge to 1596+ and commit bridge exec bit

* refactor: trim frame-guard comments and drop issue refs from code
2026-08-09 13:22:25 +01:00
Rohit Ghumare 6cc9b9f0fe fix: env hydration, indexing, consolidation lifecycle, connector activation, hardening (#1136)
* fix: env hydration, indexing, consolidation, connectors, hardening

- config: hydrate ~/.agentmemory/.env into process.env at boot so all modules see it
- search: shared indexRecords() so export-import and replay populate BM25 and vector (#1072)
- snapshot: wire the periodic timer (#1006), clamp non-positive intervals, add a reentrancy guard
- schema: CJK-aware jaccard dedup plus exact-match fallback for short memories
- embeddings: shared resolveDimensions() so openrouter stops hardcoding 1536 (#1002)
- viewer: buffer request bodies before decoding to fix multibyte corruption (#930)
- providers: retry 429/503 with Retry-After under a total-elapsed budget cap
- consolidation: fire on session stop (#1087), gate keyless installs, debounce the per-turn stop hook, drop the client-side double-fire
- evict: bound stale-session recovery to one consolidation pass
- api/patterns: bound session fan-out (#1100)
- connect: write a memory-usage guideline into each hook-less agent's native rules file (12 agents, doc-verified paths, --no-guidelines opt-out)
- graph: import graphify's graph.json via mem::graph::import-graphify + POST /agentmemory/graph/import-graphify; shared persistGraphDelta with endpoint remap so merged nodes never leave dangling or duplicate edges
- fs-watcher: stat roots before fs.watch so missing roots fail deterministically on Node 24+
- test: regression tests for every fix

* fix: address review findings on import, debounce, and connect paths

- guidelines: refuse to touch files with a lone or reversed marker pair
- export-import/replay: indexing after committed writes is best-effort,
  logged instead of failing the import; flatten the nested runChunked so
  replace-mode deletes stay bounded to one chunk
- graph: persist the snapshot when merge-only batches mutate cached
  topNodes/topEdges entries
- graph-import: async fs, typeof validation on path/cwd; REST handler
  whitelists the payload and 400s non-string values
- fetch: cancel discarded response bodies before retrying
- events: serialize the consolidation cooldown check so concurrent stops
  cannot both pass the read-check-write window
- evict: gate recovered-session consolidation on isConsolidationEnabled
  and mirror the stop path's force flag
- search: rebuild indexes per session chunk to bound peak memory
- test: regression coverage for each (malformed markers, concurrent
  stops, snapshot persistence, AMBIGUOUS/default mappings, env isolation)
2026-08-02 11:16:30 +01:00
Rohit Ghumare 52e5f3f5f1 fix(pi): send tool_input/tool_output instead of input/output (#772)
pi integration emitted post_tool_use observations with fields named
'input' and 'output', but observe.ts reads 'tool_input'/'tool_output'.
The field mismatch caused observe to record undefined inputs/outputs,
so the compress pipeline produced empty observations rendered as
'No content provided' in the viewer.
2026-06-02 00:51:15 +01:00
Rohit Ghumare d38a1c6950 fix(hermes): seed AGENTMEMORY_URL default at import (closes #520) (#643)
Hermes was reporting agentmemory as Missing/not-available even when the
service was running fine and live sessions could use it. Root cause:
`hermes memory status` reads os.environ in the Hermes CLI process. When
agentmemory is launched by systemd (or any process manager that loads
~/.agentmemory/.env via EnvironmentFile=), those values never reach the
interactive shell. Our plugin already preloads ~/.agentmemory/.env via
setdefault, but only catches uncommented key=value lines. Users whose
.env file has AGENTMEMORY_URL commented out (because it matches the
default http://localhost:3111) end up with the var unset, and Hermes
reports Missing.

Fix: after preload, os.environ.setdefault('AGENTMEMORY_URL',
DEFAULT_BASE_URL). Guarantees the var is set to the documented default
so Hermes status reflects the real runtime. Anything explicit from the
shell or .env still wins (setdefault is no-op when value is present).

AGENTMEMORY_SECRET stays untouched — it's optional per the config
schema.
2026-05-25 11:49:24 +01:00
이민재 c1c2c3a944 fix(hermes): declare all plugin hooks (#486)
* fix(hermes): declare all plugin hooks

* test(hermes): compare manifest hooks to provider

---------

Co-authored-by: honor2030 <19909783+honor2030@users.noreply.github.com>
2026-05-19 19:19:24 +01:00
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