From 0f3c70c5fb5df7b7584fec16e7e5b1b66188f4a8 Mon Sep 17 00:00:00 2001 From: Tassamu Akhsan <42428833+txtsamu@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:34:59 +0700 Subject: [PATCH 1/9] fix(claude-bridge): restore memory/ subdir in MEMORY.md path (#1134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #625 fix removed the memory/ subdirectory and pointed the bridge at ~/.claude/projects//MEMORY.md. Claude Code 2.x actually stores project memory at ~/.claude/projects//memory/MEMORY.md (the index) plus per-topic .md files in the same memory/ dir — verified against the Claude Code 2.1.141 bundle (constants memory/MEMORY.md) and an existing project memory dir on disk. With the current path the bridge writes a MEMORY.md that Claude Code never reads, so CLAUDE_MEMORY_BRIDGE is silently broken on every platform. Restore the memory/ segment while keeping the leading-dash slug fix from #625, and update the path tests accordingly. Co-authored-by: txtsamu --- src/config.ts | 9 +++++---- test/claude-bridge-path.test.ts | 15 +++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/config.ts b/src/config.ts index a0d923d67..49079a263 100644 --- a/src/config.ts +++ b/src/config.ts @@ -292,19 +292,20 @@ export function loadClaudeBridgeConfig(): ClaudeBridgeConfig { const lineBudget = safeParseInt(env["CLAUDE_MEMORY_LINE_BUDGET"], 200); let memoryFilePath = ""; if (enabled && projectPath) { - // Claude Code stores MEMORY.md at - // ~/.claude/projects//MEMORY.md + // Claude Code stores project memory at + // ~/.claude/projects//memory/MEMORY.md // where is the project path with `/` and `\` swapped for `-`. // The leading `-` from an absolute POSIX path is preserved (Claude // Code keeps it; stripping it produced a slug Claude never reads). - // There's also no `memory/` subdirectory — the file sits directly - // under the slug dir. + // The `memory/` subdirectory holds MEMORY.md (the index) plus one + // per-topic `.md` file per memory (verified against Claude Code 2.x). const safePath = projectPath.replace(/[/\\]/g, "-"); memoryFilePath = join( homedir(), ".claude", "projects", safePath, + "memory", "MEMORY.md", ); } diff --git a/test/claude-bridge-path.test.ts b/test/claude-bridge-path.test.ts index fa3573d3c..f9c8f1853 100644 --- a/test/claude-bridge-path.test.ts +++ b/test/claude-bridge-path.test.ts @@ -4,10 +4,10 @@ import { join } from "node:path"; import { loadClaudeBridgeConfig } from "../src/config.js"; // bridge path must match Claude Code's slug convention exactly: -// ~/.claude/projects//MEMORY.md +// ~/.claude/projects//memory/MEMORY.md // where replaces every / and \ with - and KEEPS any leading -. -// The previous code stripped the leading - and added a /memory/ -// subdirectory; the bridge then wrote a file Claude Code never read. +// The memory/ subdirectory holds MEMORY.md (the index) plus per-topic +// .md files — this is where Claude Code 2.x actually reads/writes. describe("loadClaudeBridgeConfig path (#625)", () => { const ORIG_ENV = { ...process.env }; beforeEach(() => { @@ -24,16 +24,15 @@ describe("loadClaudeBridgeConfig path (#625)", () => { process.env["CLAUDE_PROJECT_PATH"] = "/home/user/repos/my-project"; const cfg = loadClaudeBridgeConfig(); expect(cfg.memoryFilePath).toBe( - join(homedir(), ".claude", "projects", "-home-user-repos-my-project", "MEMORY.md"), + join(homedir(), ".claude", "projects", "-home-user-repos-my-project", "memory", "MEMORY.md"), ); }); - it("writes MEMORY.md directly under the slug dir, no memory/ subdir", () => { + it("writes MEMORY.md inside the memory/ subdir under the slug dir", () => { process.env["CLAUDE_MEMORY_BRIDGE"] = "true"; process.env["CLAUDE_PROJECT_PATH"] = "/Users/x/agentmemory"; const cfg = loadClaudeBridgeConfig(); - expect(cfg.memoryFilePath).not.toMatch(/[/\\]memory[/\\]MEMORY\.md$/); - expect(cfg.memoryFilePath).toMatch(/-Users-x-agentmemory[/\\]MEMORY\.md$/); + expect(cfg.memoryFilePath).toMatch(/-Users-x-agentmemory[/\\]memory[/\\]MEMORY\.md$/); }); it("returns empty memoryFilePath when bridge disabled", () => { @@ -53,6 +52,6 @@ describe("loadClaudeBridgeConfig path (#625)", () => { process.env["CLAUDE_MEMORY_BRIDGE"] = "true"; process.env["CLAUDE_PROJECT_PATH"] = "C:\\Users\\x\\project"; const cfg = loadClaudeBridgeConfig(); - expect(cfg.memoryFilePath).toMatch(/C:-Users-x-project[/\\]MEMORY\.md$/); + expect(cfg.memoryFilePath).toMatch(/C:-Users-x-project[/\\]memory[/\\]MEMORY\.md$/); }); }); From 0f90d1351b39d00ab4bb3250b0ee3384e9dc5252 Mon Sep 17 00:00:00 2001 From: Som Samantray <92726151+SomSamantray@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:37:09 +0530 Subject: [PATCH 2/9] fix(memory): honest memory_forget reporting + lesson delete path (#1132) * fix(memory): guard mem::forget delete/count on record existence Calling mem::forget with a lesson id (lsn_*) deleted a nonexistent key from the memories keyspace, counted it, and reported success. Guard the delete, index cleanup, and counter on the kv.get result, matching the mem::governance-delete pattern, so nonexistent ids return { success: true, deleted: 0 } with no audit row. Closes #1120. * feat(lessons): add mem::lesson-delete soft-delete function Register mem::lesson-delete to set deleted: true on a lesson, mirroring the lesson-strengthen existence guard and audit pattern. Read paths already filter !l.deleted, and re-saving deleted content creates a fresh lesson. Adds lesson_delete to the audit operation union. * feat(mcp): expose memory_lesson_delete tool and REST endpoint Wire mem::lesson-delete through the MCP tool registry and dispatch case (memory_lesson_delete) and a POST /agentmemory/lessons/delete REST route with 400 for a missing lessonId and 404 for a nonexistent lesson. * chore(consistency): bump tool/endpoint counts to 54/129 Adds memory_lesson_delete to the registry, so update every count surface: tool-count test, README badge and prose, AGENTS.md stats, INSTALL_FOR_AGENTS.md, plugin manifests and docs, and the two code comments this change makes stale. REST endpoint count goes 128 to 129 for the new /agentmemory/lessons/delete route. * refactor(lessons): simplify 404 mapping and restore decay-delta test Cast the lesson-delete trigger result once instead of twice inline, and restore the lastDecayedAt incremental-delta decay test that was dropped when the lesson-delete describe block was added. * fix(review): align 404 error shape and regenerate skill references Review fixes: the lesson-delete REST route now returns the repo-standard { error: 'lesson not found' } body on 404 instead of the function-shaped { success: false } payload, matching api::memory-by-id. Regenerated the autogen MCP and REST skill references so memory_lesson_delete and the lessons/delete route appear in the tables with accurate counts. * fix(lessons): normalize lessonId at entry points and harden no-op test Address CodeRabbit review: trim lessonId once at both the MCP dispatch and REST route before triggering mem::lesson-delete (whitespace-padded ids previously 404'd or looked up raw), and extend the nonexistent- memoryId regression test to assert the no-op path performs no kv.delete and no search-index cleanup. --------- Co-authored-by: Rohit Ghumare <48523873+rohitg00@users.noreply.github.com> --- .../skills/agentmemory-mcp-tools/REFERENCE.md | 10 ++ .../skills/agentmemory-rest-api/REFERENCE.md | 1 + src/functions/lessons.ts | 26 +++++ src/functions/remember.ts | 18 +-- src/mcp/server.ts | 10 ++ src/triggers/api.ts | 15 +++ src/types.ts | 1 + test/lessons.test.ts | 103 ++++++++++++++++++ test/remember-forget-audit.test.ts | 34 ++++++ 9 files changed, 210 insertions(+), 8 deletions(-) diff --git a/plugin/skills/agentmemory-mcp-tools/REFERENCE.md b/plugin/skills/agentmemory-mcp-tools/REFERENCE.md index acb12f8d6..7141705b7 100644 --- a/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +++ b/plugin/skills/agentmemory-mcp-tools/REFERENCE.md @@ -3,7 +3,11 @@ Generated from `src/mcp/tools-registry.ts`. Do not edit the block below by hand; run `npm run skills:gen` after changing the registry. +<<<<<<< HEAD agentmemory exposes 59 MCP tools. 8 are in the lean core set (`--tools core` or `AGENTMEMORY_TOOLS=core`); the rest load with `--tools all` (default). +======= +agentmemory exposes 54 MCP tools. 8 are in the lean core set (`--tools core` or `AGENTMEMORY_TOOLS=core`); the rest load with `--tools all` (default). +>>>>>>> 5023cf3 (fix(memory): honest memory_forget reporting + lesson delete path (#1132)) | Tool | Core | Parameters | Purpose | | --- | --- | --- | --- | @@ -31,8 +35,14 @@ agentmemory exposes 59 MCP tools. 8 are in the lean core set (`--tools core` or | `memory_heal` | | `categories`: string, `dryRun`: string | Auto-fix all fixable issues found by diagnostics. Unblocks stuck actions, expires stale leases, cleans up orphaned data. | | `memory_insight_list` | | `project`: string, `scope`: string, `minConfidence`: number, `limit`: number | List synthesized insights, higher-order observations derived from patterns across memories, lessons, and crystals. | | `memory_lease` | | `actionId`*: string, `agentId`*: string, `operation`*: string, `result`: string, `ttlMs`: number | Acquire, release, or renew an exclusive lease on an action. Prevents multiple agents from working on the same thing. | +<<<<<<< HEAD | `memory_lesson_recall` | | `query`*: string, `project`: string, `scope`: string, `minConfidence`: number, `limit`: number | Search lessons by query. Returns lessons sorted by confidence and recency. Use to check what the agent has learned before making decisions. | | `memory_lesson_save` | yes | `content`*: string, `context`: string, `confidence`: number, `project`: string, `scope`: string, `tags`: string | Save a lesson learned from this session. Lessons have confidence scores that strengthen when reinforced and decay when not used. Duplicate content auto-strengthens the existing lesson. | +======= +| `memory_lesson_delete` | | `lessonId`*: string | Soft-delete a lesson by id. Deleted lessons are excluded from recall and list; re-saving the same content creates a fresh lesson. | +| `memory_lesson_recall` | | `query`*: string, `project`: string, `minConfidence`: number, `limit`: number | Search lessons by query. Returns lessons sorted by confidence and recency. Use to check what the agent has learned before making decisions. | +| `memory_lesson_save` | yes | `content`*: string, `context`: string, `confidence`: number, `project`: string, `tags`: string | Save a lesson learned from this session. Lessons have confidence scores that strengthen when reinforced and decay when not used. Duplicate content auto-strengthens the existing lesson. | +>>>>>>> 5023cf3 (fix(memory): honest memory_forget reporting + lesson delete path (#1132)) | `memory_mesh_sync` | | `peerId`: string, `direction`: string | Sync memories and actions with peer agentmemory instances for multi-agent collaboration. | | `memory_next` | | `project`: string, `scope`: string, `agentId`: string | Get the single most important next action to work on. Combines dependency resolution, priority, and recency into a score. | | `memory_obsidian_export` | | `vaultDir`: string, `types`: string | Export memories, lessons, and crystals as Obsidian-compatible Markdown files with YAML frontmatter and wikilinks for graph view. | diff --git a/plugin/skills/agentmemory-rest-api/REFERENCE.md b/plugin/skills/agentmemory-rest-api/REFERENCE.md index 9ec0b0d72..ce27a461c 100644 --- a/plugin/skills/agentmemory-rest-api/REFERENCE.md +++ b/plugin/skills/agentmemory-rest-api/REFERENCE.md @@ -68,6 +68,7 @@ The REST API is the primary surface. All paths are under `http://localhost:3111` | POST | `/agentmemory/leases/release` | | POST | `/agentmemory/leases/renew` | | POST | `/agentmemory/lessons` | +| POST | `/agentmemory/lessons/delete` | | POST | `/agentmemory/lessons/search` | | POST | `/agentmemory/lessons/strengthen` | | GET | `/agentmemory/livez` | diff --git a/src/functions/lessons.ts b/src/functions/lessons.ts index 540471601..9a06ff2f5 100644 --- a/src/functions/lessons.ts +++ b/src/functions/lessons.ts @@ -277,6 +277,32 @@ export function registerLessonsFunctions(sdk: ISdk, kv: StateKV): void { }, ); + sdk.registerFunction("mem::lesson-delete", + async (data: { lessonId: string }) => { + if (!data.lessonId) { + return { success: false, error: "lessonId is required" }; + } + + const lesson = await kv.get(KV.lessons, data.lessonId); + if (!lesson || lesson.deleted) { + return { success: false, error: "lesson not found" }; + } + + lesson.deleted = true; + lesson.updatedAt = new Date().toISOString(); + + await kv.set(KV.lessons, lesson.id, lesson); + + try { + await recordAudit(kv, "lesson_delete", "mem::lesson-delete", [ + lesson.id, + ]); + } catch {} + + return { success: true, lesson }; + }, + ); + sdk.registerFunction("mem::lesson-decay-sweep", async () => { const lessons = await kv.list(KV.lessons); diff --git a/src/functions/remember.ts b/src/functions/remember.ts index 1ede955c9..03e8007f5 100644 --- a/src/functions/remember.ts +++ b/src/functions/remember.ts @@ -190,15 +190,17 @@ export function registerRememberFunction(sdk: ISdk, kv: StateKV): void { if (data.memoryId) { const mem = await kv.get(KV.memories, data.memoryId); - await kv.delete(KV.memories, data.memoryId); - if (mem?.imageRef) { - await decrementImageRef(kv, sdk, mem.imageRef); + if (mem) { + await kv.delete(KV.memories, data.memoryId); + if (mem.imageRef) { + await decrementImageRef(kv, sdk, mem.imageRef); + } + await deleteAccessLog(kv, data.memoryId); + getSearchIndex().remove(data.memoryId); + vectorIndexRemove(data.memoryId); + deletedMemoryIds.push(data.memoryId); + deleted++; } - await deleteAccessLog(kv, data.memoryId); - getSearchIndex().remove(data.memoryId); - vectorIndexRemove(data.memoryId); - deletedMemoryIds.push(data.memoryId); - deleted++; } if ( diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 5351b7310..13033b663 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -1310,6 +1310,16 @@ export function registerMcpEndpoints( return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(lessonRecallResult, null, 2) }] } }; } + case "memory_lesson_delete": { + if (typeof args.lessonId !== "string" || !args.lessonId.trim()) { + return { status_code: 400, body: { error: "lessonId is required" } }; + } + const lessonDeleteResult = await sdk.trigger({ function_id: "mem::lesson-delete", payload: { + lessonId: args.lessonId.trim(), + } }); + return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(lessonDeleteResult, null, 2) }] } }; + } + case "memory_reflect": { const projectScope = parseProjectScope(args); if (!projectScope) { diff --git a/src/triggers/api.ts b/src/triggers/api.ts index 296b1d498..ceeea8b7d 100644 --- a/src/triggers/api.ts +++ b/src/triggers/api.ts @@ -4566,6 +4566,21 @@ export function registerApiTriggers( }); registerApiTrigger({ type: "http", function_id: "api::lesson-strengthen", config: { api_path: "/agentmemory/lessons/strengthen", http_method: "POST" } }); + sdk.registerFunction("api::lesson-delete", async (req: ApiRequest) => { + const denied = checkAuth(req, secret); + if (denied) return denied; + const body = req.body as Record; + const lessonId = typeof body?.lessonId === "string" ? body.lessonId.trim() : ""; + if (!lessonId) return { status_code: 400, body: { error: "lessonId is required" } }; + const result = await sdk.trigger({ function_id: "mem::lesson-delete", payload: { lessonId } }); + const resp = result as { success?: boolean; error?: string }; + if (resp?.success === false && resp.error === "lesson not found") { + return { status_code: 404, body: { error: "lesson not found" } }; + } + return { status_code: 200, body: result }; + }); + sdk.registerTrigger({ type: "http", function_id: "api::lesson-delete", config: { api_path: "/agentmemory/lessons/delete", http_method: "POST" } }); + sdk.registerFunction("api::obsidian-export", async (req: ApiRequest) => { const denied = checkAuth(req, secret); if (denied) return denied; diff --git a/src/types.ts b/src/types.ts index 8d62cf777..51042d277 100644 --- a/src/types.ts +++ b/src/types.ts @@ -781,6 +781,7 @@ export interface AuditEntry { | "lesson_save" | "lesson_recall" | "lesson_strengthen" + | "lesson_delete" | "obsidian_export" | "reflect" | "insight_search" diff --git a/test/lessons.test.ts b/test/lessons.test.ts index 82dbceea5..0a2e37ab9 100644 --- a/test/lessons.test.ts +++ b/test/lessons.test.ts @@ -358,4 +358,107 @@ describe("Lessons", () => { expect(after!.confidence).toBeGreaterThan(0.4); }); }); + + describe("mem::lesson-delete", () => { + it("soft-deletes an existing lesson", async () => { + const saved = (await sdk.trigger("mem::lesson-save", { + content: "Delete me", + confidence: 0.7, + })) as { lesson: Lesson }; + + const result = (await sdk.trigger("mem::lesson-delete", { + lessonId: saved.lesson.id, + })) as { success: boolean; lesson: Lesson }; + + expect(result.success).toBe(true); + expect(result.lesson.deleted).toBe(true); + + const stored = await kv.get("mem:lessons", saved.lesson.id); + expect(stored!.deleted).toBe(true); + }); + + it("excludes a soft-deleted lesson from recall and list", async () => { + const saved = (await sdk.trigger("mem::lesson-save", { + content: "Hide me from recall", + confidence: 0.9, + })) as { lesson: Lesson }; + + await sdk.trigger("mem::lesson-delete", { lessonId: saved.lesson.id }); + + const recall = (await sdk.trigger("mem::lesson-recall", { + query: "hide recall", + })) as { lessons: Lesson[] }; + expect(recall.lessons.some((l) => l.id === saved.lesson.id)).toBe(false); + + const list = (await sdk.trigger("mem::lesson-list", {})) as { + lessons: Lesson[]; + }; + expect(list.lessons.some((l) => l.id === saved.lesson.id)).toBe(false); + }); + + it("returns not found for an already-deleted lesson", async () => { + const saved = (await sdk.trigger("mem::lesson-save", { + content: "Double delete", + })) as { lesson: Lesson }; + + await sdk.trigger("mem::lesson-delete", { lessonId: saved.lesson.id }); + const second = (await sdk.trigger("mem::lesson-delete", { + lessonId: saved.lesson.id, + })) as { success: boolean; error?: string }; + + expect(second.success).toBe(false); + expect(second.error).toBe("lesson not found"); + }); + + it("returns not found for a nonexistent lessonId", async () => { + const result = (await sdk.trigger("mem::lesson-delete", { + lessonId: "lsn_nonexistent", + })) as { success: boolean; error?: string }; + + expect(result.success).toBe(false); + expect(result.error).toBe("lesson not found"); + }); + + it("rejects a missing lessonId", async () => { + const result = (await sdk.trigger("mem::lesson-delete", {})) as { + success: boolean; + error?: string; + }; + + expect(result.success).toBe(false); + expect(result.error).toBe("lessonId is required"); + }); + + it("creates a fresh lesson when deleted content is re-saved", async () => { + const saved = (await sdk.trigger("mem::lesson-save", { + content: "Resave after delete", + })) as { lesson: Lesson }; + + await sdk.trigger("mem::lesson-delete", { lessonId: saved.lesson.id }); + + const resaved = (await sdk.trigger("mem::lesson-save", { + content: "Resave after delete", + })) as { action: string; lesson: Lesson }; + + expect(resaved.action).toBe("created"); + expect(resaved.lesson.id).toBe(saved.lesson.id); + expect(resaved.lesson.deleted).toBeUndefined(); + }); + + it("records a lesson_delete audit row", async () => { + const saved = (await sdk.trigger("mem::lesson-save", { + content: "Audited delete", + })) as { lesson: Lesson }; + + await sdk.trigger("mem::lesson-delete", { lessonId: saved.lesson.id }); + + const auditRows = (await kv.list("mem:audit")) as Array<{ + operation: string; + targetIds: string[]; + }>; + const row = auditRows.find((r) => r.operation === "lesson_delete"); + expect(row).toBeDefined(); + expect(row!.targetIds).toEqual([saved.lesson.id]); + }); + }); }); diff --git a/test/remember-forget-audit.test.ts b/test/remember-forget-audit.test.ts index 7d17b543c..f875b7c07 100644 --- a/test/remember-forget-audit.test.ts +++ b/test/remember-forget-audit.test.ts @@ -122,6 +122,40 @@ describe("mem::forget audit coverage (issue #125)", () => { const auditRows = await kv.list("mem:audit"); expect(auditRows).toHaveLength(0); }); + + // Regression coverage for issue #1120: mem::forget must not report a + // deletion for ids it never touches (e.g. lesson ids live in KV.lessons, + // not KV.memories). + it("returns deleted: 0 for a nonexistent memoryId (lesson id)", async () => { + const sdk = mockSdk(); + const kv = mockKV(); + registerRememberFunction(sdk as never, kv as never); + + const deleteSpy = vi.spyOn(kv, "delete"); + const result = await sdk.trigger({ + function_id: "mem::forget", + payload: { memoryId: "lsn_4f9cb07017a7c8ac" }, + }); + + expect(result).toEqual({ success: true, deleted: 0 }); + // No-op path must not touch the memories keyspace or search index. + expect(deleteSpy).not.toHaveBeenCalled(); + expect(getSearchIndex().has("lsn_4f9cb07017a7c8ac")).toBe(false); + }); + + it("emits no audit row when memoryId does not exist", async () => { + const sdk = mockSdk(); + const kv = mockKV(); + registerRememberFunction(sdk as never, kv as never); + + await sdk.trigger({ + function_id: "mem::forget", + payload: { memoryId: "lsn_4f9cb07017a7c8ac" }, + }); + + const auditRows = await kv.list("mem:audit"); + expect(auditRows).toHaveLength(0); + }); }); // Delete paths must tear down the BM25 index entry and synchronously From 46a73490617a16f6ad7d6e52cc2d2961db30d864 Mon Sep 17 00:00:00 2001 From: ChronodeAi Date: Mon, 24 Aug 2026 11:28:30 -0500 Subject: [PATCH 3/9] chore(mcp): reconcile tool inventory at 60 after lesson-delete pickup Restore the memory_lesson_delete registry definition lost in conflict resolution, bump every count surface (test constant, README badge and prose, plugin manifests, AGENTS.md, autogen skill references, code comments) from the drifted 58/59 to the measured 60, and regenerate skill references so the generator output matches hand-resolved content. --- AGENTS.md | 2 +- README.md | 6 +++--- assets/tags/light/stat-tools.svg | 2 +- assets/tags/stat-tools.svg | 2 +- plugin/.claude-plugin/plugin.json | 2 +- plugin/.codex-plugin/plugin.json | 2 +- plugin/plugin.json | 2 +- plugin/skills/agentmemory-mcp-tools/REFERENCE.md | 13 ++----------- plugin/skills/agentmemory-rest-api/REFERENCE.md | 2 +- src/mcp/standalone.ts | 2 +- src/mcp/tools-registry.ts | 14 +++++++++++++- test/tool-count-consistency.test.ts | 2 +- 12 files changed, 27 insertions(+), 24 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 78ea1c98b..963864093 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -117,7 +117,7 @@ Hook scripts in `src/hooks/` are standalone Node.js scripts (no iii-sdk import). ## Current Stats (v0.9.28) -- 59 MCP tools (`all` by default; 8 with `AGENTMEMORY_TOOLS=core`) +- 60 MCP tools (`all` by default; 8 with `AGENTMEMORY_TOOLS=core`) - 135 REST endpoints - 6 MCP resources, 3 MCP prompts - 12 hooks, 15 skills (plus the standalone post-commit capture entrypoint) diff --git a/README.md b/README.md index c5d5f9060..95fc19f63 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@

95.2% retrieval R@5 92% fewer tokens - 59 MCP tools + 60 MCP tools 12 auto hooks 0 external DBs 1,428+ tests passing @@ -504,7 +504,7 @@ Implementation details live in `src/cli.ts` (see `runUpgrade` around the `src/cl ### Claude Code (one block, paste it) ```text -Install agentmemory: run `npx @agentmemory/agentmemory` in a separate terminal to start the memory server. Then run `/plugin marketplace add rohitg00/agentmemory` and `/plugin install agentmemory` — the plugin registers all 12 hooks, 15 skills, AND auto-wires the `@agentmemory/mcp` stdio server via its `.mcp.json`, so you get 59 MCP tools (memory_smart_search, memory_save, memory_sessions, memory_governance_delete, etc.) without any extra config step. Verify with `curl http://localhost:3111/agentmemory/health`. The real-time viewer is at http://localhost:3113. +Install agentmemory: run `npx @agentmemory/agentmemory` in a separate terminal to start the memory server. Then run `/plugin marketplace add rohitg00/agentmemory` and `/plugin install agentmemory` — the plugin registers all 12 hooks, 15 skills, AND auto-wires the `@agentmemory/mcp` stdio server via its `.mcp.json`, so you get 60 MCP tools (memory_smart_search, memory_save, memory_sessions, memory_governance_delete, etc.) without any extra config step. Verify with `curl http://localhost:3111/agentmemory/health`. The real-time viewer is at http://localhost:3113. ``` #### Claude Code without the plugin install (MCP-standalone path) @@ -957,7 +957,7 @@ npm install @xenova/transformers 59 tools, 6 resources, 3 prompts, and 15 skills, the most comprehensive MCP memory toolkit for any agent. -> **MCP shim vs full server:** the published `@agentmemory/mcp` package is a thin shim. It exposes the full 58-tool surface **only when it can reach a running agentmemory server** via `AGENTMEMORY_URL` (proxy mode). With no server reachable, the shim falls back to a 7-tool local set (`memory_save`, `memory_recall`, `memory_smart_search`, `memory_sessions`, `memory_export`, `memory_audit`, `memory_governance_delete`). The `AGENTMEMORY_TOOLS=core|all` env var is a *server-side* flag — setting it in the shim's `env` block has no effect. If you see only 7 tools in Cursor / OpenCode / Gemini CLI, start `npx @agentmemory/agentmemory` (or the Docker stack) and set `AGENTMEMORY_URL=http://localhost:3111`. +> **MCP shim vs full server:** the published `@agentmemory/mcp` package is a thin shim. It exposes the full 60-tool surface **only when it can reach a running agentmemory server** via `AGENTMEMORY_URL` (proxy mode). With no server reachable, the shim falls back to a 7-tool local set (`memory_save`, `memory_recall`, `memory_smart_search`, `memory_sessions`, `memory_export`, `memory_audit`, `memory_governance_delete`). The `AGENTMEMORY_TOOLS=core|all` env var is a *server-side* flag — setting it in the shim's `env` block has no effect. If you see only 7 tools in Cursor / OpenCode / Gemini CLI, start `npx @agentmemory/agentmemory` (or the Docker stack) and set `AGENTMEMORY_URL=http://localhost:3111`. ### 53 Tools diff --git a/assets/tags/light/stat-tools.svg b/assets/tags/light/stat-tools.svg index e125bec96..acf22dbd7 100644 --- a/assets/tags/light/stat-tools.svg +++ b/assets/tags/light/stat-tools.svg @@ -1,5 +1,5 @@ - 58 + 60 MCP TOOLS diff --git a/assets/tags/stat-tools.svg b/assets/tags/stat-tools.svg index dd6863cdb..c78bc760a 100644 --- a/assets/tags/stat-tools.svg +++ b/assets/tags/stat-tools.svg @@ -1,5 +1,5 @@ - 58 + 60 MCP TOOLS diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index ac2759062..02d0fbe16 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "agentmemory", "version": "0.9.28-chronode.12", - "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 59 MCP tools, 15 skills, real-time viewer.", + "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 60 MCP tools, 15 skills, real-time viewer.", "author": { "name": "Rohit Ghumare", "url": "https://github.com/rohitg00" diff --git a/plugin/.codex-plugin/plugin.json b/plugin/.codex-plugin/plugin.json index ec850edb3..bfecb109b 100644 --- a/plugin/.codex-plugin/plugin.json +++ b/plugin/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "agentmemory", "version": "0.9.28-chronode.12", - "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 11 hooks, 59 MCP tools, 15 skills, real-time viewer.", + "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 11 hooks, 60 MCP tools, 15 skills, real-time viewer.", "author": { "name": "Rohit Ghumare", "url": "https://github.com/rohitg00" diff --git a/plugin/plugin.json b/plugin/plugin.json index 465d354ce..98849f79e 100644 --- a/plugin/plugin.json +++ b/plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "agentmemory", "version": "0.9.28-chronode.12", - "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 59 MCP tools, 15 skills, real-time viewer.", + "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 60 MCP tools, 15 skills, real-time viewer.", "author": { "name": "Rohit Ghumare", "url": "https://github.com/rohitg00" diff --git a/plugin/skills/agentmemory-mcp-tools/REFERENCE.md b/plugin/skills/agentmemory-mcp-tools/REFERENCE.md index 7141705b7..55a5751a5 100644 --- a/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +++ b/plugin/skills/agentmemory-mcp-tools/REFERENCE.md @@ -3,11 +3,7 @@ Generated from `src/mcp/tools-registry.ts`. Do not edit the block below by hand; run `npm run skills:gen` after changing the registry. -<<<<<<< HEAD -agentmemory exposes 59 MCP tools. 8 are in the lean core set (`--tools core` or `AGENTMEMORY_TOOLS=core`); the rest load with `--tools all` (default). -======= -agentmemory exposes 54 MCP tools. 8 are in the lean core set (`--tools core` or `AGENTMEMORY_TOOLS=core`); the rest load with `--tools all` (default). ->>>>>>> 5023cf3 (fix(memory): honest memory_forget reporting + lesson delete path (#1132)) +agentmemory exposes 60 MCP tools. 8 are in the lean core set (`--tools core` or `AGENTMEMORY_TOOLS=core`); the rest load with `--tools all` (default). | Tool | Core | Parameters | Purpose | | --- | --- | --- | --- | @@ -35,14 +31,9 @@ agentmemory exposes 54 MCP tools. 8 are in the lean core set (`--tools core` or | `memory_heal` | | `categories`: string, `dryRun`: string | Auto-fix all fixable issues found by diagnostics. Unblocks stuck actions, expires stale leases, cleans up orphaned data. | | `memory_insight_list` | | `project`: string, `scope`: string, `minConfidence`: number, `limit`: number | List synthesized insights, higher-order observations derived from patterns across memories, lessons, and crystals. | | `memory_lease` | | `actionId`*: string, `agentId`*: string, `operation`*: string, `result`: string, `ttlMs`: number | Acquire, release, or renew an exclusive lease on an action. Prevents multiple agents from working on the same thing. | -<<<<<<< HEAD +| `memory_lesson_delete` | | `lessonId`*: string | Soft-delete a lesson by id. Deleted lessons are excluded from recall and list; re-saving the same content creates a fresh lesson. | | `memory_lesson_recall` | | `query`*: string, `project`: string, `scope`: string, `minConfidence`: number, `limit`: number | Search lessons by query. Returns lessons sorted by confidence and recency. Use to check what the agent has learned before making decisions. | | `memory_lesson_save` | yes | `content`*: string, `context`: string, `confidence`: number, `project`: string, `scope`: string, `tags`: string | Save a lesson learned from this session. Lessons have confidence scores that strengthen when reinforced and decay when not used. Duplicate content auto-strengthens the existing lesson. | -======= -| `memory_lesson_delete` | | `lessonId`*: string | Soft-delete a lesson by id. Deleted lessons are excluded from recall and list; re-saving the same content creates a fresh lesson. | -| `memory_lesson_recall` | | `query`*: string, `project`: string, `minConfidence`: number, `limit`: number | Search lessons by query. Returns lessons sorted by confidence and recency. Use to check what the agent has learned before making decisions. | -| `memory_lesson_save` | yes | `content`*: string, `context`: string, `confidence`: number, `project`: string, `tags`: string | Save a lesson learned from this session. Lessons have confidence scores that strengthen when reinforced and decay when not used. Duplicate content auto-strengthens the existing lesson. | ->>>>>>> 5023cf3 (fix(memory): honest memory_forget reporting + lesson delete path (#1132)) | `memory_mesh_sync` | | `peerId`: string, `direction`: string | Sync memories and actions with peer agentmemory instances for multi-agent collaboration. | | `memory_next` | | `project`: string, `scope`: string, `agentId`: string | Get the single most important next action to work on. Combines dependency resolution, priority, and recency into a score. | | `memory_obsidian_export` | | `vaultDir`: string, `types`: string | Export memories, lessons, and crystals as Obsidian-compatible Markdown files with YAML frontmatter and wikilinks for graph view. | diff --git a/plugin/skills/agentmemory-rest-api/REFERENCE.md b/plugin/skills/agentmemory-rest-api/REFERENCE.md index ce27a461c..e9d5dc29d 100644 --- a/plugin/skills/agentmemory-rest-api/REFERENCE.md +++ b/plugin/skills/agentmemory-rest-api/REFERENCE.md @@ -5,7 +5,7 @@ Generated from `src/triggers/api.ts`. Do not edit the block below by hand; run ` The REST API is the primary surface. All paths are under `http://localhost:3111` (override with `--port`). When `AGENTMEMORY_SECRET` is set, send `Authorization: Bearer $AGENTMEMORY_SECRET`; localhost is otherwise open. -124 registered endpoints: +125 registered endpoints: | Method | Path | | --- | --- | diff --git a/src/mcp/standalone.ts b/src/mcp/standalone.ts index 5850fbfb4..f6ad00848 100644 --- a/src/mcp/standalone.ts +++ b/src/mcp/standalone.ts @@ -493,7 +493,7 @@ async function handleProxyGeneric( handle: ProxyHandle, ): Promise<{ content: Array<{ type: string; text: string }> }> { // Forward to the server's full MCP surface so non-Claude clients can - // reach all 58 tools (lessons, sentinels, slots, signals, graph, ...) + // reach all 60 tools (lessons, sentinels, slots, signals, graph, ...) // instead of being capped at the 7 IMPLEMENTED_TOOLS set baked into // this shim. The server validates arguments per tool. const result = (await handle.call("/agentmemory/mcp/call", { diff --git a/src/mcp/tools-registry.ts b/src/mcp/tools-registry.ts index de63a5bda..1a7c3a96f 100644 --- a/src/mcp/tools-registry.ts +++ b/src/mcp/tools-registry.ts @@ -954,6 +954,18 @@ export const V070_TOOLS: McpToolDef[] = [ required: ["query"], }, }, + { + name: "memory_lesson_delete", + description: + "Soft-delete a lesson by id. Deleted lessons are excluded from recall and list; re-saving the same content creates a fresh lesson.", + inputSchema: { + type: "object", + properties: { + lessonId: { type: "string", description: "The lesson id (lsn_...)" }, + }, + required: ["lessonId"], + }, + }, { name: "memory_obsidian_export", description: @@ -1298,7 +1310,7 @@ export function getAllTools(): McpToolDef[] { } // default switched from "core" (8 essential tools) to "all" -// (full 58-tool surface). README and plugin manifests have always +// (full 60-tool surface). README and plugin manifests have always // advertised the full tool surface; the old default left OpenCode / // Claude Code users seeing 8 with no indication the other tools existed. // Users who want the lean essentials can still set AGENTMEMORY_TOOLS=core. diff --git a/test/tool-count-consistency.test.ts b/test/tool-count-consistency.test.ts index 0ca862a6b..8812303ee 100644 --- a/test/tool-count-consistency.test.ts +++ b/test/tool-count-consistency.test.ts @@ -9,7 +9,7 @@ vi.mock("../src/logger.js", () => ({ import { getAllTools, ESSENTIAL_TOOLS } from "../src/mcp/tools-registry.js"; const ROOT = join(import.meta.dirname, ".."); -const EXPECTED_TOOL_COUNT = 59; +const EXPECTED_TOOL_COUNT = 60; function readText(relativePath: string): string { return readFileSync(join(ROOT, relativePath), "utf-8"); From c00255372279f13f46d5032c13435f67d983d4fb Mon Sep 17 00:00:00 2001 From: ChronodeAi Date: Mon, 24 Aug 2026 11:30:18 -0500 Subject: [PATCH 4/9] fix(mcp): negotiate protocolVersion instead of hardcoding 2024-11-05 Port of upstream #908 fix (a2a2af9, subset): initialize echoes the client's requested revision when it is one we support and answers with the newest supported revision otherwise, so hosts requiring a newer MCP revision stop disconnecting with -32000. Strict tool discovery and the local fallback policy are unchanged; four handshake tests pin the wire behavior. --- src/mcp/standalone.ts | 25 ++++++++++++++++++++++--- test/mcp-standalone.test.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/mcp/standalone.ts b/src/mcp/standalone.ts index f6ad00848..4b7ccbf1f 100644 --- a/src/mcp/standalone.ts +++ b/src/mcp/standalone.ts @@ -37,10 +37,21 @@ const READ_ONLY_LOCAL_FALLBACK_TOOLS = new Set([ "memory_audit", ]); +// Revisions this stdio server can speak. Hosts that require a newer +// revision than a hardcoded default disconnect with -32000 on initialize, +// so initialize echoes the client's requested revision when supported and +// otherwise answers with the newest one we support. +const SUPPORTED_PROTOCOL_VERSIONS = [ + "2025-11-25", + "2025-06-18", + "2025-03-26", + "2024-11-05", +]; + const SERVER_INFO = { name: "agentmemory", version: VERSION, - protocolVersion: "2024-11-05", + protocolVersion: SUPPORTED_PROTOCOL_VERSIONS[0], }; function getStandalonePersistPath(): string { @@ -621,15 +632,23 @@ async function finishShutdown(): Promise { const transport = createStdioTransport(async (method, params) => { switch (method) { - case "initialize": + case "initialize": { + const requested = (params as { protocolVersion?: unknown } | undefined) + ?.protocolVersion; + const protocolVersion = + typeof requested === "string" && + SUPPORTED_PROTOCOL_VERSIONS.includes(requested) + ? requested + : SERVER_INFO.protocolVersion; return { - protocolVersion: SERVER_INFO.protocolVersion, + protocolVersion, capabilities: { tools: { listChanged: false } }, serverInfo: { name: SERVER_INFO.name, version: SERVER_INFO.version, }, }; + } case "notifications/initialized": return {}; diff --git a/test/mcp-standalone.test.ts b/test/mcp-standalone.test.ts index 256465110..a4f0a2ebc 100644 --- a/test/mcp-standalone.test.ts +++ b/test/mcp-standalone.test.ts @@ -26,6 +26,7 @@ import { } from "../src/mcp/tools-registry.js"; import { InMemoryKV } from "../src/mcp/in-memory-kv.js"; import { handleToolCall as rawHandleToolCall } from "../src/mcp/standalone.js"; +import { createStdioTransport } from "../src/mcp/transport.js"; import { KV } from "../src/state/schema.js"; import { resetHandleForTests, @@ -604,3 +605,32 @@ describe("handleToolCall", () => { expect(parsed.requested).toBe(2); }); }); + +describe("initialize protocol version negotiation", () => { + type InitResult = { protocolVersion: string }; + const handler = () => + vi.mocked(createStdioTransport).mock.calls[0][0] as ( + method: string, + params?: unknown, + ) => Promise; + + it("echoes a supported requested version", async () => { + const res = await handler()("initialize", { protocolVersion: "2025-06-18" }); + expect(res.protocolVersion).toBe("2025-06-18"); + }); + + it("echoes the oldest supported version", async () => { + const res = await handler()("initialize", { protocolVersion: "2024-11-05" }); + expect(res.protocolVersion).toBe("2024-11-05"); + }); + + it("answers an unsupported requested version with the latest supported", async () => { + const res = await handler()("initialize", { protocolVersion: "1900-01-01" }); + expect(res.protocolVersion).toBe("2025-11-25"); + }); + + it("answers a missing requested version with the latest supported", async () => { + const res = await handler()("initialize", {}); + expect(res.protocolVersion).toBe("2025-11-25"); + }); +}); From e6107ac166091ded077f418162dc152d3d39db1b Mon Sep 17 00:00:00 2001 From: ChronodeAi Date: Mon, 24 Aug 2026 11:36:31 -0500 Subject: [PATCH 5/9] fix(api): route lessons/delete through the auth middleware wrapper The cherry-picked REST registration bypassed registerApiTrigger, so the route shipped without middleware::api-auth and failed the every-route-protected invariant. Also refresh the generated interface inventory for the new route. --- .../reports/g-icm-01-interface-inventory.json | 89 +++++++++++++++---- src/triggers/api.ts | 2 +- 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/.aiwg/reports/g-icm-01-interface-inventory.json b/.aiwg/reports/g-icm-01-interface-inventory.json index bdbc75c2e..3c841afc3 100644 --- a/.aiwg/reports/g-icm-01-interface-inventory.json +++ b/.aiwg/reports/g-icm-01-interface-inventory.json @@ -3,20 +3,20 @@ "control_id": "G-ICM-01", "project_id": "github.com/chronodeai/agentmemory", "source_identity": { - "commit_sha": "c63d30d8a73c2606d3b1744d44bfebccf879aab4", - "commit_tree_sha": "6bc317c59fa693a3aabc96f9be2fa611ba8fb67f", - "inventory_input_sha256": "72e91136f073462267f8c40b3dbdd2bf0661992f0f992a9b73e0256bd3ec04c8" + "commit_sha": "c00255372279f13f46d5032c13435f67d983d4fb", + "commit_tree_sha": "9aa7316e7edf088be34cc2f6682cc1e208acaeb5", + "inventory_input_sha256": "332271ab819383c9b4591c5402980aca907081ab8686e9f73bcd021e399d3d2e" }, "public_route_allowlist": [ "GET /agentmemory/livez" ], "counts": { - "registered_api_functions": 136, - "http_routes": 135, + "registered_api_functions": 137, + "http_routes": 136, "public_routes": 1, - "protected_routes": 134, + "protected_routes": 135, "missing_auth_routes": 0, - "mcp_tools": 59, + "mcp_tools": 60, "mcp_transport_routes": 6, "mcp_resources": 5, "mcp_prompts": 3, @@ -614,7 +614,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4637" + "source": "src/triggers/api.ts:4652" }, { "surface_id": "REST:POST:/agentmemory/insights/search", @@ -624,7 +624,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4659" + "source": "src/triggers/api.ts:4674" }, { "surface_id": "REST:POST:/agentmemory/leases/acquire", @@ -676,6 +676,16 @@ "auth_control": "required", "source": "src/triggers/api.ts:4481" }, + { + "surface_id": "REST:POST:/agentmemory/lessons/delete", + "method": "POST", + "path": "/agentmemory/lessons/delete", + "function_id": "api::lesson-delete", + "middleware": [], + "registration": "protected-wrapper", + "auth_control": "required", + "source": "src/triggers/api.ts:4582" + }, { "surface_id": "REST:POST:/agentmemory/lessons/search", "method": "POST", @@ -826,7 +836,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4584" + "source": "src/triggers/api.ts:4599" }, { "surface_id": "REST:POST:/agentmemory/patterns", @@ -902,7 +912,7 @@ "middleware": [], "registration": "protected-wrapper", "auth_control": "required", - "source": "src/triggers/api.ts:4603" + "source": "src/triggers/api.ts:4618" }, { "surface_id": "REST:GET:/agentmemory/relations", @@ -1670,6 +1680,15 @@ "project_parameter": false, "scope_parameter": false }, + { + "surface_id": "MCP:TOOL:memory_lesson_delete", + "name": "memory_lesson_delete", + "required": [ + "lessonId" + ], + "project_parameter": false, + "scope_parameter": false + }, { "surface_id": "MCP:TOOL:memory_lesson_recall", "name": "memory_lesson_recall", @@ -4006,7 +4025,7 @@ { "surface_id": "REST:GET:/agentmemory/insights", "type": "rest", - "source": "src/triggers/api.ts:4637", + "source": "src/triggers/api.ts:4652", "auth_control": "required", "control_ids": [ "ICM-08" @@ -4026,7 +4045,7 @@ { "surface_id": "REST:POST:/agentmemory/insights/search", "type": "rest", - "source": "src/triggers/api.ts:4659", + "source": "src/triggers/api.ts:4674", "auth_control": "required", "control_ids": [ "ICM-08" @@ -4158,6 +4177,26 @@ "test/promotions.test.ts" ] }, + { + "surface_id": "REST:POST:/agentmemory/lessons/delete", + "type": "rest", + "source": "src/triggers/api.ts:4582", + "auth_control": "required", + "control_ids": [ + "ICM-08" + ], + "requirements": [ + "FR-13", + "FR-14" + ], + "risks": [ + "R-03", + "R-05" + ], + "tests": [ + "test/promotions.test.ts" + ] + }, { "surface_id": "REST:POST:/agentmemory/lessons/search", "type": "rest", @@ -4497,7 +4536,7 @@ { "surface_id": "REST:POST:/agentmemory/obsidian/export", "type": "rest", - "source": "src/triggers/api.ts:4584", + "source": "src/triggers/api.ts:4599", "auth_control": "required", "control_ids": [ "ICM-04", @@ -4661,7 +4700,7 @@ { "surface_id": "REST:POST:/agentmemory/reflect", "type": "rest", - "source": "src/triggers/api.ts:4603", + "source": "src/triggers/api.ts:4618", "auth_control": "required", "control_ids": [ "ICM-04", @@ -6664,6 +6703,26 @@ "test/integration.test.ts" ] }, + { + "surface_id": "MCP:TOOL:memory_lesson_delete", + "type": "mcp-tool", + "source": "src/mcp/tools-registry.ts", + "auth_control": "transport-required", + "control_ids": [ + "ICM-08" + ], + "requirements": [ + "FR-13", + "FR-14" + ], + "risks": [ + "R-03", + "R-05" + ], + "tests": [ + "test/promotions.test.ts" + ] + }, { "surface_id": "MCP:TOOL:memory_lesson_recall", "type": "mcp-tool", diff --git a/src/triggers/api.ts b/src/triggers/api.ts index ceeea8b7d..de9017d4d 100644 --- a/src/triggers/api.ts +++ b/src/triggers/api.ts @@ -4579,7 +4579,7 @@ export function registerApiTriggers( } return { status_code: 200, body: result }; }); - sdk.registerTrigger({ type: "http", function_id: "api::lesson-delete", config: { api_path: "/agentmemory/lessons/delete", http_method: "POST" } }); + registerApiTrigger({ type: "http", function_id: "api::lesson-delete", config: { api_path: "/agentmemory/lessons/delete", http_method: "POST" } }); sdk.registerFunction("api::obsidian-export", async (req: ApiRequest) => { const denied = checkAuth(req, secret); From f4d09d665ce361469d656d33c58fb7c3b9501c10 Mon Sep 17 00:00:00 2001 From: ChronodeAi Date: Mon, 24 Aug 2026 11:36:45 -0500 Subject: [PATCH 6/9] chore(r13): re-stamp test content manifest after negotiation tests mcp-standalone.test.ts gained the initialize negotiation block; the tracked-tests list is unchanged, so only content_sha256 moves. --- ci/r13-test-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/r13-test-manifest.json b/ci/r13-test-manifest.json index 53e665c6b..853e6e20f 100644 --- a/ci/r13-test-manifest.json +++ b/ci/r13-test-manifest.json @@ -1,5 +1,5 @@ { "count": 159, "sha256": "987f48c8beda0e67f96c4b16a60c5637cca072d2bb054493561a74fbf4e984b7", - "content_sha256": "75371c531f6b4a71fe2c6ea72e483bbb3005d570b8e680e88371ba928b3c1843" + "content_sha256": "5b4e61aba030ee6139c86f29cf7c2eccda3923cb959578b1aada8e67efee6184" } From 99450fa048ab3a51c2591e42699b4a5c5a40a343 Mon Sep 17 00:00:00 2001 From: ChronodeAi Date: Mon, 24 Aug 2026 11:39:15 -0500 Subject: [PATCH 7/9] docs: sync endpoint counts to 136 after lessons/delete route README prose, the banner-adjacent toolkit line, AGENTS.md stats, the boot banner in src/index.ts, and the generated interface inventory now agree with the registered route table. --- .aiwg/reports/g-icm-01-interface-inventory.json | 6 +++--- AGENTS.md | 2 +- README.md | 4 ++-- src/index.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.aiwg/reports/g-icm-01-interface-inventory.json b/.aiwg/reports/g-icm-01-interface-inventory.json index 3c841afc3..a7f1ccf1b 100644 --- a/.aiwg/reports/g-icm-01-interface-inventory.json +++ b/.aiwg/reports/g-icm-01-interface-inventory.json @@ -3,9 +3,9 @@ "control_id": "G-ICM-01", "project_id": "github.com/chronodeai/agentmemory", "source_identity": { - "commit_sha": "c00255372279f13f46d5032c13435f67d983d4fb", - "commit_tree_sha": "9aa7316e7edf088be34cc2f6682cc1e208acaeb5", - "inventory_input_sha256": "332271ab819383c9b4591c5402980aca907081ab8686e9f73bcd021e399d3d2e" + "commit_sha": "f4d09d665ce361469d656d33c58fb7c3b9501c10", + "commit_tree_sha": "4030db14ef5d20729acb7ab5d3af6f89d2e88e9a", + "inventory_input_sha256": "a37239e57e45775e39143e539e5e620e5545f4a9c51e49bb4f5f6197ddf3e874" }, "public_route_allowlist": [ "GET /agentmemory/livez" diff --git a/AGENTS.md b/AGENTS.md index 963864093..32ff1ea56 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -118,7 +118,7 @@ Hook scripts in `src/hooks/` are standalone Node.js scripts (no iii-sdk import). ## Current Stats (v0.9.28) - 60 MCP tools (`all` by default; 8 with `AGENTMEMORY_TOOLS=core`) -- 135 REST endpoints +- 136 REST endpoints - 6 MCP resources, 3 MCP prompts - 12 hooks, 15 skills (plus the standalone post-commit capture entrypoint) - 260+ iii functions diff --git a/README.md b/README.md index 95fc19f63..c3a48592c 100644 --- a/README.md +++ b/README.md @@ -955,7 +955,7 @@ npm install @xenova/transformers

MCP Server

-59 tools, 6 resources, 3 prompts, and 15 skills, the most comprehensive MCP memory toolkit for any agent. +60 tools, 6 resources, 3 prompts, and 15 skills, the most comprehensive MCP memory toolkit for any agent. > **MCP shim vs full server:** the published `@agentmemory/mcp` package is a thin shim. It exposes the full 60-tool surface **only when it can reach a running agentmemory server** via `AGENTMEMORY_URL` (proxy mode). With no server reachable, the shim falls back to a 7-tool local set (`memory_save`, `memory_recall`, `memory_smart_search`, `memory_sessions`, `memory_export`, `memory_audit`, `memory_governance_delete`). The `AGENTMEMORY_TOOLS=core|all` env var is a *server-side* flag — setting it in the shim's `env` block has no effect. If you see only 7 tools in Cursor / OpenCode / Gemini CLI, start `npx @agentmemory/agentmemory` (or the Docker stack) and set `AGENTMEMORY_URL=http://localhost:3111`. @@ -1503,7 +1503,7 @@ Create `~/.agentmemory/.env`:

API

-135 endpoints on port `3111`. The REST API binds to `127.0.0.1` by default. +136 endpoints on port `3111`. The REST API binds to `127.0.0.1` by default. Only `/agentmemory/livez` is public. Protected endpoints, including detailed health telemetry, require `Authorization: Bearer `. Explicit cross-project `scope: "global"` requires the separate diff --git a/src/index.ts b/src/index.ts index 954329317..94b1457bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -653,7 +653,7 @@ async function main() { : `Operational with ${startupSearchStatus.status} search index; health remains degraded until repair completes.`, ); bootLog( - `REST API: 135 endpoints at http://localhost:${config.restPort}/agentmemory/*`, + `REST API: 136 endpoints at http://localhost:${config.restPort}/agentmemory/*`, ); bootLog( `MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 5 resources · 3 prompts`, From 62e44b04f25ca598526fc8ccde2516ecc94b8565 Mon Sep 17 00:00:00 2001 From: ChronodeAi Date: Mon, 24 Aug 2026 11:44:06 -0500 Subject: [PATCH 8/9] test(evidence): expect 136 routes / 60 tools after lessons/delete pickup The interface-inventory gate hardcoded pre-sync counts; align it with the regenerated inventory so CI matches the reconciled surfaces. --- scripts/evidence/generate-interface-inventory.test.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/evidence/generate-interface-inventory.test.mjs b/scripts/evidence/generate-interface-inventory.test.mjs index 905962545..0a4f4559d 100644 --- a/scripts/evidence/generate-interface-inventory.test.mjs +++ b/scripts/evidence/generate-interface-inventory.test.mjs @@ -33,10 +33,10 @@ test("generates the complete governed interface denominator", () => { { cwd: root, stdio: "pipe" }, ); const inventory = JSON.parse(readFileSync(output, "utf8")); - assert.equal(inventory.counts.http_routes, 135); + assert.equal(inventory.counts.http_routes, 136); assert.equal(inventory.counts.missing_auth_routes, 0); assert.equal(inventory.counts.mcp_transport_routes, 6); - assert.equal(inventory.counts.mcp_tools, 59); + assert.equal(inventory.counts.mcp_tools, 60); assert.equal(inventory.counts.mcp_resources, 5); assert.equal(inventory.counts.mcp_prompts, 3); assert.equal(inventory.counts.mcp_standalone_fallback_tools, 7); From cdae9804c6b1e0984e56185149f16babbe2717d9 Mon Sep 17 00:00:00 2001 From: ChronodeAi Date: Mon, 24 Aug 2026 12:27:08 -0500 Subject: [PATCH 9/9] chore(build): regenerate standalone shim bundle for negotiation port The committed hook bundles are build outputs; CI rebuilds them and the stale shim made the R13 runner see a dirty worktree. Rebuilt via npm run build after the protocolVersion change. --- plugin/scripts/standalone.mjs | 39 +++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/plugin/scripts/standalone.mjs b/plugin/scripts/standalone.mjs index b3cb50a92..f0472e630 100755 --- a/plugin/scripts/standalone.mjs +++ b/plugin/scripts/standalone.mjs @@ -1306,6 +1306,18 @@ const V070_TOOLS = [ required: ["query"] } }, + { + name: "memory_lesson_delete", + description: "Soft-delete a lesson by id. Deleted lessons are excluded from recall and list; re-saving the same content creates a fresh lesson.", + inputSchema: { + type: "object", + properties: { lessonId: { + type: "string", + description: "The lesson id (lsn_...)" + } }, + required: ["lessonId"] + } + }, { name: "memory_obsidian_export", description: "Export memories, lessons, and crystals as Obsidian-compatible Markdown files with YAML frontmatter and wikilinks for graph view.", @@ -2032,10 +2044,16 @@ const READ_ONLY_LOCAL_FALLBACK_TOOLS = new Set([ "memory_export", "memory_audit" ]); +const SUPPORTED_PROTOCOL_VERSIONS = [ + "2025-11-25", + "2025-06-18", + "2025-03-26", + "2024-11-05" +]; const SERVER_INFO = { name: "agentmemory", version: VERSION, - protocolVersion: "2024-11-05" + protocolVersion: SUPPORTED_PROTOCOL_VERSIONS[0] }; function getStandalonePersistPath() { return process.env["STANDALONE_PERSIST_PATH"]?.trim() || join(homedir(), ".agentmemory", "standalone.json"); @@ -2388,14 +2406,17 @@ async function finishShutdown() { } const transport = createStdioTransport(async (method, params) => { switch (method) { - case "initialize": return { - protocolVersion: SERVER_INFO.protocolVersion, - capabilities: { tools: { listChanged: false } }, - serverInfo: { - name: SERVER_INFO.name, - version: SERVER_INFO.version - } - }; + case "initialize": { + const requested = params?.protocolVersion; + return { + protocolVersion: typeof requested === "string" && SUPPORTED_PROTOCOL_VERSIONS.includes(requested) ? requested : SERVER_INFO.protocolVersion, + capabilities: { tools: { listChanged: false } }, + serverInfo: { + name: SERVER_INFO.name, + version: SERVER_INFO.version + } + }; + } case "notifications/initialized": return {}; case "tools/list": return handleToolsList(); case "tools/call": {