release: v2.3.0 — image-baked fallback skills + opencode 1.17.10 + mempalace 3.5.0
- Add image-baked fallback skills (opencode-devbox-environment, mempalace) + harness instruction (instructions/opencode-devbox.md) under /usr/local/share/opencode-devbox/, symlinked in by entrypoint-user.sh (skills only-when-absent; instruction symlink to image, never copied into the devbox-opencode-config volume). Ported from pi-devbox v1.2.0/v1.2.1, adapted to opencode's ~/.config/opencode/instructions/ auto-load model. No pi-extensions skill (opencode has no fork/recall). - Bump opencode 1.17.8 -> 1.17.10. - Bump mempalace 3.4.0 -> 3.5.0 (lockstep with pi-devbox v1.2.2); remove the obsolete diary_write anyOf perl workaround (fixed upstream, issue #1728). - Fix stale ssh-lan.conf ProxyJump guidance comment in setup-lan-access.sh (mirrors pi-devbox 8de0fad); comment-only. - smoke-test.sh + recreate-sanity-check.sh assert baked source + resolved links. - Docs: README Custom skills, AGENTS.md duties + MINOR example, CHANGELOG.
This commit is contained in:
@@ -18,7 +18,7 @@ Docker image packaging [opencode](https://opencode.ai) into a production-ready d
|
|||||||
- `Dockerfile.base` — variant-independent layers (apt, locales, AWS CLI, Node.js, mempalace, gitea-mcp, user setup, chromadb prewarm, ENVs, entrypoints). Published as `joakimp/opencode-devbox:base-<sha12>`. Rebuilt only when its content hash changes.
|
- `Dockerfile.base` — variant-independent layers (apt, locales, AWS CLI, Node.js, mempalace, gitea-mcp, user setup, chromadb prewarm, ENVs, entrypoints). Published as `joakimp/opencode-devbox:base-<sha12>`. Rebuilt only when its content hash changes.
|
||||||
- `Dockerfile.variant` — `FROM`s the base and adds only opencode/omos installs gated by build args: `INSTALL_OPENCODE` (default true), `INSTALL_OMOS`, and `INSTALL_MEMPALACE`. All GitHub-sourced binaries are pinned with version ARGs. Two variants: `base` (`INSTALL_OPENCODE=true`) and `omos` (`+INSTALL_OMOS=true`).
|
- `Dockerfile.variant` — `FROM`s the base and adds only opencode/omos installs gated by build args: `INSTALL_OPENCODE` (default true), `INSTALL_OMOS`, and `INSTALL_MEMPALACE`. All GitHub-sourced binaries are pinned with version ARGs. Two variants: `base` (`INSTALL_OPENCODE=true`) and `omos` (`+INSTALL_OMOS=true`).
|
||||||
- `entrypoint.sh` — runs as root: UID/GID adjustment, SSH permissions, volume ownership fixes (skipped via `.devbox-owner` sentinel when ownership is already correct). Then drops to developer via gosu.
|
- `entrypoint.sh` — runs as root: UID/GID adjustment, SSH permissions, volume ownership fixes (skipped via `.devbox-owner` sentinel when ownership is already correct). Then drops to developer via gosu.
|
||||||
- `entrypoint-user.sh` — runs as developer: git config, opencode.jsonc generation (delegated to `generate-config.py`), LAN-access setup (delegated to `setup-lan-access.sh`), a one-time npm-global prefix migration shim (legacy `~/.pi/npm-global` → `~/.config/opencode/npm-global`), skillset auto-deploy from mounted skillset repo, OMOS bundled-skills reconcile (symlinks the image's bundled skills into `~/.agents/skills/`), OMOS config setup.
|
- `entrypoint-user.sh` — runs as developer: git config, opencode.jsonc generation (delegated to `generate-config.py`), LAN-access setup (delegated to `setup-lan-access.sh`), a one-time npm-global prefix migration shim (legacy `~/.pi/npm-global` → `~/.config/opencode/npm-global`), skillset auto-deploy from mounted skillset repo, OMOS bundled-skills reconcile (symlinks the image's bundled skills into `~/.agents/skills/`), image-baked fallback-skills reconcile (symlinks `/usr/local/share/opencode-devbox/skills/*` into `~/.agents/skills/` only-when-absent) + harness-instruction reconcile (symlinks `/usr/local/share/opencode-devbox/instructions/*.md` into `~/.config/opencode/instructions/`), OMOS config setup.
|
||||||
- `rootfs/usr/local/lib/opencode-devbox/setup-lan-access.sh` — host-OS-agnostic LAN reachability helper. Always writes the writable `~/.ssh-local/config` sidecar on **every** host OS: a `Host *` block that redirects `ControlPath` into `~/.ssh-local/cm/` (first-value-wins over any read-only `~/.ssh`-bound per-host setting) plus `Include ~/.ssh/config`. On VM-backed hosts (macOS OrbStack / Docker Desktop, detected via `host.docker.internal` resolution) it additionally inserts the host-jump block; on native Linux that block is omitted (LAN is reachable directly) but the sidecar is still rendered. Previously the script exited early on native Linux, leaving `dssh`/`dscp` broken when `~/.ssh` was read-only there. Controlled by `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` / `DEVBOX_HOST_ALIAS` / `DEVBOX_LAN_AUTOJUMP_PRIVATE`. Ships the mechanism only (generic `host` jump alias); user targets stay host-side — named-peer `ProxyJump host` overrides go in a bind-mounted `~/.config/devbox-shell/ssh-lan.conf` (Included before `~/.ssh/config`), never baked into the image. **Scoping invariant:** every `Include` in the generated config MUST be preceded by a bare `Host *` reset — an `Include` is scoped to the enclosing `Host`/`Match` block, so without the reset the included config only applies when targeting `host`/`mac` and named peers fall back to SSH defaults. Non-fatal. Counted in the base hash, so editing it advances `base-latest`.
|
- `rootfs/usr/local/lib/opencode-devbox/setup-lan-access.sh` — host-OS-agnostic LAN reachability helper. Always writes the writable `~/.ssh-local/config` sidecar on **every** host OS: a `Host *` block that redirects `ControlPath` into `~/.ssh-local/cm/` (first-value-wins over any read-only `~/.ssh`-bound per-host setting) plus `Include ~/.ssh/config`. On VM-backed hosts (macOS OrbStack / Docker Desktop, detected via `host.docker.internal` resolution) it additionally inserts the host-jump block; on native Linux that block is omitted (LAN is reachable directly) but the sidecar is still rendered. Previously the script exited early on native Linux, leaving `dssh`/`dscp` broken when `~/.ssh` was read-only there. Controlled by `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` / `DEVBOX_HOST_ALIAS` / `DEVBOX_LAN_AUTOJUMP_PRIVATE`. Ships the mechanism only (generic `host` jump alias); user targets stay host-side — named-peer `ProxyJump host` overrides go in a bind-mounted `~/.config/devbox-shell/ssh-lan.conf` (Included before `~/.ssh/config`), never baked into the image. **Scoping invariant:** every `Include` in the generated config MUST be preceded by a bare `Host *` reset — an `Include` is scoped to the enclosing `Host`/`Match` block, so without the reset the included config only applies when targeting `host`/`mac` and named peers fall back to SSH defaults. Non-fatal. Counted in the base hash, so editing it advances `base-latest`.
|
||||||
- `rootfs/usr/local/lib/opencode-devbox/generate-config.py` — generates `~/.config/opencode/opencode.jsonc` from env vars. Never overwrites an existing config (checks both `.json` and `.jsonc`). Auto-registers MCP servers for detected tools (mempalace via `mempalace-mcp`, gitea-mcp, context7 remote endpoint).
|
- `rootfs/usr/local/lib/opencode-devbox/generate-config.py` — generates `~/.config/opencode/opencode.jsonc` from env vars. Never overwrites an existing config (checks both `.json` and `.jsonc`). Auto-registers MCP servers for detected tools (mempalace via `mempalace-mcp`, gitea-mcp, context7 remote endpoint).
|
||||||
- `scripts/smoke-test.sh` — post-build image verification. Asserts binary presence, opencode startup, entrypoint correctness, config generation idempotency, and image size thresholds. Used by both CI workflows.
|
- `scripts/smoke-test.sh` — post-build image verification. Asserts binary presence, opencode startup, entrypoint correctness, config generation idempotency, and image size thresholds. Used by both CI workflows.
|
||||||
@@ -45,7 +45,8 @@ repo, which decoupled from the pi tool version at its own `v1.0.0`.
|
|||||||
reference example.
|
reference example.
|
||||||
- **MINOR** — backward-compatible features: new variants/tags, new opt-in
|
- **MINOR** — backward-compatible features: new variants/tags, new opt-in
|
||||||
behavior, new env vars, or changed-but-compatible semantics. Example: `v2.1.0`
|
behavior, new env vars, or changed-but-compatible semantics. Example: `v2.1.0`
|
||||||
added the OMOS bundled-skills image-symlink mechanism.
|
added the OMOS bundled-skills image-symlink mechanism; `v2.3.0` added the
|
||||||
|
image-baked fallback skills + harness-instruction mechanism.
|
||||||
- **PATCH** — opencode/tool version bumps and small fixes that don't change the
|
- **PATCH** — opencode/tool version bumps and small fixes that don't change the
|
||||||
contract. When a release pairs a tool bump with a feature, the feature wins
|
contract. When a release pairs a tool bump with a feature, the feature wins
|
||||||
and it's a minor.
|
and it's a minor.
|
||||||
|
|||||||
@@ -6,6 +6,80 @@ Tags follow **independent semver** (since `v2.0.0`) — they version *this image
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v2.3.0 — 2026-06-25
|
||||||
|
|
||||||
|
Minor release. Adds an **image-baked fallback skills + harness-instruction**
|
||||||
|
mechanism (ported from pi-devbox v1.2.0/v1.2.1, adapted to opencode's
|
||||||
|
`instructions/` model), bumps **opencode `1.17.8` → `1.17.10`** and **mempalace
|
||||||
|
`3.4.0` → `3.5.0`** (dropping the now-obsolete `diary_write` schema
|
||||||
|
workaround), and ports the one outstanding doc fix from pi-devbox's SSH sidecar
|
||||||
|
work. Defaults are unchanged when a skillset is mounted, so the canonical CI
|
||||||
|
build differs only by the version bumps and the additive image layer.
|
||||||
|
|
||||||
|
### Added: image-baked fallback skills + harness instruction
|
||||||
|
|
||||||
|
The image now ships two skills and one harness instruction under
|
||||||
|
`/usr/local/share/opencode-devbox/`, linked into place by `entrypoint-user.sh`
|
||||||
|
on every start, so a container behaves correctly **even with no `skillset` repo
|
||||||
|
mounted**:
|
||||||
|
|
||||||
|
- **`opencode-devbox-environment`** (authored here) — the container-shaped facts
|
||||||
|
an agent needs: the three persistence tiers (and why image-owned content must
|
||||||
|
live under `/usr` rather than a home dir the `devbox-opencode-config` volume
|
||||||
|
shadows), the interactive-vs-tool-shell alias gotcha (`dssh`/`dscp`/`cat`→`bat`
|
||||||
|
only exist in interactive bash), host + LAN SSH reachability via the
|
||||||
|
`~/.ssh-local` sidecar and ControlMaster, split-horizon DNS, uv-first Python,
|
||||||
|
and the OMOS variant. Adapted from pi-devbox's `pi-devbox-environment`, minus
|
||||||
|
the pi-only pieces (pi-studio, fork/recall).
|
||||||
|
- **`mempalace`** — a vendored snapshot of the skillset's consumer skill
|
||||||
|
(memory-continuity protocol). No `pi-extensions` skill is carried over —
|
||||||
|
opencode has no `fork`/`recall` extensions.
|
||||||
|
- **`instructions/opencode-devbox.md`** — symlinked into
|
||||||
|
`~/.config/opencode/instructions/`, which opencode auto-loads as a
|
||||||
|
session-start system prompt. It proactively points the agent at the
|
||||||
|
`opencode-devbox-environment` skill and the MemPalace continuity protocol, so
|
||||||
|
a fresh container picks them up rather than relying on description-matching.
|
||||||
|
|
||||||
|
Precedence is preserved: skills link **only-when-absent** (a mounted skillset or
|
||||||
|
an OMOS-bundled skill of the same name always wins), and the instruction uses a
|
||||||
|
distinct filename so it never collides with a mounted skillset's instructions.
|
||||||
|
Link targets live in the image, so `docker compose pull` + recreate refreshes
|
||||||
|
them for free — nothing is copied into the persistent config volume (which would
|
||||||
|
freeze it; cf. the OMOS-skills history in `docs/omos-skills.md`). Build-time
|
||||||
|
(`smoke-test.sh`) and runtime (`recreate-sanity-check.sh`) assertions cover both
|
||||||
|
the baked source and the resolved links. See
|
||||||
|
`rootfs/usr/local/share/opencode-devbox/skills/VENDORED.md`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **opencode `1.17.8` → `1.17.10`.** Highlights across 1.17.9–1.17.10: MCP
|
||||||
|
server instructions are now added to session context; MCP resource template
|
||||||
|
listing + resource read tools; a new `--mini` CLI mode; skill base
|
||||||
|
directories emitted as filesystem paths instead of `file://` URLs; assorted
|
||||||
|
MCP/OAuth and agent-step-limit bugfixes. (Source: `anomalyco/opencode`
|
||||||
|
releases.)
|
||||||
|
- **mempalace `3.4.0` → `3.5.0`** (lockstep with pi-devbox v1.2.2). 3.5.0 ships
|
||||||
|
the upstream fix for the top-level-`anyOf` `diary_write` schema (issue #1728 /
|
||||||
|
PR #1717, merged 2026-06-14): the advertised schema is now
|
||||||
|
`"required": ["agent_name"]` with entry/content enforced at dispatch, which
|
||||||
|
the Anthropic tools API accepts.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- **The `diary_write` top-level-`anyOf` workaround in `Dockerfile.base`.** The
|
||||||
|
`perl` patch of the installed `mcp_server.py` is gone now that 3.5.0 fixes it
|
||||||
|
at the source (verified against the published wheel).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Stale `ssh-lan.conf` guidance comment** in `setup-lan-access.sh`. The
|
||||||
|
`INCLUDE_BLOCK` comment previously implied LAN-peer `ProxyJump` overrides go
|
||||||
|
in `~/.ssh/config` (typically bind-mounted read-only); corrected to point at
|
||||||
|
the host-owned `~/.config/devbox-shell/ssh-lan.conf` (mirrors pi-devbox
|
||||||
|
`8de0fad`). Comment-only; no behavior change.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v2.2.0 — 2026-06-19
|
## v2.2.0 — 2026-06-19
|
||||||
|
|
||||||
Ports the build-provenance, CI-hardening, SSH and shell fixes that landed in
|
Ports the build-provenance, CI-hardening, SSH and shell fixes that landed in
|
||||||
|
|||||||
+17
-37
@@ -272,7 +272,13 @@ ARG INSTALL_MEMPALACE=true
|
|||||||
# diary_write top-level-anyOf schema (3.3.x/3.4.0) that breaks the
|
# diary_write top-level-anyOf schema (3.3.x/3.4.0) that breaks the
|
||||||
# Anthropic tools API; pinning makes every bump a deliberate, reviewable
|
# Anthropic tools API; pinning makes every bump a deliberate, reviewable
|
||||||
# diff. Bump this in lockstep with pi-devbox's MEMPALACE_VERSION.
|
# diff. Bump this in lockstep with pi-devbox's MEMPALACE_VERSION.
|
||||||
ARG MEMPALACE_VERSION=3.4.0
|
#
|
||||||
|
# 3.5.0 (2026-06) ships the upstream fix for that top-level-anyOf schema
|
||||||
|
# (issue #1728 / PR #1717, merged 2026-06-14): diary_write now advertises
|
||||||
|
# `"required": ["agent_name"]` with entry/content enforced at dispatch, which
|
||||||
|
# the Anthropic tools API accepts — so the perl mcp_server.py workaround that
|
||||||
|
# used to live below is gone. (pi-devbox dropped it in its v1.2.2.)
|
||||||
|
ARG MEMPALACE_VERSION=3.5.0
|
||||||
ENV UV_TOOL_DIR=/opt/uv-tools
|
ENV UV_TOOL_DIR=/opt/uv-tools
|
||||||
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
||||||
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
||||||
@@ -281,42 +287,10 @@ RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
|||||||
/opt/uv-tools/mempalace/bin/python -c "import mempalace; print('mempalace', mempalace.__version__ if hasattr(mempalace, '__version__') else 'installed')" ; \
|
/opt/uv-tools/mempalace/bin/python -c "import mempalace; print('mempalace', mempalace.__version__ if hasattr(mempalace, '__version__') else 'installed')" ; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── workaround: strip top-level anyOf from mempalace_diary_write schema ──
|
# (The mempalace diary_write top-level-anyOf workaround that patched
|
||||||
# Mempalace 3.3.x/3.4.0 advertise diary_write's input_schema with a
|
# mcp_server.py here was removed when MEMPALACE_VERSION moved to 3.5.0 —
|
||||||
# top-level `anyOf: [{required:[entry]}, {required:[content]}]` to express
|
# fixed upstream via issue #1728 / PR #1717 (merged 2026-06-14). Mirrors
|
||||||
# "either entry or content must be supplied". Anthropic's tools API rejects
|
# pi-devbox v1.2.2. See CHANGELOG.md.)
|
||||||
# top-level anyOf/oneOf/allOf, so pi/Claude fail at session start with
|
|
||||||
# `tools.<n>.custom.input_schema: input_schema does not support oneOf,
|
|
||||||
# allOf, or anyOf at the top level`.
|
|
||||||
#
|
|
||||||
# Patch the advertised schema to require ["agent_name", "entry"] and remove
|
|
||||||
# the anyOf block. The handler keeps accepting `content` server-side as a
|
|
||||||
# kwarg alias so existing callers still work.
|
|
||||||
#
|
|
||||||
# Idempotent and self-deactivating: once upstream releases the fix the
|
|
||||||
# regex no longer matches (and the WARN below fires) — that's the signal
|
|
||||||
# to delete this RUN.
|
|
||||||
# Upstream status (last checked 2026-06-14):
|
|
||||||
# issue #1728 — STILL OPEN (root-level anyOf rejected by Anthropic/Codex)
|
|
||||||
# PR #1735 — CLOSED UNMERGED 2026-06-11; do NOT watch it (dead)
|
|
||||||
# PR #1717 — open; the current live fix candidate to watch
|
|
||||||
# mempalace PyPI latest = 3.4.0 (== our pin) → no release contains the fix yet
|
|
||||||
# https://github.com/MemPalace/mempalace/issues/1728
|
|
||||||
# https://github.com/MemPalace/mempalace/pull/1717
|
|
||||||
# TODO: remove this RUN once a mempalace release > 3.4.0 that actually strips
|
|
||||||
# the root-level anyOf ships on PyPI and is installed by the line above.
|
|
||||||
# Keep MEMPALACE_VERSION in lockstep with pi-devbox when bumping.
|
|
||||||
# See AGENTS.md “Critical conventions” for the full watch-target rationale.
|
|
||||||
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
|
||||||
MP_FILE="$(find /opt/uv-tools/mempalace -path '*/mempalace/mcp_server.py' | head -n1)" && \
|
|
||||||
if [ -z "$MP_FILE" ]; then echo "mempalace mcp_server.py not found" >&2; exit 1; fi && \
|
|
||||||
perl -0777 -i -pe 's/(?:[ \t]*\#[^\n]*\n)*[ \t]*"required":\s*\[\s*"agent_name"\s*\]\s*,\s*\n[ \t]*"anyOf":\s*\[\s*\n[ \t]*\{\s*"required":\s*\[\s*"entry"\s*\]\s*\}\s*,\s*\n[ \t]*\{\s*"required":\s*\[\s*"content"\s*\]\s*\}\s*,?\s*\n[ \t]*\]\s*,\s*\n/ "required": ["agent_name", "entry"],\n/s' "$MP_FILE" && \
|
|
||||||
if grep -q '"required": \["agent_name", "entry"\]' "$MP_FILE"; then \
|
|
||||||
echo "mempalace diary_write anyOf workaround: applied (or already clean)"; \
|
|
||||||
else \
|
|
||||||
echo "WARN: mempalace diary_write anyOf workaround did not match expected schema — upstream may have changed shape" >&2; \
|
|
||||||
fi ; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── mempalace-toolkit — bash wrappers for session/docs mining ────────
|
# ── mempalace-toolkit — bash wrappers for session/docs mining ────────
|
||||||
ARG INSTALL_MEMPALACE_TOOLKIT=true
|
ARG INSTALL_MEMPALACE_TOOLKIT=true
|
||||||
@@ -458,6 +432,12 @@ COPY rootfs/home/developer/.inputrc /etc/skel-devbox/.inputrc
|
|||||||
|
|
||||||
# ── Entrypoint ────────────────────────────────────────────────────────
|
# ── Entrypoint ────────────────────────────────────────────────────────
|
||||||
COPY rootfs/usr/local/lib/opencode-devbox/ /usr/local/lib/opencode-devbox/
|
COPY rootfs/usr/local/lib/opencode-devbox/ /usr/local/lib/opencode-devbox/
|
||||||
|
# Image-baked skills + harness instruction. Under /usr/local so a named volume
|
||||||
|
# over a home dir (e.g. devbox-opencode-config on ~/.config/opencode) can't
|
||||||
|
# shadow them; entrypoint-user.sh links them into ~/.agents/skills/ and
|
||||||
|
# ~/.config/opencode/instructions/ on every start. See
|
||||||
|
# rootfs/usr/local/share/opencode-devbox/skills/VENDORED.md.
|
||||||
|
COPY rootfs/usr/local/share/opencode-devbox/ /usr/local/share/opencode-devbox/
|
||||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh
|
COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh \
|
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh \
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ ARG USER_NAME=developer
|
|||||||
# edit, so the cache-hit class of bug that bit pi-devbox v0.74.0..
|
# edit, so the cache-hit class of bug that bit pi-devbox v0.74.0..
|
||||||
# v0.75.5 cannot apply here.
|
# v0.75.5 cannot apply here.
|
||||||
ARG INSTALL_OPENCODE=true
|
ARG INSTALL_OPENCODE=true
|
||||||
ARG OPENCODE_VERSION=1.17.8
|
ARG OPENCODE_VERSION=1.17.10
|
||||||
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
||||||
NPM_CONFIG_PREFIX=/usr npm install -g opencode-ai@${OPENCODE_VERSION} && \
|
NPM_CONFIG_PREFIX=/usr npm install -g opencode-ai@${OPENCODE_VERSION} && \
|
||||||
opencode --version ; \
|
opencode --version ; \
|
||||||
|
|||||||
@@ -234,6 +234,26 @@ When a skillset repo is detected, its skills are symlinked into `~/.agents/skill
|
|||||||
|
|
||||||
On the OMOS variant, the five skills bundled with oh-my-opencode-slim are also symlinked into `~/.agents/skills/` on each start — **from the image**, so pulling a newer image updates them with no installer run and no config reset. See [docs/omos-skills.md](docs/omos-skills.md).
|
On the OMOS variant, the five skills bundled with oh-my-opencode-slim are also symlinked into `~/.agents/skills/` on each start — **from the image**, so pulling a newer image updates them with no installer run and no config reset. See [docs/omos-skills.md](docs/omos-skills.md).
|
||||||
|
|
||||||
|
#### Image-baked fallback skills (work with no skillset mounted)
|
||||||
|
|
||||||
|
Even with **no skillset repo mounted**, the image ships two skills and one
|
||||||
|
harness instruction so a fresh container still knows how to behave here:
|
||||||
|
|
||||||
|
- `opencode-devbox-environment` and `mempalace` skills are baked under
|
||||||
|
`/usr/local/share/opencode-devbox/skills/` and symlinked into
|
||||||
|
`~/.agents/skills/` on start — **only when a skill of that name is not already
|
||||||
|
present**, so a mounted skillset or an OMOS-bundled skill always wins.
|
||||||
|
- `instructions/opencode-devbox.md` is symlinked into
|
||||||
|
`~/.config/opencode/instructions/` (which opencode auto-loads at session
|
||||||
|
start). It points the agent at the `opencode-devbox-environment` skill and the
|
||||||
|
MemPalace continuity protocol. It uses a distinct filename, so it never
|
||||||
|
collides with a mounted skillset's own instructions.
|
||||||
|
|
||||||
|
Because the link targets live in the image (not the persistent
|
||||||
|
`devbox-opencode-config` volume), `docker compose pull` + recreate refreshes
|
||||||
|
them for free. See
|
||||||
|
[`rootfs/usr/local/share/opencode-devbox/skills/VENDORED.md`](rootfs/usr/local/share/opencode-devbox/skills/VENDORED.md).
|
||||||
|
|
||||||
### Neovim configuration
|
### Neovim configuration
|
||||||
|
|
||||||
The image includes neovim 0.12 with `EDITOR=nvim` set by default. To use your own neovim config (and have plugins auto-install via lazy.nvim on first start), mount it from the host:
|
The image includes neovim 0.12 with `EDITOR=nvim` set by default. To use your own neovim config (and have plugins auto-install via lazy.nvim on first start), mount it from the host:
|
||||||
|
|||||||
@@ -184,6 +184,56 @@ if [ "${OMOS_SKILLS:-true}" = "true" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Image-baked fallback skills + harness instruction ────────────
|
||||||
|
# Baked under /usr/local/share/opencode-devbox/ (see that dir's VENDORED.md).
|
||||||
|
# Linked into place on every start so a container behaves correctly even with
|
||||||
|
# NO skillset mounted. Targets live in the image, so `docker compose pull` +
|
||||||
|
# recreate refreshes them for free. Whole block is non-fatal (`{ … } || true`):
|
||||||
|
# a transient link failure must never brick container startup.
|
||||||
|
#
|
||||||
|
# Precedence is intentional: this runs AFTER the skillset deploy and the OMOS
|
||||||
|
# bundled-skills block above, and the skill links are ONLY-WHEN-ABSENT, so a
|
||||||
|
# mounted skillset or an OMOS-bundled skill of the same name always wins; the
|
||||||
|
# baked copies merely fill gaps.
|
||||||
|
DEVBOX_SKILLS_SRC=/usr/local/share/opencode-devbox/skills
|
||||||
|
if [ -d "$DEVBOX_SKILLS_SRC" ]; then
|
||||||
|
{
|
||||||
|
mkdir -p "$HOME/.agents/skills"
|
||||||
|
for _sk in "$DEVBOX_SKILLS_SRC"/*/; do
|
||||||
|
[ -d "$_sk" ] || continue
|
||||||
|
_skname=$(basename "$_sk")
|
||||||
|
# Only-when-absent: never clobber a skillset/OMOS/user skill of this name.
|
||||||
|
if [ ! -e "$HOME/.agents/skills/$_skname" ]; then
|
||||||
|
ln -s "${_sk%/}" "$HOME/.agents/skills/$_skname"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
} || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Harness instructions: opencode auto-loads ~/.config/opencode/instructions/*.md
|
||||||
|
# as a session-start system prompt. ~/.config/opencode is the persistent
|
||||||
|
# devbox-opencode-config volume, so we SYMLINK to the fixed image path (never
|
||||||
|
# copy — a copy would freeze in the volume) and refresh our own symlink each
|
||||||
|
# start. A real file of the same name (e.g. one a user dropped) is left alone;
|
||||||
|
# the baked filename (opencode-devbox.md) is distinct from skillset's
|
||||||
|
# instructions, so there is no collision with a mounted skillset.
|
||||||
|
DEVBOX_INSTR_SRC=/usr/local/share/opencode-devbox/instructions
|
||||||
|
if [ -d "$DEVBOX_INSTR_SRC" ]; then
|
||||||
|
{
|
||||||
|
mkdir -p "$HOME/.config/opencode/instructions"
|
||||||
|
for _instr in "$DEVBOX_INSTR_SRC"/*.md; do
|
||||||
|
[ -f "$_instr" ] || continue
|
||||||
|
_iname=$(basename "$_instr")
|
||||||
|
_dst="$HOME/.config/opencode/instructions/$_iname"
|
||||||
|
# Leave a real file alone; (re)create our own symlink otherwise.
|
||||||
|
if [ -e "$_dst" ] && [ ! -L "$_dst" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
ln -sfn "$_instr" "$_dst"
|
||||||
|
done
|
||||||
|
} || true
|
||||||
|
fi
|
||||||
|
|
||||||
CONFIG_DIR="$HOME/.config/opencode"
|
CONFIG_DIR="$HOME/.config/opencode"
|
||||||
OMOS_CONFIG="$CONFIG_DIR/oh-my-opencode-slim.json"
|
OMOS_CONFIG="$CONFIG_DIR/oh-my-opencode-slim.json"
|
||||||
|
|
||||||
|
|||||||
@@ -188,7 +188,9 @@ if [ -r "${HOME}/.ssh/config" ]; then
|
|||||||
|
|
||||||
# Your own target hosts. Scope reset to match-all so this Include applies to
|
# Your own target hosts. Scope reset to match-all so this Include applies to
|
||||||
# every target (an Include is otherwise scoped to the enclosing Host block).
|
# every target (an Include is otherwise scoped to the enclosing Host block).
|
||||||
# Add 'ProxyJump host' to LAN entries here (or in ssh-lan.conf above).
|
# To make a LAN peer jump via the host, add 'ProxyJump host' to its entry in
|
||||||
|
# the host-owned ~/.config/devbox-shell/ssh-lan.conf (Included above) — NOT
|
||||||
|
# here in ~/.ssh/config, which is typically bind-mounted read-only.
|
||||||
Host *
|
Host *
|
||||||
Include ~/.ssh/config
|
Include ~/.ssh/config
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<!-- opencode-devbox:managed-instruction — baked into the image at
|
||||||
|
/usr/local/share/opencode-devbox/instructions/opencode-devbox.md and
|
||||||
|
symlinked into ~/.config/opencode/instructions/ by entrypoint-user.sh on
|
||||||
|
every container start (opencode auto-loads instructions/*.md as a
|
||||||
|
session-start system prompt). A mounted skillset deploys its own
|
||||||
|
instructions under different filenames, so this never collides. Keep this
|
||||||
|
short: it is a pointer, the depth lives in the skills. -->
|
||||||
|
|
||||||
|
## Running inside opencode-devbox
|
||||||
|
|
||||||
|
If the directory `/usr/local/lib/opencode-devbox/` exists (or your shell prompt
|
||||||
|
is prefixed `[devbox]`, or `~/.ssh-local/config` is present), you are in an
|
||||||
|
**opencode-devbox container** — a Docker environment whose persistence,
|
||||||
|
networking, DNS, host/LAN reachability, and Python/REPL behaviour differ from a
|
||||||
|
normal workstation. Before any task that touches **reaching the host or its
|
||||||
|
LAN, SSH, DNS/name resolution, what survives container recreate, running
|
||||||
|
Python/REPLs, or the oh-my-opencode-slim variant**, read
|
||||||
|
`~/.agents/skills/opencode-devbox-environment/SKILL.md`.
|
||||||
|
|
||||||
|
Key reflex from that skill: **the deployment specifics are not universal** — the
|
||||||
|
host OS, hostnames, internal domains, and nameservers vary per instance and must
|
||||||
|
be discovered at runtime, never assumed. And interactive shell aliases
|
||||||
|
(`dssh`, `dscp`, `cat`→`bat`) do **not** exist in your non-interactive bash
|
||||||
|
tool, so spell out the underlying command (e.g.
|
||||||
|
`ssh -F "$HOME/.ssh-local/config" mac …`).
|
||||||
|
|
||||||
|
## Session start: load the mempalace skill
|
||||||
|
|
||||||
|
If MemPalace MCP tools (e.g. `mempalace_status`, `mempalace_search`,
|
||||||
|
`mempalace_diary_write`) are in your tool list, **read
|
||||||
|
`~/.agents/skills/mempalace/SKILL.md` before doing non-trivial work** and follow
|
||||||
|
its protocol: search the palace before answering about past work, and write a
|
||||||
|
diary entry before the session ends. This is especially load-bearing here — an
|
||||||
|
opencode-devbox container is frequently recreated, so the palace is your only
|
||||||
|
memory across recreates. Without the habit it is just storage, not memory.
|
||||||
|
(The skill is the consumer side; feeding the palace is the separate
|
||||||
|
`opencode-mempalace-bridge` skill, if present.)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Vendored fallback skills + harness instruction
|
||||||
|
|
||||||
|
This tree is **image-baked** content that `entrypoint-user.sh` links into place
|
||||||
|
on container start so the container behaves correctly **even when no private
|
||||||
|
`skillset` repo is mounted**:
|
||||||
|
|
||||||
|
- `skills/*` → symlinked into `~/.agents/skills/` (only when a skill of the same
|
||||||
|
name is not already present, so a mounted `skillset` or the OMOS bundled
|
||||||
|
skills always win).
|
||||||
|
- `instructions/*.md` → symlinked into `~/.config/opencode/instructions/`, which
|
||||||
|
opencode auto-loads as a session-start system prompt (only when a real file of
|
||||||
|
the same name is not already there).
|
||||||
|
|
||||||
|
| item | owner | how it gets here |
|
||||||
|
|------|-------|------------------|
|
||||||
|
| `skills/opencode-devbox-environment` | opencode-devbox (this repo) | authored here; the canonical copy |
|
||||||
|
| `skills/mempalace` | the `skillset` repo | **vendored fallback** (snapshot only) |
|
||||||
|
| `instructions/opencode-devbox.md` | opencode-devbox (this repo) | authored here; the proactive-load pointer |
|
||||||
|
|
||||||
|
## Why fallbacks exist
|
||||||
|
|
||||||
|
opencode discovers skills under `~/.agents/skills/` and loads harness
|
||||||
|
instructions from `~/.config/opencode/instructions/*.md` at session start. A
|
||||||
|
container started **without** the private `skillset` repo mounted would have
|
||||||
|
neither — so the agent would not know the container-shaped facts and would not
|
||||||
|
follow the MemPalace continuity protocol. Baking `opencode-devbox-environment`
|
||||||
|
and `mempalace` closes that *availability* gap, and the baked
|
||||||
|
`instructions/opencode-devbox.md` adds the matching *proactive-load* directive
|
||||||
|
(read those two skills at session start) so a fresh container actually picks
|
||||||
|
them up rather than relying on description-matching.
|
||||||
|
|
||||||
|
Note there is **no `pi-extensions` skill here**: opencode has no `fork`/`recall`
|
||||||
|
extensions (that is a pi-only concern), so the pi-devbox vendored set does not
|
||||||
|
carry over 1:1.
|
||||||
|
|
||||||
|
## Filename discipline
|
||||||
|
|
||||||
|
`instructions/opencode-devbox.md` deliberately uses a name distinct from the
|
||||||
|
skillset's `instructions/mempalace.md`. Both can be deployed at once (a mounted
|
||||||
|
skillset adds its own); the distinct name means our fallback is collision-free
|
||||||
|
and the entrypoint's never-overwrite-a-real-file guard never has to arbitrate.
|
||||||
|
|
||||||
|
## Refreshing the snapshots
|
||||||
|
|
||||||
|
cp <skillset>/skills/mempalace/SKILL.md skills/mempalace/SKILL.md
|
||||||
|
|
||||||
|
Snapshot provenance at last refresh: skillset `8e8db64`.
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
---
|
||||||
|
name: mempalace
|
||||||
|
description: MemPalace agent memory protocol. Use on every session to maintain continuity across conversations — search before answering about past work, write diary entries before session ends, and mine new projects into the palace. Load this skill at session start.
|
||||||
|
---
|
||||||
|
|
||||||
|
# MemPalace Agent Memory Protocol
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
MemPalace gives you persistent memory across sessions via an MCP server. It stores project knowledge (mined from files), conversation summaries (diary entries), and entity relationships (knowledge graph). Without this protocol, you have tools but no habits — and memory without habits is just storage.
|
||||||
|
|
||||||
|
**Core principle:** Storage is not memory. Storage + protocol = memory.
|
||||||
|
|
||||||
|
## When to Load This Skill
|
||||||
|
|
||||||
|
- At the **start of every session** (proactively, before the user asks)
|
||||||
|
- When the user mentions **past conversations, decisions, or work**
|
||||||
|
- When working on a **new project or repository** for the first time
|
||||||
|
- When the user asks about **people, projects, or relationships**
|
||||||
|
|
||||||
|
## Session Lifecycle
|
||||||
|
|
||||||
|
### Phase 1: Wake Up (session start)
|
||||||
|
|
||||||
|
Run these immediately when a session begins, before responding to the user:
|
||||||
|
|
||||||
|
1. **Load palace overview:**
|
||||||
|
```
|
||||||
|
mempalace_status
|
||||||
|
```
|
||||||
|
This returns wing/room counts, the AAAK spec, and the memory protocol reminder.
|
||||||
|
|
||||||
|
2. **Read your recent diary:**
|
||||||
|
```
|
||||||
|
mempalace_diary_read(agent_name="<your_agent_name>", last_n=5)
|
||||||
|
```
|
||||||
|
Scan for context about recent sessions — what was worked on, what matters, what's pending.
|
||||||
|
|
||||||
|
3. **Check the knowledge graph** for the user or active project if relevant:
|
||||||
|
```
|
||||||
|
mempalace_kg_query(entity="<project_or_person>")
|
||||||
|
```
|
||||||
|
|
||||||
|
Do NOT announce this to the user. Just do it silently to orient yourself.
|
||||||
|
|
||||||
|
### Phase 2: Active Session (during work)
|
||||||
|
|
||||||
|
#### Search Before You Speak
|
||||||
|
|
||||||
|
Before answering questions about past work, decisions, people, or projects:
|
||||||
|
|
||||||
|
```
|
||||||
|
mempalace_search(query="<keywords>", wing="<project>")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Never guess about facts that might be in the palace.** Wrong is worse than slow. Say "let me check" and query.
|
||||||
|
|
||||||
|
#### Mine New Projects
|
||||||
|
|
||||||
|
When working on a new codebase for the first time:
|
||||||
|
|
||||||
|
1. Check if it's already mined:
|
||||||
|
```
|
||||||
|
mempalace_list_wings
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Decide what to mine — docs first, code never (by default).**
|
||||||
|
|
||||||
|
The palace is for *context and intent*, not code recall. Code is better read from the working tree via `Read`/`Grep`/`glob` — always authoritative, never stale. Embedding source code produces thousands of low-signal drawers (e.g. `def __init__(self, ...)` across every class) that pollute search for years.
|
||||||
|
|
||||||
|
**Mine by default:**
|
||||||
|
- `*.md`, `*.rst`, `*.txt` — docs, READMEs, CHANGELOGs, architecture notes
|
||||||
|
- `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md`, design/decision docs — highest signal per byte
|
||||||
|
- `*.sh`, `Dockerfile`, `Makefile`, entrypoints — small, intent-bearing
|
||||||
|
- `*.yml`, `*.yaml`, `*.toml`, selective `*.json` (`docker-compose`, `pyproject`, `mkdocs.yml`, CI workflows) — skip lockfiles
|
||||||
|
|
||||||
|
**Do NOT mine by default:**
|
||||||
|
- `*.py`, `*.ts`, `*.tsx`, `*.js`, `*.go`, `*.rs`, `*.java`, `*.cpp`, `*.c`, `*.rb` — raw source code
|
||||||
|
- Test files, fixtures, generated code
|
||||||
|
- `node_modules/`, `.venv/`, `__pycache__/`, `.mypy_cache/`, `.pytest_cache/`, `.ruff_cache/` (the miner respects `.gitignore` but double-check)
|
||||||
|
|
||||||
|
Exception: if a code file *is* the documentation (e.g. a heavily-commented reference script, or a protocol definition), file it manually via `mempalace_add_drawer`.
|
||||||
|
|
||||||
|
3. **Before mining**, inspect the repo to estimate drawer count:
|
||||||
|
```bash
|
||||||
|
# Quick audit — what will actually get mined?
|
||||||
|
find <dir> -type f \
|
||||||
|
-not -path '*/.git/*' -not -path '*/node_modules/*' \
|
||||||
|
-not -path '*/.venv/*' -not -path '*/__pycache__/*' \
|
||||||
|
\( -name '*.md' -o -name '*.sh' -o -name '*.yml' -o -name '*.yaml' \
|
||||||
|
-o -name '*.toml' -o -name 'Dockerfile*' -o -name 'Makefile' \) | wc -l
|
||||||
|
```
|
||||||
|
A docs-heavy repo should produce ~5–10 drawers per file. If a mine produces >15 drawers/file on average, code leaked in — investigate.
|
||||||
|
|
||||||
|
4. Run the mine:
|
||||||
|
```bash
|
||||||
|
mempalace init --yes <directory>
|
||||||
|
mempalace mine <directory> --agent <your_agent_name>
|
||||||
|
```
|
||||||
|
|
||||||
|
The miner currently lacks a `--docs-only` or `--exclude-ext` flag (as of v3.3.3). Until it does, either:
|
||||||
|
- (a) Add a `mempalace.yaml` at the repo root with explicit include globs, OR
|
||||||
|
- (b) Mine everything, then surgically remove code-sourced drawers via SQL on `~/.mempalace/palace/chroma.sqlite3` (delete by `embedding_metadata.source_file LIKE '%.py'`), followed by `mempalace repair --yes`.
|
||||||
|
|
||||||
|
5. If the CLI miner misses a file you *do* want (e.g., `.zsh`, an undocumented extension), file it manually:
|
||||||
|
```
|
||||||
|
mempalace_add_drawer(wing="<project>", room="<aspect>", content="<verbatim content>", source_file="<path>")
|
||||||
|
```
|
||||||
|
|
||||||
|
6. After mining, reconnect to pick up the new embeddings:
|
||||||
|
```
|
||||||
|
mempalace_reconnect
|
||||||
|
```
|
||||||
|
If search errors occur after mining ("Error finding id"), repair the index:
|
||||||
|
```bash
|
||||||
|
mempalace repair --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Track Facts in the Knowledge Graph
|
||||||
|
|
||||||
|
When you learn new facts about people, projects, or relationships:
|
||||||
|
|
||||||
|
```
|
||||||
|
mempalace_kg_add(subject="ProjectX", predicate="uses", object="PostgreSQL")
|
||||||
|
mempalace_kg_add(subject="Alice", predicate="owns", object="ProjectX", valid_from="2026-01-15")
|
||||||
|
```
|
||||||
|
|
||||||
|
When facts change (ended, no longer true):
|
||||||
|
|
||||||
|
```
|
||||||
|
mempalace_kg_invalidate(subject="Alice", predicate="works_at", object="OldCorp", ended="2026-03-01")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Cross-Reference with Tunnels
|
||||||
|
|
||||||
|
When content in one project relates to another, create a tunnel:
|
||||||
|
|
||||||
|
```
|
||||||
|
mempalace_create_tunnel(
|
||||||
|
source_wing="project_api", source_room="endpoints",
|
||||||
|
target_wing="project_db", target_room="schema",
|
||||||
|
label="API endpoints map to these DB tables"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Feeding opencode session history (opencode + mempalace-toolkit only)
|
||||||
|
|
||||||
|
MemPalace has no upstream integration with [opencode](https://github.com/anomalyco/opencode) as of v3.3.3 — `hooks_cli.py` only supports `claude-code` and `codex` harnesses. Opencode persists every turn in a local SQLite DB at `~/.local/share/opencode/opencode.db`, but nothing moves that data into the palace automatically.
|
||||||
|
|
||||||
|
On a machine with opencode + the [`mempalace-toolkit`](https://gitea.jordbo.se/joakimp/mempalace-toolkit) installed, session history is fed into `wing_conversations` via `mempalace-session` — either manually, or on a weekly systemd user timer / cron schedule shipped in `mempalace-toolkit/contrib/`. If this is missing, opencode conversations exist only in the local SQLite DB and are invisible to `mempalace_search`.
|
||||||
|
|
||||||
|
**How to tell if it's set up:**
|
||||||
|
|
||||||
|
```
|
||||||
|
mempalace_list_wings
|
||||||
|
```
|
||||||
|
|
||||||
|
If `wing_conversations` exists and has a drawer count comparable to the user's opencode session count, session feeding is working. If it's empty or suspiciously small, suggest:
|
||||||
|
|
||||||
|
1. Check if the toolkit is installed: `which mempalace-session`.
|
||||||
|
2. If installed, suggest running `mempalace-session --dry-run` to preview and `mempalace-session` to file.
|
||||||
|
3. If not installed, point the user at `gitea.jordbo.se/joakimp/mempalace-toolkit` for setup.
|
||||||
|
|
||||||
|
**Don't try to paper over the gap by dumping turn-level content into the palace manually via `mempalace_add_drawer`** — that reinvents what `mempalace-session` does with normalization and dedup. Use the tool.
|
||||||
|
|
||||||
|
Full routine (triggers, cadence, automation) is in the [`opencode-mempalace-bridge`](https://gitea.jordbo.se/joakimp/mempalace-toolkit) skill and the toolkit's `ARCHITECTURE.md` §5. The two skills pair: this one (`mempalace`) covers using the palace; that one (`opencode-mempalace-bridge`) covers feeding it from opencode.
|
||||||
|
|
||||||
|
### Phase 3: Wind Down (session end)
|
||||||
|
|
||||||
|
**Always write a diary entry before the session ends.** This is the most important habit.
|
||||||
|
|
||||||
|
```
|
||||||
|
mempalace_diary_write(
|
||||||
|
agent_name="<your_agent_name>",
|
||||||
|
entry="<AAAK compressed summary>",
|
||||||
|
topic="session-summary"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Why still write diaries when sessions may be mined automatically?
|
||||||
|
|
||||||
|
On machines running opencode + `mempalace-toolkit`, every session is mined into `wing_conversations` on a weekly (or user-defined) schedule. A common and incorrect conclusion: *"since every turn is captured automatically, writing a diary entry is redundant."* It isn't.
|
||||||
|
|
||||||
|
Session mining captures **what was said** (every turn, verbatim). A diary captures **what the session meant** — editorial judgment by the agent who lived it:
|
||||||
|
|
||||||
|
- Lessons learned, patterns noticed, pending items rolled forward
|
||||||
|
- Meta-observations that were never said aloud during the session
|
||||||
|
- Aggregate counts (commits shipped, bugs fixed, hours spent)
|
||||||
|
- A compressed, recency-scannable summary for the *next* agent's wake-up
|
||||||
|
|
||||||
|
Mining raw turns cannot surface these because the words don't exist verbatim — they're the agent's reflection at wind-down. Think of the split as *release notes* (diary) vs. *git log with diffs* (session mine): a repo keeps both because they answer different questions. So does the palace.
|
||||||
|
|
||||||
|
**Practical rule:** automated mining does not replace Phase 3. Both systems cover each other's failure modes — a skipped diary is recovered from the raw turns; a missed mine is recovered from the diary summary. For the full treatment (comparison table, retrieval patterns, token economics), see [`mempalace-toolkit/ARCHITECTURE.md` §5 → "Diary vs session mine: why keep both?"](https://gitea.jordbo.se/joakimp/mempalace-toolkit/src/branch/main/ARCHITECTURE.md#diary-vs-session-mine-why-keep-both).
|
||||||
|
|
||||||
|
#### AAAK Diary Format
|
||||||
|
|
||||||
|
Write diary entries in compressed AAAK format for efficiency. Structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
SESSION:<date>|<what.you.worked.on>|
|
||||||
|
TASKS:
|
||||||
|
1.<task.description>→<outcome>|
|
||||||
|
2.<task.description>→<outcome>|
|
||||||
|
DISCOVERED:<unexpected.findings>|
|
||||||
|
ENTITIES:<people.or.projects.encountered>|
|
||||||
|
<importance: one to five stars>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
SESSION:2026-04-28|api.refactor+db.migration|
|
||||||
|
TASKS:
|
||||||
|
1.refactored.auth.endpoints→split.into.3.modules|
|
||||||
|
2.added.user.roles.migration→postgres.enum.type|
|
||||||
|
DISCOVERED:legacy.session.table.unused.since.v2|
|
||||||
|
ENTITIES:ProjectX;Alice(reviewer)|
|
||||||
|
***
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Use dots instead of spaces within phrases
|
||||||
|
- Use pipes as field separators
|
||||||
|
- Use arrows for cause/effect or transitions
|
||||||
|
- Stars indicate session importance (one to five)
|
||||||
|
- Keep it tight — a future agent should get the gist in seconds
|
||||||
|
|
||||||
|
#### What to Capture
|
||||||
|
|
||||||
|
Prioritize recording:
|
||||||
|
- **Decisions made** and their rationale
|
||||||
|
- **Discoveries** — things that surprised you or that a future session needs to know
|
||||||
|
- **Unfinished work** — what's pending, what was deferred
|
||||||
|
- **User preferences** observed during the session
|
||||||
|
- **Entities encountered** — people, projects, tools, services
|
||||||
|
|
||||||
|
### Phase 4: Fact Updates
|
||||||
|
|
||||||
|
If facts changed during the session, update the knowledge graph before writing the diary:
|
||||||
|
|
||||||
|
```
|
||||||
|
mempalace_kg_invalidate(subject="...", predicate="...", object="...", ended="<today>")
|
||||||
|
mempalace_kg_add(subject="...", predicate="...", object="...", valid_from="<today>")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Palace Structure
|
||||||
|
|
||||||
|
### Wings
|
||||||
|
|
||||||
|
Wings are top-level categories, typically one per project or domain:
|
||||||
|
- Named after the project directory (e.g., `cli_utils`, `opencode_devbox`)
|
||||||
|
- Agent diaries live in `wing_<agent_name>` (e.g., `wing_orchestrator`, `wing_pi`)
|
||||||
|
|
||||||
|
#### Multi-harness palace
|
||||||
|
|
||||||
|
A single palace can be fed by multiple coding-agent harnesses. On this machine the palace is shared between **opencode** and **pi** (Mario Zechner's pi-coding-agent). Implications:
|
||||||
|
|
||||||
|
- **`wing_conversations` mixes sources.** Both harnesses' session feeders write into the same wing. To tell them apart, look at the `source_file` metadata on each drawer:
|
||||||
|
- `pi_<uuid>.jsonl` → pi session
|
||||||
|
- `<slug>_ses_<id>.jsonl` → opencode session
|
||||||
|
- The first chunk of each session also carries a `| source: opencode` or `| source: pi` marker in the synthetic header line.
|
||||||
|
- **Other wings may belong to other harnesses.** For example `wing_pi` is pi's diary, not opencode's. Don't assume every diary entry was written by you — check `agent_name` on the entry.
|
||||||
|
- **Session feeders run on different schedules.** Pi sessions are fed Tue 03:00, opencode sessions Mon 03:00. Recent sessions from either harness can lag the palace by up to a week, so absence-of-evidence in `wing_conversations` is not evidence-of-absence for recent work.
|
||||||
|
- **Reading another harness's diary is useful.** When orienting after a gap, `mempalace_diary_read agent_name=pi` (or whichever sibling agent has been active) often gives a fresher picture than waiting for the conversations feeder to catch up.
|
||||||
|
|
||||||
|
### Rooms
|
||||||
|
|
||||||
|
Rooms are aspects within a wing:
|
||||||
|
- `fzf`, `scripts`, `configuration`, `general` — whatever the miner detects
|
||||||
|
- Diary entries go into rooms by topic tag
|
||||||
|
|
||||||
|
### Drawers
|
||||||
|
|
||||||
|
Drawers hold verbatim content — never summarized, always searchable.
|
||||||
|
|
||||||
|
### Tunnels
|
||||||
|
|
||||||
|
Cross-wing connections linking related content across projects.
|
||||||
|
|
||||||
|
### Knowledge Graph
|
||||||
|
|
||||||
|
Entity-relationship triples with temporal validity. Query with `mempalace_kg_query`, browse with `mempalace_kg_timeline`.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Problem | Fix |
|
||||||
|
|---|---|
|
||||||
|
| "No palace found" | Run `mempalace init <dir>` then `mempalace mine <dir>` |
|
||||||
|
| "Error finding id" after mining | Run `mempalace repair --yes` then `mempalace_reconnect` |
|
||||||
|
| Search returns irrelevant results | Use `max_distance=1.0` for stricter matching; add `wing` filter |
|
||||||
|
| Miner skips file types | File manually with `mempalace_add_drawer` or use `--no-gitignore` |
|
||||||
|
| Stale results after external changes | Call `mempalace_reconnect` |
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
- **Don't guess when you can search.** If a question touches past work, search first.
|
||||||
|
- **Don't skip the diary.** A session without a diary entry is a session forgotten.
|
||||||
|
- **Don't summarize drawer content.** File verbatim — the embedding model needs the original words.
|
||||||
|
- **Don't mine .git directories or node_modules.** The CLI miner respects .gitignore by default.
|
||||||
|
- **Don't create duplicate drawers.** Use `mempalace_check_duplicate` before adding manually.
|
||||||
|
- **Don't treat the palace as a task list.** It's for knowledge and context, not todos.
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
---
|
||||||
|
name: opencode-devbox-environment
|
||||||
|
description: >-
|
||||||
|
Operate correctly inside an opencode-devbox container. Load when running
|
||||||
|
inside opencode-devbox (detection: the directory `/usr/local/lib/opencode-devbox/`
|
||||||
|
exists, the shell prompt is prefixed `[devbox]`, or `~/.ssh-local/config` is
|
||||||
|
present) and the task touches any of: reaching the Docker host or its LAN,
|
||||||
|
SSH, DNS name resolution, what survives container recreate (persistence vs
|
||||||
|
ephemerality), running Python or other REPLs, or the oh-my-opencode-slim
|
||||||
|
(OMOS) variant. Covers the persistence model (and why image-owned content
|
||||||
|
must live under /usr, not a home dir a named volume shadows), the
|
||||||
|
interactive-vs-tool-shell alias gotcha (dssh/dscp/cat=bat exist only in
|
||||||
|
interactive bash), host + LAN SSH reachability and ControlMaster, split-horizon
|
||||||
|
DNS mechanisms, and uv-first Python. This skill teaches MECHANISMS only —
|
||||||
|
concrete hostnames, usernames, internal domains, nameservers, and even the
|
||||||
|
host OS vary per deployment and MUST be discovered at runtime, never assumed
|
||||||
|
or hardcoded.
|
||||||
|
---
|
||||||
|
|
||||||
|
# opencode-devbox environment
|
||||||
|
|
||||||
|
You are (or may be) running inside **opencode-devbox**: a Docker container that
|
||||||
|
ships opencode, MemPalace, and a curated tool stack, with the host source tree
|
||||||
|
mounted at `/workspace`. This skill is about the *container-shaped* facts that
|
||||||
|
change how you should act — things that are easy to get wrong because they
|
||||||
|
differ from a normal workstation shell.
|
||||||
|
|
||||||
|
> **Golden rule: this environment is a template, not a fixed deployment.**
|
||||||
|
> The host could be macOS, Windows, or Linux. There may or may not be LAN
|
||||||
|
> peers, a VPN, split-DNS, a skillset mount, or the `-omos` variant. Detect
|
||||||
|
> and verify the specifics live (commands below) — do **not** assume any
|
||||||
|
> particular hostname, domain, nameserver, or OS. Where this skill shows
|
||||||
|
> example values they are illustrative placeholders.
|
||||||
|
|
||||||
|
## 0. Am I in opencode-devbox, and what's true *here*?
|
||||||
|
|
||||||
|
Cheap detection signals (any one is sufficient):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[ -d /usr/local/lib/opencode-devbox ] && echo "opencode-devbox image"
|
||||||
|
[ -r "$HOME/.ssh-local/config" ] && echo "LAN/host SSH sidecar present"
|
||||||
|
case "$PS1" in *'[devbox]'*) echo "interactive devbox shell";; esac
|
||||||
|
```
|
||||||
|
|
||||||
|
Then orient before acting:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat /etc/os-release | head -2 # container distro (usually Debian)
|
||||||
|
ls -la /usr/local/lib/opencode-devbox/ # which devbox helpers exist
|
||||||
|
sed -n '/^Host /,$p' ~/.ssh-local/config 2>/dev/null # host/LAN reachability, if any
|
||||||
|
mount | grep -E ' /workspace | /home/\S+/\.ssh ' # what's bind-mounted
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1. Persistence vs ephemerality — know before you write
|
||||||
|
|
||||||
|
The container has **three storage tiers with very different lifetimes**. Pick
|
||||||
|
the right one or work is silently lost on the next recreate/update.
|
||||||
|
|
||||||
|
| Tier | Examples | Survives `down`? | Survives `down -v`? | Survives image update / `--force-recreate`? |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Host bind-mount** | `/workspace`, usually `~/.ssh` (ro), optionally `~/.mempalace` | yes | yes (lives on host) | yes |
|
||||||
|
| **Named volume** | `~/.config/opencode`, `~/.local/share/opencode`, `~/.local/state/opencode`, `~/.ssh-local`, `~/.cache/bash`, `~/.local/share/{uv,nvim,zoxide}` | yes | **no** | yes |
|
||||||
|
| **Writable container layer** | anything else: `sudo apt install …`, `rustup`/`ghc`/`R` toolchains, files in `/tmp`, `/opt` edits | yes | **no** | **no** |
|
||||||
|
|
||||||
|
Practical consequences:
|
||||||
|
|
||||||
|
- **Durable work goes in `/workspace`** (it's the host filesystem, UID-aligned —
|
||||||
|
what you write appears with the user's normal ownership on the host).
|
||||||
|
- **Runtime-installed system packages and language toolchains are ephemeral.**
|
||||||
|
If a task needs them reproducibly, it belongs in the image (Dockerfile) or a
|
||||||
|
project manifest, not an ad-hoc `apt install`. Tell the user when you install
|
||||||
|
something that won't survive.
|
||||||
|
- **`~/.config/opencode` is a named volume** (`devbox-opencode-config`), so
|
||||||
|
things baked into the *image* under `/home/<user>/...` are **shadowed** by the
|
||||||
|
volume on existing containers and only seen on a fresh volume. This is exactly
|
||||||
|
why image-owned content that must always be live (the OMOS bundled skills, the
|
||||||
|
image-baked fallback skills, the harness instructions) lives under an image
|
||||||
|
path like `/usr/local/share/opencode-devbox/...` and is **symlinked** in by
|
||||||
|
the entrypoint on every start — never copied into a home dir a volume covers.
|
||||||
|
Copying image content into `~/.config/opencode` *freezes* it: a later
|
||||||
|
`docker compose pull` will not refresh it.
|
||||||
|
|
||||||
|
## 2. Interactive shell vs. your tool shell (a real footgun)
|
||||||
|
|
||||||
|
The conveniences below are defined in `~/.bash_aliases` and **only exist in an
|
||||||
|
interactive login shell.** Your `bash` *tool* runs non-interactively, so these
|
||||||
|
are "command not found" there — you must spell out the underlying command.
|
||||||
|
|
||||||
|
| Interactive alias | Non-interactive equivalent to actually run |
|
||||||
|
|---|---|
|
||||||
|
| `dssh <host>` | `ssh -F "$HOME/.ssh-local/config" <host>` |
|
||||||
|
| `dscp …` | `scp -F "$HOME/.ssh-local/config" …` |
|
||||||
|
| `cat file` (→ `bat`) | `cat file` works, but output differs; use `command cat` for raw |
|
||||||
|
| `ll`, `la` (→ `eza`/`ls`) | `ls -lh`, `ls -lha` |
|
||||||
|
|
||||||
|
If a command "works in my terminal but not when the agent runs it," this alias
|
||||||
|
gap is the first thing to suspect.
|
||||||
|
|
||||||
|
## 3. Reaching the Docker host and its LAN over SSH
|
||||||
|
|
||||||
|
When the host is VM-backed (e.g. OrbStack / Docker Desktop on macOS) the
|
||||||
|
entrypoint's `setup-lan-access.sh` writes a **writable SSH sidecar** at
|
||||||
|
`~/.ssh-local/config`. It always provides:
|
||||||
|
|
||||||
|
- A `Host *` block redirecting `ControlPath` into the writable `~/.ssh-local/cm`
|
||||||
|
(because `~/.ssh` is typically bind-mounted **read-only**, so a master socket
|
||||||
|
can't be created under it), plus `Include ~/.ssh/config`.
|
||||||
|
- Aliases **`host` / `mac`** → `host.docker.internal` (user comes from
|
||||||
|
`HOST_SSH_USER`) — i.e. SSH back into the Docker host.
|
||||||
|
- On VM-backed hosts only: an **SSH-jump-via-host** block so the container can
|
||||||
|
reach the host's directly-attached LAN peers (`ProxyJump host`). On a native
|
||||||
|
Linux host the LAN is usually reachable directly and this jump block is
|
||||||
|
omitted — **so don't assume a jump path exists; read the sidecar.**
|
||||||
|
|
||||||
|
Use it (remember §2 — spell it out in tool bash):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ssh -F "$HOME/.ssh-local/config" mac 'hostname; whoami' # reach the host
|
||||||
|
ssh -F "$HOME/.ssh-local/config" <lan-peer> '…' # reach a LAN peer (if configured)
|
||||||
|
```
|
||||||
|
|
||||||
|
Related mechanisms (don't reinvent them):
|
||||||
|
|
||||||
|
- **ControlMaster multiplexing** is preconfigured (sockets under
|
||||||
|
`~/.ssh-local/cm`) to survive CGNAT per-destination flow caps on residential
|
||||||
|
ISPs. If `~/.ssh/config` pins a `ControlPath` under the read-only `~/.ssh`,
|
||||||
|
override with `-o ControlPath=none` (or use the sidecar, which already
|
||||||
|
redirects it).
|
||||||
|
- To name a LAN peer (give it a stable alias + `ProxyJump host`), put the block
|
||||||
|
in the host-owned `~/.config/devbox-shell/ssh-lan.conf` (bind-mounted in and
|
||||||
|
`Include`d), **not** in the read-only `~/.ssh/config`. opencode has no
|
||||||
|
built-in "run my tools on a remote host" rewiring — the sidecar + plain `ssh`
|
||||||
|
is the path.
|
||||||
|
|
||||||
|
## 4. DNS / name resolution — environment-specific, verify live
|
||||||
|
|
||||||
|
How a name resolves here is **not universal** and depends on the host's
|
||||||
|
networking. The container's own resolver is just `/etc/resolv.conf`, but the
|
||||||
|
*host* (which you reach via §3, and whose DNS the container may inherit) can use
|
||||||
|
**split-horizon DNS** to send certain internal domains to specific nameservers
|
||||||
|
while everything else goes to a default resolver/VPN gateway. The mechanism is
|
||||||
|
OS-specific and **may not be present at all**:
|
||||||
|
|
||||||
|
- **macOS host:** per-domain files in `/etc/resolver/<domain>`, each listing
|
||||||
|
`nameserver` lines. Reading them (over `ssh … mac`) is a fine way to learn the
|
||||||
|
real split-DNS map — *for that one machine.*
|
||||||
|
- **Linux host:** typically `systemd-resolved` split DNS (per-link `Domains=`
|
||||||
|
routing) or `/etc/resolv.conf` `search`/`nameserver`.
|
||||||
|
- **Windows host:** the NRPT (Name Resolution Policy Table) plays the per-suffix
|
||||||
|
role; WSL2 inherits host resolution via mirrored networking + DNS tunneling.
|
||||||
|
|
||||||
|
Operating rules:
|
||||||
|
|
||||||
|
1. **Never hardcode a domain→nameserver mapping or a specific nameserver IP** —
|
||||||
|
it is per-deployment and changes between users and even VPN states.
|
||||||
|
2. **Verify by reading the live config**, e.g. `cat /etc/resolv.conf` in the
|
||||||
|
container, or `ssh … mac 'cat /etc/resolver/* 2>/dev/null'` on a macOS host.
|
||||||
|
3. **Reachability needs both DNS *and* a route.** A name resolving to an
|
||||||
|
internal address is useless if packets to that subnet don't have a path
|
||||||
|
(e.g. via the VPN or the §3 jump). Check both when something "resolves but
|
||||||
|
won't connect."
|
||||||
|
4. If you discover deployment-specific facts (a domain, a nameserver, a
|
||||||
|
reachable peer), prefer recording them in MemPalace over baking them into
|
||||||
|
code or this skill.
|
||||||
|
|
||||||
|
## 5. Python and other languages: uv-first, toolchains are ephemeral
|
||||||
|
|
||||||
|
- A system `python3` exists, but **prefer `uv`** for REPLs and project envs —
|
||||||
|
it's installed and its store (`~/.local/share/uv`) is a persisted volume.
|
||||||
|
- Throwaway REPL: `uv run --with ipython ipython`
|
||||||
|
- Project env: `cd /workspace/proj && uv init && uv add <pkgs> && uv run …`
|
||||||
|
(the `pyproject.toml` + `uv.lock` travel with the repo — the durable choice).
|
||||||
|
- Other language toolchains (Rust via rustup, R, GHC, Clojure, Go) are
|
||||||
|
**runtime opt-ins on the ephemeral layer** unless baked into the image — they
|
||||||
|
do not survive `down -v` or an image update. Flag this when installing.
|
||||||
|
|
||||||
|
## 6. The oh-my-opencode-slim (OMOS) variant
|
||||||
|
|
||||||
|
Present only in the `-omos` image (detection: `/usr/lib/node_modules/oh-my-opencode-slim`
|
||||||
|
exists, or `ENABLE_OMOS=true`). It adds the bun runtime and multi-agent
|
||||||
|
orchestration. Two environment facts matter:
|
||||||
|
|
||||||
|
- **OMOS skills are symlinked from the image** into `~/.agents/skills/` by the
|
||||||
|
entrypoint on every start — they are **not** installed into the
|
||||||
|
`~/.config/opencode` volume (that froze them historically; see §1). A
|
||||||
|
`docker compose pull` + recreate refreshes them for free. Don't run the OMOS
|
||||||
|
installer with `--skills` to "fix" missing skills; the symlinks are the
|
||||||
|
mechanism.
|
||||||
|
- Optional tmux orchestration is gated by `OMOS_TMUX`; the OMOS config lives at
|
||||||
|
`~/.config/opencode/oh-my-opencode-slim.json` and is regenerated only via
|
||||||
|
`OMOS_RESET=true`.
|
||||||
|
|
||||||
|
## 7. MemPalace is the shared brain
|
||||||
|
|
||||||
|
MemPalace data is usually a **host bind-mount** (or an optional `devbox-palace`
|
||||||
|
named volume), so an opencode on the host and one in this container can share
|
||||||
|
one palace (SQLite WAL: many readers, one writer). Use it to persist the
|
||||||
|
deployment-specific facts this skill deliberately refuses to hardcode. Details
|
||||||
|
are in the `mempalace` skill — and the harness instruction shipped with this
|
||||||
|
image points you at it at session start.
|
||||||
|
|
||||||
|
## Checklist before acting in this environment
|
||||||
|
|
||||||
|
- [ ] Writing durable output? → `/workspace`, not the ephemeral layer.
|
||||||
|
- [ ] Using `dssh`/`dscp`/`ll` in the bash tool? → spell out the real command.
|
||||||
|
- [ ] Assuming a hostname / domain / nameserver / host OS? → stop, detect it.
|
||||||
|
- [ ] "Resolves but won't connect"? → check route *and* DNS (§3 + §4).
|
||||||
|
- [ ] `apt`/toolchain install? → tell the user it's ephemeral unless imaged.
|
||||||
|
- [ ] Shipping image-owned content opencode reads from `~/.config/opencode`? →
|
||||||
|
symlink it from `/usr/...`, never copy into the volume (§1).
|
||||||
@@ -155,6 +155,28 @@ else
|
|||||||
echo " - skipped (base variant)"
|
echo " - skipped (base variant)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "-- Image-baked fallback skills + harness instruction (both variants) --"
|
||||||
|
# Baked under /usr/local/share and linked in by entrypoint-user.sh.
|
||||||
|
# opencode-devbox-environment uses a name unlikely to be overridden, so it is a
|
||||||
|
# reliable probe that the skills reconcile ran; the instruction confirms the
|
||||||
|
# ~/.config/opencode/instructions symlink survived the named volume.
|
||||||
|
if [ -e "$HOME/.agents/skills/opencode-devbox-environment/SKILL.md" ]; then
|
||||||
|
pass "~/.agents/skills/opencode-devbox-environment resolves"
|
||||||
|
else
|
||||||
|
fail "~/.agents/skills/opencode-devbox-environment missing"
|
||||||
|
fi
|
||||||
|
if [ -e "$HOME/.agents/skills/mempalace/SKILL.md" ]; then
|
||||||
|
pass "~/.agents/skills/mempalace resolves"
|
||||||
|
else
|
||||||
|
fail "~/.agents/skills/mempalace missing"
|
||||||
|
fi
|
||||||
|
if [ -e "$HOME/.config/opencode/instructions/opencode-devbox.md" ]; then
|
||||||
|
pass "~/.config/opencode/instructions/opencode-devbox.md resolves"
|
||||||
|
else
|
||||||
|
fail "~/.config/opencode/instructions/opencode-devbox.md missing"
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "-- Shell defaults re-seeded from /etc/skel-devbox --"
|
echo "-- Shell defaults re-seeded from /etc/skel-devbox --"
|
||||||
if [ -f "$HOME/.bash_aliases" ]; then
|
if [ -f "$HOME/.bash_aliases" ]; then
|
||||||
|
|||||||
@@ -197,6 +197,18 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "-- Image-baked fallback skills + harness instruction --"
|
||||||
|
# Baked under /usr/local/share (base image, both variants). entrypoint-user.sh
|
||||||
|
# symlinks these into ~/.agents/skills/ and ~/.config/opencode/instructions/ on
|
||||||
|
# container start; assert the SOURCE here (smoke runs with --entrypoint="").
|
||||||
|
run "baked opencode-devbox-environment skill" \
|
||||||
|
"test -f /usr/local/share/opencode-devbox/skills/opencode-devbox-environment/SKILL.md && echo ok"
|
||||||
|
run "baked mempalace fallback skill" \
|
||||||
|
"test -f /usr/local/share/opencode-devbox/skills/mempalace/SKILL.md && echo ok"
|
||||||
|
run "baked harness instruction (opencode-devbox.md)" \
|
||||||
|
"test -f /usr/local/share/opencode-devbox/instructions/opencode-devbox.md && echo ok"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "-- Build provenance (manifest + OCI labels) --"
|
echo "-- Build provenance (manifest + OCI labels) --"
|
||||||
run "/etc/opencode-devbox/build-manifest.json present" \
|
run "/etc/opencode-devbox/build-manifest.json present" \
|
||||||
|
|||||||
Reference in New Issue
Block a user