Restored formatter output handling for stdout/stderr-writing formatters;
warping a session to another workspace can now carry over uncommitted
file changes; restored custom provider setup in /connect; macOS Settings
menu entry; desktop local server split into a separate utility process;
ACP clients restore last model/mode/effort when loading sessions and can
close sessions cleanly.
No container-level changes.
Mempalace's embedding function is chromadb's ONNXMiniLM_L6_V2, which
downloads ~80 MB of all-MiniLM-L6-v2 ONNX weights from chromadb's CDN
on first use. Without pre-warming this happened silently in the
entrypoint init step (output redirected to /dev/null) and stalled
first container start by multiple minutes on slow networks — the
symptom user reported as 'hangs at Initializing MemPalace for
workspace'.
Fix: invoke the embedding function once at build time as gosu
developer so the cache lands at the runtime user's
~/.cache/chroma/onnx_models/all-MiniLM-L6-v2/ with correct ownership
and survives container recreate (cache path is not on a named
volume, so it lives in the image layer).
Build-time cost: ~3-5 s to download. Runtime saving: minutes per
fresh container.
Image size: 2110 → 2277 MB for the with-pi variant. Still within
the 2700 MB smoke-test threshold.
Optional integration of pi-coding-agent alongside opencode in the same
container. Both harnesses share the mempalace install and palace path —
wing/diary entries are mutually visible.
Build:
--build-arg INSTALL_PI=true # opt-in
--build-arg PI_VERSION=0.73.1 # pin a version (default: latest)
--build-arg INSTALL_OPENCODE=false # build pi-only image
Dockerfile:
• New INSTALL_PI block: npm install -g @mariozechner/pi-coding-agent
+ git-clones pi-toolkit and pi-extensions to /opt/.
• Existing opencode install gated behind new INSTALL_OPENCODE arg
(default true; existing builds unaffected).
• mkdir adds ~/.pi/agent/extensions for the named volume mount root.
• CMD changed from ['opencode'] to ['bash', '-l']. compose run --rm
devbox now drops to a login shell so users pick the harness; pass
'opencode' or 'pi' explicitly to launch directly. compose exec
workflows are unaffected (bypass entrypoint+CMD).
entrypoint.sh:
• Adds ~/.pi to volume ownership loop.
entrypoint-user.sh:
• New 'pi: deploy toolkit + extensions + mempalace bridge' block runs
pi-toolkit/install.sh, pi-extensions/install.sh, settings.json
template bootstrap, then symlinks the mempalace.ts bridge directly.
Order: toolkit before extensions before bridge. mempalace-toolkit's
full install.sh is intentionally NOT called (its install_skill
would race with skillset auto-deploy --prune-stale).
docker-compose.yml:
• New devbox-pi-config named volume mounted at /home/developer/.pi.
Persists user toggles (/ext-disabled extensions) and settings.json
edits across container recreate. Mirrors devbox-opencode-config
pattern from v1.14.33.
scripts/smoke-test.sh:
• New --variant with-pi (threshold 2700 MB) and --variant omos-with-pi
(3400 MB).
• Pi assertions gated on `command -v pi`: version, /opt/pi-toolkit
clone HEAD, /opt/pi-extensions clone HEAD, deployed keybindings
symlink, ≥4 extension symlinks, mempalace.ts bridge symlink,
settings.json bootstrap.
• Pi state assertions use docker exec from the host (not 'run'),
since the container has no docker CLI.
• opencode core test now gated on INSTALL_OPENCODE presence.
scripts/generate-dockerhub-md.py:
• SECTION_RULES adds 'pi (alternative/complementary harness)': drop.
Section stays in README; dropped from DOCKER_HUB.md to keep under
the 25 kB Docker Hub limit.
Docs:
• README adds full 'pi (alternative/complementary harness)' section.
• AGENTS.md codifies pi install contract, deploy ordering, named
volume rationale, and CMD change.
• CHANGELOG.md gets an Unreleased entry.
• .env.example documents new build args.
• docker-compose.yml example args block updated.
Verification (local builds on arm64):
• Default (INSTALL_PI=false): 1871 MB, all assertions pass — no
regression.
• INSTALL_PI=true: 2110 MB (within 2700 threshold), 37 assertions
pass including pi version, all 7 extensions deployed (6 from
pi-extensions + mempalace.ts bridge), settings.json bootstrap.
Not yet:
• CI workflow updates to add -with-pi tag variants. Deferred until
local path stabilizes through user testing.
• pi-devbox separate repo for fully stripped pi-only image. Phase 2.
The v1.14.30b publish failed on both variants with 'No space left on
device' — arm64 QEMU-emulated layers were stored alongside amd64 on the
same ~40 GB runner, and the mempalace-toolkit bake-in from v1.14.30b
tipped peak disk over the edge during the nodejs dpkg unpack and the
git-lfs layer export.
Refactor docker-publish.yml to the canonical push-by-digest +
manifest-merge pattern: smoke test (amd64) runs on its own runner, each
(variant x arch) push target runs on its own fresh runner with
outputs=type=image,push-by-digest=true,push=true (no local image
store), then a tiny merge job assembles the multi-arch manifest with
docker buildx imagetools create from digest artifacts. Per-runner disk
peak is roughly one-quarter of the old single-job peak. The four
Docker Hub tags per release are unchanged. As a bonus, amd64 and arm64
now build in parallel.
No image-level changes beyond the opencode bump.
The scheduler templates in mempalace-toolkit's contrib/ assume
mempalace-session is available inside the container, but the image
never actually installed it. Users following the *-devbox scheduler
docs would silently lose the wrappers on every container recreate,
because the only way to get them was a post-hoc install.sh inside
the container — which lives in the ephemeral layer. The host-side
systemd timer would then fire, docker exec in, and hit
"mempalace-session: command not found".
Caught during runtime validation on 2026-04-30: host-side systemd
unit ran cleanly at 16:15 today, then the container was rebuilt
and recreated, and the wrappers were gone. The rebuild produced
an image that the scheduler template's own documented precondition
did not hold for.
Fix: new Dockerfile block clones mempalace-toolkit at build time
(depth-1) to /opt/mempalace-toolkit/, symlinks bin/mempalace-session
and bin/mempalace-docs into /usr/local/bin/, asserts both respond
to --help before the layer succeeds. Gated by
INSTALL_MEMPALACE_TOOLKIT=true (defaults on, depends on
INSTALL_MEMPALACE=true). Floated ref via MEMPALACE_TOOLKIT_REF=main
for auto-picking-up toolkit updates; override for reproducible
builds once the toolkit starts tagging releases.
Smoke test gains three assertions (mempalace-session --help,
mempalace-docs --help, symlink target check). Resolved-versions
preamble logs the toolkit git short-SHA alongside the other
floated components, so CI logs always record what got baked in.
README gains a Scheduled mining (mempalace-toolkit) subsection
and a build-args row. DOCKER_HUB.md regenerated; sync-check passes.
The mempalace Python package ships a 'mempalace-mcp' console entry
point; 'uv tool install' places it on PATH as a shim whose shebang
points at the isolated venv's Python. Our hand-rolled wrapper at
/usr/local/bin/mempalace-mcp-server was duplicating what uv installs
for free — one less file to maintain.
Fixes the MCP error users saw after the v1.14.28b → v1.14.29 upgrade
path: custom opencode.json files typically had the pre-v1.14.29
command ['python3', '-m', 'mempalace.mcp_server'] which worked with
the old pip install but fails silently after the uv-tool migration
because system python3 cannot import from the venv. Opencode surfaced
this as 'MCP error -32000: connection closed'.
- generate-config.py now emits ['mempalace-mcp'] and keys its detect
on shutil.which('mempalace-mcp').
- Dockerfile drops 'COPY rootfs/usr/local/bin/' and the chmod of the
wrapper. Build shrinks from 30 to 29 stages.
- rootfs/usr/local/bin/ removed entirely.
- Smoke test asserts /usr/local/bin/mempalace-mcp is executable and
prints its symlink target.
- README's MemPalace section shows ['mempalace-mcp'] and explicitly
warns against the old pattern with the observed failure mode.
- CHANGELOG adds a v1.14.29c entry.
Pair 'apt-get upgrade -y --no-install-recommends' with the existing
update + install in the first RUN step. Picks up security/CVE fixes
that land in the Debian repos between base-image rebuilds. Same layer
as the install to avoid bloating history; combined with apt-get clean
and rm -rf /var/lib/apt/lists/* at the end so no index cache is kept.
Today this is a no-op (debian:trixie-slim is current: 0 upgraded).
Future-proofs against the lag between a CVE fix being published and
the next base-image rebuild.
The first v1.14.29b build attempt failed with an HTTP 502 from
GitHub's release CDN mid-download of zoxide. Single-shot curl had no
retry, so one transient 502 failed the entire OMOS build.
- curl --retry 5 --retry-delay 5 --retry-all-errors on every tool
download (both -fsSL GETs and -sI HEAD redirect lookups).
- [ -n "$V" ] assertion after each version-resolution step, so a
failed HEAD lookup fails fast with a clear message instead of
producing an empty tag that then 404s on the download URL.
- Same hardening applied to the optional Go install block and the
nodesource setup_22.x pipe.
entrypoint-user.sh gated OMOS auto-install on 'command -v bunx', but
neither upstream bun installer nor our Dockerfile creates a bunx
symlink — only the bun binary exists on PATH. The check always failed
on a fresh OMOS image, printing 'ENABLE_OMOS=true but bun is not
installed.' even though bun was right there. Latent until now because
the only exercised path had a persisted oh-my-opencode-slim.json from
a prior install.
Fixes:
- Gate on 'command -v bun' instead of bunx.
- Call 'bun x oh-my-opencode-slim@latest install ...' (bun x is the
real subcommand that actually works with only the bun binary).
- Add 'ln -sf bun /usr/local/bin/bunx' in the Dockerfile OMOS block
so interactive users can still type bunx by habit.
- Smoke test asserts the bunx symlink exists on the OMOS variant.
Also verify 'test -L /usr/local/bin/bunx' as a build-time sanity check.
Can't use 'bunx --help' for that — bun exits 1 on help output even
though it prints the usage correctly.
Also finalizes the CHANGELOG Unreleased section as v1.14.29 — 2026-04-28.
This is the first release to carry the infrastructure pass from the
preceding commit: floating upstream versions, uv-tool-installed
mempalace, sentinel-based chown optimization, and the new CI validate
workflow with smoke tests.
Main changes:
- Extract opencode.json generation from entrypoint-user.sh into a
standalone Python script (rootfs/usr/local/lib/opencode-devbox/
generate-config.py). Preserves the never-overwrite-existing-config
guarantee. Cuts entrypoint-user.sh from 176 to 97 lines.
- Install MemPalace via 'uv tool install' into an isolated venv at
/opt/uv-tools/mempalace/ with a /usr/local/bin/mempalace-mcp-server
wrapper, replacing the 'pip install --break-system-packages' escape
hatch. The wrapper is what generate-config.py references in the
auto-generated opencode.json. Also fix 'mempalace init' in
entrypoint-user.sh to use --yes so first-start initialization isn't
interactive (this used to hang or print prompts into the user's
terminal). Gated by INSTALL_MEMPALACE build arg (default true) so
users who don't need AI memory can shave ~300 MB.
- Sentinel-file pattern in entrypoint.sh volume-ownership loop: write
.devbox-owner after a successful chown -R, skip the recursive walk
on subsequent starts when the sentinel matches FINAL_UID:FINAL_GID.
Cuts multi-second startup costs to milliseconds on large volumes
(nvim plugins, palace data). UID changes still trigger a full chown.
- Float all GitHub/Gitea-hosted binary versions: gosu, fzf, git-lfs,
neovim, bat, eza, zoxide, uv, gitea-mcp now default to 'latest' and
resolve the newest upstream release at build time via the /releases/
latest redirect. Go (go.dev JSON feed) and oh-my-opencode-slim (npm
@latest) likewise. Intentional pins still in place: OPENCODE_VERSION,
NODE_VERSION=22, DEBIAN_VERSION=trixie-slim. Each *_VERSION ARG
accepts an explicit value to lock a specific version when needed.
- New scripts/smoke-test.sh verifies binary presence, opencode startup,
entrypoint user drop, generate-config idempotency, bun's presence-
per-variant, and image size against thresholds (2500 MB base, 3000
MB OMOS). Prints resolved component versions as its first step so
CI logs always record what got baked into a given image.
- New .gitea/workflows/validate.yml runs on push to main and PRs:
single-arch amd64 build, smoke test, DOCKER_HUB.md sync check. Tag-
triggered docker-publish.yml now smoke-tests each variant on amd64
before the full multi-arch push.
- scripts/generate-dockerhub-md.py auto-generates DOCKER_HUB.md from
README.md using explicit SECTION_RULES. --check mode fails CI when
the committed file is out of sync. Enforces the 25 kB Docker Hub
limit. Adding a new README section forces an explicit keep/drop/
replace decision.
- Remove dead INSTALL_PYTHON build arg (was a no-op since mempalace
added python3 unconditionally).
Python 3 has been unconditionally present since the Debian trixie
upgrade (e58962a, Apr 13) — python3 3.13 ships as a transitive
dependency of the trixie base image. python3-pip (e1029bb) and
python3-venv (3a7ec45) were later added to the base layer on Apr 23
so Mason could install Python-based LSPs (ruff, ansible-lint) into
venvs on nvim startup. MemPalace's pip install (b9c08c3) just
piggybacks on what was already there.
In other words, INSTALL_PYTHON=true has been a no-op reinstall of
already-installed packages for two weeks before MemPalace existed.
The flag is dead weight and the docs that advertise it as meaningful
are misleading. Remove it everywhere.
Users who want Python tooling should use the pre-installed uv/uvx.
Install gitea-mcp v1.1.0 (Go binary from gitea.com/gitea/gitea-mcp)
using the same multi-arch pattern as gosu/fzf/bat. Provides 50+ MCP
tools for Gitea API — repos, issues, PRs, releases, branches, wiki,
and Actions.
Disabled by default in auto-generated opencode.json (requires
GITEA_ACCESS_TOKEN and GITEA_HOST to be useful). Users enable it by
setting those env vars in .env and flipping enabled to true in their
opencode.json.
Env vars forwarded into the container via docker-compose.yml and
docker-compose.shared.yml environment blocks. Same {env:VAR} pattern
as the GitHub MCP server.
Docs updated: README.md (new Gitea MCP section with setup steps),
DOCKER_HUB.md (tools list), CHANGELOG.md (v1.14.28b entry).
Install mempalace via pip in the Dockerfile. Provides 29 MCP tools
for semantic search over conversation history, knowledge graph
queries, agent diaries, and wing/room/drawer management. Everything
runs locally — no API keys, no data egress.
Integration:
- Dockerfile: pip install mempalace (with --break-system-packages
for Debian trixie PEP 668 compliance)
- entrypoint-user.sh: auto-initializes palace for /workspace on
first run (idempotent, skips if palace exists)
- entrypoint.sh: adds ~/.mempalace to the volume ownership-fix loop
- docker-compose.yml + shared: optional devbox-palace named volume
at ~/.mempalace (commented out by default — user opts in)
Users configure MCP integration by adding a mempalace server entry
to their opencode.json. No wrapper plugin needed — the upstream
Python MCP server is used directly.
Docs updated: README.md (new MemPalace section with setup, MCP
config, usage examples, storage details), DOCKER_HUB.md (data
storage table + tools list), CHANGELOG.md (unreleased entry).
python3-pip alone wasn't enough — Debian trixie ships python3 and
python3-pip as separate packages from python3.13-venv. Mason creates
a venv per package then pip-installs into it. Without python3-venv,
'python3 -m venv' fails with 'ensurepip is not available' and every
Mason Python package (ruff, ansible-lint, etc.) errors on every nvim
start.
Adding python3-venv (which pulls in ensurepip + pip-whl + setuptools-whl)
completes the chain: venv creation works, pip is available inside the
venv, Mason installs succeed.
Mason (neovim's package manager) creates a Python venv and runs
'pip install' inside it to install Python-based LSP servers like
ruff and ansible-lint. Debian trixie's python3 package ships without
ensurepip, so the venv has no pip and Mason fails with
'spawn: python3 failed with exit code 1'.
Adding python3-pip to the apt install list gives Mason what it needs.
uv is still available as the preferred user-facing Python tool
manager; pip is here specifically for Mason's internal use.
Bump OPENCODE_VERSION ARG from 1.14.19 to 1.14.20 to track the new
upstream release on npm.
Clarify the tagging convention in AGENTS.md: the first build on a new
opencode version uses the bare 'v{opencode_version}' tag (no letter
suffix). Letter suffixes (a, b, c, ...) are reserved for container-
level rebuilds on the same opencode version (CVE fixes, doc changes,
entrypoint bugs). The previous wording implied a letter was always
required, which was never the actual behaviour.
Previous behaviour (e4063b5) COPY'd .bash_aliases and .inputrc
directly into /home/developer/ during image build. That silently
shadowed any host bind-mount or in-container customization for users
upgrading from v1.14.19b — if you'd written your own .bash_aliases
and rebuilt the container, our baked version would overwrite it
without warning.
Ship the files to /etc/skel-devbox/ instead. The entrypoint copies
them to $HOME only if the target file does not already exist, so:
- Fresh containers get the defaults automatically (unchanged)
- Host bind-mounts win (they materialize before the entrypoint runs)
- Existing in-container customizations survive upgrades
- Defaults remain discoverable at /etc/skel-devbox/ for anyone who
wants to copy, diff, or reset back to upstream
Docs (README.md, DOCKER_HUB.md, deploy/README.md) describe the new
skel layout and the restore/diff commands.
Two changes that address a longstanding frustration: bash history is
lost on every container recreate, and the container's ~/.bashrc and
~/.inputrc are stock Debian (no history tuning, no prefix search on
arrow keys, no integrations).
Added a named volume 'devbox-shell-history' mounted at ~/.cache/bash
with HISTFILE pointing there; history now survives 'docker compose up
--force-recreate'. The volume is added to both docker-compose.yml and
docker-compose.shared.yml, and ~/.cache/bash is registered in the
entrypoint ownership-fix loop per the AGENTS.md convention.
Baked rootfs/home/developer/.bash_aliases (sourced automatically by
Debian's default ~/.bashrc) and rootfs/home/developer/.inputrc into
the image. They give new containers: 100k-entry timestamped dedup
history with per-prompt flush, Up/Down arrow prefix history search,
case-insensitive coloured completion, aliases that prefer eza and
bat when present, git shortcuts, interactive rm/mv/cp, zoxide and
fzf (via 'fzf --bash') integration, and a [devbox] prompt marker.
The fzf integration uses 'fzf --bash' because we install fzf from
GitHub releases, not apt — the apt-path key-bindings aren't present.
Users who prefer their host's own shell config can uncomment two
commented bind-mount lines in docker-compose.yml to shadow the
baked defaults.
Reduces locale generation from 200+ to 16 targeted locales (major world
languages + Nordic + key European). Saves build time and image size.
Users can add more at runtime via locale-gen.
Install rustup-init binary from Rust CDN. Users bootstrap Rust with
'rustup-init -y' — persists via devbox-rustup and devbox-cargo volumes.
Add JavaScript/TypeScript development docs (Node.js + npm in base, Bun in OMOS).
Install uv from GitHub releases (~23MB). Users can install Python with
'uv python install 3.12' — persists across restarts via devbox-uv volume.
Eliminates need for a separate Python image variant.
Replace vim-tiny with neovim from GitHub releases (pinned, multi-arch).
Add bat, eza, zoxide from GitHub releases and tmux, htop, patch from apt.
Move tmux from OMOS-only to base image. Set EDITOR=nvim.
Add neovim config mount option to docker-compose and docs.
Integrate oh-my-opencode-slim as an opt-in feature via INSTALL_OMOS build arg.
A single build arg installs Bun, tmux, and the plugin; runtime activation is
controlled by ENABLE_OMOS and related env vars in the entrypoint.