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.
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.