mempalace: drop the stage ENV pin, fix the shared-server compose
Lint / actionlint (push) Successful in 21s
Lint / hadolint (push) Successful in 29s

The feeder now defaults to <palace-root>/pi-stage upstream, so pinning
MEMPALACE_PI_STAGE into ~/.pi here is unnecessary -- and was actively wrong. It
created a second convention that could still diverge from the palace: keep the
devbox-palace volume, drop devbox-pi-config, and a scoped `mempalace sync`
prunes every conversation drawer, because dedup keys on the staged path. Both
the ENV and the entrypoint export are gone; a comment explains why adding one
back re-introduces the split it was meant to fix.

docker-compose.mempalace.yml was broken on mempalace 3.6.0 in both directions:
  - `--host 0.0.0.0` with no token in the environment makes the server refuse
    to start, crash-looping under `restart: unless-stopped`.
  - Supply a token and the healthcheck's unauthenticated `tools/list` POST 401s,
    marking a perfectly healthy server unhealthy forever.
Now the token is required via ${MEMPALACE_REMOTE_TOKEN:?...} so it fails fast at
`docker compose up` with a readable message, and the healthcheck probes the
deliberately token-free /healthz. The "no authentication of its own" security
note has been stale since 3.6.0 and is replaced with the actual posture
(bearer token + Host pin + Origin allowlist), including why browser-shaped auth
must not be put in front of it.

Dockerfile.base: mempalace-pi-session symlinked onto PATH, with a build-time
`--help` check so a broken feeder fails the image build rather than the first
session.

smoke-test: assert the stage resolves beside the palace (default, and following
$MEMPALACE_PALACE_PATH) instead of asserting the removed ENV pin. The two
behavioural guards -- a synthetic session that must be captured, an abandoned
one that must not be -- are unchanged.

.env.example: recommend `mempalace serve` on the docker0 gateway rather than
`mempalace-mcp --transport http --host 0.0.0.0`, with the two binds to avoid.
This commit is contained in:
Joakim Persson
2026-08-12 17:04:14 +02:00
parent 7649d53f3b
commit 7c00dd6001
6 changed files with 265 additions and 13 deletions
+76
View File
@@ -11,6 +11,82 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
---
## Unreleased
Minor-shaped. Headline: **pi sessions now feed MemPalace by themselves.** The
image already shipped `mempalace-toolkit`, but its pi feeder
(`mempalace-pi-session`) was never symlinked onto `PATH`, so nothing ever mined
pi's transcripts — the palace only ever contained what an agent remembered to
file by hand. A container that gets recreated regularly has no other memory, so
a missed wind-down was a permanently lost session.
*Why event-driven and not a timer:* there is nothing schedulable inside the
container — PID 1 is `bash -l`, with no systemd and no cron — and anything
installed would not survive recreate anyway. The triggers therefore live where
the events already are: pi's own lifecycle, plus container start.
### Added
- **`mempalace-pi-session` symlinked onto `PATH`** (`Dockerfile.base`,
alongside its `mempalace-session` / `mempalace-docs` siblings, with the same
`--help` build-time check). `entrypoint-user.sh` also self-heals the symlink
into `~/.local/bin` (already ahead of `/usr/local/bin` on `PATH`, and
writable by `developer`) so the feature works on images whose base predates
this change.
- **Container-start catch-up feed** (`entrypoint-user.sh`, backgrounded). pi's
mempalace extension feeds the palace on `session_shutdown` and on a debounced
`agent_settled`, but a hard kill (`docker kill`, OOM, host reboot) runs no
handler at all; this is the only trigger that can recover the previous life's
transcripts. Skipped when a remote palace is configured without an inbox to
ship to, and skippable entirely with `MEMPALACE_FEED=0`.
- **`MEMPALACE_PI_STAGE` no longer needs pinning here — the feeder's default
was fixed upstream instead.** It used to stage under `~/.cache`, which is
disposable in a container; the first cut of this change pinned the env var
into the persisted `~/.pi` volume. That was the wrong fix: it created a second
convention that could still diverge from the palace (keep the palace volume,
drop `devbox-pi-config`, and a scoped `mempalace sync` prunes every
conversation drawer, because dedup keys on the *staged* path). The feeder now
defaults to `<palace-root>/pi-stage`, resolved with mempalace's own
precedence (`$MEMPALACE_PALACE_PATH``$MEMPAL_PALACE_PATH`
`~/.mempalace/config.json``~/.mempalace/palace`), so the stage inherits
whatever persistence the palace has and the two cannot be separated by
accident. No `ENV` and no entrypoint export: adding one back would
re-introduce exactly the split it removes.
- **Transcript inbox mount in `docker-compose.mempalace.yml`**
(`${MEMPALACE_FEED_DIR:-./feed}:/data/feed:ro`). A client cannot mine into a
remote palace directly: `mempalace_mine` expands its source path in the
*server* process, so the server can only see paths inside its own container.
Clients rsync their staged exports to a per-device subdirectory and then ask
the server to mine `/data/feed/<device>`. Read-only because mining only reads
sources — all locks live palace-side.
- **Smoke tests** for the above: `mempalace-pi-session` on `PATH`, two
assertions that the stage resolves next to the palace (default, and following
`$MEMPALACE_PALACE_PATH`), and two behavioural guards that feed the exporter a
synthetic pi session — one that must be captured, one abandoned session that
must not be. The second matters because pi expands skills/context into the
user prompt, so an abandoned session can look substantial by byte count while
containing no assistant output; and if pi's JSONL shape ever changes, the
exporter would silently capture nothing.
- **`.env.example`**: documents `MEMPALACE_FEED`,
`MEMPALACE_FEED_DEBOUNCE_MS`, `MEMPALACE_FEED_WING`, and the remote-palace
shipping vars `MEMPALACE_PI_SSH_TARGET`, `MEMPALACE_PI_REMOTE_PATH`,
`MEMPALACE_PI_DEVICE`.
### Notes
- The `Dockerfile.base` change moves the base hash, so this needs a base
rebuild; the `~/.local/bin` self-heal exists so the feature does not have to
wait for one.
- Requires the matching `mempalace-toolkit` change (`--prepare` two-phase
split, remote transport, and the auto-feed triggers in
`extensions/pi/mempalace.ts`). The split exists because the palace is
single-writer: a live pi session holds it through the extension's own
`mempalace-mcp`, so a CLI `mempalace mine` during a session fails with
"palace ... is held by PID". Staging is therefore done by the CLI and the
mine itself by whichever process already holds the palace.
---
## v1.7.0 — 2026-08-07
Minor release. Headline: **pi-atelier is now part of the image** — the TUI