With tracker.descendants on, the tracker project is the root of a tree.
Each reconcile reads the sync bot's project list (archived projects and
negative pseudo-project ids left out) and walks it by parent_project_id.
A child that passes the install checks is read through its board, and
one that doesn't is watch-only (bucket 0) and never refuses the
business. A tick reads each project in the tree. A child answering 403
or 404 leaves the tree only if GET /projects/<id> then answers 403 or
404 too, and its open tasks get the one-by-one read. Task refs carry
the project, so a task moved inside the tree shows under its new ref
and the old ref gets its tombstone. tracker.descendants reaches the adapter
through the business var and packages/cli/src/config.mjs (lead decision
87). Off, the adapter reads as v0 did.
Darkwing built it (agents/darkwing/work/queue-62), in three rounds.
Round 3 was approved by Filbert (27317) and Dewey (27338) on manifest
8b81e836, 13 paths. Their non-blocking notes and Darkwing's follow-ups
(two surviving mutants in test 18, the fake's view-404 code) go to a
follow-up row.
Sage's gate on 918a4a19, sequential and teed: node suites tasks 71,
business 60, bus 80, cli 110 and queue 150, 0 failed; webui 39 of 40,
then 40 of 40 on three reruns (row 62 touches no webui file; a timing
test under load 17 with Dewey's gate running beside it); test-auth 15,
test-conductor 17, test-config 24, test-discord 66,
test-extension-package 18, test-foundation 44, test-queue 27,
test-release 14, test-task 98, all 0 failed (Docker on). edc5de71..HEAD
touches none of the 13 paths.
Live read, 2026-10-11T21:28:03Z, tasks.woltje.com v2.7.0, root #19,
descendants on, sync key only, GETs only (the startup scope probes not
sent): reconcile 15 GETs, 42 snapshots; tick 8 GETs, 0 snapshots.
Children 18, 20 and 21 are watch-only. A missing view id on #18 answers
404 (code 3014) while #18 reads 200, as the README says. Record:
agents/sage/work/vikunja-setup/2026-10-11_tick-62.json.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.