joakimp 118bd20fec feat(extensions/pi): ship pi-env.zsh shell loader
The loader that sources ~/.config/pi/.env into every shell was only
living in the myconfigs tor-ms22 backup \u2014 a fresh machine had nowhere
to get it from except copying by hand. Now canonical here.

- extensions/pi/pi-env.zsh: 20-line POSIX-compatible loader
  (set -a; source ~/.config/pi/.env; set +a). Works in bash and zsh.
- install.sh install_pi_env_loader:
  * oh-my-zsh detected (~/.oh-my-zsh/custom/ exists)
    \u2192 cp into that dir (NOT symlink \u2014 that dir is typically part of
      a dotfiles backup, and a symlink to mempalace-toolkit would
      break when restored on another host).
    \u2192 Idempotent: if target content matches repo, says 'already
      installed'. If it differs, leaves user edits alone and points
      at diff for manual reconcile.
  * No oh-my-zsh \u2192 prints source-this-line snippet for ~/.zshrc or
    ~/.bashrc (derived from $SHELL). Does NOT auto-edit rc files.
- install.sh uninstall: only removes the copy if content still matches
  repo. Local edits preserved.
- Docs:
  * extensions/pi/README.md Environment setup section rewritten with
    both install paths, step 5 of deploy recipe updated.
  * AGENTS.md Structure block lists pi-env.zsh.
  * Root README repo-contents line mentions it.

Verified on tor-ms22: install fresh \u2192 uninstall (content match \u2192 remove)
\u2192 reinstall \u2192 zsh -ic loads AWS vars correctly. Also tested bash fallback
path via HOME=/tmp/fake-home SHELL=/bin/bash \u2014 prints right .bashrc snippet.
2026-05-05 16:58:56 +02:00
2026-04-30 07:31:14 +02:00

mempalace-toolkit

Producer-side tooling for MemPalace — bridges that feed opencode session history and project documentation into the palace. Pairs with the consumer-side mempalace agent skill.

What this repo contains:

  • bin/mempalace-session — exports opencode session history from its local SQLite DB to Claude Code JSONL, then mines it via mempalace mine --mode convos.
  • bin/mempalace-docs — mines project directories into MemPalace while excluding source code, keeping the palace signal-dense.
  • ARCHITECTURE.mdcanonical spec: architecture diagram, component details, setup recipe, operational notes, upstream-retirement roadmap.
  • SKILL.md — the companion agent skill, symlinked into ~/.agents/skills/opencode-mempalace-bridge/ on install.
  • extensions/pi/ — pi coding-agent bridge: the MemPalace MCP extension (symlinked), a mosh/tmux-friendly keybindings file (symlinked), a pi-env.zsh shell loader (copied into ~/.oh-my-zsh/custom/ on zsh+omz hosts, or source'd from .bashrc / .zshrc otherwise), and a settings.example.json template for starting pi without --model. install.sh also probes for AWS_PROFILE/AWS_REGION (needed by pi's Bedrock provider) and points at the recommended ~/.config/pi/.env layout if missing.

If you're just trying to get this working on a new machine → jump to Setup. If you want the full architecture story → read ARCHITECTURE.md.


Why this exists

MemPalace is the agent memory layer. Its stock CLI has two gaps that bite on a machine running opencode with a docs-first palace policy:

  1. mempalace mine floods the palace with source code — every __init__ fragment, every generated file, hundreds of low-signal drawers per project. mempalace-docs fixes this by staging only documentation-class files (*.md, *.yml, Dockerfile, etc.) before mining.
  2. mempalace mine --mode convos can't read opencode's SQLite DB — only file-based chat formats (Claude Code JSONL, Claude.ai JSON, ChatGPT, Slack, Codex). Opencode persists every turn in ~/.local/share/opencode/opencode.db and has no upstream hook into mempalace's auto-save. mempalace-session fixes this by exporting each session to Claude Code JSONL before mining.

Both wrappers follow the same stage-to-cache-then-mine idiom. Neither reimplements the miner; they curate input and delegate.

