diff --git a/docs/rfc-001-global-palace.md b/docs/rfc-001-global-palace.md index 911582d..32b1e98 100644 --- a/docs/rfc-001-global-palace.md +++ b/docs/rfc-001-global-palace.md @@ -6,6 +6,7 @@ | **Created** | 2026-08-08 | | **Applies to** | mempalace 3.6.0, mempalace-toolkit @ `96699f2`, pi-devbox ≥ v1.3.0 | | **Decision** | Phases 0–2 + 4 in scope. **Phase 3 (full pull replication) explicitly deferred** — "a laptop that can reach its own stuff plus whatever it can reach" is good enough. **Centralization is strictly opt-in — solitary devbox operation remains the default and must not change (§1.1).** | +| **Recon update** | **2026-08-09 — §9 Q1 and Q6 are RESOLVED, both in the permissive direction** (opencode supports remote MCP; opencode-devbox already templates the mempalace entry). Neither is a blocker. See those entries for evidence; §2, §4.1 and R5 were corrected accordingly. | **Read this first if you are asked to "centralize MemPalace" / "sync palaces between machines".** Most of the hard-won facts below are non-obvious and two of them are actively destructive if you guess wrong @@ -45,7 +46,7 @@ contain **zero** mempalace references. | **R2** | **Opt-in lives in `docker-compose.yml` + `.env`** — the mechanism users already know. No opt-in via image rebuild, no baked-in defaults, no `latest-central` variant. | | **R3** | **Credentials only in `.env`** (`chmod 600`, gitignored). Never in `docker-compose.yml`, never in the image, never on a command line (visible in `ps`), never logged. Compose passes them through with a `${VAR:-}` empty default so solitary users never define them. | | **R4** | **No new required services.** The primary stays in the separate standalone `docker-compose.mempalace.yml` project; it is never merged into the main compose file. A solitary `docker compose up` starts exactly what it starts today. | -| **R5** | **Degrade, never fail, when mempalace is absent.** opencode-devbox may not ship mempalace at all, so any wiring must be additive and skipped when the binary is missing — the probe-and-warn idiom `install.sh` already uses (`warn` + `return 0`, never halt). | +| **R5** | **Degrade, never fail, when mempalace is absent.** The published `opencode-devbox` image *does* ship mempalace by default (`Dockerfile.base:380,402` — `ARG INSTALL_MEMPALACE=true`, `MEMPALACE_VERSION=3.6.0`, installed via `uv tool install`), but it is a build arg precisely so it can be omitted to save ~300 MB — and a bind-mounted host config or a non-devbox client may have no mempalace either. So any wiring must stay additive and skip when the binary is missing — the probe-and-warn idiom `install.sh` already uses (`warn` + `return 0`, never halt). | | **R6** | **Reversible.** Commenting the `.env` lines out returns the container to pure solitary operation, with the local palace intact and readable. | **Acceptance test for R1** (must pass before Phase 2 ships): bring up a devbox with no `MEMPALACE_*` @@ -89,7 +90,7 @@ Two of the three pieces already exist. This is not greenfield. | **Primary server** | ✅ **Exists.** `mempalace serve --host --port --token --tls-cert --tls-key --read-only --allow-insecure`. Bearer token compared with `hmac.compare_digest`, **mandatory** on non-loopback binds (unless `--allow-insecure`), TLS 1.2+ resolved *before* bind, Host-header pinning + `Origin` allowlist (anti-DNS-rebinding), 16 MiB body cap, token-free `/healthz`. | `cli.py:cmd_serve` (~1448); `mcp_server.py:5205-5215`, `5284-5289` | | **Remote client** | ⚠️ **Exists for pi only, and it is either/or.** `createClient()` picks stdio *or* HTTP once at process start. | `extensions/pi/mempalace.ts:629-641` | | **Fallback + resync** | ❌ **Absent everywhere.** On remote failure the pi bridge re-handshakes the same URL, then **de-registers all palace tools** and runs blind. | `extensions/pi/mempalace.ts:665-673` | -| **opencode client** | ❌ **stdio only.** `{"type":"local","command":["mempalace-mcp"]}` in `~/.config/opencode/opencode.json`. No remote entry exists anywhere in `myconfigs`. | `myconfigs/tor-ms22.home.arpa/.config/opencode/opencode.json` | +| **opencode client** | ✅ **Remote is supported and already wired.** opencode's published schema (`https://opencode.ai/config.json`, `$defs.McpRemoteConfig`) makes `{"type":"remote","url","headers","oauth"}` a first-class sibling of `McpLocalConfig`, `headers` being a free string→string map (so bearer is a convention, not a constraint). `opencode-devbox` already emits exactly that entry when `MEMPALACE_REMOTE_URL` is set. The all-`type:local` configs in `myconfigs` are a *deployment* fact, not a capability limit. | `generate-config.py:107-118`; hook at `entrypoint-user.sh:117`; schema `$defs.McpRemoteConfig` | | **Server compose** | ✅ Exists: `pi-devbox/docker-compose.mempalace.yml` (canonical) + a tor-ms22 derivative (`docker-compose-repo/tor-ms22/pi-devbox/`, `df2c2ae`, port 8766, uid 1000, binds the real `~/.mempalace`). Not enabled. | pi-devbox CHANGELOG v1.3.0 (2026-07-02) | > **Stale docs warning.** `docker-compose.mempalace.yml`, `.env.example` and CHANGELOG v1.3.0 all say the @@ -185,10 +186,25 @@ image (it costs nothing unused) and inserted into the path only when `MEMPALACE_ - **pi**: `createClient()` (`extensions/pi/mempalace.ts:629`) already branches on env at startup — add a third branch. Zero change to the default path. -- **opencode**: the MCP server entry is static JSON, so the entrypoint must template it — write - `command: ["mempalace-edge"]` only when opted in, otherwise leave today's - `["mempalace-mcp"]` untouched. If mempalace is absent entirely (as in all three sampled - opencode-devbox deployments), write nothing and warn (R5). +- **opencode**: the MCP server entry is static JSON — but **the templating step already exists and + already implements the first two rungs of the §1.2 ladder.** + `rootfs/usr/local/lib/opencode-devbox/generate-config.py` (run unconditionally from + `entrypoint-user.sh:117`) registers `mempalace` as `{"type":"remote", url, headers:{Authorization: + Bearer …}}` when `MEMPALACE_REMOTE_URL` is set, else `{"type":"local","command":["mempalace-mcp"]}` + when the binary is on PATH, else nothing — R5-compliant already. Its own comment states it uses the + "*same env contract as the mempalace.ts pi extension … so one shared MemPalace can serve pi + + opencode + native*", i.e. the two images are deliberately kept in step. **Phase 2's opencode work is + therefore a third branch in an existing script, not a new mechanism.** + + > ⚠️ **But the opt-in does not propagate to an existing container.** `generate-config.py` *never* + > overwrites an existing config, and `~/.config/opencode` is the named volume + > `devbox-opencode-config` (`docker-compose.yml:64,153`) — so a config generated during solitary use + > survives recreate, and later setting `MEMPALACE_REMOTE_URL`/`MEMPALACE_EDGE` only produces a + > non-loaded `opencode.jsonc.proposed` sidecar for manual merge (`generate-config.py:203-244`). + > Flipping the `.env` alone is a no-op there. Phase 1/2 docs must say: merge the sidecar, or + > `docker volume rm` the config volume, to adopt the change — and the same applies in reverse for R6 + > (reversibility). This asymmetry with pi — whose `createClient()` re-reads env every start — is the + > single biggest behavioural difference between the two harnesses under this RFC. ### 4.2 Routing policy @@ -492,8 +508,8 @@ across models silently degrades recall. | Phase | Effort | Deliverable | | --- | --- | --- | | **0 — hygiene** | hours | §7 runbook: move KG/hallways/entities, ban `sync` on shared palaces, fix stale "unauthenticated" docs (incl. `pi-devbox/.env.example:21`). **No provenance work here** — it is not backfill-critical (§7.3.3) and belongs to the stamper, not the agent | -| **1 — primary up** | hours, **no code** | `mempalace serve --token --tls-cert` on a private-net host (reuse `docker-compose.mempalace.yml` — keep it a **separate standalone project**, R4 — and mind port 8765 vs pi-studio; tor-ms22 already moved to 8766). Repoint pi clients via `MEMPALACE_REMOTE_URL`/`MEMPALACE_REMOTE_TOKEN`. **Shared memory today, no offline.** | -| **2 — `mempalace-edge`** | ~1 week | The actual ask: local-first writes + outbox flush + merged reads + per-wing policy. Fixes opencode as a side effect. **Ships with the §1.2 opt-in wiring (compose + `.env.example` + entrypoint templating) and must pass the R1 acceptance test.** | +| **1 — primary up** | hours, **no code** | `mempalace serve --token --tls-cert` on a private-net host (reuse `docker-compose.mempalace.yml` — keep it a **separate standalone project**, R4 — and mind port 8765 vs pi-studio; tor-ms22 already moved to 8766). Repoint pi clients via `MEMPALACE_REMOTE_URL`/`MEMPALACE_REMOTE_TOKEN`. **opencode clients can be repointed in the same breath** — remote MCP is supported and `generate-config.py` already emits it (§9.1, §9.6), subject to the sidecar caveat in §4.1. **Shared memory today, no offline.** | +| **2 — `mempalace-edge`** | ~1 week | The actual ask: local-first writes + outbox flush + merged reads + per-wing policy. **Not** "fixes opencode" — opencode's *transport* is already fine after Phase 1; what edge adds there is offline/local-first, since `generate-config.py`'s switch is remote **or** local with no failover. **Ships with the §1.2 opt-in wiring (compose + `.env.example` + a third branch in the existing `generate-config.py`) and must pass the R1 acceptance test.** | | **3 — pull replication** | ~1 week | **DEFERRED (2026-08-08).** Server-side op-log with monotonic seq → each edge a full offline replica. Only needed if a laptop must hold *everything* offline. Accepted trade-off: offline recall = own writes + last-reachable state. | | **4 — authz** | days | Per-wing ACL, per-device scopes, audit log, token rotation | @@ -504,10 +520,18 @@ for online clients. ## 9. Open questions -1. **Does opencode's MCP config support a remote/HTTP transport at all?** Every sampled entry in - `myconfigs` is `"type":"local"`; no documentation found either way. **If it does not, the edge - proxy is not merely nicer — it is the only option for opencode clients.** Verify against opencode's - MCP client source before Phase 2. +1. ~~**Does opencode's MCP config support a remote/HTTP transport at all?**~~ **RESOLVED 2026-08-09: + YES.** opencode's published JSON Schema (`https://opencode.ai/config.json`) defines + `$defs.McpRemoteConfig` = `{type:"remote" (enum), url (required), headers?: {string:string}, + oauth?, enabled?, timeout?}` as a sibling of `McpLocalConfig` inside + `Config.properties.mcp.additionalProperties.anyOf`. So `headers` carries any bearer/API-key scheme, + and set `oauth: false` to stop opencode probing the URL for OAuth. The earlier "every sampled entry + is `type:local`" observation was **deployment evidence being read as schema evidence** — the schema + settles it in one fetch. **Consequence: the edge proxy is *not* the only option for opencode.** A + plain Phase 1 `mempalace serve --token` can be consumed directly, so nothing in the phasing depends + on this question any more. What opencode still lacks is *local-first writes and offline fallback* — + `generate-config.py`'s switch is remote **or** local per container, with no failover — and that is + the real Phase 2 justification for it. 2. **Upstream or local?** `mempalace-edge` needs no core changes, so it belongs in this repo (`bin/` + `extensions/`). But `origin_device` stamping from the authenticated token, per-wing ACL, the `kg_supersede` classification fix, and a `sync --refuse-shared` guard all want to go **upstream** @@ -525,13 +549,19 @@ for online clients. 5. **Does `_HTTP_REQUEST_LOCK` stay held for the duration of an MCP-triggered `mine`?** Strongly suggested by the code shape; if yes, a remote mine makes the primary unusable for its duration (another argument for §5). -6. **How does opencode-devbox learn the opt-in?** pi's transport choice is code (`createClient()`), so - it reads `.env` for free. opencode's MCP registration is **static JSON** in - `~/.config/opencode/opencode.json`, which per R1/R2 must be templated at container start rather - than baked. Unknown: whether opencode-devbox's entrypoint has a config-templating step to hook, or - whether the file is user-owned and bind-mounted (in which case the opt-in is a documented manual - edit — acceptable, but say so). Also unknown whether the published `opencode-devbox` image ships - mempalace at all; if not, edge mode there is host-side only until it does. +6. ~~**How does opencode-devbox learn the opt-in?**~~ **RESOLVED 2026-08-09: it already does.** The + image ships `rootfs/usr/local/lib/opencode-devbox/generate-config.py`, invoked from + `entrypoint-user.sh:117` on every start, which auto-registers the `mempalace` MCP server — + remote+bearer when `MEMPALACE_REMOTE_URL`/`MEMPALACE_REMOTE_TOKEN` are set, local stdio when + `mempalace-mcp` is on PATH, nothing otherwise — documented in that repo's `.env.example:38-49` and + `.env.shared.example:30-35`. It also ships mempalace by default (see R5). So the config *is* + templated at container start, not baked, and Phase 2 only adds a third branch. **Two residual + constraints replace the original unknown, and both are documented in §4.1:** (a) the script never + overwrites an existing config and `~/.config/opencode` is a *named volume*, so an env flip yields + only an `opencode.jsonc.proposed` sidecar — adopting or reverting the opt-in needs a manual merge + or a volume removal; (b) it no-ops entirely unless `OPENCODE_PROVIDER` is set. Prefer extending + this script over inventing a parallel mechanism — treat it as the reference implementation, and + check it before designing any pi-devbox-side mechanism with an opencode counterpart. ---