Files
stack/docs/design/framework-constitution/debate/position-aiml.md
Jason Woltje c70b217a5c
Some checks failed
ci/woodpecker/push/ci Pipeline failed
docs(design): mosaic framework constitution — expert conference output
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>
2026-06-15 23:47:49 -05:00

209 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.