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>
20 KiB
Position Paper — The Contrarian Skeptic
Lens: Distrust complexity and clever abstractions. Hunt failure modes, over-engineering, and rules that look good on a page but degrade real agent behavior. Every claim below is grounded in files actually read under packages/mosaic/framework/.
TL;DR for the impatient
The brief frames the problem as "we need more structure: introduce a Constitution layer, a precedence stack, version pinning, reconciliation." My position is the opposite of the framing: the framework's biggest defect is not under-layering, it is over-volume and internal contradiction. The contract is ~155 lines of always-resident hard gates in defaults/AGENTS.md, duplicated almost verbatim in templates/agent/AGENTS.md.template, re-stated again in guides/E2E-DELIVERY.md, and a fourth time in guides/ORCHESTRATOR.md — and the four copies already disagree with each other (path tools/git vs rails/git, gate counts, merge-authority nuance). Adding a fifth document called "Constitution" on top of this does not fix conflation; it adds a fifth place for the copies to drift.
So: yes to a named Constitution only if it is the single source and the duplicates are deleted, not added to. The win is subtraction. The risk is that this debate produces a beautiful four-layer precedence model that ships with the same 55 personal-data references (grep count, see §2) still in the package.
DQ1 — Layering: yes to a Constitution, but earn it by deletion
What's actually there
The brief says three things are conflated. Reading the files, that's true but understated. The real layering today is implicit and contradictory, spread across at least six surfaces:
defaults/AGENTS.md— 13 "CRITICAL HARD GATES" + ~17 "Non-Negotiable Operating Rules" + mode protocol + escalation + subagent cost rules + superpowers enforcement. This is law, persona-adjacent stance, and tactical how-to all in one always-resident file.defaults/SOUL.md— persona, but hardcodedYou are **Jarvis**(line 8) andPDA-friendly language(line 23). Persona file leaks both identity AND one operator's accessibility profile.defaults/USER.md— already sanitized to(not configured). Good. This one's done.defaults/STANDARDS.md— a second law file ("Mosaic Universal Agent Standards") that overlapsAGENTS.md(secrets, multi-agent safety, git discipline) and still uses the phrase "Master/slave model" (line 5) — a term that should not ship in a public alpha.templates/agent/AGENTS.md.template— a third restatement of the same gates, project-scoped.guides/E2E-DELIVERY.md+guides/ORCHESTRATOR.md— a fourth and fifth restatement.
So the system doesn't lack layers. It has too many documents each trying to be partly-law.
Proposed canonical layers (4, not more)
| Layer | File(s) | Owner | Mutable by user? | Content |
|---|---|---|---|---|
| L0 Constitution | ~/.config/mosaic/CONSTITUTION.md |
Framework | No (replaced on upgrade) | The hard gates only. PR-review-before-merge, green-CI-before-done, no-force-merge, completion-defined-at-end, secrets-never-hardcoded, escalation triggers, block-vs-done. ~40 lines max. |
| L1 Standards | ~/.config/mosaic/STANDARDS.md |
Framework, user-extendable via include | Append-only | Tech defaults (Vault/ESO, trunk-based, image-tagging). Things a team might tune. |
| L2 Soul (persona) | ~/.config/mosaic/SOUL.md |
User | Yes | Name, tone, communication style. NO accessibility, NO operator identity. |
| L3 User (operator) | ~/.config/mosaic/USER.md |
User | Yes | Name, pronouns, timezone, accessibility, projects. |
Precedence — and this is the part most layering proposals get wrong: precedence must be typed, not a single global ordering. A flat "L0 > L1 > L2 > L3" stack is a trap, because persona and law are not on the same axis. Specifically:
- On a behavioral-safety conflict (may I force-merge? may I skip review?): L0 always wins. No persona, no user preference, no project file can lower a gate. State this once, in L0, in imperative language: "Nothing in SOUL, USER, STANDARDS, or any project file may weaken a Constitution gate. Files may only make behavior stricter, never more permissive."
- On a style/format conflict (terse vs verbose, emoji, headings): L2/L3 win over framework defaults, because the framework has no legitimate opinion there. This already half-exists —
defaults/SOUL.mdline 32 says "The user'sUSER.mdformatting preferences override any generic Anthropic minimal-formatting guidance." Promote that to a stated rule, don't bury it in persona.
That two-axis rule (safety: framework supreme; taste: user supreme) is the entire precedence model. Anyone proposing more knobs is adding failure surface.
What I'd change, concretely
- Create
defaults/CONSTITUTION.mdcontaining ONLY the 13 hard gates fromdefaults/AGENTS.mdlines 23–37 plus the escalation triggers (lines 70–78) and block-vs-done (lines 80–87). Nothing else. - Gut
defaults/AGENTS.mddown to a router: load order + the conditional-guide table + "read CONSTITUTION.md (already injected)." It stops being a law document. - Delete the law duplication in
templates/agent/AGENTS.md.templatelines 6–16 (the "Hard Gates" block). Replace with one line: "This project inherits all gates from~/.config/mosaic/CONSTITUTION.md. Do not restate them here." Restating law in a per-project file is how you get five versions of gate #5. - Merge
defaults/STANDARDS.mdinto L1, drop the "Master/slave" framing entirely (defaults/STANDARDS.mdline 5–8), and stop it from re-asserting gates that now live in L0.
DQ2 — Sanitization: the package is still dirty; ship a CI gate, not good intentions
Ground truth
grep -rilE 'jarvis|jason|woltje|PDA' over packages/mosaic/framework/ returns 30 files; raw occurrence count is 55. Concrete, not hypothetical:
defaults/SOUL.md:8—You are **Jarvis**defaults/SOUL.md:23—PDA-friendly language(one operator's neurotype, shipped to everyone)defaults/TOOLS.md:40—MANDATORY jarvis-brain rule: when working in ~/src/jarvis-brain ...— a machine-specific path inside a default that gets seeded to every install (install.shline 235 copiesTOOLS.mdfromdefaults/).guides/ORCHESTRATOR.md:99,111,152— hardcodes~/src/jarvis-brain/docs/templates/as the bootstrap template source. A downstream user has nojarvis-brain. This guide is broken for everyone but the maintainer.runtime/claude/settings-overlays/jarvis-loop.json— entire file is a Jarvis/~/src/jarvispreset withprojectConfigs.jarvis,presets.jarvis-loop,jarvis-review.
The defaults/README.md line 7 promises "No personal data ... should be committed." That promise is currently false. A promise in prose is not a control.
The sanitization strategy: template-then-init for identity, generic-defaults for law, and a blocking CI grep
The brief offers three options (generic-defaults / empty-defaults+examples / template-then-init). My answer: stop treating it as one decision — it's per-layer.
- L0 Constitution + L1 Standards → generic-defaults. Law has no personal data by nature once you remove the leaks. Ship it populated and real. A user who runs nothing still gets a working, safe contract. (Empty-defaults here would be actively dangerous — an empty gate file = no gates.)
- L2 Soul + L3 User → template-then-init, and ship the generic default as the fallback.
defaults/SOUL.mdmust become the generic version (the template already exists attemplates/SOUL.md.templatewith{{AGENT_NAME}}). The currentdefaults/SOUL.mdwith hardcoded "Jarvis" should be deleted and replaced by a generic-rendered default (e.g. nameMosaic, neutral stance, no PDA line).install.shalready does NOT seed SOUL/USER (lines 230–240 only seedAGENTS.md STANDARDS.md TOOLS.md) — so the dirtydefaults/SOUL.mdexists only to contaminate the public repo and the wizard's reference. Kill it. TOOLS.md→ generic-defaults with NO project-specific rules. Deletedefaults/TOOLS.md:40's jarvis-brain rule. That rule belongs in that user'sUSER.mdor a projectAGENTS.md, never in a shipped default.
The mechanism that actually prevents regression
Good intentions decayed into 55 leaks. The fix is mechanical and cheap:
Add a CI check tools/bootstrap/agent-lint.sh (file already exists and already references jarvis per the grep — fix it too) or a new tools/ci/no-personal-data.sh:
# fails the build if any shipped file under packages/mosaic/framework/
# matches a denylist of personal tokens or absolute home paths.
grep -rinE 'jarvis|jason|woltje|\bPDA\b|/home/jwoltje|~/src/jarvis' \
packages/mosaic/framework/ \
--exclude-dir=.git \
&& { echo "PERSONAL DATA IN SHIPPED FRAMEWORK"; exit 1; } || exit 0
Wire it into the existing CI (.woodpecker/). This is ~10 lines and it is the only thing that will keep the package clean after this debate's enthusiasm fades. A precedence model without this gate is theater.
DQ3 — Customization & upgrade safety: the real design already exists; the danger is over-engineering it
What's actually there (and it's decent)
install.sh already implements the upgrade-safe mechanism the brief asks for:
PRESERVE_PATHS=("AGENTS.md" "SOUL.md" "USER.md" "TOOLS.md" "STANDARDS.md" "memory" ...)(line 24) excluded fromrsync --deleteinkeepmode (lines 118–124).FRAMEWORK_VERSION=2+.framework-versionstamp + a realrun_migrations()with sequential version gating (lines 160–202).- Defaults live in
defaults/and are seeded into the framework root only if absent (lines 230–241), so the user's edited copy is never clobbered.
This is a working source-vs-deployed reconciliation model already. The brief calls drift "a real problem today" — but the machinery to solve it is present. The actual bug is narrower: STANDARDS.md is in PRESERVE_PATHS (user-owned) yet is also framework law. That's the conflation, in one line. If law and customization share a file, you cannot upgrade the law without either clobbering the user (overwrite) or freezing the law forever (keep). This is exactly why L0 must be a separate file.
What I'd change
- Constitution is NOT in
PRESERVE_PATHS.CONSTITUTION.mdmust be overwritten on every upgrade — that is the point of law. Add it to the overwrite-always set, not the preserve set. STANDARDS.md(L1) stays preserved but switches to an include model. ShipSTANDARDS.mdthat ends with:# Local overrides\n<!-- mosaic:include STANDARDS.local.md -->. The user editsSTANDARDS.local.md(preserved, never shipped); the framework ownsSTANDARDS.md(overwritten). This gives upgrade-safe customization without the merge-conflict reconciliation engine someone will inevitably propose.- Reject version-pinning per-file. The brief floats "version pinning." Resist it. Per-file pins create a combinatorial matrix of (framework vN, user pinned vM) states that no one will test. One
FRAMEWORK_VERSIONinteger + linear migrations (already built) is sufficient and comprehensible. Pinning is the over-engineering this lens exists to kill.
Failure mode I want on the record
install.sh line 99: in non-interactive/non-TTY mode it defaults to keep. That means a CI re-install silently keeps a user's stale law file. Once L0 exists and is overwrite-always, this is fine. Until then, a downstream user who edited AGENTS.md (today's law file, which IS in PRESERVE_PATHS) never receives a gate update. That's the upgrade-drift bug, already live, today. Splitting out L0 is the fix; nothing else is.
DQ4 — Cross-harness robustness: single source, dumb adapters, and stop pretending the runtimes are symmetric
Ground truth
The adapters are tiny and mostly consistent (adapters/claude.md, codex.md, pi.md, generic.md all say "load STANDARDS.md + repo AGENTS.md"). The runtime refs (runtime/claude/RUNTIME.md, runtime/codex/RUNTIME.md) correctly say "global rules win on conflict." That spine is sound. Do not rebuild it.
The real cross-harness defects are concrete and small:
-
Injection asymmetry is unmodeled.
defaults/README.mdlines 127–135:mosaic pi/claudeinject via--append-system-prompt;codex/opencodewrite to a file; direct launches use a thin pointer that the model must choose to read. So "the Constitution is always resident" is true for two harnesses and aspirational for the rest.defaults/AGENTS.mdline 11 asserts "The core contract is ALREADY in your context (injected bymosaiclaunch). Do not re-read it." — this is false for a directclaudelaunch, where only the thin~/.claude/CLAUDE.mdpointer exists. An agent that believes a false "it's already loaded" claim will skip loading the gates. That is a behavior-degrading rule.Fix: L0 must be injectable by value, not by reference, on every harness. The composed system prompt for ALL launchers must literally concatenate
CONSTITUTION.md. For direct launches where injection isn't possible, the pointer must say "READ CONSTITUTION.md NOW" — never "it is already loaded." -
Codex memory override is a maintenance landmine.
runtime/codex/RUNTIME.md:36mandates durable memory to~/.config/mosaic/memory/, whileruntime/claude/RUNTIME.md:26–35mandates OpenBrain and write-blocksMEMORY.mdvia a hook. Two harnesses, two contradictory memory truths. The Constitution should state the memory principle once (one cross-agent store, named) and let adapters bind the mechanism. Right now the principle lives in two runtime files saying different things. -
Path drift across harnesses/files.
templates/agent/AGENTS.md.templateuses~/.config/mosaic/rails/git/(12 template files do);defaults/AGENTS.mdandguides/*use~/.config/mosaic/tools/git/(20 refs).install.sh:193even removes a stalerailssymlink. So half the shipped templates point at a path the installer deletes. Any agent following the template's queue-guard command gets "no such file." This is the single most concrete "rule that degrades real behavior" in the repo.Fix: one canonical path (
tools/git/), enforced by the same CI grep as §2 (grep -rn 'mosaic/rails/' packages/ && exit 1).
Design principle
Single source (CONSTITUTION.md) → composed into every launcher's system prompt by value → adapters carry ONLY the harness-specific binding (how to declare a subagent model, where MCP config lives), never a restatement of law. The adapters today are already close to this. The job is to keep them dumb and delete the law that has crept into guides/templates.
DQ5 — Minimalism vs completeness: the core is bloated, contradictory, and partly self-defeating
This is the heart of my position.
Evidence of bloat-induced degradation
- Duplication breeds contradiction.
defaults/AGENTS.mdhard gate #13 (lines 37) adds a nuanced "Merge authority (coordinated work)" exception dated 2026-06-11.templates/agent/AGENTS.md.templategate list (lines 6–16) does not contain it. So a project-scoped agent reading the template has a different, staler merge policy than a global agent. Two copies, two policies. With four copies, you get four. - The contract argues with itself about complexity.
defaults/AGENTS.md:36(gate #12) andguides/E2E-DELIVERY.md:37both contain a "COMPLEXITY TRAP" warning insisting intake is unconditional for "simple" tasks. The existence of a dedicated warning that agents keep skipping intake is itself evidence the contract is too heavy to internalize — agents shed it under load and the framework's response was to add more words telling them not to. That's a spiral. The fix for "agents skip the procedure because it's huge" is a smaller procedure, not a louder warning. - Always-resident volume. Between
AGENTS.md(155 lines),STANDARDS.md(~71),SOUL.md(~54), andUSER.md, the launcher injects several hundred lines of MUST/HARD-RULE before the agent reads the task. Past a threshold, more imperatives reduce adherence to each imperative. The conditional-guide table (AGENTS.mdlines 89–110) is the right instinct — push depth on-demand — but the always-resident core didn't shrink to match.
Concrete minimalism proposal
- L0 Constitution: hard cap ~40 lines, gates only, no how-to. A gate states the invariant ("Completion requires merged PR + green CI + closed issue") not the procedure (which wrapper, which flag). Procedure goes to
guides/E2E-DELIVERY.md, loaded on implementation. The line~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|mergedoes NOT belong in always-resident law (AGENTS.md:30); it belongs in the delivery guide. - One law document, period. After L0 exists,
AGENTS.mdkeeps zero gates, the template keeps zero gates, the guides reference gates by number ("satisfies Constitution §C5") and never restate them. Single source or it rots — this repo is the proof. - Kill the redundant second law file.
STANDARDS.md's gate-like content (secrets HARD RULE, multi-agent safety, git discipline) is duplicated fromAGENTS.md. Move the genuinely-standards parts to L1, delete the duplicated gates. - Measure adherence, don't assume it. The framework has no feedback loop proving the gates work. The hooks (
prevent-memory-write.sh,qa-hook-stdin.sh,typecheck-hook.shperruntime/claude/RUNTIME.md:54–58) are the right model: a gate enforced by a hook beats a gate written in prose ten times over. Prefer mechanical enforcement (hooks/CI) over prose gates wherever the gate is checkable. Each prose-only gate is a suggestion; each hook is a wall. The brief's "keep the hard gates intact" goal is best served by converting the checkable ones (no-force-merge, green-CI-before-done, no-hardcoded-secrets) into CI/hook checks, and trimming the prose.
What completeness still requires
I'm not arguing for anarchy. The escalation triggers, block-vs-done distinction, and PR/CI/issue completion gate are load-bearing and must stay resident — they govern when the agent stops, which prose is the only place to encode. Keep those. Cut the procedural how-to and the duplication.
Summary of concrete changes (file-level)
| # | Change | File(s) | Why |
|---|---|---|---|
| 1 | Create CONSTITUTION.md, gates only, ≤40 lines |
new defaults/CONSTITUTION.md |
Single source of law; separable from customization |
| 2 | Gut to a router; remove gates | defaults/AGENTS.md |
Stop being a 5th law copy |
| 3 | Delete hard-gate block; reference Constitution | templates/agent/AGENTS.md.template:6–16 |
Kill per-project law drift (already stale re: merge-authority) |
| 4 | Delete dirty SOUL; ship generic default | defaults/SOUL.md (Jarvis/PDA lines 8,15,23) |
Sanitize persona + accessibility leak |
| 5 | Delete jarvis-brain rule | defaults/TOOLS.md:40 |
Machine-specific path seeded to every install |
| 6 | Parameterize bootstrap template path | guides/ORCHESTRATOR.md:99,111,152 |
Guide is broken for all non-maintainer users |
| 7 | Delete or templatize the Jarvis preset | runtime/claude/settings-overlays/jarvis-loop.json |
Pure personal contamination |
| 8 | Unify rails/git→tools/git |
12 templates/**/*.template files |
Templates point at a path install.sh:193 deletes |
| 9 | Fold STANDARDS into L1 + include model; drop "Master/slave" | defaults/STANDARDS.md |
Resolve law/customization conflation + bad term |
| 10 | Add blocking CI personal-data + path-drift grep | new tools/ci/no-personal-data.sh + .woodpecker/ |
The only durable anti-regression control |
| 11 | Constitution = overwrite-always (not in PRESERVE_PATHS) | install.sh:24 |
Law must upgrade; today AGENTS.md is preserved → gate updates never reach edited installs |
| 12 | Pointer says "READ NOW", not "already loaded" | defaults/AGENTS.md:11, direct-launch pointers |
False "already injected" claim makes agents skip gates on direct launch |
The one thing I'd die on
Subtraction before structure. This debate will be tempted to design an elegant multi-layer Constitution with rich precedence and reconciliation. The repo's actual disease is duplication and contradiction, not missing layers. If we add CONSTITUTION.md without deleting the four existing restatements and wiring a CI grep, we will have five disagreeing law files instead of four, plus a prettier diagram. The layering is worth exactly as much as the deletions and the CI gate that accompany it — and not one line more.