Follow-up to ec436ed, closing the one item that commit left as "unverified".
Verified on the EMB-7KJ4VR4G host: native pi is not installed at all -- no `pi`
or `mempalace` on PATH, no ~/.config/pi/, no ~/.pi/agent/extensions/. That
host's ~/.mempalace exists solely to back the devbox container through the bind
mount (.devbox-owner holds 1000:1000). So the 2026-08-14 flip covers every pi
on that machine and there is no split-brain to fix. The general hazard stays
documented, because it is a per-machine question.
Also documents how a native install *would* be flipped, since the obvious guess
is wrong: pi loads no dotenv file and has no `env` block in settings.json, and
the extension reads process.env only. The launching shell is the sole hook, so
the vars must be exported from a shell rc (a GUI-launched pi may not read one),
and ~/.config/pi/.env is not sourced by anything automatically.
Audit of every doc touching the global-palace rollout against the running
fleet. Each correction below was verified against the filesystem or the host,
not against another doc:
- synlig-primary-runbook: the decommission `rm -rf ~/.mempalace` now carries a
STOP block. That tree holds the fleet palace *and* the only copy of the
bearer token every client authenticates with; the old "empty today" comment
stopped being true when the palace was seeded on 2026-08-14. Adds an ordered
safe decommission, and drops count-based join verification.
- phase-1-exposure-runbook: new S3.8, how to verify a flip actually took --
the procedure that until now existed only in an untracked handover file.
Three claims that fail independently (env var / curl / the palace-path
discriminator) plus an explicit list of checks that produce FALSE POSITIVES:
drawer counts (both sides were seeded from the same palace, and `status`
counts chunks not drawers), write-then-read through the same transport, and
the `mempalace` CLI -- which has no remote support at all, so post-flip it
reads the dead local archive and reports success.
- rfc-001: status Draft -> Phases 0-1 implemented. Records that the join was a
file-level copy, which SIDESTEPPED the S7.6 diary-dedup question rather than
answering it -- so S7.6 remains a hard blocker for the second machine, which
is the one that will actually exercise merge semantics.
- ARCHITECTURE, SKILL, contrib/README, extensions/pi/README all claimed pi
feeds the palace automatically, unconditionally. That is gated on
mempalace-toolkit >= 29e660e and every deployed image predates it, so the
claim is currently false fleet-wide. Each site now states the gate plus a
check that inspects the *deployed* file rather than repo HEAD.
- extensions/pi/README: plaintext http://mempalace.lan example -> https
endpoint; the two transports are either/or (no dual-write, no local mirror);
the bridge fails CLOSED, so "the agent has no mempalace_* tools" is the
expected symptom of a server/token/DNS fault, not of a broken install.
- contrib/README: documents mempalace-serve.service, which this directory has
shipped since day one without explaining it (linger, the load-bearing
172.17.0.1 bind and why loopback is the unsafe-looking-safe option, the
token path, and an uninstall warning).
- Fixes a pre-existing stray ```sh fence that was swallowing S3.2's heading and
the token command into a code block.
Docs only; no behaviour change.
Staging default moves out of ~/.cache to <palace-root>/pi-stage (pi) and
<palace-root>/opencode-stage (opencode), resolved with mempalace's own
palace-path precedence ($MEMPALACE_PALACE_PATH -> $MEMPAL_PALACE_PATH ->
~/.mempalace/config.json -> ~/.mempalace/palace), then dirname.
Why: the convos miner keys dedup on the *staged* path, so a wiped stage plus a
sync scoped to include it prunes the drawers mined from those sources --
deleting memories, not a cache. Under ~/.cache that state was reachable by
anything treating a cache as disposable. Staging inside the palace makes the
coupling structural: the stage cannot be wiped without touching the palace
itself. Overrides ($MEMPALACE_PI_STAGE / $MEMPALACE_SESSION_STAGE, --stage) are
unchanged. Note the old default had never been created on any host, so this
closed a latent hazard, not a live one.
Measured, and the docs now claim only this much: sync prunes only within the
scope it is given -- wing-only, 1299 scanned / 1299 out of scope / 0 removed;
scoped at the palace root, 651 kept / 648 out of scope. The previous blanket
"sync prunes every drawer" wording overstated it, which is a liability: the next
reader disproves the overstatement and discards the real constraint with it.
Also in this change:
- cron log dir ~/.cache/mempalace-session -> ~/.cache/mempalace-logs. The stage
left that namespace, so the old name now read as "the stage".
- AGENTS.md: the convos miner *does* check mtime (verified against upstream
convo_miner.py); the previous "no mtime check" claim was wrong.
- smoke-test assertions use `mktemp -d` for --sessions-dir. One pointed at /tmp,
which still held earlier synthetic transcripts, so a --dry-run exported a fake
session into the real stage: --dry-run skips the mine, not the export.
docs/phase-1-exposure-runbook.md -- the newt/DNS/auth step that RFC 001 and the
synlig runbook leave open (runbook section 4, items 2 and 5). Port 8765 at /mcp,
newt targets 172.17.0.1, and the authentication is the single shared bearer
token (RFC 6.2, decided 2026-08-09) rather than per-device proxy users. The
latter cannot work today: mempalace validates exactly one token, and Pangolin's
SSO/PIN/password are browser-shaped while every client here is a headless
JSON-RPC POST -- enabling that protection breaks the clients it protects. The
per-device axis that *does* exist is the feeder's SSH key + per-device inbox.
New finding recorded there: a loopback bind does not merely 403 behind a tunnel
(already known, runbook 2.4) -- it also silently starts the server with no token
at all, because auto-minting is gated on the bind being non-loopback.
extensions/pi/README.md: the HTTP transport IS authenticated as of mempalace
3.6.0; the "sessionless and unauthenticated" note dated from the v1.3.0 era.
Closes the RFC section 8 Phase-0 hygiene item.
Let the pi<->mempalace bridge connect to a shared MemPalace over HTTP instead
of always spawning a local mempalace-mcp:
- Extract IMcpClient; rename McpClient -> StdioMcpClient (ctor command, arg-less start()).
- Add RemoteMcpClient (vendored from pi-extensions/mcp-loader.ts): streamable-HTTP
with AbortController timeouts, protocolVersion pinned 2024-11-05, alive/ensureAlive.
mempalace-mcp --transport http is sessionless JSON-RPC today; session/SSE/404
branches retained for a future streamable-HTTP server.
- createClient() selects transport from MEMPALACE_REMOTE_URL; MEMPALACE_REMOTE_TOKEN
-> Authorization: Bearer. Lifecycle automation (wake-up, /mempalace-diary) unchanged.
- scripts/check-mcp-client-sync.sh: drift guard vs canonical mcp-loader.ts.
- README: document local-vs-external transport.
Typechecks clean (strict); both transports smoke-tested against live mempalace-mcp.
A stall-kill (or any crash) of mempalace-mcp was a permanent latch:
available flipped off and stayed off until pi restart. Now the next tool
call transparently respawns the server and retries.
- ensureAlive(): bounded respawn with capped exponential backoff
(MEMPALACE_MCP_MAX_RESPAWNS, default 2; MEMPALACE_MCP_RESPAWN_BACKOFF_MS,
default 1000). Respawn budget resets on any successful JSON-RPC response,
so a recovered server regains full patience while a persistently-broken
one hits the cap and stays down (no hot-loop).
- Init timeout default raised 120000 -> 300000 (scoped to init only): a
genuine virtiofs cold-open shouldn't be killed mid-progress only to
respawn and re-pay the same cost. Per-call timeout stays 60000.
- Concurrency hardening: generation counter so a late exit from a killed
old process can't tear down a fresh respawn; explicit healthy flag
replaces racy proc!=null liveness check.
- README: document self-heal, new env vars, and why generous-init +
bounded-respawn compose rather than overlap.
A wedged mempalace-mcp (classically an OrbStack virtiofs cold-open of a
large chroma.sqlite3 / HNSW load) left the awaiting JSON-RPC promise
pending forever, freezing the pi TUI uninterruptibly: ESC cancels the
LLM stream, not a pending tool execute().
The JSON-RPC client now arms a per-request timer. On expiry it rejects
the request AND kills the stalled child (SIGTERM->SIGKILL), so pi gets
an error instead of hanging; the extension flips available=false so
later calls fail fast (restart pi to retry). Per-REQUEST, not
per-process: the long-lived server only dies on a genuine stall.
Knobs: MEMPALACE_MCP_TIMEOUT_MS (default 60000),
MEMPALACE_MCP_INIT_TIMEOUT_MS (default 120000), 0 = disable.
This supersedes the planned standalone stdio-watchdog shim: the
extension already owns request/response correlation, so a separate
framing-reparsing shim is unnecessary.
Pi moved to its new home at earendil-works on 2026-05-07
(https://pi.dev/news/2026/5/7/pi-has-a-new-home).
Sweep:
- extensions/pi/mempalace.ts: 'import type { ExtensionAPI } from
"@mariozechner/pi-coding-agent"' -> @earendil-works/pi-coding-agent.
- README and extensions/pi/README: github.com/mariozechner/pi-coding-agent
URL refs -> github.com/earendil-works/pi.
- install.sh: same URL substitution in the user-facing pointer line.
Brew install references (`brew install pi-coding-agent`) left as-is:
formula still works at 0.73.1, tap update tracked upstream at
earendil-works/pi#2755.
Parallel to the opencode-toolkit split earlier today. Pi's own config
(keybindings, shell env loader, settings template) moves to a new
sibling repo so opencode-devbox's mempalace opt-out can build slim
containers that include pi without dragging in chromadb + embedding
models (~300 MB).
What moved to pi-toolkit (https://gitea.jordbo.se/joakimp/pi-toolkit):
- extensions/pi/keybindings.json (mosh/tmux newline fix)
- extensions/pi/pi-env.zsh (sources ~/.config/pi/.env)
- extensions/pi/settings.example.json (Bedrock template)
- install.sh::install_pi_keybindings (symlink step)
- install.sh::install_pi_env_loader (cp step + bash fallback)
- install.sh::check_pi_settings (probe)
- install.sh::check_aws_env (probe)
What stays here (this is the pi\u2194mempalace bridge, mempalace-side):
- extensions/pi/mempalace.ts (the MCP extension)
- install.sh::install_pi_extension (symlink step)
- NEW: install.sh::check_pi_toolkit (probe: warns if pi is
installed but pi-toolkit's
artifacts are missing, with
git-clone pointer)
install.sh shrank from 520 to 403 lines. Uninstall mirror correctly
does NOT touch pi-toolkit-owned files (explicit comment).
Docs updated:
- extensions/pi/README.md: rewritten as 'pi\u2194MemPalace MCP bridge',
recipe becomes 'Deploying pi with mempalace' (pi-toolkit step 3,
this repo step 5).
- AGENTS.md: Structure block + 'What install.sh does' section reflect
the narrower scope and list the four things that moved out.
- README.md: repo-contents line + Setup section's deploy summary.
Verified on tor-ms22: full install\u2192uninstall\u2192reinstall lifecycle clean.
After mempalace-toolkit uninstall, pi-toolkit artifacts
(~/.pi/agent/keybindings.json, ~/.oh-my-zsh/custom/pi-env.zsh) remain
intact \u2014 correctly untouched. check_pi_toolkit probe fires green when
both exist.
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).
The loader that sources ~/.config/pi/.env into every shell was only
living in the myconfigs tor-ms22 backup \u2014 a fresh machine had nowhere
to get it from except copying by hand. Now canonical here.
- extensions/pi/pi-env.zsh: 20-line POSIX-compatible loader
(set -a; source ~/.config/pi/.env; set +a). Works in bash and zsh.
- install.sh install_pi_env_loader:
* oh-my-zsh detected (~/.oh-my-zsh/custom/ exists)
\u2192 cp into that dir (NOT symlink \u2014 that dir is typically part of
a dotfiles backup, and a symlink to mempalace-toolkit would
break when restored on another host).
\u2192 Idempotent: if target content matches repo, says 'already
installed'. If it differs, leaves user edits alone and points
at diff for manual reconcile.
* No oh-my-zsh \u2192 prints source-this-line snippet for ~/.zshrc or
~/.bashrc (derived from $SHELL). Does NOT auto-edit rc files.
- install.sh uninstall: only removes the copy if content still matches
repo. Local edits preserved.
- Docs:
* extensions/pi/README.md Environment setup section rewritten with
both install paths, step 5 of deploy recipe updated.
* AGENTS.md Structure block lists pi-env.zsh.
* Root README repo-contents line mentions it.
Verified on tor-ms22: install fresh \u2192 uninstall (content match \u2192 remove)
\u2192 reinstall \u2192 zsh -ic loads AWS vars correctly. Also tested bash fallback
path via HOME=/tmp/fake-home SHELL=/bin/bash \u2014 prints right .bashrc snippet.
Consolidates the step-by-step recipe that's been living in diary entries
and session chat into the canonical pi bring-up doc. Covers:
0. Prerequisites (zsh+oh-my-zsh, uv, tmux 3.2+, AWS creds)
1. Dotfiles: myconfigs provision (tmux CSI-u, ~/.config/pi/.env, zsh loader)
2. pi install (upstream brew/npm)
3. mempalace CLI (uv tool install) + mempalace-toolkit install.sh
4. pi settings bootstrap (start without --model, region prefix table)
5. AWS env verification (git-crypt unlock gotcha)
6. Opencode MCP registration pointer (if applicable)
7. First run + wake-up injection smoke test
+ Verification checklist + uninstall
Root README.md adds a short summary box in the Setup section pointing at
the full recipe, so readers coming in from the front door find the pi
path immediately but the details stay with the files they install.
Covers: macOS + Linux. Works for homelab / work-macos / any myconfigs
profile that ships .config/pi/ + pi-env.zsh.
Round out the pi bring-up story so a fresh machine can reach a working
pi+mempalace install with just `git clone && ./install.sh`:
- extensions/pi/keybindings.json: generic mosh/tmux newline fix
(shift+enter, ctrl+j, alt+j). Safe on any machine — not
region/account-specific. Symlinked into ~/.pi/agent/.
- extensions/pi/settings.example.json: template for `settings.json`
so pi can start without --provider/--model. NOT symlinked — pi
rewrites settings.json at runtime (lastChangelogVersion bumps),
which would dirty the repo. Installer prints the cp + edit hint.
- install.sh: new install_pi_keybindings + uninstall mirror; new
check_pi_settings probe (warns if settings.json missing); new
check_aws_env probe (warns if AWS_PROFILE/AWS_REGION unset and
settings.json selects amazon-bedrock). All new steps gated on
pi being installed (~/.pi/agent/extensions/ exists).
- extensions/pi/README.md: documents keybindings rationale,
settings bootstrap, and the recommended ~/.config/pi/.env +
~/.oh-my-zsh/custom/pi-env.zsh env layout (paired with the
myconfigs commit 884e329 that split AWS vars out of
~/.config/opencode/.env).
Verified on tor-ms22: full install → uninstall → reinstall cycle,
new shell loads AWS_PROFILE/AWS_REGION from the new pi-env.zsh hook.
Works on macOS and Linux (plain ln -s, POSIX bash).
The pi coding-agent extension at ~/.pi/agent/extensions/mempalace.ts was
living only on tor-ms22, including hand-edited fixes (Type.Unsafe
schema-passthrough for MCP tool parameters). One disk wipe away from
losing it, and no way to reproduce the install on a new machine.
- extensions/pi/mempalace.ts: canonical copy (matches tor-ms22 byte-for-byte)
- extensions/pi/README.md: what it does, the schema-passthrough gotcha,
debugging knobs
- install.sh: new install_pi_extension step — gated on ~/.pi/agent/extensions/
existing, backs up any real file in the way, idempotent re-runs, mirror
block in uninstall. Works on macOS and Linux (plain ln -s, readlink -f).
- README.md: mention extensions/pi/ in the repo-contents list and in the
Setup section
Verified on tor-ms22: install (backs up existing real file) → uninstall
(removes symlink) → reinstall (clean symlink). Re-runs are no-ops.