Long-term, both should retire:

  • mempalace-docs → retires when MemPalace PR #1213 (exclude_patterns in mempalace.yaml) merges.
  • mempalace-session → retires when opencode session-stopping hooks (PR #16598 et al.) merge and hooks_cli.py gains an opencode harness. Until both land, this repo fills the gap.

See ARCHITECTURE.md §6 for the full upstream roadmap.


Setup

Prerequisites

  • MemPalace CLI v3.3.3+ — see Installing mempalace itself below if you haven't already.
  • Python 3 (stdlib sqlite3 only — no extra deps)
  • opencode with an active session DB at ~/.local/share/opencode/opencode.db (only needed for mempalace-session)
  • The mempalace wake-up protocol at ~/.config/opencode/instructions/mempalace.md — without it, opencode loads the mempalace skill but never auto-runs it at session start, so most of mempalace's value is forfeited silently. Shipped by the skillset repo; deploy via ./deploy-skills.sh --bootstrap once per machine. mempalace-toolkit/install.sh probes for this file and warns if it's missing.
  • The mempalace MCP server registered in ~/.config/opencode/opencode.json — without this, opencode has no way to reach the mempalace tools and every mempalace_* call silently fails. See Registering mempalace with opencode below for the one-line JSON stanza. install.sh probes for this too.

Installing mempalace itself (prerequisite)

mempalace-toolkit wraps the mempalace CLI but does not bundle it. The upstream MemPalace repo documents pip install mempalace as the install method; uv tool install is cleaner and is the flow used in production containers like opencode-devbox.

Why uv over pip:

  • Isolated venv per tool — mempalace's dependencies (chromadb, embedding model runtime, …) don't leak into system Python or your project venvs.
  • No PEP 668 fight — modern Debian / Ubuntu / Homebrew Python all refuse pip install into the system site-packages. uv tool install sidesteps this entirely.
  • The shim (~/.local/bin/mempalace by default) is a thin wrapper that automatically activates the isolated venv on invocation, so mempalace is available from any bash or zsh terminal without manual source venv/bin/activate.

Install uv if it's not already on the machine:

# macOS / Linux, official installer — puts uv in ~/.local/bin
curl -LsSf https://astral.sh/uv/install.sh | sh

# Or: Homebrew on macOS
brew install uv

# Verify
uv --version
# Installs mempalace into an isolated venv under ~/.local/share/uv/tools/mempalace/,
# puts the `mempalace` shim into ~/.local/bin/.
uv tool install mempalace

# Make sure ~/.local/bin is on $PATH (uv prints this if it isn't)
export PATH="$HOME/.local/bin:$PATH"   # add to ~/.bashrc or ~/.zshrc

# Verify
mempalace --version     # should print the installed version
which mempalace         # should point into ~/.local/bin/

After this, mempalace works the same from any bash or zsh terminal — interactive shell, script, cron, systemd user service, launchd agent, all fine.

To upgrade later: uv tool upgrade mempalace (or --all). To uninstall: uv tool uninstall mempalace.

System-wide / container install (opencode-devbox pattern)

For a Docker image or a multi-user box where the shim should live on the system PATH rather than in each user's ~/.local/bin, use UV_TOOL_DIR + UV_TOOL_BIN_DIR to relocate both the venv and the shim:

# In the Dockerfile — this is the pattern used by opencode-devbox
ENV UV_TOOL_DIR=/opt/uv-tools
ENV UV_TOOL_BIN_DIR=/usr/local/bin

RUN mkdir -p /opt/uv-tools && \
    uv tool install --no-cache mempalace && \
    /opt/uv-tools/mempalace/bin/python -c "import mempalace; print('mempalace installed')"

After this:

  • /opt/uv-tools/mempalace/ — the isolated venv.
  • /usr/local/bin/mempalace — the CLI shim (globally on PATH, works for every user).

The last python -c line in the RUN step is a build-time sanity check: if the install silently failed, the build fails here rather than at runtime.

See opencode-devbox/Dockerfile §"MemPalace install" for the full production version (adds INSTALL_MEMPALACE=true build arg so the install can be skipped to shave ~300 MB off the image).

Registering mempalace with opencode (or other MCP clients)

Installing mempalace via uv tool install puts two shims on PATH: mempalace (the CLI) and mempalace-mcp (the MCP server). Neither the skill nor mempalace-toolkit will give you anything useful in opencode until the MCP server is registered in ~/.config/opencode/opencode.json.

Add to opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "mempalace": {
      "type": "local",
      "command": ["mempalace-mcp"]
    }
  }
}

