Mosaic Stack — new foundation
The active rebuild is at this repository's root. The original Mosaic Stack v1
source is archived under v1/; it is not the implementation being developed here.
- Canonical checkout:
/mnt/storage/src/mosaic-stack - Repository:
mosaicstack/stack - Working branch:
refactor - Former
~/src/mosaic-stack-dev-test: compatibility symlink to this same checkout
Both original Git histories and pending development work are preserved. See the conversion record and current next action. Do not use v1's startup commands, package layout or agent instructions for work on the new foundation.
Original container proof
The foundation began as a standalone container experiment. 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 was verified to return exactly
MOSAIC_HELLO_OK. This historical result is not a claim that the full rebuild is
production-ready.
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.
Missions & tasks (M2)
Missions and tasks are validated JSON data (strict schemas, version-pinned). The M2 layer is host-side only: mission directives are recorded for provenance but do not yet reach the runtime system prompt (capability/policy layer comes later).
missions/hello.json objective + directives (missionVersion 1)
tasks/hello-marker.json prompt + optional mission ref + expectExact + timeout
<dataRoot>/runs/r-<id>/ immutable run record: task.json, mission.json,
stderr.txt, result.json (all write-once)
Usage:
scripts/run-task.sh validate tasks/hello-marker.json # strict validation, writes nothing
scripts/run-task.sh run tasks/hello-marker.json # execute; result recorded under dataRoot/runs
scripts/mosaic-task.mjs list # list runs and statuses
scripts/test-task.sh # selftests (schema negatives + live runs)
A run exits 0 only when its expectation is met (expectExact match); mismatches, nonzero agent exits, and timeouts record status: failed in result.json and exit 1. Each run gets a unique directory — rerunning never rewrites history.
Release model (M3)
RELEASE single-sources the release version (0.0.X until declared stable); the image tag derives from it plus the pinned Pi version. Activation is health-gated and every event is recorded:
scripts/release.sh package # build + tag the release image
scripts/release.sh activate # health check (exact marker) -> atomic pointer swap
scripts/release.sh activate --fault-injection # prove the refusal path (drills only)
scripts/release.sh rollback # health-gated return to the previous release
scripts/release.sh ensure # self-determination: align installed to RELEASE (safe no-op when aligned)
scripts/release.sh status # release, tag, active pointer, recent log
scripts/test-release.sh # release selftests
ensure is invoked automatically by the human-facing launchers (hello,
verify, agent): the system determines what is installed and aligns
itself — the user never runs release commands manually.
<dataRoot>/state/active.json— the activation pointer (atomic tmp+rename replace)<dataRoot>/state/activation-log.jsonl— append-only history: package / activate / refused / rollback
A failed health check never activates; the previously active release remains deployed. Updating the software therefore cannot corrupt the running installation: package beside, gate, then flip. Verified by the update/refusal/rollback drills in BUILD-LOG Phase 7.
Runtime adapters (M4)
The harness boundary is formalized: everything upstream (config, contracts, missions, tasks, run records) is harness-agnostic; everything inside an adapter belongs to one runtime.
adapters/<name>/adapter.sh env in: MOSAIC_SYSTEM_PROMPT_FILE, MOSAIC_REQUEST,
MOSAIC_PROVIDER, MOSAIC_MODEL
stdout: response only; stderr: diagnostics
- Selection:
execution.adapterin config.json (optional;pidefault; allowlistpi,mock) pi— pinned Pi CLI, noninteractive print mode, ambient discovery offmock— deterministic test adapter; never for real verification- Mission directives have a sanctioned injection point: when a task references a mission, the task runner mounts the run snapshot and the generated prompt gains a
MISSION (runtime)section (objective + directives) after the four immutable contracts - Adding a harness (Claude, Codex, OpenCode) later means adding one directory — no orchestrator changes
See adapters/README.md for the full contract.
Workspaces, capabilities, sessions (M5/M6)
Optional task fields extend what an agent can do — all defaulting to the previous behavior:
{
"workspace": "demo", // ":run" ephemeral, or persistent dataRoot/workspaces/<name>
"capabilities": { "tools": ["bash", "read"] }, // pi tool allowlist; absent = no tools
"session": "demo" // persistent session at dataRoot/sessions/<name>
}
- The adapter runs inside the workspace; files it writes are host-visible (
dataRoot/workspaces/<name>). - Sessions persist via pi's documented
--session-dir; a follow-up run in the same session resumes the conversation (-c) and can recall prior context. Distinct names never share state. Ephemeral (--no-session) remains the default when no session is declared. - Selection authority: config for adapter/provider/model; the task file for workspace/capabilities/session.
Inspect anything:
node scripts/mosaic-task.mjs list # runs with task/workspace/session columns
node scripts/mosaic-task.mjs show <runId> # full record + snapshots + artifacts
Demo fixtures: tasks/workspace-demo.json, tasks/session-demo-1.json + tasks/session-demo-2.json.
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.shbuilds the release image (mosaic-poc-agent:<pi>-r<release>, tag derived fromRELEASE+ the pinned Pi version) with 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/run-task.sh # run a mission/task file (see Missions & tasks)
scripts/release.sh # package / activate / rollback / status (see Release model)
scripts/test-config.sh # fast config-layer selftests (no Docker)
scripts/test-task.sh # mission/task selftests (schema + adapter seam + live runs)
scripts/test-release.sh # release selftests
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.
Mosaic-managed named accounts (agent.sh --auth) live under the data root
(auth/<account>.json, 0600) — the stack never writes into ~/.pi.
.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.