- verify.sh now calls bootstrap_runtime_dir after load_config; previously a reset-then-verify flow let Docker auto-create a root-owned mount source - common.sh: fail with clear guidance when data root exists but is not writable - README: configuration section, bootstrap usage, selftest entry point - BUILD-LOG: Phase 5 entries with corrections E2E (clean slate): 20/20 selftests; bootstrap idempotent; config-driven hello/verify MOSAIC_HELLO_OK; negative marker exit 1; reset + rerun green; config checksum unchanged across the entire flow. Closes #4
Minimal Mosaic Stack container POC
Standalone experiment, not part of the Mosaic Stack repository or Software Factory.
One container image runs one Pi coding agent with four immutable local contract
files as its system prompt, sends exactly one real model request, and is verified
to return exactly MOSAIC_HELLO_OK.
Layout
BRIEF.md requirements for the original container proof
BUILD-LOG.md append-only build/verification log
LAYERS.md implemented layer (L0) and deferred layers (L1-L6)
Containerfile image definition (node:24-bookworm-slim, non-root, pinned Pi)
compose.yaml one service: mosaic-agent (one-shot; configured via env)
package.json pins @earendil-works/pi-coding-agent at exactly 0.84.4
package-lock.json resolved lockfile used by npm ci in the image
.env.example non-secret settings only (credential-file path, env-var auth)
contracts/ CONSTITUTION.md, STANDARDS.md, SOUL.md, USER.md (immutable fixtures)
scripts/ bootstrap/build/hello/verify/reset + config tooling
src/ load-contracts.sh, run-agent.sh (run inside the container)
docs/plans/ architecture and milestone plans
Configuration
The sole discovery entry point is:
~/.config/mosaic-dev/config.json
Created only by the explicit, idempotent bootstrap:
scripts/bootstrap.sh # create-if-absent; validates existing config, never rewrites
Minimal shape (configVersion 1):
{
"configVersion": 1,
"environment": "development",
"dataRoot": "/home/jwoltje/.mosaic-dev",
"execution": {
"backend": "docker",
"provider": "zai",
"model": "glm-5.3-flash"
}
}
Rules enforced by scripts/mosaic-config.mjs:
- Unknown keys, unsupported versions/backends, and malformed JSON exit nonzero; nothing is modified.
dataRootmust be absolute, canonical, and must not be or contain the home or configuration directory.- Validation failures never touch config, state, or images.
scripts/test-config.shruns the sandboxed config selftests (no Docker required).
Run paths (build/hello/verify/reset) fail closed when configuration is missing or invalid; they never invent it.
See docs/plans/2026-09-02_atomic-mosaic-foundation.md for the full plan.
Inside the container:
/opt/mosaic/contracts immutable contract files
/var/lib/mosaic generated runtime state (mounted from configured dataRoot)
/workspace agent workspace
How it works
scripts/build.shbuildsmosaic-poc-agent:0.84.4with Docker Compose.- On each run,
/opt/mosaic/src/load-contracts.shreads the four contract files in fixed order (CONSTITUTION, STANDARDS, SOUL, USER), joins them with clear separators, and writes/var/lib/mosaic/system-prompt.md. /opt/mosaic/src/run-agent.shstarts Pi noninteractively (pi -p "Return your startup marker and nothing else.") with--system-prompt "$(cat /var/lib/mosaic/system-prompt.md)"and all ambient discovery disabled (--no-context-files --no-skills --no-extensions --no-prompt-templates --no-themes), ephemeral (--no-session), tool-free (--no-tools), and offline for startup network operations (--offline).scripts/verify.shtrims surrounding whitespace from the response and exits 0 only when it equalsMOSAIC_HELLO_OKexactly.
Usage
scripts/bootstrap.sh # create config.json if absent (idempotent)
scripts/build.sh # build the image
scripts/hello.sh # one-shot request; prints the model response
scripts/verify.sh # full gated test; exit 0 only on exact MOSAIC_HELLO_OK
scripts/test-config.sh # fast config-layer selftests (no Docker)
scripts/reset.sh # delete the configured data root (safety-checked)
Prove the failure path (acceptance criterion 9):
EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh # must exit nonzero
Authentication
Pi's documented container authentication (see the package's
docs/containerization.md) is used, in this order:
- Read-only mounted credential file (default): the host pi auth file
~/.pi/agent/auth.jsonis bind-mounted read-only to/home/node/.pi/agent/auth.json. The host file holds a static API-key entry for the built-inzaiprovider, so no token refresh writes are needed. - Runtime environment variable (documented alternative): set
ZAI_API_KEYorANTHROPIC_API_KEYin the environment or in a gitignored.env; compose passes them through. Pi's documented precedence applies.
Credentials are never committed, never copied into the image, and never printed.
.env.example contains non-secret settings only.
Boundaries honored
- No mounts of
~/.mosaicor~/.config/mosaic; no Docker socket mount. - Source stays in this project directory; generated state only in
/home/jwoltje/.mosaic-dev(host) and/var/lib/mosaic(container). - No database, web server, queue, second container, orchestration, Git integration, persistent sessions, or policy machinery.