If you already have other MCP servers configured, add the mempalace entry into the existing mcp object — don't replace the whole file. opencode's JSON is merged shallow-ly.

Minimal full opencode.json for someone starting fresh (adjust model to your preferred provider):

{
  "$schema": "https://opencode.ai/config.json",
  "model": "anthropic/claude-opus-4-6",
  "share": "disabled",
  "autoupdate": false,
  "instructions": [
    "~/.config/opencode/instructions/mempalace.md"
  ],
  "mcp": {
    "mempalace": {
      "type": "local",
      "command": ["mempalace-mcp"]
    }
  }
}

Note the instructions array: this is what tells opencode to load the wake-up protocol at session start (see the previous section on installing that file via skillset --bootstrap).

Custom palace path (rare — default ~/.mempalace/palace/ works for everyone):

"command": ["mempalace-mcp", "--palace", "/path/to/palace"]

Claude Code has a one-liner helper:

claude mcp add mempalace -- mempalace-mcp

…or mempalace mcp prints the current recommended snippets on demand:

mempalace mcp   # shows `claude mcp add` + direct-run commands

After editing the config, restart opencode (or your MCP client). Verify the connection:

# From inside an opencode session:
#   mempalace_status   → should return palace stats, not "tool unavailable"

If the MCP tools don't show up in your agent's tool list, the most common causes are:

Symptom Likely cause Fix
mempalace_* tools absent from tool list entirely opencode.json not re-read Restart opencode.
Server reported "unavailable" at startup Shim not on PATH, or CLI itself broken Run mempalace-mcp --help manually; fix PATH or re-run uv tool install mempalace.
ModuleNotFoundError: No module named 'mempalace' in logs You configured ["python3", "-m", "mempalace.mcp_server"] instead of ["mempalace-mcp"] Use the shim. See the legacy-fallback section below if you specifically need the python-invocation form.

Legacy fallback: mempalace-mcp-server wrapper

Older MCP configs sometimes reference ["python3", "-m", "mempalace.mcp_server"]. This worked when mempalace was installed via pip install --break-system-packages into the system site-packages, but breaks after switching to uv tool install — system python3 cannot import mempalace from the isolated venv. On opencode-devbox a thin wrapper script on PATH bridged the two worlds during the transition:

#!/bin/sh
# /usr/local/bin/mempalace-mcp-server
exec /opt/uv-tools/mempalace/bin/python -m mempalace.mcp_server "$@"

With the wrapper in place, MCP configs referencing ["mempalace-mcp-server"] work on a uv tool install setup.

This is legacy; don't use it for new installs. The modern ["mempalace-mcp"] form — the uv-tool shim — does the same job without the extra script. The wrapper is documented here only so you know what to look for if you encounter an older config or a machine that was set up during the transition.

Verification checklist

After any install (personal or system-wide), confirm:

# CLI reachable from PATH
which mempalace                    # → a shim path
mempalace --version                # → v3.3.3+ without import errors

# CLI can import its own modules (catches venv vs site-packages mismatch)
mempalace status 2>&1 | head -3    # → either palace stats or "No palace found" — not a Python traceback

# MCP shim reachable and runnable
which mempalace-mcp                # → a shim path matching the `mempalace` CLI location
mempalace-mcp --help 2>&1 | head -5   # → MCP server help, not a Python traceback

