# Mosaic Framework Constitution — Canonical Design (Alpha) **Status:** CANONICAL. This is the single design of record for the alpha. It supersedes `synthesis-v1.md` where they differ. It integrates `synthesis-v1.md` and the three red-team passes (`debate/redteam-contrarian.md`, `debate/redteam-devex.md`, `debate/redteam-steward.md`), each finding either mitigated here or explicitly accepted with rationale (§9). A PRD derives from this document; implementation derives from the PRD. **Scope:** DQ1–DQ5 of `BRIEF.md`, plus the non-DQ release blockers (LICENSE, hardcoded credential path) the debate surfaced. Every claim is grounded in the real tree at `packages/mosaic/framework/` and `packages/mosaic/src/`; paths and line numbers were re-verified against the working copy, not trusted from the prior papers. --- ## 0. What changed vs synthesis-v1 The synthesis layer model and "subtraction not addition" doctrine survive the red team intact and are adopted wholesale. What the red team **broke** — and this document fixes — is the seam between the spec and the mechanisms it assumed already existed. Three facts re-verified here change the plan: 1. **The resident contract is the root file `~/.config/mosaic/AGENTS.md`, seeded once and never re-seeded.** `launch.ts:326` reads root `AGENTS.md`; `install.sh:236` seeds it only when absent (`[[ ! -f ... ]]`); `file-adapter.ts:187` (`if (existsSync(dest)) continue`) does the same in the npm path. **Removing files from `PRESERVE_PATHS` does NOT update them** — it only stops preserving a file the seed loop then declines to recreate. The synthesis's headline drift fix is mechanically wrong (contrarian R1, steward RISK-04). Fixed in §3/§5. 2. **`mosaic ` already self-heals a missing `SOUL.md`** via `checkSoul()` (`launch.ts:55-68`): it runs the setup wizard, so deleting `defaults/SOUL.md` does **not** brick a `mosaic`-launched session. The real hole is (a) bare launches that bypass `mosaic`, and (b) the wizard hanging on a non-TTY host (devex B1, contrarian R4). Fixed in §3/§4. 3. **The contamination is broader than synthesis-v1 enumerated** — re-grep finds the private credential path in **three** scripts (incl. `tools/health/stack-health.sh:23`), a private domain `brain.woltje.com` in the shipped `prevent-memory-write.sh` hook, and operator tokens across `tools/`, `guides/`, and the init generator's default role string — none of which the synthesis fix list or the proposed grep scope covered (devex B3, steward RISK-01/03). Fixed in §6. There are **two dual implementations** of the upgrade logic (`install.sh` bash + `file-adapter.ts` npm), kept in sync only by a comment (`file-adapter.ts:148`). Every mechanism change in this document is specified as **"in both installers, proven by one shared fixture suite"** (contrarian R10). This is promoted to a first-class design constraint, not an afterthought. --- ## 1. Layering & Precedence (final model) ### 1.1 The legitimacy test A layer boundary is legitimate **iff** the two sides differ in **owner**, **upgrade-fate**, OR **residency**. This single test (from `synthesis-v1.md` §1, banked by all three red teams) decides every split below and rejects gratuitous ones. ### 1.2 The canonical layers Five concerns, **four owned layers** plus a non-resident governance spec. | # | Layer | Owns | Owner | Upgrade fate | Residency | Deployed path | |---|-------|------|-------|--------------|-----------|---------------| | **L0** | **Constitution** | Irreducible non-negotiable law: the hard gates, escalation triggers, block-vs-done, mode declaration, the two-axis precedence rule, the "hooks are the gate" doctrine, the "no operator context in framework PRs" firewall, and the **universal merge-disambiguation rule** (see §1.4) | Framework | **Overwritten wholesale every upgrade** (unconditional copy, never seed-if-absent). User MUST NOT edit. | Always resident, byte-budgeted | `~/.config/mosaic/CONSTITUTION.md` | | **L1** | **Standards & Guides** | How to do the work well: secrets/ESO, trunk-based git, image tagging, E2E procedure, QA matrix, orchestrator protocol, all `guides/*` | Framework; a deployment may **tighten** via overlay | Overwritten; user delta lives in `STANDARDS.local.md`; guides never forked | `STANDARDS.md` resident; `guides/*` on-demand | `~/.config/mosaic/STANDARDS.md`, `~/.config/mosaic/guides/*` | | **L2** | **Persona (SOUL)** | Agent name, tone, role, communication style, persona principles | User (init-generated) | **Never overwritten.** Generated from template. | Always resident, byte-budgeted | `~/.config/mosaic/SOUL.md` (+ optional `SOUL.local.md`) | | **L3** | **Operator (USER)** | Human name, pronouns, timezone, accessibility, comms prefs, projects, **operator policy** (e.g. merge-authority delegation), operator tool paths/env | User (init-generated) | **Never overwritten.** | Always resident, byte-budgeted | `~/.config/mosaic/USER.md` (+ optional `USER.local.md`, optional `policy/*.md`) | | **L4** | **Project / Runtime mechanism** | Per-repo `AGENTS.md` deltas; harness-specific **mechanism only** (subagent syntax, hook/MCP wiring, injection tier) | Repo / framework | Project file user-owned; runtime mechanism overwritten | Project in-repo; runtime resident, ~15 lines | `/AGENTS.md`, `~/.config/mosaic/runtime//RUNTIME.md` | | — | **Layer-Model spec** (governance) | The definition of the layers, precedence, and "what may live in L0" | Framework maintainers | Source-only, **never deployed** | Not resident | `packages/mosaic/framework/constitution/LAYER-MODEL.md` | Deployed `AGENTS.md` is **not a layer** — it is the thin **load-order dispatcher + Conditional Guide Loading table** that routes to L0–L4. Framework-owned, overwritten on upgrade. ### 1.3 Precedence — typed two-axis, not a flat stack Stated verbatim in L0: > **Safety axis (gates, integrity, destructive actions):** L0 Constitution is supreme. Nothing in > STANDARDS, SOUL, USER, `policy/`, project `AGENTS.md`, runtime, or any injected reminder may relax, > suspend, or contradict a Constitution gate. A lower layer may only make behavior **stricter**, never > more permissive. > > **Taste axis (tone, formatting, verbosity, iconography):** the operator layers (SOUL/USER) win over > generic framework or model defaults. The framework has no legitimate opinion on style. ### 1.4 The merge-disambiguation correction (contrarian R6 — accepted and fixed) The synthesis moved the entire gate #13 to an opt-in example. That silently weakens a hard gate: by the stricter-only rule, a deployment that does **not** adopt the example defaults to the *strictest* reading of "No self-merge" — never merge without the human — which **contradicts** gates #2/#9 the BRIEF says to preserve. Gate #13 is therefore **split**: - **Universal law (stays in L0, operator-agnostic):** *"A 'No self-merge' note on a PR means no UNREVIEWED self-merge; it does not suspend a coordinator-authorized merge. When a coordinator session is active, the post-review merge go-ahead is the coordinator's; once review gates pass, proceed on the coordinator's confirmation."* - **Operator delegation (→ `examples/policy/merge-authority.example.md`):** *"don't wait on `{{OPERATOR_NAME}}` personally."* The named-person clause and only that clause leaves L0. This keeps the gate-interaction semantics universal while removing the PII. ### 1.5 Enforcement strength is a ranked ladder, not a choice ``` mechanical (hook / CI) > resident-by-value (system-prompt injection) > file-read (self-load fallback) ``` 1. **Mechanical first.** Every *checkable* gate becomes a hook or CI check (no-force-merge, green-CI-before-done, no-hardcoded-secrets, no-PII, no-dead-paths, no-unrendered-tokens). This drains prose from the resident core — the precondition that makes tiers 2–3 viable. Precedent: `prevent-memory-write.sh` (`runtime/claude/RUNTIME.md:30`) — "the rule alone proved insufficient; the hook is the hard gate." 2. **Resident-by-value second.** The irreducible *non-checkable* stop-condition gates (block-vs-done, escalation, completion-definition) injected by value at primacy, restated as a ≤5-bullet anchor at recency (bottom). 3. **File-read third (fallback).** Tier-3/bare launches: **unconditional** read (see §1.6). ### 1.6 Tier-aware self-load (contrarian R9 / steward RISK-07 — accepted) The fallback read instruction differs by tier: - **Tier-1 (injected by value):** *"`CONSTITUTION.md` is already in your context above; do not re-read."* (true, because the launcher demonstrably injected it). - **Tier-3 (bare-launch pointer):** **unconditional** — *"READ `~/.config/mosaic/CONSTITUTION.md` now, before your first action."* No "if not already in context" introspection — models are unreliable at judging their own window, and this is the exact drift-prone path the fallback exists to protect. This removes the false unconditional "already in your context — do not re-read" at `defaults/AGENTS.md:11` (every paper flagged it; it is still live in the tree). --- ## 2. File-by-File Move / Sanitize Plan ### 2a. New files | New file | Content | Source | |----------|---------|--------| | `defaults/CONSTITUTION.md` → deploys to `~/.config/mosaic/CONSTITUTION.md` | **L0, one flat file, ~70–90 lines.** The 13 hard gates with the §1.4 split applied (operator name removed, disambiguation kept); 5 escalation triggers; block-vs-done; mode-declaration; the §1.3 two-axis precedence rule **verbatim**; the "hooks are the gate" doctrine; the §4 "no operator context in framework PRs" firewall; the §1.6 tier-aware self-load lines; one pointer to the guide index. Gates keep full wording; procedure (wrapper paths, `--purpose` flags) moves to L1. **L0 is authored in capability verbs** — no tool-named "else stop" (see §7, devex M7). | Extracted from `defaults/AGENTS.md:23-87,143` | | `constitution/LAYER-MODEL.md` | The §1 model + precedence + "what may live in L0" + the overlay-eligibility list (§4). **Source-only, never deployed, never resident.** | This document | | `examples/personas/execution-partner.md` | Sanitized, placeholdered essence of the Jarvis persona — a worked example, copied on request, never auto-loaded | `defaults/SOUL.md` (sanitized) | | `examples/overlays/e2e-loop.json` | Sanitized essence of `jarvis-loop.json` (`~/src/` placeholders) | `runtime/claude/settings-overlays/jarvis-loop.json` | | `examples/policy/merge-authority.example.md` | The operator delegation clause from §1.4 | `defaults/AGENTS.md:37` | | `LICENSE` (monorepo root) + `packages/mosaic/framework/LICENSE` | MIT text + `"license": "MIT"` in `package.json` | new (D8) | | `CONTRIBUTING.md` (framework package) | Layer model, PII/secrets prohibition, dedup rule, how to add a harness adapter, the re-contamination rule, the **dual-installer parity rule**, the **known-limitations** list (§9) | new | | `tools/quality/scripts/verify-sanitized.sh` | The blocking CI gate (§6) | new | | `.woodpecker.yml` (framework package or monorepo root) | Wires `verify-sanitized.sh`, the resident line-count check, and the composer unit test as **blocking** steps | new (steward RISK-02 — the gate is prose until wired) | ### 2b. Files that shrink / change role | File | Change | DQ | |------|--------|----| | `defaults/AGENTS.md` | Gut 155→~50-line dispatcher: load order + Conditional Guide table + tier-aware self-load. **Zero restated gates.** Remove the false line 11. **Change seed semantics to unconditional overwrite** (see §3). | DQ1, DQ5 | | `defaults/STANDARDS.md` | Drop "Master/slave" framing (line 5 → "Primary / satellite"); stop re-asserting L0 gates; end with the `STANDARDS.local.md` additive-include convention. Becomes overwrite-on-upgrade. | DQ1,3,5 | | `defaults/TOOLS.md` | Delete the `MANDATORY jarvis-brain rule` block (lines 40-44). Generic index only. | DQ2 | | `defaults/README.md:72` | `--name Jarvis --user-name Jason --timezone America/Chicago` → placeholder names. | DQ2 | | `templates/SOUL.md.template` | Already clean. Keep. Ensure every `{{TOKEN}}` resolves to a non-empty value in init (no token survives into a resident file). | DQ2 | | `templates/agent/AGENTS.md.template` **and** `templates/agent/projects/*/{AGENTS,CLAUDE}.md.template` | **Delete the restated Hard-Gates block.** Replace with: *"This project is governed by `~/.config/mosaic/CONSTITUTION.md`. Add only project-specific extensions below."* **Fix every `rails/git/`→`tools/git/`, `rails/codex/`→`tools/codex/`** across BOTH `AGENTS.md.template` and `CLAUDE.md.template` families (devex m10 — synthesis named only the AGENTS family). | DQ4,5 | | `runtime/{claude,codex,pi,opencode}/RUNTIME.md` | Strip restated policy. Reduce to harness mechanism + one-line `CONSTITUTION.md` reference. **Rewrite the four "sequential-thinking MCP is required / else stop" lines** to capability-verb form (§7). | DQ4,5 | | `tools/_lib/credentials.sh:19`, `tools/git/detect-platform.sh:89`, **`tools/health/stack-health.sh:23`** | `${MOSAIC_CREDENTIALS_FILE:-$HOME/src/jarvis-brain/credentials.json}` → `${MOSAIC_CREDENTIALS_FILE:?MOSAIC_CREDENTIALS_FILE must be set}` (fast-fail per `STANDARDS.md:35`). Document the env var in `USER.md.template` under `## Tool Paths`. **Three sites, not two** (steward RISK-01, devex B3). | DQ2 (blocker) | | `tools/qa/prevent-memory-write.sh:29` | `https://brain.woltje.com/v1/thoughts` → `${OPENBRAIN_URL:?OPENBRAIN_URL must be set}/v1/thoughts`. This hook prints its URL to the agent on every blocked write — a private domain in every install. | DQ2 (blocker-class) | | `tools/_scripts/mosaic-init:277-278` | Default `AGENT_NAME "Assistant"` + the verbatim Jarvis role string (`"execution partner and visibility engine"`). **Fail-closed** on persona in `--non-interactive` unless `--agent-name` given; replace the role default with a neutral placeholder. (devex B2 — the generator re-creates the bug `verify-sanitized.sh` can't see.) | DQ2 | | `tools/_scripts/mosaic-doctor:312` | `mosaic-jarvis` skill → `mosaic-agent` (generic). | DQ2 | | `guides/ORCHESTRATOR.md` (99,111,152), `ORCHESTRATOR-LEARNINGS.md:127`, `ORCHESTRATOR-PROTOCOL.md:4`, `TOOLS-REFERENCE.md` (149,182,226), `BOOTSTRAP.md` | Replace `jarvis-brain/...` paths with `~/.config/mosaic/...` canonical paths; remove the `MANDATORY jarvis-brain rule` block. (steward RISK-03 — broader than synthesis named.) | DQ2 | ### 2c. Files deleted / relocated | File | Action | Why | |------|--------|-----| | `defaults/SOUL.md` | **Delete.** Persona generated at init from template; `mosaic` self-heals via `checkSoul()`; bare-launch hole closed in §3. | Primary contamination vector | | `runtime/claude/settings-overlays/jarvis-loop.json` | **Delete** → sanitized `examples/overlays/e2e-loop.json` | Personal project map | | `defaults/AUDIT-2026-02-17-framework-consistency.md` | **Move** to monorepo `docs/` | Maintainer artifact, not agent context | --- ## 3. Customization + Upgrade-Safety Mechanism **The single sentence a user can rely on:** *"Edit `SOUL.md`/`USER.md` and the `*.local.md` overlays freely — upgrades never touch them. Never edit `CONSTITUTION.md`/`STANDARDS.md`/`guides/*`/`AGENTS.md` — they update automatically every upgrade. To change framework behavior, add a `.local.md` overlay or a `policy/` file (tighten-only)."* ### 3.1 The seam = ownership, enforced by overwrite semantics (contrarian R1 / steward RISK-04 — the central fix) The synthesis's "remove from `PRESERVE_PATHS`" is **necessary but not sufficient**. The seed-if-absent logic must be **replaced with unconditional overwrite for the framework-owned root files**, in BOTH installers: 1. **Split the seed lists by ownership.** `DEFAULT_SEED_FILES` (`file-adapter.ts:16`) and the `install.sh:236` seed loop are split into: - **`FRAMEWORK_OWNED`** = `CONSTITUTION.md`, `AGENTS.md`, `STANDARDS.md` → **always copied (overwrite) on every upgrade.** Never in `PRESERVE_PATHS`. - **`USER_SEEDED`** = `TOOLS.md` (generated-then-tuned) → seed-if-absent, kept in `PRESERVE_PATHS`, retains `.bak.`-on-regenerate. 2. **`SOUL.md`, `USER.md`, `*.local.md`, `policy/`, `memory`, `sources`, `credentials`** are the **only** `PRESERVE_PATHS` entries. `AGENTS.md` and `STANDARDS.md` are **removed**. 3. **Test the injected bytes, not file presence** (contrarian R1). The migration fixtures assert what `buildPrompt`/`launch.ts:325-333` composes, because testing `defaults/AGENTS.md` content would pass while the resident root contract stayed stale. ### 3.2 Additive overlays, launcher-composed (steward RISK-06 / devex M6 — build it, don't assume it) `mosaic compose-contract ` **does not exist** and is **alpha-blocking**, not assumed. Minimum viable spec: - Concatenates, in precedence order, base + `.local` deltas **before** injection, so the model gets one pre-merged blob (no redundant read-merge ritual). - **Per-harness emission** (the four harnesses are not symmetric): - **Pi / `mosaic claude` / `mosaic codex`** — append the merged blob via `--append-system-prompt`. - **Codex / OpenCode** — write the merged blob into the instructions file (`~/.codex/instructions.md`, `~/.config/opencode/AGENTS.md`). - **Bare launches that bypass `mosaic`** get **base-only** overlays (the launcher never ran to compose them). This is **documented loudly** as a known limitation (§9), and the `AGENTS.md` self-load fallback emits a one-line "overlays require `mosaic `; run `mosaic doctor`" nudge. - **Alpha scope cut (accepted):** ship **`SOUL.local.md` + `USER.local.md`** (the two files users actually customize) and **`STANDARDS.local.md`**. Defer `policy/*.md` composition to v2 if build budget is tight — but the L0 merge-disambiguation rule (§1.4) means `policy/` is *additive delegation only*, never load-bearing for a gate, so deferral is safe. ### 3.3 Versioning & migration 1. **One global `FRAMEWORK_VERSION` integer + linear migrations** (existing `install.sh:157-198` scaffold). No per-layer version matrix (combinatorial test cliff). Per-layer template versions survive only as a `mosaic doctor` advisory. 2. **Bump `FRAMEWORK_VERSION` 2→3.** The v2→v3 migration: - **Snapshot `~/.config/mosaic/` → `~/.config/mosaic/.backup-v3/` first** (contrarian R2 — today there is *no* snapshot; the `cp`-fallback `rm -rf` at `install.sh:140` can lose `SOUL.md`/ `credentials` on interrupt). Implement as atomic snapshot → sync → on-failure-restore in BOTH installers; a fixture kills the process mid-sync and asserts no data loss. - **Vendor the v2 baseline** of `AGENTS.md`/`STANDARDS.md` into the migration. If the installed file **differs** from the v2 baseline (it was user-edited — the *sanctioned* customization until now), **copy it to `AGENTS.md.pre-constitution.bak` / `STANDARDS.local.md`** and print a one-line notice **before** overwriting (contrarian R2, devex M5). Never silently delete; never auto-merge (Markdown has no merge semantics — a half-resolved merge leaves `<<<<<<<` markers in the resident identity file). Fixture 3 asserts the delta **landed in `.local.md`**, not merely that a backup exists. - Install `CONSTITUTION.md` as a **new** file nothing previously owned (avoids reclassifying a user-edited flat `AGENTS.md`). 3. **Headless bootstrap (devex B1 / contrarian R4 — the hole `checkSoul` half-covers).** `mosaic ` self-heals a missing `SOUL.md` via `checkSoul()` (`launch.ts:55`), but the wizard hangs on a non-TTY host. Fix: `install.sh` runs `mosaic-init --non-interactive` after sync so a valid `SOUL.md`/`USER.md` always exists post-install; the wizard's non-interactive path is **fail-closed on persona** (devex B2) — it errors asking for `--agent-name` rather than silently shipping an agent named "Assistant" with the Jarvis role string. ### 3.4 The migration is the biggest risk — gate the alpha on a falsifiable fixture matrix Alpha **cannot tag** until these pass with **no interactive prompt, no hang**, run against **both** `install.sh` and `FileConfigAdapter.syncFramework` from **one shared suite** (contrarian R10): 1. **Fresh install** → valid resident `CONSTITUTION.md`+`AGENTS.md`+`SOUL.md`+`USER.md` exist; assert *injected bytes*. 2. **Legacy-flat user-edited install** (`MOSAIC_INSTALL_MODE=keep`, the upgrade default; steward RISK-05) → law moves to `CONSTITUTION.md`, root `AGENTS.md` is **overwritten** with the new dispatcher, the user's old edits land in `AGENTS.md.pre-constitution.bak`, `SOUL.md`/`credentials` survive. 3. **User-tuned-standard install** → the `STANDARDS.md` delta survives **as `STANDARDS.local.md`** and the framework `STANDARDS.md` updates. 4. **Unattended install (no TTY)** → valid resident `SOUL.md`/`USER.md` exist, **zero `read` calls**, no agent named "Assistant". 5. **Interrupt-during-sync** → snapshot restore leaves no data loss. ### 3.5 Detection without enforcement `mosaic doctor` reports drift / unrendered-tokens / budget-overflow / template-version-skew as **advisories** (warn, never block launch). `--check-constitution` is opt-in diagnostic, not a gate. **Accepted limitation:** drift on bare launches that never invoke `mosaic` is undetected by `doctor` (devex m9) — documented in `CONTRIBUTING.md`; the self-load fallback nudges the user toward `doctor`. --- ## 4. Sanitization — per-layer strategy + a class-closing CI gate **Ships generic (PII-free, complete):** `CONSTITUTION.md`, `AGENTS.md` (dispatcher), `STANDARDS.md`, `TOOLS.md` (generic index), all `guides/*` (purged), `templates/*` (token-only), `examples/*` (placeholdered), `runtime/*/RUNTIME.md` (mechanism-only), `adapters/*.md`, `LICENSE`, `CONTRIBUTING.md`. **Generated at `mosaic init`:** `SOUL.md`, `USER.md`, `TOOLS.md`, `*.local.md`, optional `policy/*.md`, per-harness runtime copies. **Deleted / relocated:** per §2c. ### 4.1 The CI gate — honest scope (contrarian R7 / devex B3 / steward RISK-01,02,03) `verify-sanitized.sh` is split into two rule-classes so it neither false-positives into being disabled nor under-scopes past the runnable contamination: - **Structural rules (operator-independent, always valid):** unrendered `{{...}}`/`${...}` in *resident* files; dead `/rails/` tokens; **L0 must contain no tool-named hard-stop** (`grep CONSTITUTION.md for 'sequential-thinking|MCP.*REQUIRED|else stop' → fail`, §7); no `${VAR:-$HOME/...}` private-default in any `*.sh`. - **Current-contaminant denylist (labeled one-time regression guard, NOT a general PII detector):** `jarvis|jason|woltje|\bPDA\b|jarvis-brain|brain\.woltje\.com`, and the specific absolute path `/home/jwoltje/`. Anchored to avoid `comparison`/`jsonwebtoken` false hits. - **Scope:** `defaults/ guides/ templates/ runtime/ adapters/ tools/` over **both `*.md` and `*.sh`** (the credential leak and the hook URL live in `*.sh` under `tools/` — the synthesis grep covered neither). **Excludes `examples/`.** - **Self-test:** the gate plants a `jarvis-brain` token in a fixture and asserts the gate fails, so a grep-syntax error can't silently no-op the gate (steward RISK-02). - **Wired blocking** in `.woodpecker.yml`. Until green-and-wired, the alpha cannot tag. ### 4.2 The durable class-closer is the L0 prose firewall + human review, with the grep as backup The primary author of future framework PRs is an agent running with *some* operator's SOUL/USER in context; a 6-token denylist cannot generalize to the next operator's name. So the **primary** control is the L0 rule, stated verbatim in `CONSTITUTION.md`: > *"When proposing a framework PR or capturing a `framework-improvement`/`tooling-gap`, you MUST NOT > include content derived from SOUL.md, USER.md, or operator-specific context. If you cannot express it > operator-agnostically, it belongs in `policy/` or a project `AGENTS.md`, not the framework."* The grep is the **backup** regression guard, explicitly labeled as such — not oversold as closing the PII class. --- ## 5. Cross-Harness Adapter Strategy **Single source:** L0 `CONSTITUTION.md` is the one law text. No harness gets a forked copy; runtime files and project templates **reference** it, never restate it. **Adapter contract (mechanism only):** `adapters/.md` / `runtime//RUNTIME.md` may specify only (a) the injection channel + tier, and (b) how L0's **capability verbs** bind to concrete tools and whether absence is a hard stop. The Constitution says *"use structured reasoning before planning"*; the Claude adapter binds it to `sequential-thinking` MCP (gate=true); the Pi adapter to native thinking (gate=false). For the alpha, this binding is a **markdown table**; JSON manifests are v2. **Tiered, honest injection (the four harnesses are not symmetric — verified):** | Harness | Channel | Tier | L0 delivery | |---------|---------|------|-------------| | Pi | `--append-system-prompt`, no hook backstop (`adapters/pi.md:14`) | 1 | By value at primacy; keep L0 tiny — resident fidelity is Pi's only enforcement | | `mosaic claude` / `mosaic codex` | system-prompt append (`launch.ts:518,551`) | 1 | By value at primacy + ≤5-bullet recency anchor | | Codex / OpenCode | instructions file | 2 | Resident-ish; composer writes merged blob; self-load backup | | bare `claude`/`codex`/`opencode` | thin pointer | 3 | ≤5-bullet anchor inline + **unconditional** "READ CONSTITUTION.md NOW" | **Tier-3 anchor must be a literal L0 substring, not a paraphrase (devex M4 — accepted).** You cannot forbid paraphrasing gates (D7) and then ship a 5-bullet paraphrase as the Tier-3 payload. The anchor is the *exact bytes* of the 5 irreducible stop-condition gate lines, so Tier-3 is a strict **subset** of Tier-1, never a divergent text. The composer unit test asserts **byte-equality** of the anchor against its L0 source lines. **Verification control — re-scoped (contrarian R3 / steward RISK-11 — accepted).** The synthesis's "live-launch each harness in CI and assert effective context" is impractical (no Codex/OpenCode prompt dump; Tier-3 unassertable without reading model behavior). Replace with a **composer unit test**: assert `buildPrompt(harness)` output contains the irreducible-gate anchor for each tier, and that the Tier-3 anchor is byte-equal to its L0 source. This is real and cheap. Live-launch smoke testing is a **v2 aspiration**. Codex/OpenCode **hook parity** is a **tracked gap** in `CONTRIBUTING.md`'s compliance matrix, not something the alpha closes. **sequential-thinking contradiction (devex M7 — accepted).** It lives in **four** RUNTIME files (`runtime/{claude,codex,opencode}/RUNTIME.md:3` say "required"; `runtime/pi/RUNTIME.md:61` says "not gated"). All four are rewritten in the same PR to capability-verb form; L0 carries **no** tool-named "else stop"; the structural CI rule (§4.1) enforces it; a fixture asserts a bare `pi` launch does not emit a sequential-thinking halt. --- ## 6. Phased Implementation Plan (alpha — ordered, each phase independently shippable) Each phase is a self-contained, CI-green PR. Order is dependency-driven: legal/safety first, then the extraction the rest depends on, then mechanism, then cross-harness, then the gate that locks it. ### Phase 0 — Legal & runnable-leak blockers (no behavior change) - Add MIT `LICENSE` (root + framework) + `"license": "MIT"` in `package.json`. - Fix the credential path in **all three** `*.sh` sites → `${MOSAIC_CREDENTIALS_FILE:?...}`. - Fix `brain.woltje.com` in `prevent-memory-write.sh` → `${OPENBRAIN_URL:?...}`. - **Ships independently;** closes the legal window and the executable-leak class. No layer changes yet. ### Phase 1 — The sanitization gate (the lock comes before the cleanup) - Write `verify-sanitized.sh` with the §4.1 two-class rules + self-test; wire blocking in `.woodpecker.yml`. Build goes **red** on the current contamination — intended; it scopes Phase 2. - **Ships independently** as "CI now fails on operator data," even before the data is removed (the red build is the worklist). ### Phase 2 — Sanitize the existing tree to green (mechanical, no architecture) - Purge all operator tokens across `guides/`, `defaults/TOOLS.md`, `README.md`, `mosaic-doctor`, `mosaic-init` defaults; `rails/`→`tools/` across **both** template families; drop "Master/slave". - Delete `defaults/SOUL.md`, `jarvis-loop.json`; relocate the AUDIT file; create `examples/*`. - Phase 1's gate goes green. **Ships independently;** package is now PII-free but still pre-Constitution. ### Phase 3 — Extract L0 by subtraction - Create `defaults/CONSTITUTION.md` (gates one place, §1.4 split, capability-verb authored, precedence verbatim, firewall rule, tier-aware self-load). - Gut `defaults/AGENTS.md` to the ~50-line dispatcher; remove the false line 11. - Create `constitution/LAYER-MODEL.md`. Strip restated policy from `STANDARDS.md` + the four RUNTIME files; rewrite the sequential-thinking lines to capability verbs. - Add the L0 line-count CI ceiling over framework-owned resident files only (§7). - **Ships independently;** no install/migration changes yet — fresh installs get the new structure. ### Phase 4 — Overwrite semantics + migration + headless bootstrap - Split seed lists into `FRAMEWORK_OWNED` (overwrite) vs `USER_SEEDED` (seed-if-absent) in BOTH installers; remove `AGENTS.md`/`STANDARDS.md` from `PRESERVE_PATHS`; add `CONSTITUTION.md`. - Implement snapshot→sync→restore; vendor the v2 baseline; v2→v3 migration moves user edits to `.local`/`.bak`. Bump `FRAMEWORK_VERSION=3`. - `install.sh` runs `mosaic-init --non-interactive` (fail-closed persona). - Land the **shared fixture suite** (§3.4) run against both installers. **Gates the tag.** ### Phase 5 — Overlay composer + cross-harness composer test - Build `mosaic compose-contract ` per §3.2 (`SOUL.local.md`+`USER.local.md`+ `STANDARDS.local.md`; per-harness emission; documented bare-launch base-only behavior). - Composer unit test (§5): per-tier anchor present; Tier-3 byte-equal to L0. - **Ships independently** as "customization now survives upgrades." ### Phase 6 — Docs, compliance matrix, alpha tag - `CONTRIBUTING.md` (operator-hygiene, dual-installer parity rule, known-limitations §9, harness×gate compliance matrix with the hook-parity gap marked). - PRD ↔ design reconciliation; tag the alpha after the full DoD (§8) is green. --- ## 7. Resident-token budget (steward RISK / contrarian R5 / devex m8 — accepted, re-scoped) Budget the **container** by line count, keep gate **wording** intact. But CI cannot see user-generated `SOUL.md`/`USER.md`, and the resident set varies per harness tier (contrarian R5). So the control is **split**: - **CI (package-side):** a line-count ceiling over **framework-owned resident files only** (`CONSTITUTION.md` + dispatcher `AGENTS.md` + the resident `RUNTIME.md` slice). Real and enforceable. - **`mosaic doctor` (runtime advisory):** sums the *actual* composed prompt — including `SOUL.md`/ `USER.md` and the per-harness tier — and warns the operator. This is the only place the total resident budget is visible, and it is per-harness, not a single global number (devex m8: hook-less harnesses like Pi need more resident, so the advisory threshold is per-harness). Gates keep full wording; *procedure* (wrapper paths, flags) moves to on-demand `E2E-DELIVERY.md`. Reject "exactly 500 words for L0" — gate #13 alone is ~110 words; a word cap forces paraphrasing law, the exact drift vector being killed. --- ## 8. Alpha Definition of Done (for the PRD) Blocking, all CI-green: MIT LICENSE + `package.json` field; **three** credential-path sites + the hook URL fast-failed; `verify-sanitized.sh` (two-class, `*.sh`+`*.md`, self-tested) wired blocking; operator data purged from the full set (guides/tools/init-generator included); `rails/`→`tools/` in both template families; `defaults/SOUL.md`+`jarvis-loop.json` deleted; `CONSTITUTION.md` extracted (gates one place, capability-verb, §1.4 split, no false "already loaded"); `AGENTS.md`/`STANDARDS.md` out of `PRESERVE_PATHS` **and** seed-semantics switched to overwrite in **both** installers; snapshot/ migration v2→v3 moving user edits to `.local`/`.bak`; `mosaic-init --non-interactive` fail-closed persona; **5-fixture matrix** (§3.4) green against both installers asserting **injected bytes**; `compose-contract` built + composer unit test (per-tier anchor, Tier-3 byte-equality); resident line-count ceiling enforced; `CONTRIBUTING.md` + compliance matrix; tag the alpha. PRD precedes implementation. **Deferred to v2 (explicit):** `constitution/` deploy directory; `adapters/.capabilities.json`; 3-way merge; live-launch cross-harness smoke test; `policy/*.md` composition; per-layer version stamps as a migration driver; DCO CI. --- ## 9. Red-Team Disposition (every finding mitigated or accepted) | Finding | Disposition | |---------|-------------| | **contrarian R1 / steward RISK-04** — "remove from PRESERVE_PATHS" doesn't update resident root file | **Mitigated** §3.1: split seed lists, unconditional overwrite for framework-owned, in BOTH installers; test injected bytes | | **contrarian R2** — snapshot/restore described but unimplemented; cp-fallback can lose data | **Mitigated** §3.3: atomic snapshot→sync→restore + interrupt fixture; user-edited `AGENTS.md`→`.pre-constitution.bak` | | **contrarian R3 / steward RISK-11** — live-launch smoke test impractical | **Mitigated** §5: re-scoped to composer unit test; live-launch → v2; hook-parity tracked in compliance matrix | | **contrarian R4 / devex B1** — deleting `defaults/SOUL.md` + interactive init bricks headless first-run | **Mitigated** §3.3: `checkSoul()` self-heals `mosaic` launches; `install.sh` runs `--non-interactive` init; fixture 4 | | **contrarian R5 / devex m8** — line budget can't see user files / varies per tier | **Mitigated** §7: CI ceiling on framework files only; `doctor` per-harness runtime advisory | | **contrarian R6** — extracting gate #13 weakens a hard gate for non-adopters | **Mitigated** §1.4: split #13 — disambiguation stays universal in L0; only the named delegation leaves | | **contrarian R7 / devex B3 / steward RISK-03** — denylist false-positives / misses the class | **Mitigated** §4.1-4.2: two rule-classes (structural + labeled denylist); L0 prose firewall is the primary class-closer | | **contrarian R8 / steward RISK-06 / devex M6** — compose-contract is a new subsystem called "zero" | **Accepted + scoped** §3.2: alpha-blocking work item with tests; `policy/` composition deferred to v2 with rationale | | **contrarian R9 / steward RISK-07** — conditional self-load asks model to introspect | **Mitigated** §1.6: Tier-3 read is unconditional; conditional only on Tier-1 | | **contrarian R10** — two installers synced by a comment, TS path ignored | **Mitigated** throughout: every mechanism "in both installers, one shared fixture suite" | | **devex B2** — non-interactive init ships "Assistant" + Jarvis role | **Mitigated** §2b/§3.3: fail-closed persona; grep init defaults | | **devex B3 / steward RISK-01** — credential leak in 6+/3 files, grep misses `tools/`+`*.sh` | **Mitigated** §2b/§4.1: all three `*.sh` sites + hook URL; grep scoped to `tools/` and `*.sh` | | **devex M4** — Tier-3 paraphrase = two "Mosaics" | **Mitigated** §5: Tier-3 anchor is a literal L0 substring; byte-equality asserted | | **devex M5 / steward RISK-05** — pulling from PRESERVE clobbers existing edits; non-TTY false-green | **Mitigated** §3.3/§3.4: vendor v2 baseline, extract delta→`.local` before overwrite; fixtures pin `MOSAIC_INSTALL_MODE` | | **devex M7** — sequential-thinking contradiction in 4 files; L0 "else stop" halts Pi | **Mitigated** §5/§4.1: rewrite all 4; L0 capability-verb only; structural CI rule + Pi fixture | | **devex m9** — `doctor` drift advisory absent on bare launches | **Accepted** §3.5: documented limitation; self-load nudge | | **devex m10 / steward RISK-08** — `CLAUDE.md.template` siblings keep `rails/` + gates | **Mitigated** §2b: both template families; CI `/rails/` rule over `templates/` | | **devex m11** — dead-path/legacy-term sanitization is one-off | **Mitigated** §4.1: structural rules close the dead-path class | | **steward RISK-02** — `verify-sanitized.sh` doesn't exist / unwired | **Mitigated** §2a/§4.1/Phase 1: built, self-tested, wired blocking | | **steward RISK-09** — "Master/slave" framing | **Mitigated** §2b: → "Primary / satellite" | | **steward RISK-10** — no LICENSE | **Mitigated** Phase 0 | **Accepted residual risks (stated in `CONTRIBUTING.md`):** bare-launch overlay no-op (base-only) and bare-launch drift-undetected-by-`doctor` — both inherent to launches that bypass `mosaic`; mitigated by the unconditional Tier-3 self-load + nudge, not eliminated. Codex/OpenCode hook parity is a tracked v2 gap. Live-launch cross-harness verification is v2.