diff --git a/.claude-plugin/README.md b/.claude-plugin/README.md index e9e6468..8bc47ce 100644 --- a/.claude-plugin/README.md +++ b/.claude-plugin/README.md @@ -1,6 +1,6 @@ # MemPalace Claude Code Plugin -A Claude Code plugin that gives your AI a persistent memory system. Mine projects and conversations into a searchable palace backed by ChromaDB, with 33 MCP tools, auto-save hooks, and 5 guided skills. +A Claude Code plugin that gives your AI a persistent memory system. Mine projects and conversations into a searchable palace backed by ChromaDB, with 34 MCP tools, auto-save hooks, and 5 guided skills. ## Prerequisites @@ -50,7 +50,7 @@ Set the `MEMPAL_DIR` environment variable to a directory path to automatically r ## MCP Server -The plugin automatically configures a local MCP server with 33 tools for storing, searching, and managing memories. No manual MCP setup is required -- `/mempalace:init` handles everything. +The plugin automatically configures a local MCP server with 34 tools for storing, searching, and managing memories. No manual MCP setup is required -- `/mempalace:init` handles everything. ## Full Documentation diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 52226cb..4e9e51e 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,7 @@ { "name": "mempalace", "source": "./.claude-plugin", - "description": "AI memory system — mine projects and conversations into a searchable palace. 33 MCP tools, auto-save hooks, guided setup.", + "description": "AI memory system — mine projects and conversations into a searchable palace. 34 MCP tools, auto-save hooks, guided setup.", "version": "3.4.1", "author": { "name": "milla-jovovich" diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index aa0cba6..90628f6 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "mempalace", "version": "3.4.1", - "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 33 MCP tools, auto-save hooks, and guided setup.", + "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 34 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" }, diff --git a/.codex-plugin/README.md b/.codex-plugin/README.md index 2d2478b..dab171e 100644 --- a/.codex-plugin/README.md +++ b/.codex-plugin/README.md @@ -1,6 +1,6 @@ # MemPalace - Codex CLI Plugin -Give your AI a persistent memory -- mine projects and conversations into a searchable palace backed by ChromaDB, with 33 MCP tools, auto-save hooks, and guided skills. +Give your AI a persistent memory -- mine projects and conversations into a searchable palace backed by ChromaDB, with 34 MCP tools, auto-save hooks, and guided skills. ## Prerequisites diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 462f401..1e27a28 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "mempalace", "version": "3.4.1", - "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 33 MCP tools, auto-save hooks, and guided setup.", + "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 34 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" }, @@ -27,7 +27,7 @@ "interface": { "displayName": "MemPalace", "shortDescription": "AI memory system for Codex", - "longDescription": "Give your AI a persistent memory — mine projects and conversations into a searchable palace backed by ChromaDB, with 33 MCP tools, auto-save hooks, and guided skills.", + "longDescription": "Give your AI a persistent memory — mine projects and conversations into a searchable palace backed by ChromaDB, with 34 MCP tools, auto-save hooks, and guided skills.", "developerName": "milla-jovovich", "category": "Coding", "capabilities": [ diff --git a/.cursor-plugin/README.md b/.cursor-plugin/README.md index 6ba9ba4..dcf28a7 100644 --- a/.cursor-plugin/README.md +++ b/.cursor-plugin/README.md @@ -1,6 +1,6 @@ # MemPalace Cursor Plugin -A Cursor IDE plugin that gives your agent a persistent memory system. Auto-registers the `mempalace-mcp` server (33 MCP tools), ships 5 slash commands, two model-invocable skills (setup/mining/search and a recall protocol), and an optional recall rule. +A Cursor IDE plugin that gives your agent a persistent memory system. Auto-registers the `mempalace-mcp` server (34 MCP tools), ships 5 slash commands, two model-invocable skills (setup/mining/search and a recall protocol), and an optional recall rule. > Hooks (auto-save + session-start memory recall) are shipped separately under `hooks/cursor/` so the plugin is safe to install in any Cursor workspace without touching the agent loop. See [Hooks](#hooks-optional) below. @@ -87,7 +87,7 @@ This plugin ships `mcp.json` at the plugin root, so Cursor auto-loads the `mempa } ``` -All 33 MemPalace MCP tools (`mempalace_search`, `mempalace_add_drawer`, `mempalace_diary_write`, `mempalace_check_duplicate`, `mempalace_diary_read`, …) become available to the agent immediately. No manual `~/.cursor/mcp.json` edit required. +All 34 MemPalace MCP tools (`mempalace_search`, `mempalace_add_drawer`, `mempalace_diary_write`, `mempalace_check_duplicate`, `mempalace_diary_read`, …) become available to the agent immediately. No manual `~/.cursor/mcp.json` edit required. If the server doesn't appear, confirm `mempalace-mcp` is on the user `$PATH`: diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index bd3ed05..07b12e2 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -8,7 +8,7 @@ { "name": "mempalace", "source": ".", - "description": "AI memory system — mine projects and conversations into a searchable palace. 33 MCP tools, slash commands, and a guided skill for Cursor.", + "description": "AI memory system — mine projects and conversations into a searchable palace. 34 MCP tools, slash commands, and a guided skill for Cursor.", "author": { "name": "milla-jovovich" } diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index b3be76b..c8cee46 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 33 MCP tools, slash commands, and a guided skill for Cursor.", + "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 34 MCP tools, slash commands, and a guided skill for Cursor.", "author": { "name": "milla-jovovich" }, diff --git a/README.md b/README.md index 6f74c5b..100d4bd 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,7 @@ Usage and tool reference: ## MCP server -33 MCP tools cover palace reads/writes, knowledge-graph operations, +34 MCP tools cover palace reads/writes, knowledge-graph operations, cross-wing navigation, drawer management, and agent diaries. Installation and the full tool list: [mempalaceofficial.com/reference/mcp-tools](https://mempalaceofficial.com/reference/mcp-tools.html). diff --git a/hooks/cursor/mempal_save_hook_cursor.sh b/hooks/cursor/mempal_save_hook_cursor.sh index 8a3290d..19ed26a 100755 --- a/hooks/cursor/mempal_save_hook_cursor.sh +++ b/hooks/cursor/mempal_save_hook_cursor.sh @@ -163,14 +163,12 @@ _mempal_build_followup() { import json, sys wing = sys.argv[1] if len(sys.argv) > 1 else "cursor_session" msg = ( - "MemPalace save checkpoint. " - "(1) Call mempalace_check_duplicate on the key topics, decisions, " - "and verbatim quotes from this session. " - "(2) For each non-duplicate, call mempalace_add_drawer (wing=" - + wing + ", room=, content=verbatim quote). " - "(3) Call mempalace_diary_write (agent_name=cursor-ide, wing=" - + wing + ", entry=AAAK-format summary). " - "Then stop." + "MemPalace save checkpoint. Call mempalace_checkpoint ONCE with: " + "items=[{wing: " + wing + ", room: , content: }, ...] for the key topics, decisions, and verbatim quotes from " + "this session; and diary={agent_name: cursor-ide, wing: " + wing + ", " + "entry: }. It dedups, files non-duplicates, and " + "writes the diary in one call. Then stop." ) print(json.dumps({"followup_message": msg})) ' "$WING" diff --git a/mempalace/README.md b/mempalace/README.md index ddeef06..f8f3320 100644 --- a/mempalace/README.md +++ b/mempalace/README.md @@ -16,7 +16,7 @@ The Python package that powers MemPalace. All modules, all logic. | `dialect.py` | AAAK compression — entity codes, emotion markers, 30x lossless ratio | | `knowledge_graph.py` | Temporal entity-relationship graph — SQLite, time-filtered queries, fact invalidation | | `palace_graph.py` | Room-based navigation graph — BFS traversal, tunnel detection across wings | -| `mcp_server.py` | MCP server — 33 tools, AAAK auto-teach, Palace Protocol, agent diary | +| `mcp_server.py` | MCP server — 34 tools, AAAK auto-teach, Palace Protocol, agent diary | | `onboarding.py` | Guided first-run setup — asks about people/projects, generates AAAK bootstrap + wing config | | `entity_registry.py` | Entity code registry — maps names to AAAK codes, handles ambiguous names | | `entity_detector.py` | Auto-detect people and projects from file content | diff --git a/mempalace/instructions/help.md b/mempalace/instructions/help.md index 5cb70fa..8461ed3 100644 --- a/mempalace/instructions/help.md +++ b/mempalace/instructions/help.md @@ -29,6 +29,7 @@ AI memory system. Store everything, find anything. Local, free, no API key. ### Palace (write) - mempalace_add_drawer -- Add a new memory (drawer) +- mempalace_checkpoint -- Save a whole session in one call (dedup + file + diary) - mempalace_delete_drawer -- Delete a memory (drawer) ### Knowledge Graph diff --git a/mempalace/mcp_server.py b/mempalace/mcp_server.py index 071ab61..615fb73 100644 --- a/mempalace/mcp_server.py +++ b/mempalace/mcp_server.py @@ -2937,6 +2937,52 @@ def tool_reconnect(): return {"success": False, "error": str(e)} +def tool_checkpoint(items, diary=None, dedup_threshold=0.9): + """Batch session save in a single call. + + Semantic-dedups each item, files the non-duplicates as drawers, then + writes one diary entry. Collapses the per-item ``check_duplicate`` / + ``add_drawer`` / ``diary_write`` sequence into one MCP request so the + host UI renders a single tool-call card (and keeps its spinner up for + the whole save) instead of one card per underlying call. + + ``items`` is a list of ``{"wing", "room", "content"}`` dicts. ``diary`` + is an optional ``{"agent_name", "entry", "topic"?, "wing"?}`` dict. + Reuses the existing single-item handlers so dedup/idempotency/WAL + behaviour is identical to calling them directly. + """ + out = {"added": [], "duplicates": [], "errors": []} + if not isinstance(items, list): + return {"error": "items must be a list of {wing, room, content} objects"} + for item in items: + if not isinstance(item, dict): + out["errors"].append({"item": item, "error": "item must be an object"}) + continue + wing = item.get("wing") + room = item.get("room") + content = item.get("content") + if not (wing and room and content): + out["errors"].append({"item": item, "error": "wing, room, content required"}) + continue + dup = tool_check_duplicate(content, threshold=dedup_threshold) + if dup.get("is_duplicate"): + out["duplicates"].append({"room": room, "matches": dup.get("matches", [])}) + continue + res = tool_add_drawer(wing=wing, room=room, content=content, added_by="checkpoint") + if res.get("success"): + out["added"].append(res) + else: + out["errors"].append(res) + if isinstance(diary, dict) and (diary.get("entry") or diary.get("content")): + out["diary"] = tool_diary_write( + agent_name=diary.get("agent_name", "cursor-ide"), + entry=diary.get("entry") or diary.get("content"), + topic=diary.get("topic", "session-checkpoint"), + wing=diary.get("wing", ""), + ) + return out + + # ==================== MCP PROTOCOL ==================== TOOLS = { @@ -3247,6 +3293,52 @@ TOOLS = { }, "handler": tool_add_drawer, }, + "mempalace_checkpoint": { + "description": "Save a whole session in one call: semantic-dedups each item, files non-duplicates as drawers, then writes one diary entry. Use this instead of many separate check_duplicate/add_drawer/diary_write calls — it renders as a single tool-call card in the host UI.", + "input_schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "description": "Verbatim items to file. Each is {wing, room, content} — content is the exact words, never summarized.", + "items": { + "type": "object", + "properties": { + "wing": {"type": "string", "description": "Wing (project name)"}, + "room": { + "type": "string", + "description": "Room (short topic: decisions, backend...)", + }, + "content": { + "type": "string", + "description": "Verbatim content to store", + }, + }, + "required": ["wing", "room", "content"], + }, + }, + "diary": { + "type": "object", + "description": "Optional diary entry written after filing: {agent_name, entry, topic?, wing?}. entry is AAAK-format.", + "properties": { + "agent_name": { + "type": "string", + "description": "Agent name (e.g. cursor-ide)", + }, + "entry": {"type": "string", "description": "Diary entry in AAAK format"}, + "topic": {"type": "string", "description": "Topic tag (optional)"}, + "wing": {"type": "string", "description": "Target wing (optional)"}, + }, + }, + "dedup_threshold": { + "type": "number", + "description": "Similarity threshold 0-1 for the per-item dedup check (default 0.9)", + }, + }, + "required": ["items"], + }, + "handler": tool_checkpoint, + }, "mempalace_delete_drawer": { "description": "Delete a drawer by ID. Irreversible.", "input_schema": { diff --git a/mempalace/service.py b/mempalace/service.py index b6f52a3..fc0bcca 100644 --- a/mempalace/service.py +++ b/mempalace/service.py @@ -54,6 +54,7 @@ READ_TOOLS = frozenset( WRITE_TOOLS = frozenset( { "mempalace_add_drawer", + "mempalace_checkpoint", "mempalace_delete_drawer", "mempalace_update_drawer", "mempalace_diary_write", diff --git a/skills/mempalace/SKILL.md b/skills/mempalace/SKILL.md index b318af0..22f5a64 100644 --- a/skills/mempalace/SKILL.md +++ b/skills/mempalace/SKILL.md @@ -42,6 +42,6 @@ search-before-answer so the agent reads the palace instead of guessing. ## Cursor-specific notes -- The `mempalace-mcp` server is auto-registered by this plugin. Once installed, all 33 MemPalace MCP tools (`mempalace_search`, `mempalace_add_drawer`, `mempalace_diary_write`, `mempalace_check_duplicate`, `mempalace_diary_read`, etc.) are available to the agent without any further configuration. +- The `mempalace-mcp` server is auto-registered by this plugin. Once installed, all 34 MemPalace MCP tools (`mempalace_search`, `mempalace_add_drawer`, `mempalace_diary_write`, `mempalace_check_duplicate`, `mempalace_diary_read`, etc.) are available to the agent without any further configuration. - For automatic background saving every N agent turns plus session-start memory recall, also install the Cursor hooks separately by running `hooks/cursor/install.sh --scope user` from a cloned MemPalace repo. See [`website/guide/cursor-hooks.md`](../../website/guide/cursor-hooks.md) for the full walkthrough. - The recommended `agent_name` when calling `mempalace_diary_write` from a Cursor session is `cursor-ide` (matches the precedent of `claude-code` and `codex`). diff --git a/tests/test_daemon.py b/tests/test_daemon.py index aea1828..e3868a0 100644 --- a/tests/test_daemon.py +++ b/tests/test_daemon.py @@ -170,6 +170,7 @@ def test_submit_job_uses_client_and_waits(monkeypatch, tmp_path): def test_service_tool_classification(): assert service.classify_tool("mempalace_search") == "read" assert service.classify_tool("mempalace_add_drawer") == "write" + assert service.classify_tool("mempalace_checkpoint") == "write" assert service.classify_tool("mempalace_mine") == "maintenance" assert service.classify_tool("unknown") == "unknown" diff --git a/tests/test_mcp_server.py b/tests/test_mcp_server.py index baa0398..9f5fa31 100644 --- a/tests/test_mcp_server.py +++ b/tests/test_mcp_server.py @@ -1527,6 +1527,67 @@ class TestWriteTools: assert result["vector_disabled"] is True assert result["vector_disabled_reason"] == "capacity mismatch" + def test_checkpoint_files_items_and_writes_diary(self, monkeypatch, config, palace_path, kg): + _patch_mcp_server(monkeypatch, config, kg) + _client, _col = _get_collection(palace_path, create=True) + del _client + from mempalace.mcp_server import tool_checkpoint + + result = tool_checkpoint( + items=[ + {"wing": "w", "room": "decisions", "content": "Use PostgreSQL for storage."}, + {"wing": "w", "room": "backend", "content": "Cache sessions in Redis."}, + ], + diary={"agent_name": "cursor-ide", "wing": "w", "entry": "SESSION|did.stuff|★"}, + ) + assert len(result["added"]) == 2 + assert result["duplicates"] == [] + assert result["errors"] == [] + assert all(a["success"] for a in result["added"]) + assert result["diary"]["success"] is True + + def test_checkpoint_skips_semantic_duplicates(self, monkeypatch, config, kg): + from mempalace import mcp_server + + monkeypatch.setattr( + mcp_server, + "tool_check_duplicate", + lambda content, threshold=0.9: { + "is_duplicate": True, + "matches": [{"id": "x", "similarity": 0.95}], + }, + ) + called = {"add": False} + + def _fail_add(**_kwargs): + called["add"] = True + return {"success": True} + + monkeypatch.setattr(mcp_server, "tool_add_drawer", _fail_add) + + result = mcp_server.tool_checkpoint( + items=[{"wing": "w", "room": "r", "content": "already known"}] + ) + assert result["added"] == [] + assert len(result["duplicates"]) == 1 + assert called["add"] is False + + def test_checkpoint_reports_malformed_items(self, monkeypatch, config, kg): + from mempalace import mcp_server + + monkeypatch.setattr( + mcp_server, "tool_check_duplicate", lambda *a, **k: {"is_duplicate": False} + ) + result = mcp_server.tool_checkpoint(items=[{"wing": "w", "room": "r"}, "not-a-dict"]) + assert result["added"] == [] + assert len(result["errors"]) == 2 + + def test_checkpoint_registered_in_tools(self): + from mempalace import mcp_server + + assert "mempalace_checkpoint" in mcp_server.TOOLS + assert mcp_server.TOOLS["mempalace_checkpoint"]["handler"] is mcp_server.tool_checkpoint + def test_get_drawer(self, monkeypatch, config, palace_path, seeded_collection, kg): _patch_mcp_server(monkeypatch, config, kg) from mempalace.mcp_server import tool_get_drawer diff --git a/website/guide/claude-code.md b/website/guide/claude-code.md index a3b5f61..8c6ad43 100644 --- a/website/guide/claude-code.md +++ b/website/guide/claude-code.md @@ -15,7 +15,7 @@ Restart Claude Code, then type `/skills` to verify "mempalace" appears. With the plugin installed, Claude Code automatically: - Starts the MemPalace MCP server on launch -- Has access to all 33 tools +- Has access to all 34 tools - Learns the AAAK dialect and memory protocol from the `mempalace_status` response - Searches the palace before answering questions about past work diff --git a/website/guide/mcp-integration.md b/website/guide/mcp-integration.md index 6d8c773..2ce1ed0 100644 --- a/website/guide/mcp-integration.md +++ b/website/guide/mcp-integration.md @@ -1,6 +1,6 @@ # MCP Integration -MemPalace provides 33 tools through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), giving any MCP-compatible AI full read/write access to your palace. +MemPalace provides 34 tools through the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), giving any MCP-compatible AI full read/write access to your palace. ## Setup @@ -26,7 +26,7 @@ claude mcp add mempalace -- python -m mempalace.mcp_server --palace /path/to/pal codex mcp add mempalace -- python -m mempalace.mcp_server --palace /path/to/palace ``` -Now your AI has all 33 tools available. Ask it anything: +Now your AI has all 34 tools available. Ask it anything: > *"What did we decide about auth last month?"* diff --git a/website/guide/openclaw.md b/website/guide/openclaw.md index cdfe4f5..d244e3d 100644 --- a/website/guide/openclaw.md +++ b/website/guide/openclaw.md @@ -27,7 +27,7 @@ Or by directly editing your OpenClaw configuration: ## How It Works -Once connected, OpenClaw agents receive all 33 tools along with the **Memory Protocol**—a strict behavioral guide indicating they should: +Once connected, OpenClaw agents receive all 34 tools along with the **Memory Protocol**—a strict behavioral guide indicating they should: 1. **Never guess**: Query `mempalace_search` or `mempalace_kg_query` before confidently answering. 2. **Keep an agent diary**: Maintain continuity between sessions by writing to `mempalace_diary_write`. 3. **Manage the Knowledge Graph**: Update declarative facts when things change using `mempalace_kg_add` and `mempalace_kg_invalidate`. diff --git a/website/reference/mcp-tools.md b/website/reference/mcp-tools.md index 121014a..14b77dd 100644 --- a/website/reference/mcp-tools.md +++ b/website/reference/mcp-tools.md @@ -1,6 +1,6 @@ # MCP Tools Reference -Detailed parameter schemas for all 33 MCP tools. +Detailed parameter schemas for all 34 MCP tools. ## Palace — Read Tools @@ -102,6 +102,20 @@ File verbatim content into the palace. Identical content (same deterministic dra --- +### `mempalace_checkpoint` + +Save a whole session in one call. Semantic-dedups each item, files the non-duplicates as drawers, then writes one diary entry. Use this instead of many separate `mempalace_check_duplicate` / `mempalace_add_drawer` / `mempalace_diary_write` calls — it renders as a single tool-call card in the host UI (and keeps the spinner up for the whole save). Reuses the same single-item handlers, so dedup, idempotency, and verbatim guarantees are identical. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `items` | array | **Yes** | Verbatim items to file. Each is `{ wing, room, content }` | +| `diary` | object | No | Diary entry written after filing: `{ agent_name, entry, topic?, wing? }` (`entry` is AAAK-format) | +| `dedup_threshold` | number | No | Similarity threshold 0–1 for the per-item dedup check (default 0.9) | + +**Returns:** `{ added: [...], duplicates: [...], errors: [...], diary? }` + +--- + ### `mempalace_delete_drawer` Delete a drawer by ID. Irreversible. diff --git a/website/reference/modules.md b/website/reference/modules.md index 4c12ae9..8442171 100644 --- a/website/reference/modules.md +++ b/website/reference/modules.md @@ -9,7 +9,7 @@ mempalace/ ├── README.md ← project documentation ├── mempalace/ ← core package │ ├── cli.py ← CLI entry point -│ ├── mcp_server.py ← MCP server (33 tools) +│ ├── mcp_server.py ← MCP server (34 tools) │ ├── knowledge_graph.py ← temporal entity graph │ ├── palace_graph.py ← room navigation graph │ ├── dialect.py ← AAAK compression @@ -56,7 +56,7 @@ Argparse-based CLI with subcommands: `init`, `mine`, `split`, `search`, `compres ### `mcp_server.py` — MCP Server -JSON-RPC over stdin/stdout. Implements the MCP protocol with 33 tools covering palace read/write, drawer CRUD, knowledge graph, navigation, tunnels, agent diary, and system operations. Includes the Memory Protocol and AAAK Spec in status responses. +JSON-RPC over stdin/stdout. Implements the MCP protocol with 34 tools covering palace read/write, drawer CRUD, knowledge graph, navigation, tunnels, agent diary, and system operations. Includes the Memory Protocol and AAAK Spec in status responses. ### `searcher.py` — Semantic Search