If any of these produce ModuleNotFoundError, the isolated venv is broken — re-run uv tool install --force mempalace (or the system-install equivalent with UV_TOOL_DIR set) and check the verification again.

Install mempalace-toolkit

git clone ssh://git@gitea.jordbo.se:2222/joakimp/mempalace-toolkit.git ~/mempalace-toolkit
cd ~/mempalace-toolkit
./install.sh

The installer symlinks bin/* into ~/.local/bin/ and installs the agent skill into ~/.agents/skills/opencode-mempalace-bridge/. If pi is installed (detected via ~/.pi/agent/extensions/), it also symlinks extensions/pi/mempalace.ts into that directory so the pi↔mempalace bridge tracks version control. On machines without pi this step is silently skipped. Works on macOS and Linux.

Ensure ~/.local/bin is on $PATH:

export PATH="$HOME/.local/bin:$PATH"

If install.sh reports Skipping <name> — already exists: there's a leftover symlink or file at ~/.local/bin/<name> from a previous install (e.g. the pre-split cli_utils days). The installer prints the exact rm && ./install.sh command to fix it — remove the stale entry and re-run. It will never clobber an existing file without the user explicitly removing it first.

Deploying pi on a new machine (full recipe)

If the target machine also runs pi, there's a longer multi-step recipe covering dotfiles provisioning (tmux CSI-u keys, ~/.config/pi/.env, zsh loader), mempalace install, pi settings bootstrap (starting pi without --model), and the AWS env verification. It lives in extensions/pi/README.md § Deploying pi on a new machine so the step-by-step stays next to the files it installs.

Quick summary:

# 1. Dotfiles (tmux extended-keys, ~/.config/pi/.env, pi-env.zsh)
git clone <myconfigs> && cd myconfigs && ./provision.sh --profile <profile>

# 2. pi (upstream)         3. mempalace CLI
brew install pi-coding-agent    uv tool install mempalace

# 4. This repo's install.sh
cd ~/mempalace-toolkit && ./install.sh

# 5. pi settings (one-time bootstrap, region-specific)
cp extensions/pi/settings.example.json ~/.pi/agent/settings.json
$EDITOR ~/.pi/agent/settings.json   # adjust eu./us./anthropic: prefix

# 6. Open fresh shell, run `pi`. Wake-up auto-injection proves end-to-end.

First mine

# Mine opencode session history into wing_conversations (no init needed)
mempalace-session --dry-run      # preview qualifying sessions
mempalace-session                # do it (~20 min per 60 sessions)

# Mine a project (docs only). If you want to pre-init the project with a
# custom wing name or entity config, run `mempalace init --yes <dir>` first;
# otherwise `mempalace-docs` derives the wing from the directory name.
mempalace-docs /workspace/my_project --dry-run
mempalace-docs /workspace/my_project

Note: mempalace has no one-time global init. The palace itself is created lazily on first write (at ~/.mempalace/palace/). mempalace init <dir> is a per-project command that sets up a mempalace.yaml + entity list for a specific source directory — optional, not a prerequisite for either wrapper.

Diary vs session mine: why keep both?

Automated session mining captures every turn verbatim into wing_conversations. But agents are still expected to write a short AAAK-compressed diary entry at wind-down (the consumer-side mempalace skill calls this out as mandatory). They're not redundant — they answer different questions:

  • Session mine = git log with diffs. "What did we say exactly?" Raw, searchable, complete. High noise.
  • Diary = release notes. "What did we decide / learn / accomplish?" Curated, compressed, recency-scanned. The agent's editorial judgment of what mattered, including meta-observations that were never said aloud.

A machine running only one of these has half a memory. Full treatment with practical implications in ARCHITECTURE.md §5 → "Diary vs session mine: why keep both?". Short answer: automate the mine, keep writing diaries, and let them specialize.

Keeping it fresh (automation)

Manual invocation is fine while you're actively driving the machine, but long-running devboxes benefit from a weekly automated mine. contrib/ ships ready-to-install templates:

  • systemd user timer (recommended on Linux): survives reboots, catches missed runs, logs to journalctl.
  • launchd user agent (recommended on macOS): native-equivalent — logs to ~/Library/Logs/, single-instance guarantees, ProcessType=Background throttling.
  • cron: simplest, works on BSD and systemd-less distros. No user-unit awareness needed.
  • Devbox variants (*-devbox.*): if you run mempalace-session inside a long-lived container (e.g. opencode-devbox), the scheduler lives on the host and uses docker exec to reach the tool inside the container. Systemd and cron variants are included; both guard against "container currently stopped" so the timer is safe to leave enabled across dev cycles.

Quick-start (Linux / systemd, weekly Mon 03:00 local):

mkdir -p ~/.config/systemd/user
cp contrib/systemd/*.{service,timer} ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now mempalace-session.timer
sudo loginctl enable-linger "$USER"   # optional, for headless boxes

Quick-start (macOS / launchd, same schedule):

sed "s|USER|$USER|g" contrib/launchd/se.jordbo.mempalace-session.plist \
  > ~/Library/LaunchAgents/se.jordbo.mempalace-session.plist
mkdir -p ~/Library/Logs
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/se.jordbo.mempalace-session.plist
launchctl enable "gui/$(id -u)/se.jordbo.mempalace-session"

Quick-start (host-side scheduling for a long-running opencode-devbox container):

# systemd on the host → docker exec into the container
mkdir -p ~/.config/systemd/user
cp contrib/systemd/mempalace-session-devbox.{service,timer} ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now mempalace-session-devbox.timer
# If your container isn't named 'opencode-devbox' or its user isn't
# 'developer', run `systemctl --user edit mempalace-session-devbox.service`
# to set CONTAINER / CONTAINER_USER via an override.

See contrib/README.md for full install/verify/uninstall recipes, tuning, chooser table (host vs. devbox), and devbox/container caveats. The full operational routine (triggers, cadence, verification) is in ARCHITECTURE.md §5.

Containerized (devbox) notes

On a Docker-based devbox, the palace and opencode DB should live on named volumes so they survive container recreate:

  • devbox-palace~/.mempalace/palace
  • devbox-data~/.local/share/opencode

This repo is typically bind-mounted from the host, so code survives recreate and syncs via git. After a container recreate, ~/.local/bin is wiped — just re-run ./install.sh (idempotent) to relink.


mempalace-docs

Docs-only MemPalace miner. Stages documentation files into a cache dir and runs mempalace mine against the cache — never against the raw project dir.

mempalace-docs <directory>                     # mine with wing = dirname
mempalace-docs <directory> --wing my_project   # override wing name
mempalace-docs <directory> --agent alice       # record agent on drawers
mempalace-docs <directory> --dry-run           # list files, don't file
mempalace-docs <directory> --repair            # opt-in post-mine repair (risky, interactive only)
mempalace-docs --help

What gets mined: *.md, *.mdx, *.rst, *.txt, *.yml, *.yaml, *.toml, *.json, *.sh, *.bash, *.zsh, *.fish, Dockerfile*, Makefile*, *.conf, *.cfg, *.ini, LICENSE*, COPYING*, NOTICE*.

What gets skipped: .py, .ts, .tsx, .js, .jsx, .go, .rs, .java, .cpp, .c, .rb, .kt, .swift, build output directories (.git, .venv, node_modules, __pycache__, .mypy_cache, .pytest_cache, .ruff_cache, dist, build, .next, target, coverage), lockfiles.

Rationale: the palace is for context and intent. Agents already have grep/glob/Read for code — always authoritative, never stale. Embedding source code creates a parallel, lossier, drift-prone copy that pollutes semantic search for years.


mempalace-session

Opencode → MemPalace session bridge. Reads ~/.local/share/opencode/opencode.db, transforms each session into Claude Code JSONL, and files via mempalace mine --mode convos.

mempalace-session                                 # mine all sessions (≥3 msgs)
mempalace-session --wing my_convos                # custom wing (default: wing_conversations)
mempalace-session --session ses_abc123            # one session only
mempalace-session --since 2026-04-01              # only sessions updated on/after date
mempalace-session --min-messages 6                # stricter short-session filter
mempalace-session --db /custom/path/opencode.db   # non-default DB location
mempalace-session --dry-run                       # export + list, skip mine
mempalace-session --repair                        # opt-in post-mine repair (risky, interactive only)
mempalace-session --help

What gets exported per session:

  • Synthetic header injected as the first user turn ([session: <title> | <dir> | <date>]) so the palace can find sessions by topic, not just by ID.
  • Each message → Claude Code JSONL line ({"type": "user"|"assistant", "message": {"content": ...}}).
  • Tool calls → tool_use blocks. Known tools (Bash, Read, Grep, Edit, Write) get formatted summaries; unknown tools are JSON-serialized.
  • Tool outputs → tool_result blocks in a follow-up human message, folded back into the assistant turn by the mempalace normalizer.
  • step-start / step-finish parts are dropped as noise. reasoning parts are kept with a [reasoning] prefix.

Dedup: staging at ~/.cache/mempalace-session/<wing>/ with deterministic per-session filenames (<slug>_<id>.jsonl). The convos miner keys on source_file, so re-runs skip unchanged sessions. To force re-mining a session, delete its JSONL from the staging dir.

--dry-run is dedup-aware. Each session is tagged [NEW] (would be filed) or [SKIP] (already in the palace), and the summary breaks down the count:

Exported 62 session(s) to ~/.cache/mempalace-session/wing_conversations
  0 new   → will be filed on mine
  62 already filed → will be skipped (dedup by source_file)

--dry-run: no new sessions to mine. A real run would skip all 62.

If the palace is unreachable (fresh install, moved, permission-denied) the wrapper falls back to "everything is new" — the real mine step delegates dedup to mempalace mine --mode convos, which is always the source of truth. So running mempalace-session twice in a row is never destructive or wasteful: the second run's only cost is the post-mine HNSW repair step (~5 min on a ~5k-drawer palace).

Filter: sessions with fewer than --min-messages messages (default 3) are skipped — drops throwaway /exit'd sessions that would otherwise flood the palace. On a reference 140-session corpus, 78 were filtered this way.

Cost profile: ~20 minutes per 60-session batch. Scales roughly linearly with message count. Dedup re-run: mine step instant, only the post-mine repair runs (~5 min on 5k drawers).


Companion agent skill

Installing this repo symlinks SKILL.md into ~/.agents/skills/opencode-mempalace-bridge/SKILL.md, where it's auto-discovered by opencode (and by Claude Code / Kiro if you run agents-sync from cli_utils).

The skill is the short-form checklist for agents — when to use which wrapper, failure modes, setup recipes, anti-patterns. The canonical reference is always ARCHITECTURE.md; the skill points there for deep context.

The skill pairs with the consumer-side mempalace skill — that one covers using the palace (search, diary, KG); this one covers feeding it.

Colocated skill pattern. The skill lives here (not in skillset) because it moves in lockstep with the wrappers it documents. install.sh drops a .skill-source marker file in the deployed skill directory so sibling tooling (skillset's deploy-skills.sh, cli_utils's agents-sync.zsh) can tell the directory is externally owned. See AGENTS.md for the full convention and how to adopt it for future colocated skills.


See also

  • ARCHITECTURE.md — canonical spec: diagrams, setup recipe, failure modes, upstream roadmap.
  • AGENTS.md — repo conventions for AI agents modifying this codebase.
  • MemPalace — the memory layer itself.
  • opencode — the agent harness this bridges.
  • cli_utils — sibling repo with shell quality-of-life tools (origin of these wrappers before the 2026-04-30 split).
S
Description
Support for usage of mempalace with AI harnesses.
Readme MIT 559 KiB
Languages
Shell 86.4%
TypeScript 13.6%