# 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`). **Jump to:** - [Deploying pi on a new machine](#deploying-pi-on-a-new-machine) — step-by-step recipe. - [Keybindings (mosh/tmux newline fix)](#keybindings-moshtmux-newline-fix) - [Settings template](#settings-template-start-pi-without---model) - [Environment setup](#environment-setup) --- ## Deploying pi on a new machine Full recipe from a clean macOS or Linux box to a working pi+MemPalace install with all modifications shipped by this repo and by [`myconfigs`](https://gitea.jordbo.se/joakimp/myconfigs). Follow in order. ### 0. Prerequisites - Shell: **zsh + oh-my-zsh** (the env loader is `~/.oh-my-zsh/custom/pi-env.zsh`). On bash-only hosts, adapt by sourcing `~/.config/pi/.env` from `~/.bashrc`. - `git`, `node` ≥ 20, `uv` (for installing mempalace), `tmux` ≥ 3.2. - AWS credentials reachable via `AWS_PROFILE` (either `aws configure sso` cache or static keys in `~/.aws/credentials`) — **only if** you'll use `amazon-bedrock` as pi's provider. ### 1. Clone your dotfiles repo and provision Brings `~/.tmux.conf` with CSI-u extended keys, `~/.config/pi/.env` (git-crypt encrypted), and `~/.oh-my-zsh/custom/pi-env.zsh`: ```bash git clone ssh://git@gitea.jordbo.se:2222/joakimp/myconfigs.git ~/src/src_local/myconfigs cd ~/src/src_local/myconfigs # Unlock git-crypt so ~/.config/pi/.env decrypts (skip on a box that has # never held your git-crypt key; see myconfigs/GIT-CRYPT.md to set up). git-crypt unlock ~/path/to/git-crypt-key # Provision — choose the profile matching the box (homelab, work-macos, ...). ./provision.sh --dry-run --profile homelab # preview ./provision.sh --profile homelab # apply ``` ### 2. Install pi (upstream) ```bash brew install pi-coding-agent # macOS # or: follow https://github.com/mariozechner/pi-coding-agent for Linux ``` First run creates `~/.pi/agent/`. ### 3. Install mempalace + the toolkit ```bash # MemPalace CLI (isolated venv via uv, shim in ~/.local/bin) uv tool install mempalace # mempalace-toolkit (this repo) — the bin/ wrappers, the pi extension, # keybindings, settings template, and install probes. git clone ssh://git@gitea.jordbo.se:2222/joakimp/mempalace-toolkit.git ~/mempalace-toolkit cd ~/mempalace-toolkit ./install.sh ``` `install.sh` detects pi, symlinks `mempalace.ts` + `keybindings.json` into `~/.pi/agent/`, installs the companion skill, and runs five probes. The AWS probe stays quiet until step 4 selects `amazon-bedrock`. ### 4. Bootstrap pi settings (start pi without `--model`) ```bash cp ~/mempalace-toolkit/extensions/pi/settings.example.json \ ~/.pi/agent/settings.json $EDITOR ~/.pi/agent/settings.json ``` Adjust the inference-profile prefix to match your AWS region: | Region | Prefix | Example model ID | |---|---|---| | eu-west-1 | `eu.` | `eu.anthropic.claude-sonnet-4-6` | | us-east-1 | `us.` | `us.anthropic.claude-sonnet-4-6` | | non-Bedrock | (none) | `anthropic:claude-sonnet-4-6` | Run `pi --list-models` to confirm what your credentials can actually invoke. ### 5. Ensure AWS env vars are live in your shell **On zsh + oh-my-zsh hosts:** step 3's `install.sh` already copied `pi-env.zsh` into `~/.oh-my-zsh/custom/`, so every new shell sources `~/.config/pi/.env` automatically. Verify: ```bash exec zsh echo "$AWS_PROFILE $AWS_REGION" # should print your values ``` **On bash or plain zsh (no oh-my-zsh):** `install.sh` printed a `source /extensions/pi/pi-env.zsh` snippet — add that one line to `~/.bashrc` or `~/.zshrc`, open a fresh shell, verify as above. If vars are empty, check that `~/.config/pi/.env` decrypted (`head ~/.config/pi/.env` should show plain text, not binary). `git-crypt unlock` in step 1 is the usual culprit when this is empty. ### 6. Register mempalace MCP with opencode (if using opencode too) Skip if this box is pi-only. Otherwise see [root README § Registering mempalace with opencode](../../README.md#registering-mempalace-with-opencode-or-other-mcp-clients). ### 7. First run ```bash pi # should start with the default model, no --model needed ``` Inside pi, the wake-up auto-injection should print a `mempalace-wakeup` system message with palace status and recent diary entries. If it doesn't, run `MEMPALACE_EXT_DEBUG=1 pi` to surface `mempalace-mcp` stderr. ### Verification checklist ```bash # Symlinks in place ls -la ~/.pi/agent/mempalace.ts ~/.pi/agent/keybindings.json # → repo ls -la ~/.agents/skills/opencode-mempalace-bridge/SKILL.md # → repo # Env loaded zsh -ic 'echo $AWS_PROFILE $AWS_REGION' # tmux extended keys tmux show-options -g | grep extended-keys # csi-u # Palace reachable mempalace status # Installer re-run is idempotent cd ~/mempalace-toolkit && ./install.sh --yes # all rows should say "already linked" ``` ### Uninstall ```bash cd ~/mempalace-toolkit && ./install.sh --uninstall --yes # Leaves mempalace CLI, pi binary, and ~/.config/pi/.env alone — # only removes symlinks this repo created. ``` --- ## 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 ← installed by mempalace-toolkit install.sh (sources the .env into every shell) ``` The loader file `pi-env.zsh` is canonical here in `extensions/pi/` and installed by `install.sh` in one of two ways: | Detected | Action | |---|---| | `~/.oh-my-zsh/custom/` exists | `cp` (not symlink) into that directory — auto-loaded by omz on every new shell. cp not symlink because that directory is part of the dotfiles backup, and a symlink into mempalace-toolkit would break when the backup is restored on another host. | | No oh-my-zsh | Prints a shell-specific `source /extensions/pi/pi-env.zsh` snippet for `~/.zshrc` or `~/.bashrc`. Installer does not auto-edit rc files. | The loader itself is POSIX-compatible (`set -a` / `source` / `set +a`), so bash users can source it directly — no zsh dependency in the file. Re-runs are idempotent: if the installed copy matches the repo, prints "already installed". If it differs, leaves your edits alone and points at `diff` for comparison. Uninstall only removes the file if it still matches the repo copy; local edits are preserved. 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/ ├── pi-env.zsh ← cp'd into ~/.oh-my-zsh/custom/ (or source'd manually for bash) └── 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`).