Files
stack/docs/design/framework-constitution/debate/position-contrarian.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

20 KiB
Raw Blame History

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 hardcoded You are **Jarvis** (line 8) and PDA-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 overlaps AGENTS.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.md line 32 says "The user's USER.md formatting 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

  1. Create defaults/CONSTITUTION.md containing ONLY the 13 hard gates from defaults/AGENTS.md lines 2337 plus the escalation triggers (lines 7078) and block-vs-done (lines 8087). Nothing else.
  2. Gut defaults/AGENTS.md down to a router: load order + the conditional-guide table + "read CONSTITUTION.md (already injected)." It stops being a law document.
  3. Delete the law duplication in templates/agent/AGENTS.md.template lines 616 (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.
  4. Merge defaults/STANDARDS.md into L1, drop the "Master/slave" framing entirely (defaults/STANDARDS.md line 58), 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:8You are **Jarvis**
  • defaults/SOUL.md:23PDA-friendly language (one operator's neurotype, shipped to everyone)
  • defaults/TOOLS.md:40MANDATORY jarvis-brain rule: when working in ~/src/jarvis-brain ... — a machine-specific path inside a default that gets seeded to every install (install.sh line 235 copies TOOLS.md from defaults/).
  • guides/ORCHESTRATOR.md:99,111,152 — hardcodes ~/src/jarvis-brain/docs/templates/ as the bootstrap template source. A downstream user has no jarvis-brain. This guide is broken for everyone but the maintainer.
  • runtime/claude/settings-overlays/jarvis-loop.json — entire file is a Jarvis/~/src/jarvis preset with projectConfigs.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.md must become the generic version (the template already exists at templates/SOUL.md.template with {{AGENT_NAME}}). The current defaults/SOUL.md with hardcoded "Jarvis" should be deleted and replaced by a generic-rendered default (e.g. name Mosaic, neutral stance, no PDA line). install.sh already does NOT seed SOUL/USER (lines 230240 only seed AGENTS.md STANDARDS.md TOOLS.md) — so the dirty defaults/SOUL.md exists only to contaminate the public repo and the wizard's reference. Kill it.
  • TOOLS.md → generic-defaults with NO project-specific rules. Delete defaults/TOOLS.md:40's jarvis-brain rule. That rule belongs in that user's USER.md or a project AGENTS.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 from rsync --delete in keep mode (lines 118124).
  • FRAMEWORK_VERSION=2 + .framework-version stamp + a real run_migrations() with sequential version gating (lines 160202).
  • Defaults live in defaults/ and are seeded into the framework root only if absent (lines 230241), 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

  1. Constitution is NOT in PRESERVE_PATHS. CONSTITUTION.md must be overwritten on every upgrade — that is the point of law. Add it to the overwrite-always set, not the preserve set.
  2. STANDARDS.md (L1) stays preserved but switches to an include model. Ship STANDARDS.md that ends with: # Local overrides\n<!-- mosaic:include STANDARDS.local.md -->. The user edits STANDARDS.local.md (preserved, never shipped); the framework owns STANDARDS.md (overwritten). This gives upgrade-safe customization without the merge-conflict reconciliation engine someone will inevitably propose.
  3. 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_VERSION integer + 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:

  1. Injection asymmetry is unmodeled. defaults/README.md lines 127135: mosaic pi/claude inject via --append-system-prompt; codex/opencode write 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.md line 11 asserts "The core contract is ALREADY in your context (injected by mosaic launch). Do not re-read it." — this is false for a direct claude launch, where only the thin ~/.claude/CLAUDE.md pointer 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."

  2. Codex memory override is a maintenance landmine. runtime/codex/RUNTIME.md:36 mandates durable memory to ~/.config/mosaic/memory/, while runtime/claude/RUNTIME.md:2635 mandates OpenBrain and write-blocks MEMORY.md via 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.

  3. Path drift across harnesses/files. templates/agent/AGENTS.md.template uses ~/.config/mosaic/rails/git/ (12 template files do); defaults/AGENTS.md and guides/* use ~/.config/mosaic/tools/git/ (20 refs). install.sh:193 even removes a stale rails symlink. 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.md hard gate #13 (lines 37) adds a nuanced "Merge authority (coordinated work)" exception dated 2026-06-11. templates/agent/AGENTS.md.template gate list (lines 616) 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) and guides/E2E-DELIVERY.md:37 both 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), and USER.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.md lines 89110) is the right instinct — push depth on-demand — but the always-resident core didn't shrink to match.

Concrete minimalism proposal

  1. 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|merge does NOT belong in always-resident law (AGENTS.md:30); it belongs in the delivery guide.
  2. One law document, period. After L0 exists, AGENTS.md keeps 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.
  3. Kill the redundant second law file. STANDARDS.md's gate-like content (secrets HARD RULE, multi-agent safety, git discipline) is duplicated from AGENTS.md. Move the genuinely-standards parts to L1, delete the duplicated gates.
  4. 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.sh per runtime/claude/RUNTIME.md:5458) 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:616 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/gittools/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.