# MemPalace Hooks — Auto-Save for Terminal AI Tools These hook scripts make MemPalace save automatically. No manual "save" commands needed. This deployment ships only the **remote** hook variants — the palace runs as a Docker container on a server (e.g. Unraid), and hooks `curl` the active session transcript to the server's `/ingest/transcript` endpoint over HTTPS with bearer auth. Server-side, the existing `mine_convos` pipeline handles entity detection, room assignment, dedup, and idempotency. See [`deploy/unraid/README.md`](../deploy/unraid/README.md) for the server side. ## What They Do | Hook | When It Fires | What Happens | |---|---|---| | **Save Hook** (`mempal_save_hook_remote.sh`) | Every 15 user messages (configurable via `SAVE_INTERVAL`) | Backgrounded `curl` POSTs the active transcript. Returns immediately so the AI doesn't stall. Idempotent — failed retries are safe. | | **PreCompact Hook** (`mempal_precompact_hook_remote.sh`) | Right before context compaction | Synchronous `curl` POST. Blocks until the upload completes (or the hook timeout fires) so memory is durable before context shrinks. | **Two-layer capture.** The save hook ships the JSONL transcript directly to the server (capturing raw tool output — Bash results, search findings, build errors), where the miner files it verbatim into the palace. Tool output gets stored even if the AI summarizes instead of quoting. ## Env-var contract The scripts read all configuration from environment variables. There is no script-level config to edit; the same script works against any number of machines. | Variable | Required | Purpose | |---|---|---| | `MEMPAL_REMOTE_URL` | yes | Base URL of the MemPalace server, e.g. `https://unraid.local:8443`. | | `MEMPAL_REMOTE_TOKEN` | yes | Bearer token shared with the server's `MEMPAL_TOKEN`. | | `MEMPAL_REMOTE_INSECURE` | no | Set to `1` to skip TLS verification. Use only when the server uses Caddy's `tls internal` self-signed cert and the client hasn't trusted the root CA. | | `MEMPAL_REMOTE_WING` | no | Force a specific wing for this client's transcripts. Default: server derives wing from the session id. | | `SAVE_INTERVAL` | no | Override the default of 15 user messages. | | `MEMPAL_PYTHON` | no | Path to a Python 3 interpreter. Only needs `json` + `sys` from stdlib — mempalace does not need to be installed in it. Used to parse the hook's stdin JSON. | Set these persistently: **PowerShell (Windows):** ```powershell [Environment]::SetEnvironmentVariable("MEMPAL_REMOTE_URL", "https://unraid.local:8443", "User") [Environment]::SetEnvironmentVariable("MEMPAL_REMOTE_TOKEN", "", "User") [Environment]::SetEnvironmentVariable("MEMPAL_REMOTE_INSECURE", "1", "User") # if self-signed ``` **Bash/Zsh:** add the same exports to `~/.zshrc` / `~/.bashrc`. If `MEMPAL_REMOTE_URL` or `MEMPAL_REMOTE_TOKEN` is unset, the scripts no-op and log a one-liner — they never block the AI from stopping. Safe to install on a machine that doesn't have a remote configured yet. ## Install — Claude Code Make the scripts executable: ```bash chmod +x hooks/mempal_save_hook_remote.sh hooks/mempal_precompact_hook_remote.sh ``` Add to `.claude/settings.local.json`: ```json { "hooks": { "Stop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "/absolute/path/to/hooks/mempal_save_hook_remote.sh", "timeout": 30 }] }], "PreCompact": [{ "hooks": [{ "type": "command", "command": "/absolute/path/to/hooks/mempal_precompact_hook_remote.sh", "timeout": 60 }] }] } } ``` ## Install — Codex CLI (OpenAI) Add to `.codex/hooks.json`: ```json { "Stop": [{ "type": "command", "command": "/absolute/path/to/hooks/mempal_save_hook_remote.sh", "timeout": 30 }], "PreCompact": [{ "type": "command", "command": "/absolute/path/to/hooks/mempal_precompact_hook_remote.sh", "timeout": 60 }] } ``` ## How it works ### Save Hook (Stop event) ``` User sends message → AI responds → Claude Code fires Stop hook ↓ Hook counts user messages in JSONL transcript ↓ ┌─── < SAVE_INTERVAL since last save ──→ echo "{}" (let AI stop) │ └─── ≥ SAVE_INTERVAL since last save ↓ Background curl POST → server /ingest/transcript ↓ Hook returns {} immediately (AI stops normally) ↓ Server-side miner runs in background, files drawers ``` ### PreCompact Hook ``` Context window getting full → Claude Code fires PreCompact ↓ Synchronous curl POST → server /ingest/transcript ↓ Wait for 200 OK (or hook timeout) ↓ echo "{}" → Compaction proceeds ``` Synchronous on PreCompact is intentional — this is the safety net before context shrinks. The Claude Code hook timeout (set in `settings.local.json`) bounds how long we'll wait. ## Debugging ```bash tail -f ~/.mempalace/hook_state/hook.log ``` Example: ``` [14:30:15] Session abc123: 12 exchanges, 12 since last save [14:35:22] Session abc123: 15 exchanges, 15 since last save [14:35:22] ingest ok [14:50:18] PRE-COMPACT triggered for session abc123 [14:50:19] PRE-COMPACT ingest ok ``` A 401 response means the bearer token is wrong. A connection error means the URL/cert is wrong (or the server is down). All curl output goes to the same log. ## Known limitations **Hooks require session restart after install.** Claude Code loads hooks from `settings.json` at session start only. If you edit hook config mid-session, restart Claude Code to pick up changes. **Python interpreter resolution.** The scripts parse hook stdin JSON with `python3`. When Claude Code is launched from a GUI on macOS (Spotlight, dock, `open -a`), its `PATH` is the minimal `/usr/bin:/bin:/usr/sbin:/sbin` inherited from `launchd` rather than your shell PATH. If `python3` isn't there, set `MEMPAL_PYTHON` to a known-good interpreter: ```bash export MEMPAL_PYTHON="/usr/bin/python3" # or: export MEMPAL_PYTHON="$HOME/.venvs/x/bin/python" ``` Resolution priority: `$MEMPAL_PYTHON` → `$(command -v python3)` → bare `python3`. The interpreter only needs `json` and `sys` — mempalace itself does not need to be installed. **`MineAlreadyRunning` collisions.** If two clients ingest simultaneously, the second one's request returns 500 because the server-side `mine_lock` is held. The save hook is idempotent — the next save catches up. If you see this constantly in the log, raise `SAVE_INTERVAL` on the chattier client. ## Backfilling past conversations The hooks only capture sessions going forward. To mine **past** sessions into the remote palace, loop `curl` over them: ```bash # Claude Code sessions for f in ~/.claude/projects/**/*.jsonl; do curl -k -X POST \ -H "Authorization: Bearer $MEMPAL_REMOTE_TOKEN" \ -H "X-Session-Id: $(basename "$f" .jsonl)" \ --data-binary @"$f" \ "$MEMPAL_REMOTE_URL/ingest/transcript" done # Codex CLI sessions for f in ~/.codex/sessions/**/*.jsonl; do curl -k -X POST \ -H "Authorization: Bearer $MEMPAL_REMOTE_TOKEN" \ -H "X-Session-Id: $(basename "$f" .jsonl)" \ --data-binary @"$f" \ "$MEMPAL_REMOTE_URL/ingest/transcript" done ``` The server-side miner is idempotent — re-uploading the same transcript won't double-file. Drop `-k` once Caddy's root CA is trusted on the client. ## Cost **Zero extra tokens.** The hooks save in the background — the AI doesn't need to write anything in the chat window. All filing happens server-side after the upload returns.