# pi ↔ MemPalace extension The canonical source of `~/.pi/agent/extensions/mempalace.ts` — the bridge that wires the [MemPalace](https://github.com/MemPalace/mempalace) MCP server into the [pi coding-agent](https://github.com/mariozechner/pi-coding-agent) harness. `install.sh` at the repo root symlinks `mempalace.ts` from this directory into `~/.pi/agent/extensions/` so the live file on every machine tracks version control. Works on macOS and Linux (the extension itself is plain Node / TypeScript; the symlink is a POSIX `ln -s`). --- ## What it does 1. **Spawns `mempalace-mcp`** as a subprocess and does the MCP stdio JSON-RPC handshake (`initialize` + `notifications/initialized` + `tools/list`). 2. **Registers each MCP tool** as a pi tool with its real `inputSchema` passed through via `Type.Unsafe(...)` (see gotcha below). 3. **Wake-up auto-injection** (`before_agent_start`, one-shot per fresh session): calls `mempalace_status` + `mempalace_diary_read` and injects the result as a `mempalace-wakeup` system message so the agent orients itself the way `~/.agents/skills/mempalace/SKILL.md` describes. Skipped on resume/fork (context is already in the thread). 4. **Manual wind-down** via a `/mempalace-diary [topic]` slash command: sends a prompt asking the LLM to call `mempalace_diary_write` with an AAAK-formatted entry summarizing the session. Not fully auto because pi sessions are typically short/tactical and `session_shutdown` fires too late to drive another LLM turn. ## Fail-soft If `mempalace-mcp` can't be spawned (PATH missing, binary crashes at startup, …) the extension logs to stderr and returns early. pi keeps working without palace tools rather than refusing to start. ## Identity `agent_name` for diary calls comes from `$MEMPALACE_AGENT_NAME`, defaulting to `"pi"`. First diary write against that identity creates `wing_` in the palace. Set the env var if you want to run pi under a distinct identity on a given machine (e.g. `pi-laptop` vs `pi-server`). ## Debugging - `MEMPALACE_EXT_DEBUG=1` — surface `mempalace-mcp` stderr into pi's stderr. Without this, stderr is drained silently so a misbehaving server doesn't flood the TUI. - If a tool call fails with a generic "Internal tool error", spawn `mempalace-mcp` manually with raw JSON-RPC on stdin to read the server-side error — much faster than guessing. ## The `Type.Unsafe` gotcha Earlier versions of this extension registered every MCP tool with `parameters: Type.Object({}, { additionalProperties: true })`, which discarded each tool's real `inputSchema`. The LLM then saw no parameter names and had to guess, leading to bugs like `mempalace_diary_read` being called with `agent=` instead of the required `agent_name=` and crashing the Python server with `TypeError: missing 1 required positional argument`. The fix (≈ lines 160-170) is to wrap the incoming JSON Schema with `Type.Unsafe<...>(tool.inputSchema)`. TypeBox schemas are plain JSON Schema at runtime plus a `Symbol` marker, so wrapping an externally-sourced schema with `Unsafe` is sufficient — no conversion to a full TypeBox tree is needed, and the LLM now sees every tool's real parameter names. If you ever need to re-loosen the schema for debugging, fall back to the `Type.Object({}, { additionalProperties: true })` default only for that specific tool, not globally. ## Keybindings (mosh/tmux newline fix) `keybindings.json` is symlinked so edits flow through git. Default: ```json { "tui.input.newLine": ["shift+enter", "ctrl+j", "alt+j"] } ``` Rationale: when pi runs over `kitty → mosh → tmux`, shift+enter doesn't forward cleanly (mosh uses vt220-ish emulation, no kitty-keyboard-protocol or csi-u extended keys). `ctrl+j` and `alt+j` pass through as plain control/meta bytes and give you reliable newline insertion. ## Settings template (start pi without `--model`) `settings.example.json` is a template — **not symlinked**. pi rewrites its `settings.json` at runtime (`lastChangelogVersion` bumps on upgrade), which would dirty a symlinked repo file. Instead, bootstrap with: ```bash cp /path/to/mempalace-toolkit/extensions/pi/settings.example.json \ ~/.pi/agent/settings.json $EDITOR ~/.pi/agent/settings.json ``` The Bedrock inference-profile prefix on model IDs (`eu.`, `us.`) is **region-specific** and must match `AWS_REGION` in `~/.config/pi/.env`. For a bare Anthropic provider (non-Bedrock) drop the prefix entirely and use `anthropic:claude-...`. Run `pi --list-models` to confirm what your credentials can actually invoke. `install.sh` warns (non-fatal) if `settings.json` is missing. ## Environment setup pi with `defaultProvider=amazon-bedrock` needs `AWS_PROFILE` and `AWS_REGION` exported into the shell that launches it. Recommended layout (matches the tor-ms22 dotfiles pattern): ``` ~/.config/pi/.env ← AWS_PROFILE=..., AWS_REGION=... (git-crypt encrypted in dotfiles repo) ~/.oh-my-zsh/custom/pi-env.zsh ← set -a; source ~/.config/pi/.env; set +a ``` Historical note: these vars used to live under a `# Environment variables for pi` block inside `~/.config/opencode/.env`. Split out 2026-05-05 so each tool owns its own env file. `install.sh` runs a `check_aws_env` probe that warns if the vars are missing and points back here. ## File layout ``` mempalace-toolkit/ └── extensions/ └── pi/ ├── README.md ← this file ├── mempalace.ts ← symlinked into ~/.pi/agent/extensions/ ├── keybindings.json ← symlinked into ~/.pi/agent/ └── settings.example.json ← template; copy + edit into ~/.pi/agent/ ``` `install.sh` detects pi by probing for `~/.pi/agent/extensions/` and only creates symlinks when that directory exists. On machines without pi the files stay dormant in the repo. Re-runs are idempotent (same pattern as `bin/` and `SKILL.md`).