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>
24 KiB
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 DQ1–DQ5 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:
-
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."
-
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.mdcurrently 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. -
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.mdline 37 andtemplates/agent/AGENTS.md.templateline 12 both state the ci-queue-wait rule but with different paths (tools/git/vsrails/git/) — a live contradiction that ships to the model. -
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 fourruntime/*/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. -
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.mdis 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:
- 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.
- 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/vsrails/path drift class of bug. - 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.mdis contaminated — hardcoded "Jarvis" (line 9) and "PDA-friendly" (line 23). This is the bug the brief names.defaults/USER.mdis correct — it's a clean, generic, self-describing default ("(not configured)", line 11; "Runmosaic init", line 6). This is the model to follow.templates/SOUL.md.templateis 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 233–240 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:
mosaic-doctormust 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. Todaytools/_scripts/mosaic-doctoris advisory; for resident files this specific check should be non-advisory.- Default-render fallback:
mosaic-initalready has sane defaults (mosaic-initline 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 124–128 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 116–124). 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 baseSOUL.md/USER.mdshape. - User customization that must survive and must not block upgrades goes in
*.local.md, which isPRESERVE_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 160–202, FRAMEWORK_VERSION=2). Add a v2→v3 migration that:
- Detects a user-edited
AGENTS.md(diff against the shipped v2 default). - Extracts their non-framework additions into
AGENTS.local.md(or flags them for manual review if ambiguous). - Installs the new
CONSTITUTION.md+ slimmedAGENTS.md, removesAGENTS.mdfrom PRESERVE_PATHS going forward. - Writes a one-screen
UPGRADE-NOTESso 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 127–135) 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 3–10: "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:
- Single source of truth:
CONSTITUTION.md(L0) is the one file. No harness restates its content; adapters only transport it. - 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 fourruntime/*/RUNTIME.mdfiles 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.mdlines 14–17,runtime/pi/RUNTIME.mdlines 13–16,runtime/opencode/RUNTIME.mdlines 13–17). That policy is L0/L1; delete it from the runtime files and let composition supply it once. - 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 inAGENTS.md." - Pi is the canary for over-trust.
runtime/pi/RUNTIME.mdline 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— ~60–80 lines.
Call it ~300+ lines / ~3–4K 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 89–110 plus the load-order at lines 9–22. The fix is to move bulk out of Tier 1 into Tier 2 aggressively — specifically:
- The 16 "Non-Negotiable Operating Rules" (
defaults/AGENTS.mdlines 41–55) are mostly pointers to guides already ("full detail inguides/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 111–121), Superpowers enforcement (123–139), 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)
-
Create
defaults/CONSTITUTION.md(~40 lines, L0). Extract fromdefaults/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. -
Slim
defaults/AGENTS.mdto 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 frominstall.shPRESERVE_PATHSso gate updates flow on upgrade. -
Sanitize
defaults/SOUL.md: replace "Jarvis" (line 9) and "PDA-friendly" (line 23) with generic defaults, matching the already-cleandefaults/USER.mdpattern. Real persona comes fromtemplates/SOUL.md.templateviamosaic-init. -
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. -
Add
*.local.mdoverlay support tomosaic-initandinstall.shPRESERVE_PATHS forSOUL.local.md/USER.local.md(and anAGENTS.local.mdmigration target). Loaded last-within-layer; structurally below L0. -
Harden
mosaic-doctorwith two non-advisory checks for resident files: (a) zero unrendered{{...}}/${...}tokens; (b) resident-line-count budget assertion. -
Add a framework-CI PII grep over
defaults/,guides/,templates/,runtime/,adapters/against an operator denylist; revisit the intentionally-preservedjarvis-loop.jsonreference indefaults/AUDIT-2026-02-17-framework-consistency.md(rename generically). -
Fix the
rails/vstools/path drift intemplates/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.