222 lines
10 KiB
Markdown
222 lines
10 KiB
Markdown
# 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
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
~/.config/mosaic-dev/config.json
|
|
```
|
|
|
|
Created only by the explicit, idempotent bootstrap:
|
|
|
|
```bash
|
|
scripts/bootstrap.sh # create-if-absent; validates existing config, never rewrites
|
|
```
|
|
|
|
Minimal shape (`configVersion` 1):
|
|
|
|
```json
|
|
{
|
|
"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.
|
|
- `dataRoot` must 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.sh` runs 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).
|
|
|
|
```text
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.
|
|
|
|
```text
|
|
adapters/<name>/adapter.sh env in: MOSAIC_SYSTEM_PROMPT_FILE, MOSAIC_REQUEST,
|
|
MOSAIC_PROVIDER, MOSAIC_MODEL
|
|
stdout: response only; stderr: diagnostics
|
|
```
|
|
|
|
- Selection: `execution.adapter` in config.json (optional; `pi` default; allowlist `pi`, `mock`)
|
|
- `pi` — pinned Pi CLI, noninteractive print mode, ambient discovery off
|
|
- `mock` — 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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```text
|
|
/opt/mosaic/contracts immutable contract files
|
|
/var/lib/mosaic generated runtime state (mounted from configured dataRoot)
|
|
/workspace agent workspace
|
|
```
|
|
|
|
## How it works
|
|
|
|
1. `scripts/build.sh` builds the release image (`mosaic-poc-agent:<pi>-r<release>`,
|
|
tag derived from `RELEASE` + the pinned Pi version) with Docker Compose.
|
|
2. On each run, `/opt/mosaic/src/load-contracts.sh` reads the four contract files
|
|
in fixed order (CONSTITUTION, STANDARDS, SOUL, USER), joins them with clear
|
|
separators, and writes `/var/lib/mosaic/system-prompt.md`.
|
|
3. `/opt/mosaic/src/run-agent.sh` starts 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`).
|
|
4. `scripts/verify.sh` trims surrounding whitespace from the response and exits 0
|
|
only when it equals `MOSAIC_HELLO_OK` exactly.
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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:
|
|
|
|
1. **Read-only mounted credential file** (default): the host pi auth file
|
|
`~/.pi/agent/auth.json` is bind-mounted read-only to
|
|
`/home/node/.pi/agent/auth.json`. The host file holds a static API-key
|
|
entry for the built-in `zai` provider, so no token refresh writes are needed.
|
|
2. **Runtime environment variable** (documented alternative): set `ZAI_API_KEY`
|
|
or `ANTHROPIC_API_KEY` in 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 `~/.mosaic` or `~/.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.
|