docs(design): mosaic framework constitution — expert conference output
Some checks failed
ci/woodpecker/push/ci Pipeline failed

Conference of 7 experts (architect/moonshot/contrarian/coder/aiml/devex/steward)
debated layering, sanitization, upgrade-safety, cross-harness robustness.
Artifacts: BRIEF, 7 positions, 7 rebuttals, synthesis-v1, 3 red-team passes,
canonical DESIGN.md, OPEN-QUESTIONS.md, MISSION.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 23:47:49 -05:00
parent d481a74a86
commit c70b217a5c
22 changed files with 5822 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
# Position Paper — The Prompt-Systems Lens on the Mosaic Constitution
**Author lens:** AI/ML Prompt-Systems Expert (how LLMs actually consume system prompts and context; what placement, length, and structure help vs. hurt instruction-following across models and harnesses).
**Scope:** Opinionated answers to DQ1DQ5 from `BRIEF.md`, grounded in the real files under `packages/mosaic/framework/`. I cite file paths and propose concrete structures, not principles in the abstract.
---
## TL;DR for the conference
The Constitution debate has been framed as an *ownership/governance* problem (who owns what, who can edit what, how do upgrades not clobber). That framing is correct but incomplete. From a prompt-systems view there is a second, equally hard problem hiding inside it: **the always-resident context that Mosaic injects today is already past the size and redundancy threshold where instruction-following measurably degrades, and the proposed Constitution layer will make it worse unless we treat resident-token budget as a first-class, enforced constraint.**
Concretely: `defaults/AGENTS.md` (155 lines, ~13 numbered "hard gates" + ~16 "non-negotiable rules" + 4 more rule blocks) is injected verbatim into *every* session, then `SOUL.md`, `USER.md`, the TOOLS index, and a runtime contract are stacked on top — before the agent has read a single project file. That is the worst possible place to be adding a third governance document. My recommendations below are designed to add the Constitution *layer* (which I support) while **shrinking** total resident tokens, not growing them.
---
## How LLMs actually consume this context (the physics we're designing against)
Five empirical behaviors drive every recommendation in this paper:
1. **Primacy + recency, U-shaped attention.** Instructions at the very top and very bottom of the resident context are followed most reliably; the middle is the "lost in the middle" zone. A 155-line gate document placed in the middle of a 5-file stack loses enforcement power on its *middle* rules regardless of how many times they say "MANDATORY."
2. **Instruction density decay.** Past a few dozen imperative rules, marginal rules don't just fail to help — they *dilute* the salience of the rules that matter. The model cannot tell rule #7 of 33 from rule #28; "HARD GATE" loses meaning when 30 things are hard gates. `defaults/AGENTS.md` currently has at least four parallel "these are the critical ones" sections (`CRITICAL HARD GATES`, `Non-Negotiable Operating Rules`, `Other Hard Rules`, plus the per-section "Hard Rule" tags). This is salience inflation.
3. **Contradiction is silently lossy.** When two resident sources conflict, models do not reliably pick the "higher precedence" one — they pick the *nearer*, the *more recent*, or the *more specific-sounding* one, unpredictably. So precedence cannot be enforced by prose ("global rules win"); it must be enforced by **not shipping the contradiction into the same context window**. Today `defaults/AGENTS.md` line 37 and `templates/agent/AGENTS.md.template` line 12 both state the ci-queue-wait rule but with **different paths** (`tools/git/` vs `rails/git/`) — a live contradiction that ships to the model.
4. **Repetition has a budget too.** A small amount of deliberate repetition at top-and-bottom *helps* (it's how you beat lost-in-the-middle). But Mosaic over-repeats: the mode-declaration protocol appears in `defaults/AGENTS.md`, `guides/E2E-DELIVERY.md`, `guides/ORCHESTRATOR.md`, and all four `runtime/*/RUNTIME.md`. That's not reinforcement, it's five maintenance sites and five drift opportunities, and it spends recency budget on a low-stakes rule.
5. **Structure is a parsing aid, but only if it's consistent.** Models parse Markdown headings, numbered lists, and tables as structure. The framework already does this well (the Conditional Guide Loading table in `defaults/AGENTS.md` is excellent prompt design). The failure mode is *inconsistent* structure — e.g., "Hard Rule" sometimes a heading, sometimes a parenthetical, sometimes a bare bullet — which forces the model to infer importance instead of reading it.
These five points are the throughline. Now the design questions.
---
## DQ1 — Layering: yes to a Constitution, but layer by *token-lifecycle*, not just by ownership
I support introducing an explicit Constitution layer distinct from SOUL (persona) and USER (operator). But the layering axis that matters for instruction-following is **"how often does the model need this, and is it negotiable?"** — not just "who owns it." I propose the canonical layers be defined along *both* axes simultaneously, because the residency decision (what's always in context) is where models live or die.
### Proposed canonical layers
| Layer | Owner | Residency | Negotiable? | Content |
|---|---|---|---|---|
| **L0 — Constitution** | Framework | **Always resident, ~40 lines hard cap** | No (immutable law) | The irreducible gates: completion-defined-at-merge, PR-review-before-merge, green-CI, no-forced-merge, no-hardcoded-secrets, escalation triggers, block-vs-done. The "if you violate one thing, violate nothing" set. |
| **L1 — Contract** | Framework | On-demand (guide-loaded) | No, but elaborative | The *procedures* that implement L0: the E2E execution cycle, testing matrix, orchestrator protocol, documentation gate. Today's `defaults/AGENTS.md` bulk + `guides/*`. |
| **L2 — SOUL (persona)** | Framework default, user-overridable | Always resident, ~25 lines | Soft (style, not law) | Agent name, tone, communication style, behavioral principles. |
| **L3 — USER (operator)** | User | Always resident, ~25 lines | Soft (preferences) | Name, timezone, accessibility, comms prefs, project table. |
| **L4 — Runtime adapter** | Framework | Always resident, ~15 lines | No (mechanism only) | Harness-specific *mechanism* (subagent syntax, hook config), never policy. |
| **L5 — Project** | User/repo | Loaded when in a repo | No (inherits L0) | `<repo>/AGENTS.md`. |
The key move: **L0 is a new, tiny, surgically-extracted document — not a rename of the current `AGENTS.md`.** Today `defaults/AGENTS.md` conflates L0 and L1 (it even admits this: line 6 "It carries only what must be resident" — but then carries 155 lines). The Constitution is the ~40-line subset that is *truly* non-negotiable and *truly* needs to be resident to prevent a gate violation. Everything else is L1 and moves behind conditional loading.
### Precedence order (and how to actually enforce it)
Declared precedence, highest to lowest:
```
L0 Constitution > L4 Runtime mechanism > L1 Contract > L5 Project > L2 SOUL > L3 USER
```
Rationale from the lens: **law > mechanism > procedure > project > persona > preference**. SOUL/USER are *below* the contract on purpose — a user's "be terse" preference must never be readable as license to skip a gate. The current `SOUL.md` line 32 ("USER.md formatting preferences override any generic Anthropic minimal-formatting guidance") is the *correct* shape of an override (narrow, scoped to formatting) and should be the template for how L2/L3 are allowed to win: **only over style, never over law.**
But precedence prose is unreliable (behavior #3 above). Enforce it three structural ways instead:
1. **Physical placement encodes precedence.** Put L0 at the very top of the injected blob AND restate the 5-bullet gate summary at the very bottom (the "recency anchor"). This is the one place I endorse deliberate repetition. SOUL/USER go in the *middle* — the lowest-attention zone — which is exactly right because they're the lowest-precedence, softest layers.
2. **One contradiction-free source per fact.** A rule lives in exactly one layer. If L0 owns "completion = merged PR + green CI," then L1/L5/templates *reference* it, they do not restate it with their own wording. This kills the `tools/` vs `rails/` path drift class of bug.
3. **A precedence preamble of one sentence**, not a section: "If anything below conflicts with the Constitution, the Constitution wins; report the conflict." One sentence at the L0 boundary outperforms a precedence subsection because it's short enough to survive attention.
---
## DQ2 — Sanitization: template-then-init, with *generic-but-real* defaults, and a hard PII tripwire
The brief offers three options (generic-defaults / empty-defaults+examples / template-then-init). From the lens, the deciding factor is **cold-start instruction quality**: an agent given an empty or placeholder-laden persona produces worse, more generic work because it has no concrete stance to reason from. So:
**Recommendation: template-then-init, but ship defaults that are concrete and immediately usable — never `{{PLACEHOLDER}}` tokens left in resident context.**
The current state is split-brained and should be fixed:
- `defaults/SOUL.md` is *contaminated* — hardcoded "Jarvis" (line 9) and "PDA-friendly" (line 23). This is the bug the brief names.
- `defaults/USER.md` is *correct* — it's a clean, generic, self-describing default ("(not configured)", line 11; "Run `mosaic init`", line 6). This is the model to follow.
- `templates/SOUL.md.template` is *correct* — clean `{{AGENT_NAME}}` tokens.
So the fix for SOUL is mechanical and already half-done: **`defaults/SOUL.md` should become a sanitized generic default like `defaults/USER.md` already is** — agent name "Assistant," generic role, no PDA, no Jarvis — and the *real* personalization is generated by `mosaic-init` from `templates/SOUL.md.template` (which the installer already does: `install.sh` lines 233240 deliberately exclude SOUL/USER from seeding and let `mosaic init` generate them).
**Critical prompt-systems caveat on placeholders:** a half-rendered template is *worse than no file* for an LLM. If `mosaic init` ever fails mid-render and leaves `You are **{{AGENT_NAME}}**` in `~/.config/mosaic/SOUL.md`, the model will literally adopt "{{AGENT_NAME}}" as a name or, worse, treat the unrendered braces as an instruction artifact and behave erratically. Mitigations:
1. **`mosaic-doctor` must hard-fail on any `{{...}}` or `${...}` token in a resident file** (`SOUL.md`, `USER.md`, `AGENTS.md`, `TOOLS.md`, the Constitution). This is a one-line regex gate and it closes the entire half-rendered-template failure class. Today `tools/_scripts/mosaic-doctor` is advisory; for resident files this specific check should be non-advisory.
2. **Default-render fallback:** `mosaic-init` already has sane defaults (`mosaic-init` line 277 defaults AGENT_NAME to "Assistant"). Guarantee that *every* token has a non-empty default so a non-interactive or interrupted run never emits a placeholder.
**PII tripwire (the sanitization gate the brief actually needs):** add a CI check in the framework package that greps the *shipped* tree (`defaults/`, `guides/`, `templates/`, `runtime/`, `adapters/`) for an operator denylist (`jarvis`, `jason`, `woltje`, `PDA`, home-dir usernames, emails). The brief says 29 files are contaminated; a 15-line CI grep makes that un-reintroducible. This belongs in the alpha's DoD. Note `defaults/AUDIT-2026-02-17-framework-consistency.md` lines 124128 explicitly preserve a `jarvis-loop.json` reference "by design" — that decision should be revisited; a public package should carry *zero* operator tokens, and a profile preset can be renamed generically without loss.
---
## DQ3 — Customization & upgrade safety: separate files by mutability, never co-mingle owned and generated lines
The upgrade-safety problem and the instruction-following problem have the **same root cause and the same fix**: *never put framework-owned text and user-owned text in the same file.* When they co-mingle, you get both (a) clobber-on-upgrade and (b) the model unable to tell law from preference.
The installer already implements the right primitive — `install.sh` line 24 `PRESERVE_PATHS=("AGENTS.md" "SOUL.md" "USER.md" "TOOLS.md" "STANDARDS.md" "memory" ...)` with `rsync --delete --exclude` of preserved paths (lines 116124). The problem is the *granularity*: `AGENTS.md` is in PRESERVE_PATHS, which means **once a user edits the contract, they stop receiving framework gate updates forever** — silent drift, the exact failure the brief calls out. That's a direct consequence of L0 and L5 living in one file.
### Concrete file layout that fixes both problems
Deploy to `~/.config/mosaic/` as **separately-owned files with a clear naming convention**:
```
~/.config/mosaic/
CONSTITUTION.md ← L0. Framework-owned. ALWAYS overwritten on upgrade. Never in PRESERVE_PATHS. ~40 lines.
AGENTS.md ← L1 index + load order. Framework-owned, overwritten on upgrade.
SOUL.md ← L2. Generated once from template. Preserved. User-owned.
USER.md ← L3. Generated once. Preserved. User-owned.
SOUL.local.md ← optional L2 overlay. Always preserved. (see below)
USER.local.md ← optional L3 overlay. Always preserved.
.framework-version ← schema version (already exists, install.sh line 65)
```
**The `.local.md` overlay pattern is the upgrade-safety keystone.** Instead of letting users edit framework files (which forces them out of the update stream), give them a dedicated, never-touched overlay file per layer:
- Framework owns and freely upgrades `CONSTITUTION.md`, `AGENTS.md`, the base `SOUL.md`/`USER.md` *shape*.
- User customization that must survive *and* must not block upgrades goes in `*.local.md`, which is `PRESERVE_PATHS`-protected and **loaded last within its layer** (so it wins on style per the precedence rules, but is structurally incapable of overriding L0 because L0 is injected before it and re-anchored after it).
This gives the brief's requirement — "customize and still receive framework updates" — with a mechanism the model can also reason about: *base = framework law/shape; `.local` = my deltas.* It mirrors the `settings.json` / `settings.local.json` split the Claude runtime already uses (`runtime/claude/RUNTIME.md` line 47).
### Migration path (alpha-safe)
The installer already has a versioned migration framework (`install.sh` lines 160202, `FRAMEWORK_VERSION=2`). Add a **v2→v3 migration** that:
1. Detects a user-edited `AGENTS.md` (diff against the shipped v2 default).
2. Extracts their non-framework additions into `AGENTS.local.md` (or flags them for manual review if ambiguous).
3. Installs the new `CONSTITUTION.md` + slimmed `AGENTS.md`, removes `AGENTS.md` from PRESERVE_PATHS going forward.
4. Writes a one-screen `UPGRADE-NOTES` so the change is visible, not silent.
This is backward-compatible per the brief's constraint and uses machinery that already exists.
---
## DQ4 — Cross-harness robustness: one canonical L0 text, injected by adapters, never paraphrased
The harnesses inject differently — and the README table (`defaults/README.md` lines 127135) already documents this honestly: `mosaic pi` and `mosaic claude` use `--append-system-prompt`; `codex`/`opencode` write to an instructions file; direct launches use a thin pointer that tells the agent to *read* `AGENTS.md`. Two distinct delivery channels — **injected-as-system-prompt** vs **read-as-a-file** — and they are not equivalent for instruction-following.
### The robustness rule from the lens: L0 must be injected as system-prompt text on *every* harness, identically, byte-for-byte.
Why byte-for-byte matters: if Claude gets the Constitution via `--append-system-prompt` but Codex gets a pointer saying "read `~/.config/mosaic/AGENTS.md`," the two agents have **different effective system prompts** — one has the law resident at primacy position, the other has a *deferred instruction to maybe go read the law*, which a model under task pressure will skip. The current thin-pointer pattern (`runtime/claude/CLAUDE.md` lines 310: "BEFORE responding... READ ~/.config/mosaic/AGENTS.md... Do NOT respond until both files are loaded") is asking the model to self-enforce a read. Models comply with this *most* of the time, but "most" is not a gate.
**Concrete adapter strategy:**
1. **Single source of truth:** `CONSTITUTION.md` (L0) is the one file. No harness restates its content; adapters only *transport* it.
2. **Composition at launch, not duplication at rest:** the launcher composes `CONSTITUTION.md` + `AGENTS.md`(L1 index) + `SOUL/USER` + the *adapter's own ~15-line mechanism note* into the system-prompt injection. The four `runtime/*/RUNTIME.md` files shrink to **mechanism only** (subagent syntax, hook config, MCP registration) — they currently re-litigate policy (every one of them restates "git wrappers first," "mode declaration," "runtime caution doesn't override gates" — e.g. `runtime/codex/RUNTIME.md` lines 1417, `runtime/pi/RUNTIME.md` lines 1316, `runtime/opencode/RUNTIME.md` lines 1317). That policy is L0/L1; delete it from the runtime files and let composition supply it once.
3. **For direct (non-`mosaic`) launches** where injection isn't available, the thin pointer is the only option — but make the pointer carry the *5-bullet gate summary inline* so even a model that skips the read still has the irreducible law resident. A pointer that says "read the law" is weaker than a pointer that says "here are the 5 gates; full procedures in `AGENTS.md`."
4. **Pi is the canary for over-trust.** `runtime/pi/RUNTIME.md` line 20 ("Pi operates without permission restrictions... trusts the operator") means Pi has *no mechanical backstop* for the gates — so for Pi specifically, L0 resident-text fidelity is the *only* enforcement. That's an argument for keeping L0 tiny and high-salience, not large.
Net: the cross-harness contract is "**L0 text is identical and system-prompt-resident everywhere; adapters differ only in transport mechanism and the ~15 lines of harness-native syntax.**" That's both more robust *and* less to maintain than today's four-way policy duplication.
---
## DQ5 — Minimalism vs completeness: a resident-token budget, enforced, with on-demand depth
This is the question I feel most strongly about, because it's where the current design is actively hurting model performance.
### The diagnosis
The always-resident stack today, before any project file, is roughly:
- `defaults/AGENTS.md` — 155 lines, ~33 distinct imperative rules across 4 "importance" framings.
- `SOUL.md` — ~53 lines.
- `USER.md` — ~38 lines.
- TOOLS index + a `runtime/*/RUNTIME.md` — ~6080 lines.
Call it ~300+ lines / ~34K tokens of dense, imperative, partially-redundant, partially-contradictory law resident in *every* session including "list the files in this dir." Per behaviors #2 and #4, this is past the point of diminishing returns and into the point of *negative* returns: the agent cannot weight 33 co-equal "hard" rules, and the genuinely critical ones (don't fake completion, don't force-merge, don't hardcode secrets) lose salience to the merely procedural ones (milestone versioning starts at 0.0.1).
### The fix: a two-tier model with an enforced budget
**Tier 1 — Resident (the Constitution + thin index): hard cap ~120 lines / ~1.2K tokens total across L0+L2+L3+L4+the AGENTS index.** Everything in Tier 1 earns its place by answering "would omitting this cause a *gate violation* in the first 3 tool calls?" If not, it's Tier 2.
**Tier 2 — On-demand (the Contract + guides): unbounded, loaded by the Conditional Guide Loading table.** The framework *already has this mechanism* and it's the best-designed part of the system: `defaults/AGENTS.md` lines 89110 plus the load-order at lines 922. The fix is to **move bulk out of Tier 1 into Tier 2 aggressively** — specifically:
- The 16 "Non-Negotiable Operating Rules" (`defaults/AGENTS.md` lines 4155) are mostly *pointers to guides already* ("full detail in `guides/E2E-DELIVERY.md`"). Collapse them to a 5-line "you are bound by the E2E contract; load it before implementing" and let E2E-DELIVERY carry the detail. The detail is already duplicated there.
- Subagent model-selection (lines 111121), Superpowers enforcement (123139), and the mode-declaration protocol are Tier-2 candidates — they matter at *specific decision points*, not on every turn. Trigger them via the conditional table.
- Keep in Tier 1 only: the CRITICAL HARD GATES reduced to the ~7 that are truly irreducible, block-vs-done, the escalation triggers, and the load-order/conditional-table index.
**Enforce the budget mechanically.** Add to `mosaic-doctor` (and to framework CI) a **resident-line-count assertion**: if `CONSTITUTION.md` + the AGENTS index exceeds the cap, fail. A budget that isn't enforced will be eroded one "just one more critical rule" at a time — which is exactly how `AGENTS.md` reached 155 lines. The cap is the forcing function that keeps the Constitution legible to the model.
### On "robust but not contradictory"
Minimalism *is* the contradiction fix. Every line you don't ship is a line that can't drift from its duplicate. The current `tools/` vs `rails/` path split (`defaults/AGENTS.md` line 30/37 vs `templates/agent/AGENTS.md.template` lines 5/12/13) exists *because* the same rule is written in multiple resident-ish places. One canonical line, referenced not restated, cannot contradict itself. (Note: that path drift — `rails/` in the template — also appears to be a **stale path bug** worth fixing regardless of this redesign; the live framework uses `tools/git/`.)
---
## What I would change, concretely (file-by-file)
1. **Create `defaults/CONSTITUTION.md`** (~40 lines, L0). Extract from `defaults/AGENTS.md`: the irreducible hard gates (completion-at-merge, PR-review, green-CI, no-force-merge, queue-guard, wrappers-first, block-vs-done), the 5 escalation triggers, and the one-sentence precedence preamble. Top-of-injection + bottom-anchor placement.
2. **Slim `defaults/AGENTS.md`** to an *index + load-order + Conditional Guide Loading table* (~60 lines). It stops being the law; it becomes the table of contents that triggers Tier-2 loads. Remove it from `install.sh` `PRESERVE_PATHS` so gate updates flow on upgrade.
3. **Sanitize `defaults/SOUL.md`**: replace "Jarvis" (line 9) and "PDA-friendly" (line 23) with generic defaults, matching the already-clean `defaults/USER.md` pattern. Real persona comes from `templates/SOUL.md.template` via `mosaic-init`.
4. **Strip policy from `runtime/{claude,codex,pi,opencode}/RUNTIME.md`**: delete the restated "wrappers first / mode declaration / caution-doesn't-override-gates" blocks; keep only harness-native *mechanism* (subagent syntax, hooks, MCP registration). Policy is supplied once by composition.
5. **Add `*.local.md` overlay support** to `mosaic-init` and `install.sh` PRESERVE_PATHS for `SOUL.local.md` / `USER.local.md` (and an `AGENTS.local.md` migration target). Loaded last-within-layer; structurally below L0.
6. **Harden `mosaic-doctor`** with two non-advisory checks for resident files: (a) zero unrendered `{{...}}`/`${...}` tokens; (b) resident-line-count budget assertion.
7. **Add a framework-CI PII grep** over `defaults/`, `guides/`, `templates/`, `runtime/`, `adapters/` against an operator denylist; revisit the intentionally-preserved `jarvis-loop.json` reference in `defaults/AUDIT-2026-02-17-framework-consistency.md` (rename generically).
8. **Fix the `rails/` vs `tools/` path drift** in `templates/agent/AGENTS.md.template` (lines 5, 12, 13, 91 etc.) as a correctness bug, and make the template *reference* the Constitution rather than restate gates.
---
## Biggest risk I see
**Adding the Constitution layer without enforcing the resident-token budget will make instruction-following worse, not better.** A new top-level "CONSTITUTION.md" is psychologically tempting to fill — it will accrete every rule someone considers important, and within two releases it will be the new 155-line `AGENTS.md`, now stacked *on top of* the old one we failed to fully drain. The governance win (clean ownership) would come at a real prompt-quality loss (more dense resident law → lower per-rule adherence → more gate violations, the very thing the gates exist to prevent). The mechanical line-count budget in `mosaic-doctor`/CI is not a nice-to-have; it is the load-bearing control that makes the whole re-architecture a net positive for how the model actually behaves. Ship the budget gate in the same alpha as the Constitution, or don't ship the Constitution.