diff --git a/.env.example b/.env.example index 6e870ae..d5492e1 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,19 @@ WORKSPACE_PATH=~/projects # Path to SSH keys on host SSH_KEY_PATH=~/.ssh +# ── MemPalace memory (local by default) ─────────────────────────── +# By default the mempalace.ts extension spawns a LOCAL mempalace-mcp stdio +# server (palace at ~/.mempalace). Uncomment the devbox-palace volume in +# docker-compose.yml to persist it across container recreation. +# +# To instead share ONE MemPalace across containers/harnesses (pi + opencode +# + native), set the URL below. When set, the extension connects over HTTP +# and NO local mempalace-mcp is spawned; the devbox-palace volume is then +# irrelevant. MEMPALACE_REMOTE_TOKEN, if set, is sent as a bearer token. +# Serve it with: mempalace-mcp --transport http --host 0.0.0.0 --port 8765 +# MEMPALACE_REMOTE_URL=http://mempalace.lan:8765/mcp +# MEMPALACE_REMOTE_TOKEN= + # ── LAN access from the container (host-OS-agnostic) ───────────────── # On VM-backed hosts (macOS OrbStack / Docker Desktop) the container can't # reach the host's directly-attached LAN peers by default. The entrypoint diff --git a/CHANGELOG.md b/CHANGELOG.md index d52ff98..365de12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,16 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`). ### Added +- **Share one MemPalace across containers via `MEMPALACE_REMOTE_URL`.** The + `mempalace.ts` bridge (from `mempalace-toolkit`) can now connect to a shared + MemPalace over HTTP instead of spawning a per-container local server: set + `MEMPALACE_REMOTE_URL=http://:8765/mcp` (optionally + `MEMPALACE_REMOTE_TOKEN`) in `.env` and no local `mempalace-mcp` is spawned. + A new `docker-compose.mempalace.yml` stands up such a shared server + (`mempalace-mcp --transport http`). Leaving the URL unset keeps the default + local-per-container palace. See `.env.example`. (The HTTP transport is + unauthenticated — keep it on a trusted network or behind a reverse proxy.) + - **Two non-modal terminal editors alongside `nvim`: `nano` and `micro`.** The image previously shipped only `nvim` (with `EDITOR=nvim`), a modal vi-style editor. Not everyone is comfortable with vi keybindings, so both diff --git a/README.md b/README.md index 604a984..eea10ed 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,10 @@ on the host. - ChromaDB embedding model pre-warmed at build time (`all-MiniLM-L6-v2`) The host-mounted palace at `~/.mempalace` is shared across the host and -this container so all your agents share one brain. +this container so all your agents share one brain. To instead share a palace +across *several* containers/harnesses, set `MEMPALACE_REMOTE_URL` to a shared +MemPalace HTTP endpoint (see `.env.example` and `docker-compose.mempalace.yml`); +the bridge then connects over HTTP and spawns no local server. ### Modern CLI tooling diff --git a/docker-compose.mempalace.yml b/docker-compose.mempalace.yml new file mode 100644 index 0000000..a5c20f1 --- /dev/null +++ b/docker-compose.mempalace.yml @@ -0,0 +1,84 @@ +# Shared MemPalace server (optional) — one palace for many clients. +# +# Runs `mempalace-mcp` over HTTP so several containers/harnesses (pi + +# opencode + native) can share ONE palace instead of each keeping its own. +# Point every client at it by setting, in that client's .env: +# +# MEMPALACE_REMOTE_URL=http://:8765/mcp +# +# (see .env.example). When set, the client connects over HTTP and does NOT +# spawn its own local mempalace-mcp. +# +# Start: docker compose -f docker-compose.mempalace.yml up -d +# Stop: docker compose -f docker-compose.mempalace.yml down +# Logs: docker compose -f docker-compose.mempalace.yml logs -f +# +# Why reuse the devbox image? mempalace-mcp is already installed in it, and +# reusing it GUARANTEES the server's mempalace version matches the clients' +# (both are pinned by the same image build). Override with a slimmer image via +# MEMPALACE_SERVER_IMAGE if you prefer (it must provide `mempalace-mcp`). +# +# ⚠ SECURITY: mempalace-mcp's HTTP transport has NO authentication of its own. +# Do NOT expose port 8765 to an untrusted network. The default below binds to +# 127.0.0.1 (host loopback) only. To let sibling containers reach it, either +# attach them to the shared `mempalace-net` network (container-to-container, no +# host port needed — use http://mempalace-server:8765/mcp), or front it with a +# reverse proxy that enforces MEMPALACE_REMOTE_TOKEN as `Authorization: Bearer`. + +name: mempalace-server + +services: + mempalace: + image: ${MEMPALACE_SERVER_IMAGE:-joakimp/pi-devbox:latest} + container_name: mempalace-server + # Bypass the devbox entrypoint (dev-shell/LAN/config setup) and run the + # HTTP MCP server directly. HOME + explicit --palace pin the data path so + # it does not depend on the image's default user/HOME. Runs as root so it + # can initialise the fresh named volume; the volume is dedicated to this + # server (clients reach it over HTTP, never by mounting it). + entrypoint: [] + user: "0:0" + environment: + - HOME=/data + command: + - mempalace-mcp + - --transport + - http + - --host + - "0.0.0.0" + - --port + - "8765" + - --palace + - /data/.mempalace + restart: unless-stopped + # Loopback-only by default (see SECURITY note). Use "8765:8765" to expose on + # all host interfaces, or drop `ports:` entirely and rely on mempalace-net. + ports: + - "127.0.0.1:8765:8765" + volumes: + # The shared palace data — precious; back this up. + - mempalace-shared:/data/.mempalace + # Embedding-model cache (~79 MB, disposable) so search does not re-download. + - mempalace-shared-chroma:/data/.cache/chroma + networks: + - mempalace-net + healthcheck: + # A tools/list round-trip proves the server is answering MCP (python3 is + # always present — mempalace itself is a python tool in the image). + test: + - CMD + - python3 + - -c + - "import urllib.request,json; d=json.dumps({'jsonrpc':'2.0','id':1,'method':'tools/list','params':{}}).encode(); r=urllib.request.Request('http://127.0.0.1:8765/mcp',data=d,headers={'Content-Type':'application/json','Accept':'application/json'}); urllib.request.urlopen(r,timeout=5).read()" + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + +volumes: + mempalace-shared: + mempalace-shared-chroma: + +networks: + mempalace-net: + name: mempalace-net diff --git a/docker-compose.yml b/docker-compose.yml index 657a3f0..1b16f3e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -76,7 +76,10 @@ services: # Persist uv data (Python installs, tool installs) - devbox-uv:/home/developer/.local/share/uv - # Optional: persist MemPalace data (conversation memory, knowledge graph) + # Optional: persist MemPalace data (conversation memory, knowledge graph). + # Applies to the LOCAL palace only (the default). In EXTERNAL mode + # (MEMPALACE_REMOTE_URL set in .env) the shared server owns the data, so + # this volume is irrelevant. # - devbox-palace:/home/developer/.mempalace # Optional: persist ChromaDB embedding model cache (~79 MB)