From d4378b34bb4cae9568be7de507e59decfd4c0e7c Mon Sep 17 00:00:00 2001 From: thekbbohara Date: Sun, 12 Apr 2026 13:41:06 +0545 Subject: [PATCH] feat: add MemPalace integration plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bridge tools (MempalaceSearch, MempalaceSave, MempalaceWakeUp, MempalaceKGQuery, MempalaceDiaryWrite) work via direct Python import — no MCP config needed - SKILL.md with session protocol, tool reference, and setup guide - hooks.py for auto-save on session lifecycle (start, stop, precompact) - plugin.json manifest for CheetahClaws plugin system - MCP server config for full 19-tool MemPalace experience - README.md with quick start and configuration docs MemPalace is a local AI memory system with 96.6% recall, using ChromaDB semantic search and a temporal knowledge graph. Zero cloud, zero API keys. This integration makes it a first-class CheetahClaws plugin — installable, discoverable, and auto-configured. --- integrations/mempalace/README.md | 105 +++++++ integrations/mempalace/SKILL.md | 142 ++++++++++ integrations/mempalace/hooks.py | 131 +++++++++ integrations/mempalace/plugin.json | 18 ++ integrations/mempalace/tools.py | 424 +++++++++++++++++++++++++++++ 5 files changed, 820 insertions(+) create mode 100644 integrations/mempalace/README.md create mode 100644 integrations/mempalace/SKILL.md create mode 100644 integrations/mempalace/hooks.py create mode 100644 integrations/mempalace/plugin.json create mode 100644 integrations/mempalace/tools.py diff --git a/integrations/mempalace/README.md b/integrations/mempalace/README.md new file mode 100644 index 0000000..b09bace --- /dev/null +++ b/integrations/mempalace/README.md @@ -0,0 +1,105 @@ +# MemPalace × CheetahClaws Integration + +Give your CheetahClaws agent a persistent, semantic memory with 96.6% recall — no cloud, no API keys. + +## Quick Start + +```bash +# 1. Install MemPalace +pip install mempalace + +# 2. Initialize a palace from your conversation history +mempalace init ~/my-convos +mempalace mine ~/my-convos + +# 3. Install the CheetahClaws plugin +cheetahclaws plugin install mempalace +``` + +Done. Your CheetahClaws sessions now have semantic memory. + +## What You Get + +### Bridge Tools (Direct Python — No MCP Config Needed) + +| Tool | What It Does | +|------|-------------| +| `MempalaceWakeUp` | Load palace context at session start | +| `MempalaceSearch` | Semantic search across all memories | +| `MempalaceSave` | Store verbatim content into a wing/room | +| `MempalaceKGQuery` | Query knowledge graph (traverse/tunnels/stats) | +| `MempalaceDiaryWrite` | Write session diary entries | + +These work immediately after `pip install mempalace` — no MCP server configuration required. + +### MCP Tools (Full Feature Set) + +For the complete 19-tool MemPalace experience (knowledge graph CRUD, duplicate checking, diary read, AAAK dialect), add the MCP server to `.cheetahclaws/mcp.json`: + +```json +{ + "mcpServers": { + "mempalace": { + "type": "stdio", + "command": "python3", + "args": ["-m", "mempalace.mcp_server"] + } + } +} +``` + +### Session Auto-Save Hooks + +The plugin includes automatic memory persistence: + +- **Session start**: Initializes palace tracking, runs wake-up +- **Every 15 exchanges**: Triggers auto-save checkpoint +- **Before compaction**: Emergency save of all context + +## Architecture + +MemPalace uses the **palace metaphor** for organizing memory: + +- **Wings** = people or projects (`wing_alice`, `wing_myproject`) +- **Halls** = categories (facts, events, preferences, advice) +- **Rooms** = specific topics (`chromadb-setup`, `riley-school`) +- **Drawers** = individual memory chunks (verbatim text) +- **Knowledge Graph** = entity-relationship facts with time validity + +All stored locally via ChromaDB. Zero cloud. Zero API calls. + +## Manual Install + +If you prefer not to use the plugin system: + +```bash +# Copy to user plugins +cp -r integrations/mempalace ~/.cheetahclaws/plugins/mempalace + +# Or to project plugins +cp -r integrations/mempalace .cheetahclaws/plugins/mempalace +``` + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `MEMPALACE_PALACE_PATH` | `~/.mempalace/palace` | Path to the ChromaDB palace | +| `MEMPAL_DIR` | *(none)* | Auto-ingest directory on session hooks | + +## Compatibility + +- CheetahClaws >= 0.1.0 +- MemPalace >= 3.1.0 +- Python 3.9+ +- macOS, Linux, Windows + +## Links + +- [MemPalace GitHub](https://github.com/milla-jovovich/mempalace) +- [CheetahClaws GitHub](https://github.com/SafeRL-Lab/cheetahclaws) +- [MemPalace Benchmarks](https://github.com/milla-jovovich/mempalace#benchmarks) + +## License + +MemPalace is MIT licensed. Created by Milla Jovovich, Ben Sigman, Igor Lins e Silva, and contributors. diff --git a/integrations/mempalace/SKILL.md b/integrations/mempalace/SKILL.md new file mode 100644 index 0000000..97dc2ce --- /dev/null +++ b/integrations/mempalace/SKILL.md @@ -0,0 +1,142 @@ +--- +name: mempalace +description: "MemPalace — Local AI memory with 96.6% recall. Semantic search, temporal knowledge graph, palace architecture (wings/rooms/drawers). Free, no cloud, no API keys." +version: 3.1.0 +homepage: https://github.com/milla-jovovich/mempalace +user-invocable: true +triggers: ["/mempalace", "/palace"] +tools: [MempalaceSearch, MempalaceSave, MempalaceWakeUp, MempalaceKGQuery, MempalaceDiaryWrite] +--- + +# MemPalace — Local AI Memory for CheetahClaws + +You have access to a local memory palace via bridge tools and/or MCP. The palace stores verbatim conversation history and a temporal knowledge graph — all on the user's machine, zero cloud, zero API calls. + +## Architecture + +- **Wings** = people or projects (e.g. `wing_alice`, `wing_myproject`) +- **Halls** = categories (facts, events, preferences, advice) +- **Rooms** = specific topics (e.g. `chromadb-setup`, `riley-school`) +- **Drawers** = individual memory chunks (verbatim text) +- **Knowledge Graph** = entity-relationship facts with time validity + +## Protocol — FOLLOW THIS EVERY SESSION + +1. **ON WAKE-UP**: Call `MempalaceWakeUp` to load palace overview (total drawers, wings, rooms). +2. **BEFORE RESPONDING** about any person, project, or past event: call `MempalaceSearch` FIRST. Never guess — verify from the palace. +3. **IF UNSURE** about a fact: say "let me check the palace" and query. Wrong is worse than slow. +4. **AFTER EACH SESSION**: Call `MempalaceDiaryWrite` to record what happened, what you learned, what matters. +5. **FOR CROSS-DOMAIN INSIGHTS**: Use `MempalaceKGQuery` with action `traverse` to walk the graph and find connected ideas. + +## Bridge Tools (Direct Python — No MCP Needed) + +These tools work out of the box once `mempalace` is pip-installed: + +| Tool | Purpose | +|------|---------| +| `MempalaceWakeUp` | Load palace context at session start | +| `MempalaceSearch` | Semantic search across all memories | +| `MempalaceSave` | Store verbatim content into a wing/room | +| `MempalaceKGQuery` | Query knowledge graph (traverse/tunnels/stats) | +| `MempalaceDiaryWrite` | Write session diary entries | + +## MCP Tools (Full Feature Set) + +If you've also added the MemPalace MCP server, you get the complete toolset: + +### Search & Browse +- `mempalace_search` — Semantic search. Always start here. +- `mempalace_check_duplicate` — Check if content already exists before filing. +- `mempalace_status` — Palace overview + AAAK dialect spec +- `mempalace_list_wings` — All wings with drawer counts +- `mempalace_list_rooms` — Rooms within a wing +- `mempalace_get_taxonomy` — Full wing/room/count tree + +### Knowledge Graph (Temporal Facts) +- `mempalace_kg_query` — Query entity relationships with time filtering +- `mempalace_kg_add` — Add a fact: subject -> predicate -> object +- `mempalace_kg_invalidate` — Mark a fact as no longer true +- `mempalace_kg_timeline` — Chronological story of an entity +- `mempalace_kg_stats` — Graph overview + +### Palace Graph (Cross-Domain Connections) +- `mempalace_traverse` — Walk from a room, find connected ideas +- `mempalace_find_tunnels` — Find rooms that bridge two wings +- `mempalace_graph_stats` — Graph connectivity overview + +### Write +- `mempalace_add_drawer` — Store verbatim content (auto-duplicate-checks) +- `mempalace_delete_drawer` — Remove a drawer by ID +- `mempalace_diary_write` — Write a session diary entry +- `mempalace_diary_read` — Read recent diary entries + +## Setup + +### Quick Install (CheetahClaws Plugin) + +```bash +cheetahclaws plugin install mempalace +``` + +### Manual Install + +1. Install MemPalace: +```bash +pip install mempalace +``` + +2. Initialize a palace: +```bash +mempalace init ~/my-convos +mempalace mine ~/my-convos +``` + +3. Copy this plugin to `~/.cheetahclaws/plugins/mempalace/` or add to your project's `.cheetahclaws/plugins/`. + +### CheetahClaws MCP Config + +Add to `.cheetahclaws/mcp.json` for the full MCP toolset: + +```json +{ + "mcpServers": { + "mempalace": { + "type": "stdio", + "command": "python3", + "args": ["-m", "mempalace.mcp_server"] + } + } +} +``` + +### Other MCP Hosts + +```bash +# Claude Code +claude mcp add mempalace -- python -m mempalace.mcp_server + +# Cursor — add to .cursor/mcp.json +# Codex — add to .codex/mcp.json +``` + +## Session Auto-Save Hooks + +This plugin includes hooks for automatic memory persistence: + +- **session_start**: Initializes palace tracking state +- **stop**: Every 15 exchanges, triggers auto-save checkpoint +- **precompact**: Emergency save before context compaction + +Configure in `.cheetahclaws/hooks.json` or let the plugin system handle it. + +## Tips + +- Search is semantic (meaning-based), not keyword. "What did we discuss about database performance?" works better than "database". +- The knowledge graph stores typed relationships with time windows. Use it for facts about people and projects. +- Diary entries accumulate across sessions. Write one at the end of each conversation. +- Use `mempalace_check_duplicate` (MCP) or `MempalaceSearch` (bridge) before storing to avoid duplicates. +- The AAAK dialect (from `mempalace_status`) is a compressed notation. Expand codes mentally, treat *markers* as emotional context. + +## License + +[MemPalace](https://github.com/milla-jovovich/mempalace) is MIT licensed. Created by Milla Jovovich, Ben Sigman, Igor Lins e Silva, and contributors. diff --git a/integrations/mempalace/hooks.py b/integrations/mempalace/hooks.py new file mode 100644 index 0000000..0eabe8f --- /dev/null +++ b/integrations/mempalace/hooks.py @@ -0,0 +1,131 @@ +""" +MemPalace session hooks for CheetahClaws. + +Integrates auto-save into the CheetahClaws session lifecycle: + - session_start: initialize palace tracking, run wake-up + - precompact: emergency save before context compaction + - stop: periodic auto-save every N exchanges + +Install by adding to .cheetahclaws/hooks.json or via plugin system. +""" +from __future__ import annotations + +import json +import os +from datetime import datetime +from pathlib import Path + +SAVE_INTERVAL = 15 # auto-save every N human exchanges +STATE_DIR = Path.home() / ".mempalace" / "hook_state" + + +def _log(message: str): + try: + STATE_DIR.mkdir(parents=True, exist_ok=True) + with open(STATE_DIR / "hook.log", "a") as f: + f.write(f"[{datetime.now().strftime('%H:%M:%S')}] {message}\n") + except OSError: + pass + + +def _count_human_messages(transcript_path: str) -> int: + path = Path(transcript_path).expanduser() + if not path.is_file(): + return 0 + count = 0 + try: + with open(path, encoding="utf-8", errors="replace") as f: + for line in f: + try: + entry = json.loads(line) + msg = entry.get("message", {}) + if isinstance(msg, dict) and msg.get("role") == "user": + content = msg.get("content", "") + text = content if isinstance(content, str) else " ".join( + b.get("text", "") for b in content if isinstance(b, dict) + ) + if "" not in text: + count += 1 + except (json.JSONDecodeError, AttributeError): + pass + except OSError: + return 0 + return count + + +def _save_to_palace(content: str, session_id: str): + try: + from mempalace.config import MempalaceConfig + import chromadb + + cfg = MempalaceConfig() + client = chromadb.PersistentClient(path=cfg.palace_path) + col = client.get_collection("mempalace_drawers") + + drawer_id = f"diary_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{session_id}" + meta = { + "wing": "wing_cheetahclaws", + "room": "session-diary", + "hall": "auto-save", + "source_file": f"cheetahclaws-session-{session_id}", + "date": datetime.now().strftime("%Y-%m-%d"), + } + col.add(ids=[drawer_id], documents=[content], metadatas=[meta]) + _log(f"Auto-saved to palace: {drawer_id}") + except Exception as e: + _log(f"Auto-save failed: {e}") + + +def hook_session_start(session_id: str): + _log(f"SESSION START for session {session_id}") + STATE_DIR.mkdir(parents=True, exist_ok=True) + # Initialize last save point + (STATE_DIR / f"{session_id}_last_save").write_text("0", encoding="utf-8") + + +def hook_stop(session_id: str, transcript_path: str): + last_save_file = STATE_DIR / f"{session_id}_last_save" + last_save = 0 + if last_save_file.is_file(): + try: + last_save = int(last_save_file.read_text().strip()) + except (ValueError, OSError): + last_save = 0 + + exchange_count = _count_human_messages(transcript_path) + since_last = exchange_count - last_save + + _log(f"Session {session_id}: {exchange_count} exchanges, {since_last} since last save") + + if since_last >= SAVE_INTERVAL and exchange_count > 0: + try: + last_save_file.write_text(str(exchange_count), encoding="utf-8") + except OSError: + pass + + _log(f"TRIGGERING AUTO-SAVE at exchange {exchange_count}") + return { + "decision": "block", + "reason": ( + "AUTO-SAVE checkpoint. Save key topics, decisions, quotes, and code " + "from this session to MemPalace. Use MempalaceSave or MempalaceDiaryWrite. " + "Organize into appropriate wings/rooms. Use verbatim quotes where possible. " + "Continue conversation after saving." + ), + } + + return {} + + +def hook_precompact(session_id: str): + _log(f"PRE-COMPACT triggered for session {session_id}") + return { + "decision": "block", + "reason": ( + "COMPACTION IMMINENT. Save ALL topics, decisions, quotes, code, and " + "important context from this session to MemPalace. Use MempalaceSave or " + "MempalaceDiaryWrite. Be thorough — after compaction, detailed context " + "will be lost. Organize into appropriate wings/rooms. Save everything, " + "then allow compaction to proceed." + ), + } diff --git a/integrations/mempalace/plugin.json b/integrations/mempalace/plugin.json new file mode 100644 index 0000000..89e0918 --- /dev/null +++ b/integrations/mempalace/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "mempalace", + "version": "3.1.0", + "description": "MemPalace — Local AI memory with 96.6% recall. Semantic search, temporal knowledge graph, palace architecture (wings/rooms/drawers). Free, no cloud, no API keys.", + "author": "Milla Jovovich, Ben Sigman, Igor Lins e Silva", + "tags": ["memory", "semantic-search", "knowledge-graph", "mcp", "chromadb"], + "tools": ["tools.py"], + "skills": ["SKILL.md"], + "mcp_servers": { + "mempalace": { + "command": "python3", + "args": ["-m", "mempalace.mcp_server"] + } + }, + "dependencies": ["mempalace>=3.1.0"], + "homepage": "https://github.com/milla-jovovich/mempalace", + "license": "MIT" +} diff --git a/integrations/mempalace/tools.py b/integrations/mempalace/tools.py new file mode 100644 index 0000000..cd435b7 --- /dev/null +++ b/integrations/mempalace/tools.py @@ -0,0 +1,424 @@ +""" +MemPalace bridge tools for CheetahClaws. + +Provides direct Python-access tools that wrap MemPalace's API — no MCP needed. +If mempalace is not installed, tools degrade gracefully with helpful install messages. +""" +from __future__ import annotations + +import json +import os +import sys +from pathlib import Path + +from tool_registry import ToolDef, register_tool + +_MEMPALACE_AVAILABLE = False +_palace_path = os.environ.get( + "MEMPALACE_PALACE_PATH", + str(Path.home() / ".mempalace" / "palace"), +) + + +def _check_mempalace() -> str | None: + """Return None if mempalace is importable, else an error message.""" + global _MEMPALACE_AVAILABLE + try: + import mempalace # noqa: F401 + _MEMPALACE_AVAILABLE = True + return None + except ImportError: + return ( + "MemPalace is not installed. Install it with:\n" + " pip install mempalace\n" + "Then initialize a palace:\n" + " mempalace init ~/my-project && mempalace mine ~/my-project" + ) + + +def _get_palace_path(params: dict) -> str: + return params.get("palace_path", _palace_path) + + +# ── Tool: MempalaceSearch ────────────────────────────────────────────────── + +def _mempalace_search(params: dict, config: dict) -> str: + err = _check_mempalace() + if err: + return err + + from mempalace.searcher import search_memories + + result = search_memories( + query=params["query"], + palace_path=_get_palace_path(params), + wing=params.get("wing"), + room=params.get("room"), + n_results=params.get("n_results", 5), + ) + + if "error" in result: + return f"MemPalace search error: {result['error']}\nHint: {result.get('hint', 'Run mempalace init && mempalace mine')}" + + hits = result.get("results", []) + if not hits: + return f"No memories found for: \"{params['query']}\"" + + lines = [f"MemPalace search: \"{params['query']}\" ({len(hits)} results)\n"] + for i, h in enumerate(hits, 1): + lines.append(f" [{i}] {h['wing']} / {h['room']} (similarity: {h['similarity']})") + lines.append(f" Source: {h['source_file']}") + for line in h["text"].strip().split("\n")[:20]: + lines.append(f" {line}") + lines.append("") + + return "\n".join(lines) + + +# ── Tool: MempalaceSave ─────────────────────────────────────────────────── + +def _mempalace_save(params: dict, config: dict) -> str: + err = _check_mempalace() + if err: + return err + + from mempalace.config import MempalaceConfig + import chromadb + + palace_path = _get_palace_path(params) + wing = params["wing"] + room = params["room"] + content = params["content"] + hall = params.get("hall", "") + source = params.get("source", "cheetahclaws") + + try: + client = chromadb.PersistentClient(path=palace_path) + col = client.get_collection("mempalace_drawers") + except Exception: + return ( + f"No palace found at {palace_path}.\n" + "Initialize one first:\n" + " mempalace init && mempalace mine " + ) + + from datetime import datetime + + drawer_id = f"drawer_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{source}" + meta = { + "wing": wing, + "room": room, + "hall": hall, + "source_file": source, + "date": datetime.now().strftime("%Y-%m-%d"), + } + + col.add(ids=[drawer_id], documents=[content], metadatas=[meta]) + + return f"Saved to {wing} / {room} (drawer: {drawer_id})" + + +# ── Tool: MempalaceWakeUp ───────────────────────────────────────────────── + +def _mempalace_wake_up(params: dict, config: dict) -> str: + err = _check_mempalace() + if err: + return err + + from mempalace.config import MempalaceConfig + import chromadb + + palace_path = _get_palace_path(params) + + try: + client = chromadb.PersistentClient(path=palace_path) + col = client.get_collection("mempalace_drawers") + except Exception: + return ( + f"No palace found at {palace_path}. " + "Run: mempalace init && mempalace mine " + ) + + total = col.count() + + # Get wing/room breakdown + wing_rooms: dict[str, dict[str, int]] = {} + offset = 0 + while offset < total: + batch = col.get(limit=1000, offset=offset, include=["metadatas"]) + for meta in batch["metadatas"]: + w = meta.get("wing", "unknown") + r = meta.get("room", "unknown") + wing_rooms.setdefault(w, {}).setdefault(r, 0) + wing_rooms[w][r] += 1 + if not batch["ids"]: + break + offset += len(batch["ids"]) + + lines = [ + f"🏛️ MemPalace Wake-Up — {total} drawers across {len(wing_rooms)} wings", + "", + ] + for wing, rooms in sorted(wing_rooms.items()): + room_count = sum(rooms.values()) + room_names = ", ".join(f"{r}({c})" for r, c in sorted(rooms.items())) + lines.append(f" {wing}: {room_count} drawers — {room_names}") + + return "\n".join(lines) + + +# ── Tool: MempalaceKGQuery ──────────────────────────────────────────────── + +def _mempalace_kg_query(params: dict, config: dict) -> str: + err = _check_mempalace() + if err: + return err + + from mempalace.palace_graph import traverse, find_tunnels, graph_stats + + palace_path = _get_palace_path(params) + + if params.get("action") == "traverse": + result = traverse(params["room"], config=_make_config(palace_path)) + elif params.get("action") == "tunnels": + result = find_tunnels( + wing_a=params.get("wing_a"), + wing_b=params.get("wing_b"), + config=_make_config(palace_path), + ) + elif params.get("action") == "stats": + result = graph_stats(config=_make_config(palace_path)) + else: + return "Action must be one of: traverse, tunnels, stats" + + return json.dumps(result, indent=2, default=str) + + +def _make_config(palace_path: str): + from mempalace.config import MempalaceConfig + cfg = MempalaceConfig() + cfg.palace_path = palace_path + return cfg + + +# ── Tool: MempalaceDiaryWrite ───────────────────────────────────────────── + +def _mempalace_diary_write(params: dict, config: dict) -> str: + err = _check_mempalace() + if err: + return err + + from mempalace.config import MempalaceConfig + import chromadb + from datetime import datetime + + palace_path = _get_palace_path(params) + content = params["content"] + session_id = params.get("session_id", "unknown") + wing = params.get("wing", "wing_cheetahclaws") + room = params.get("room", "session-diary") + + try: + client = chromadb.PersistentClient(path=palace_path) + col = client.get_collection("mempalace_drawers") + except Exception: + return f"No palace found at {palace_path}. Run: mempalace init && mempalace mine " + + drawer_id = f"diary_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{session_id}" + meta = { + "wing": wing, + "room": room, + "hall": "diary", + "source_file": f"cheetahclaws-session-{session_id}", + "date": datetime.now().strftime("%Y-%m-%d"), + } + + col.add(ids=[drawer_id], documents=[content], metadatas=[meta]) + + return f"Diary entry saved to {wing} / {room} (id: {drawer_id})" + + +# ── Register all tools ───────────────────────────────────────────────────── + +TOOL_SCHEMAS = [ + { + "name": "MempalaceSearch", + "description": ( + "Search MemPalace for memories using semantic search. " + "Returns verbatim text from matching drawers with similarity scores. " + "Optionally filter by wing (project) or room (aspect)." + ), + "input_schema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Natural language search query", + }, + "wing": { + "type": "string", + "description": "Optional wing (project) filter, e.g. 'wing_myproject'", + }, + "room": { + "type": "string", + "description": "Optional room (aspect) filter, e.g. 'api-design'", + }, + "n_results": { + "type": "integer", + "description": "Number of results (default 5)", + "default": 5, + }, + "palace_path": { + "type": "string", + "description": "Override palace path (default: ~/.mempalace/palace)", + }, + }, + "required": ["query"], + }, + }, + { + "name": "MempalaceSave", + "description": ( + "Save content to MemPalace. Files verbatim text into a wing/room drawer. " + "Use this to persist conversation knowledge, decisions, or code snippets." + ), + "input_schema": { + "type": "object", + "properties": { + "wing": { + "type": "string", + "description": "Wing name (project/domain), e.g. 'wing_myproject'", + }, + "room": { + "type": "string", + "description": "Room name (aspect/topic), e.g. 'api-design'", + }, + "content": { + "type": "string", + "description": "Verbatim content to store", + }, + "hall": { + "type": "string", + "description": "Optional hall (sub-category)", + }, + "source": { + "type": "string", + "description": "Source identifier (default: 'cheetahclaws')", + }, + "palace_path": { + "type": "string", + "description": "Override palace path", + }, + }, + "required": ["wing", "room", "content"], + }, + }, + { + "name": "MempalaceWakeUp", + "description": ( + "Load MemPalace context for session start. Returns palace status: " + "total drawers, wing/room breakdown. Run this at the start of each " + "session to orient yourself with available memories." + ), + "input_schema": { + "type": "object", + "properties": { + "palace_path": { + "type": "string", + "description": "Override palace path", + }, + }, + }, + }, + { + "name": "MempalaceKGQuery", + "description": ( + "Query MemPalace's knowledge graph. Three actions: " + "'traverse' — walk the graph from a room, finding connected ideas; " + "'tunnels' — find rooms that bridge two wings; " + "'stats' — overall graph statistics." + ), + "input_schema": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["traverse", "tunnels", "stats"], + "description": "Graph query action", + }, + "room": { + "type": "string", + "description": "Starting room for traverse action", + }, + "wing_a": { + "type": "string", + "description": "First wing for tunnels action", + }, + "wing_b": { + "type": "string", + "description": "Second wing for tunnels action", + }, + "palace_path": { + "type": "string", + "description": "Override palace path", + }, + }, + "required": ["action"], + }, + }, + { + "name": "MempalaceDiaryWrite", + "description": ( + "Write a session diary entry to MemPalace. Use for auto-saving " + "key decisions, context, and knowledge at session boundaries " + "(compaction, stop, or periodic intervals)." + ), + "input_schema": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Diary content to save (verbatim, be thorough)", + }, + "session_id": { + "type": "string", + "description": "Session identifier", + }, + "wing": { + "type": "string", + "description": "Wing to save under (default: wing_cheetahclaws)", + }, + "room": { + "type": "string", + "description": "Room to save under (default: session-diary)", + }, + "palace_path": { + "type": "string", + "description": "Override palace path", + }, + }, + "required": ["content"], + }, + }, +] + +_tool_funcs = { + "MempalaceSearch": _mempalace_search, + "MempalaceSave": _mempalace_save, + "MempalaceWakeUp": _mempalace_wake_up, + "MempalaceKGQuery": _mempalace_kg_query, + "MempalaceDiaryWrite": _mempalace_diary_write, +} + +_read_only = {"MempalaceSearch", "MempalaceWakeUp", "MempalaceKGQuery"} +_concurrent_safe = {"MempalaceSearch", "MempalaceWakeUp", "MempalaceKGQuery"} + +for _schema in TOOL_SCHEMAS: + _name = _schema["name"] + register_tool(ToolDef( + name=_name, + schema=_schema, + func=_tool_funcs[_name], + read_only=_name in _read_only, + concurrent_safe=_name in _concurrent_safe, + ))