Design for moving from one MemPalace per machine per harness to a single primary palace with per-machine offline fallback, plus the source-verified archaeology behind it. Key decisions recorded: - Sync operations (MCP tool calls), not databases. Embeddings are computed client-side and are not portable across architectures; the KG `triples` table has no UNIQUE(subject,predicate,object,valid_from) and its ids embed datetime.now(), so row copies duplicate facts. Replaying tool calls is idempotent where it matters. - Implement as `mempalace-edge`, a local stdio MCP proxy (child mempalace-mcp + HTTPS to the primary + outbox.sqlite), not as per-harness patches. Needs zero mempalace internals, so it serves pi, opencode and the CLI alike and survives mempalace upgrades. - Merged reads (query both, re-sort, dedupe by drawer id) give fleet-wide recall without replication — which is why Phase 3 (pull replication) is deferred: "own writes plus whatever it can reach" is good enough. - Per-wing replication policy: curated/content-addressed wings replicate, mined code/docs stay local (derived, re-mineable, path-dependent ids). Also documents two silently destructive footguns to avoid during rollout: `--palace` vs MEMPALACE_PALACE_PATH (the KG follows the flag only, so `mempalace serve` can start with a silently empty knowledge graph), and `mempalace sync`, which is gitignore-aware drawer deletion rather than replication and would wipe fleet memory when run from a host lacking the repos. Notes that mempalace 3.6.0's `serve` already ships token auth + TLS, making the "unauthenticated, front it with a proxy" notes elsewhere stale. Includes an evidence index mapping each claim to file:line in mempalace 3.6.0, and four upstream candidates (origin_host provenance, per-wing ACL, mempalace_kg_supersede missing from service.py WRITE_TOOLS, and a sync --refuse-shared guard).
11 KiB
AGENTS.md
What this is
Producer-side tooling for MemPalace. Three thin wrappers in bin/ (opencode + pi session feeders, a docs miner), a companion agent skill, and an extensions/ tree with per-harness bridges (currently pi/). Pairs with the consumer-side mempalace skill.
Read ARCHITECTURE.md first — it's the canonical spec for what this repo does and why.
Structure
install.sh # Idempotent installer — see "What install.sh does" below
ARCHITECTURE.md # Canonical spec: diagrams, setup recipe, ops notes, upstream roadmap
README.md # Human-facing quickstart + per-tool usage reference
SKILL.md # Agent skill (symlinked into ~/.agents/skills/ on install)
docs/
rfc-001-global-palace.md # Design: one primary palace + per-machine local fallback (mempalace-edge)
bin/
mempalace-docs # Docs-only MemPalace miner (bash wrapper)
mempalace-session # Opencode session → MemPalace bridge (bash + inline Python)
mempalace-pi-session # pi session → MemPalace bridge (bash + inline Python)
contrib/ # systemd / launchd / cron templates for scheduling feeders
extensions/
pi/ # pi↔mempalace MCP bridge (bridge-only; pi's own config is in the pi-toolkit repo)
mempalace.ts # Symlinked into ~/.pi/agent/extensions/ (MCP <→ pi glue)
README.md # Bridge internals, Type.Unsafe gotcha, pi+mempalace deploy recipe
What install.sh does
Idempotent, safe to re-run. Always:
- Symlinks
bin/*into~/.local/bin/. - Creates
~/.agents/skills/opencode-mempalace-bridge/with aSKILL.mdsymlink and a.skill-sourcemarker.
Gated on pi being installed (~/.pi/agent/extensions/ exists):
- Symlinks
extensions/pi/mempalace.tsinto~/.pi/agent/extensions/. Backs up any real file in the way.
Probes (never halt, warn + return 0):
~/.local/binis on$PATH.~/.config/opencode/instructions/mempalace.mdexists (opencode wake-up protocol).mempalaceis registered as an MCP server in~/.config/opencode/opencode.json.- If pi is installed: pi-toolkit artifacts (
~/.pi/agent/keybindings.jsonsymlink,~/.oh-my-zsh/custom/pi-env.zsh) exist. Warns with agit clone ssh://...pi-toolkit.gitpointer if missing.
All non-destructive: if something is already in place and points into this repo, prints "already linked" and moves on. If a non-symlink real file is in the way, backs it up with a timestamp.
Not handled here any more (split to pi-toolkit on 2026-05-05):
keybindings.jsonsymlink into~/.pi/agent/pi-env.zshcp into~/.oh-my-zsh/custom/settings.example.jsontemplate +check_pi_settingsprobecheck_aws_envprobe
Those are pi-generic concerns. This toolkit installs only the pi↔mempalace MCP bridge on top of whatever pi-toolkit set up.
Conventions
- Standalone executables in
bin/with#!/usr/bin/env bashshebang, no extension,chmod +x. Must work in non-interactive contexts (agent processes, cron, CI). - Thin wrappers only. Neither tool reimplements the mempalace miner. Both follow the stage-to-cache-then-mine idiom: curate input to
~/.cache/…/<wing>/, then delegate tomempalace mine. - Idempotent + dry-runnable. Every tool supports
--dry-run. Second invocation on unchanged input is a no-op (dedup viasource_filepath, optionally +mtime). - No external Python deps. Stdlib only (
sqlite3,json,pathlib). Inline in the bash wrapper via heredoc. - Argument parsing:
--help/-hfirst, then mode flags, then positional args. - Comment sections use
# ── Section Name ──────style (matches siblingcli_utilsrepo).
Adding a new wrapper
Three wrappers live happily as standalone scripts — no shared helper library yet, because each one's stage-to-cache logic differs enough that the common surface is thin (arg parsing + mempalace mine invocation). A fourth wrapper might tip the balance; re-evaluate then. Until then, copy the pattern from mempalace-session (richest example):
- Create
bin/<name>with#!/usr/bin/env bash+chmod +x. - Implement
--help,--dry-run,--repairflags (repair is opt-in;--no-repairkept as deprecated alias). - Stage to
~/.cache/<name>/<wing>/with deterministic filenames. - Invoke
mempalace mine ...(choose--mode convosif input is chat-like). - Do NOT end with
mempalace repairunless--repairwas explicitly passed. Repair is a destructive in-place HNSW rebuild and must never run on an unattended schedule. - Update
README.mdwith usage + rationale. - Update
install.sh? No —bin/*is auto-linked. - Update
ARCHITECTURE.mdif the wrapper fills a new architectural gap. - Update
SKILL.mdif agents should know when to invoke it.
Adding a new harness extension
extensions/<harness>/ is the home for bridges — code that lives
inside an agent runtime (pi, claude-code, kiro, …) and talks to the
mempalace MCP server. Currently only extensions/pi/ exists. If you
add a second one (e.g. extensions/claude-code/), follow the same
shape:
- One directory per harness. Never mix harnesses in one dir.
- Bridge-only scope. This toolkit owns the mempalace-side wiring;
harness-generic config (keybindings, env loaders, settings templates)
belongs in a sibling
<harness>-toolkitrepo, following the pattern established bypi-toolkitandopencode-toolkit. That boundary is load-bearing foropencode-devbox's slim container path (mempalace opt-out, ~300 MB saved). - A
README.mdcovering: what the bridge does, harness-specific install path, debug knobs, and any gotchas (e.g. the piType.Unsafeschema passthrough). May also hold the "Deploying with mempalace" recipe since that straddles the two repos. - Gate
install.shsteps on the harness being present. Detect via a well-known path (pi uses~/.pi/agent/extensions/). Skip silently on machines without that harness. Never force-install. - Symlink the bridge code.
mempalace.ts(or equivalent) gets symlinked into the harness's extensions directory so edits flow through git. Back up any pre-existing real file to<path>.bak.YYYYMMDD-HHMMSSbefore linking. - Probe for the sibling toolkit. After installing the bridge, check
whether the harness's own base config is in place (e.g. for pi-toolkit:
~/.pi/agent/keybindings.jsonsymlink,~/.oh-my-zsh/custom/pi-env.zsh). Warn with agit clonepointer if missing.warn+return 0, never halt. - Mirror in
--uninstall. Every symlink this repo creates must have a matching removal step guarded bylink_if_into_repo. Do not touch sibling-toolkit-owned files — point the user at<harness>-toolkit/install.sh --uninstallinstead. - Update the root
README.md— repo-contents list + Ecosystem diagram's "Who owns what" table + Setup section's deploy summary. - Update this file's Structure block to list the new
extensions/<harness>/contents.
See extensions/pi/README.md and the install_pi_extension +
check_pi_toolkit functions in install.sh for a worked example.
Testing
Manual only. Integration-shaped:
# Smoke test — does it parse args and list what would happen?
./bin/mempalace-session --help
./bin/mempalace-session --dry-run
# Real test on a single session (safe, deterministic)
./bin/mempalace-session --session ses_<id> --dry-run
./bin/mempalace-session --session ses_<id> # file into palace
mempalace_search "a phrase from that session" # verify visibility
./bin/mempalace-session --session ses_<id> # re-run → should skip
For mempalace-docs, test on a small repo (e.g. this one) first:
./bin/mempalace-docs "$PWD" --dry-run
Gotchas
install.shis idempotent but interactive — use--yesin non-interactive contexts.~/.local/binmust be on$PATH. The installer warns if not.- The companion skill lives at
~/.agents/skills/opencode-mempalace-bridge/SKILL.mdand is a symlink into this repo. Editing that file editsSKILL.mdhere. To propagate to Claude Code / Kiro, runagents-syncfromcli_utils. - The opencode DB path defaults to
~/.local/share/opencode/opencode.db. Override via$OPENCODE_DBor--db. - The mempalace miner skips symlinks (as of v3.3.3 —
miner.pyline ~828). That's why the wrappers usecp -p/ explicit file writes for staging, not symlinks. - The convos miner dedups on
source_filepath only (no mtime check). Staging filenames must be stable per session; deleting a staged JSONL forces a re-mine. - The docs miner dedups on
source_filepath +mtime. That's why staging usescp -p(preserves mtime).
Colocated skill pattern
This repo owns an agent skill (SKILL.md) that lives alongside the code it documents, rather than in a central skills repo like skillset. The advantages: the skill moves in lockstep with the wrappers it explains, one git clone gets you the full producer-side setup, and retirement (when upstream gaps close) removes skill + code + docs in one commit.
The convention for making this coexist cleanly with sibling tooling:
install.shcreates~/.agents/skills/<name>/as a real directory containing aSKILL.mdsymlink back into this repo. It does not create a dir-symlink, because real dirs are the signal that sibling reconcilers (skillset'sdeploy-skills.sh, cli_utils'sagents-sync.zsh) should leave the dir alone.install.shdrops a.skill-sourcemarker file at the root of the skill dir:This is a breadcrumb for humans and future tooling — it answers "who owns this skill dir?" at a glance.# skill-source: mempalace-toolkit # repo: <absolute path> # url: ssh://git@gitea.jordbo.se:2222/joakimp/mempalace-toolkit.gitdeploy-skills.shandagents-sync.zshdon't read it today (their existing logic already handles external dirs correctly) but may surface it in status reports later.install.sh --uninstallremoves the marker (only if it still saysmempalace-toolkit) and the now-empty skill dir.
If you add a third colocated skill from a new repo, follow the same convention. The marker format is shared; only the repo name changes.
History
Split out from cli_utils on 2026-04-30. The wrappers originated there but the conceptual fit was weak (cli_utils is interactive shell tools; these are agent memory infrastructure). Some older diary entries and KG facts in the palace reference the original paths.