Cross-reference the newly-extracted opencode-toolkit repo, which owns ~/.config/opencode/.env loading. The recipe now distinguishes between registering mempalace in opencode.json (still this repo's concern) and ensuring opencode's own env loader is in place (opencode-toolkit's).
12 KiB
pi ↔ MemPalace extension
The canonical source of ~/.pi/agent/extensions/mempalace.ts — the bridge
that wires the MemPalace MCP
server into the 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 — step-by-step recipe.
- Keybindings (mosh/tmux newline fix)
- Settings template
- 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. 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/.envfrom~/.bashrc. git,node≥ 20,uv(for installing mempalace),tmux≥ 3.2.- AWS credentials reachable via
AWS_PROFILE(eitheraws configure ssocache or static keys in~/.aws/credentials) — only if you'll useamazon-bedrockas 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:
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)
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
# 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)
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:
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 <repo>/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:
- Install
opencode-toolkitso~/.config/opencode/.envis sourced into every shell (GitHub / Gitea / other MCP server tokens). - Register the mempalace MCP server in
~/.config/opencode/opencode.json— see root README § Registering mempalace with opencode.
7. First run
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
# 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
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
- Spawns
mempalace-mcpas a subprocess and does the MCP stdio JSON-RPC handshake (initialize+notifications/initialized+tools/list). - Registers each MCP tool as a pi tool with its real
inputSchemapassed through viaType.Unsafe(...)(see gotcha below). - Wake-up auto-injection (
before_agent_start, one-shot per fresh session): callsmempalace_status+mempalace_diary_readand injects the result as amempalace-wakeupsystem message so the agent orients itself the way~/.agents/skills/mempalace/SKILL.mddescribes. Skipped on resume/fork (context is already in the thread). - Manual wind-down via a
/mempalace-diary [topic]slash command: sends a prompt asking the LLM to callmempalace_diary_writewith an AAAK-formatted entry summarizing the session. Not fully auto because pi sessions are typically short/tactical andsession_shutdownfires 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_<name>
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— surfacemempalace-mcpstderr 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-mcpmanually 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:
{
"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:
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 <repo>/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).