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

21 KiB
Raw Blame History

Position Paper — Pragmatic Coder Lens

Mosaic Framework Constitution: Layering, Sanitization, Upgrade Safety, Cross-Harness Robustness, Minimalism

Author role: Pragmatic Coder — cares about implementability, migration cost, and what a maintainer can actually keep working across releases.


Abstract

The Mosaic framework has the bones of a sound design but is held back by three entangled problems: personal data baked into shipped defaults, no machine-enforceable boundary between what the framework owns versus what the user owns, and a context-injection budget that is burning down faster than the delivery contract earns back in compliance value. The fixes are mechanical, not philosophical. This paper proposes a four-layer model with a strict ownership contract, a file-naming convention that rsync --exclude can enforce, a minimal "always-resident" Constitution that fits comfortably in a shared context window, and a harness-adapter pattern that keeps cross-harness robustness honest without duplicating law.


DQ1 — Layering: Four Layers, Strict Ownership

The problem with the current two-and-a-half layers

Reading defaults/AGENTS.md, defaults/SOUL.md, defaults/USER.md, and templates/SOUL.md.template together reveals an informal split that already exists but is not named or enforced. The installer's PRESERVE_PATHS array in install.sh line 24 is the only machine-enforced boundary, and it conflates three distinct concerns: SOUL.md (persona), USER.md (operator profile), and STANDARDS.md (framework rules). All three land in ~/.config/mosaic/ with no naming convention to tell them apart. Nothing stops mosaic upgrade from silently clobbering user edits in a file that accidentally got removed from PRESERVE_PATHS.

Proposed four-layer model

Layer Name Owner Deployed path User-editable?
0 Constitution Framework ~/.config/mosaic/constitution/ No
1 Persona (Soul) User (init-generated) ~/.config/mosaic/SOUL.md Yes
2 Profile (User) User (init-generated) ~/.config/mosaic/USER.md Yes
3 Project Repo <repo>/AGENTS.md Yes

Layer 0 — Constitution owns everything that must be identical for all users: the hard gates in defaults/AGENTS.md (lines 2356), the steered-autonomy escalation triggers (lines 7288), the mode declaration protocol (lines 5968), subagent tier rules (lines 112121), and the session-closure checklist (lines 148155). It ships read-only in a dedicated constitution/ subdirectory. The upgrade path is trivially safe: rsync --delete the entire directory on every upgrade because no user edits live there.

Layer 1 — Persona (SOUL) is the agent's name, tone, communication style, and guardrails that a user may customize. It is generated by mosaic init from templates/SOUL.md.template and never overwritten by upgrade. The current defaults/SOUL.md hardcodes "Jarvis" and "PDA-friendly" — both must move to template tokens.

Layer 2 — Profile (USER) is the operator's name, timezone, accessibility needs, projects. Same init-generated pattern; defaults/USER.md already has the right shape (it's the placeholder version — the problem is the operator-specific content in defaults/SOUL.md).

Layer 3 — Project stays exactly as today: AGENTS.md per repo, with the project-local template in templates/agent/AGENTS.md.template.

Precedence rule (explicit, not implicit)

When a Constitution rule conflicts with a Persona or Profile preference, Constitution wins. When a Project rule conflicts with Persona or Profile (not Constitution), Project wins for that repo. No exceptions. The SOUL.md template can reference this explicitly: "Communication style preferences apply unless they conflict with Constitution layer hard gates."

What changes in the file tree

packages/mosaic/framework/
  constitution/               # NEW — Layer 0, framework-owned
    CORE.md                   # Delivery contract, hard gates, escalation triggers
    GUIDES.md                 # Conditional guide loading table
    SUBAGENT.md               # Model tier rules
    CLOSURE.md                # Session closure checklist
  defaults/
    AGENTS.md                 # KEEP but shrink: now just load-order + pointer to constitution/
    SOUL.md                   # DELETE (contaminated) — move to templates only
    USER.md                   # KEEP (already scrubbed to placeholder)
    STANDARDS.md              # KEEP (machine-specific, not personal)
    TOOLS.md                  # KEEP
  templates/
    SOUL.md.template          # Already exists, needs {{PDA_PREFS}} removed
    USER.md.template          # Already exists

The deployed layout at ~/.config/mosaic/:

~/.config/mosaic/
  constitution/               # rsync --delete on every upgrade (no user edits)
  AGENTS.md                   # Thin dispatcher: read constitution/, then SOUL, USER, runtime
  SOUL.md                     # Init-generated, upgrade-preserved
  USER.md                     # Init-generated, upgrade-preserved
  guides/                     # On-demand depth (unchanged)
  runtime/                    # Harness-specific (unchanged)

The installer's PRESERVE_PATHS shrinks to: SOUL.md USER.md TOOLS.md memory sources credentials. constitution/ is explicitly excluded from preservation — it is always overwritten.


DQ2 — Sanitization: Template-then-Init, Zero Fallback Personal Data

The contamination is surgical, not structural

git grep -i 'jarvis\|jason\|woltje\|pda' packages/mosaic/framework/ will hit:

  • defaults/SOUL.md lines 8, 25 (hardcoded name + PDA)
  • runtime/claude/settings-overlays/jarvis-loop.json (project name + persona)
  • defaults/AUDIT-2026-02-17-framework-consistency.md (audit doc with personal refs)

That is approximately three files plus any stray refs in guides. The problem is not pervasive across the whole framework — it is concentrated and surgical to fix.

What ships vs. what is generated

Ships in the package (no personal data, no placeholder artifacts):

  • constitution/ — pure framework law
  • defaults/AGENTS.md — thin load-order dispatcher, no identity
  • defaults/USER.md — already scrubbed to "(not configured)" placeholders
  • defaults/STANDARDS.md, defaults/TOOLS.md — machine-level, not personal
  • templates/SOUL.md.template — tokens only, no "Jarvis", no "PDA"
  • templates/USER.md.template — tokens only
  • All guides — already clean (spot-check guides/E2E-DELIVERY.md, guides/ORCHESTRATOR.md: no personal refs)
  • All runtime files except settings-overlays/jarvis-loop.json

Generated at init time (never shipped):

  • SOUL.md — rendered from template with user answers
  • USER.md — rendered from template with user answers
  • Any user-project config

Delete or move:

  • defaults/SOUL.md — delete from package (was a seed copy; now generated only)
  • runtime/claude/settings-overlays/jarvis-loop.json — delete or generalize to an example overlay with no personal names
  • defaults/AUDIT-2026-02-17-framework-consistency.md — move to docs/ or delete (it's a one-time audit document, not framework content)

Out-of-box experience without personal defaults

The concern about "blank defaults" degrading out-of-box experience is real but solvable. The installer already runs mosaic init after install.sh — the init wizard generates SOUL.md and USER.md immediately. If init is skipped (non-interactive CI installs), the thin AGENTS.md still functions because it only needs constitution/ to enforce hard gates. SOUL.md absence means no agent persona customization, which is an acceptable degraded state — not a broken state. Add a one-line warning in AGENTS.md: "SOUL.md not found — agent will use default identity. Run mosaic init to configure."


DQ3 — Customization and Upgrade Safety: File Ownership as the Enforcement Mechanism

The current PRESERVE_PATHS approach is correct but incomplete

install.sh line 24 already implements the right idea: PRESERVE_PATHS=("AGENTS.md" "SOUL.md" "USER.md" "TOOLS.md" "STANDARDS.md" "memory" "sources" "credentials"). The problem is that AGENTS.md and STANDARDS.md are framework-owned files that should be freely overwritten on upgrade, but they are listed alongside user-owned files that must never be overwritten. This conflation is the root of the drift problem.

Fix: Directory ownership, not file-by-file exclusion

Replace the mixed per-file PRESERVE_PATHS with directory-level ownership:

# Framework-owned directories — always overwritten on upgrade
FRAMEWORK_DIRS=("constitution" "guides" "runtime" "templates" "tools" "profiles" "adapters")

# User-owned files — never overwritten
PRESERVE_PATHS=("SOUL.md" "USER.md" "TOOLS.md" "memory" "sources" "credentials")

# Thin dispatchers — seeded on first install, never overwritten thereafter
SEED_ONCE=("AGENTS.md" "STANDARDS.md")

The rsync command becomes:

rsync -a --delete \
  $(for d in "${FRAMEWORK_DIRS[@]}"; do echo "--include=$d/***"; done) \
  --exclude="*" \
  "$SOURCE_DIR/" "$TARGET_DIR/"

This gives the framework a clean ownership contract: everything in constitution/ is always current; user files in ~/.config/mosaic/ root are always preserved.

Upgrade-safe customization for users who need to extend guides

Some power users will want to extend guides (e.g., add a custom section to guides/E2E-DELIVERY.md). The right pattern is user-overlay files, not editing the originals:

~/.config/mosaic/
  guides/
    E2E-DELIVERY.md           # Framework-owned, always overwritten
    E2E-DELIVERY.local.md     # User-owned, never touched by upgrade

AGENTS.md load-order instructions reference .local.md variants: "After loading any guide, check for a .local.md variant and merge-read it." This is opt-in and requires no framework change to constitution/ — just a convention documented in defaults/AGENTS.md.

Version migration

The existing FRAMEWORK_VERSION variable in install.sh line 28 and run_migrations() function (lines 160202) are the right mechanism. Migration v3 should:

  1. Move any user-edited content from the old defaults/SOUL.md into SOUL.md at the root (if SOUL.md does not already exist).
  2. Delete defaults/SOUL.md.
  3. Warn if defaults/AGENTS.md has user edits (checksum diff) and offer to merge.

This is a concrete, implementable migration — not a "review manually" hand-wave.


DQ4 — Cross-Harness Robustness: Single Law File, Adapter-Only Injection Mechanics

The current adapter pattern is structurally correct but hollow

Looking at adapters/claude.md, adapters/codex.md, adapters/pi.md, adapters/generic.md: each adapter is 1020 lines and correctly says "load STANDARDS.md and project AGENTS.md." The runtime/{claude,codex,pi,opencode}/RUNTIME.md files add harness-specific mechanics (settings paths, model tier syntax, MCP config locations). This split is right. The problem is that the Constitution content currently lives in defaults/AGENTS.md which is also the load-order dispatcher — if a harness injects a slightly different path, the whole contract is at risk.

Fix: Constitution as a stand-alone file that adapters reference, not duplicate

The proposed constitution/CORE.md (from DQ1) must be the single file that all harnesses reference identically. Adapter files should contain exactly one line regarding the constitution: "Load ~/.config/mosaic/constitution/CORE.md — this is the immutable law."

Current per-harness RUNTIME.md files contain no contradictions with AGENTS.md, which is good. They add harness-specific syntax (e.g., Claude's Task tool model parameter, install.sh line for Pi's --append-system-prompt). That pattern should be preserved as-is. What must change is that RUNTIME.md files must not re-state or paraphrase Constitution rules — they must simply reference constitution/CORE.md. If a rule needs harness-specific elaboration, it goes in RUNTIME.md as an addendum, not a restatement. Restatements drift; references cannot.

Cross-harness enforcement checklist (concrete)

For each harness adapter, validate:

  1. Does injection reach constitution/CORE.md? (Yes if AGENTS.md loads it and AGENTS.md is injected.)
  2. Does the RUNTIME.md contain any rule that contradicts CORE.md? (Audit: grep for escalation triggers, hard gate paraphrases — if found, delete and replace with reference.)
  3. Does the harness have a native equivalent for sequential-thinking MCP? (Pi: yes, native thinking levels. Claude/Codex/OpenCode: MCP required. This is already documented in runtime/pi/RUNTIME.md line 61 — keep it.)

The Pi adapter runtime/pi/RUNTIME.md is the most complete and honest — it explicitly documents where Pi differs from other runtimes (no permission restrictions, native thinking, model-agnostic). The other RUNTIME.md files are thinner. That's fine; they should stay thin. Thin adapters with a single Constitution reference are more maintainable than thick adapters that duplicate law.

What to do about harness-specific settings (jarvis-loop.json)

runtime/claude/settings-overlays/jarvis-loop.json contains personal project names ("jarvis", "~/src/jarvis") and persona-specific presets. This file must not ship. Replace it with a generic example:

runtime/claude/settings-overlays/
  example-project-overlay.json    # Generic example with {{PROJECT_NAME}} tokens
  README.md                       # Explains how to create user-local overlays

User-local overlays live outside the package (e.g., ~/.config/mosaic/runtime/claude/settings-overlays/my-project.json) and are never overwritten by upgrade.


DQ5 — Minimalism vs. Completeness: Token Budget is a Real Constraint

The current "thin core" claim is not thin

defaults/AGENTS.md is 155 lines and is described as "THE source of truth" in defaults/README.md. Add defaults/SOUL.md (54 lines), defaults/USER.md (~37 lines), and the required-at-session-start guides/E2E-DELIVERY.md (which is much longer), and you are burning a meaningful fraction of a shared context window on framework overhead before any project-specific content loads.

The brief calls this out: the contract is "large and partly duplicated." Looking at both files, guides/E2E-DELIVERY.md and defaults/AGENTS.md repeat the mode declaration protocol, escalation triggers, and execution cycle. That is direct duplication — agents reading both files (as instructed) see the same rules twice.

Concrete split: what is truly always-resident

The always-resident Constitution (constitution/CORE.md) should contain only rules that an agent absolutely cannot violate without reading them first:

  1. Hard gates (the 13 bullets, AGENTS.md lines 2337) — must be resident; violating these is catastrophic and silent.
  2. Mode declaration (lines 5968) — must be resident; it's the first response.
  3. Block vs. Done distinction (lines 8088) — must be resident; determines whether agents stop prematurely.
  4. Escalation triggers (lines 7279) — must be resident; determines when to interrupt humans.
  5. Sequential-thinking requirement (line 143) — must be resident; it's a session-start prerequisite.

Everything else is on-demand:

  • Execution cycle details → guides/E2E-DELIVERY.md (already there, load on implementation tasks)
  • Subagent tier selection → guides/SUBAGENT.md (new file, extracted from AGENTS.md lines 112121; load when spawning workers)
  • Conditional guide table → remain in AGENTS.md as a compact lookup table (it's a table, not prose; low token cost)
  • Session closure checklist → guides/E2E-DELIVERY.md (already there)

The result: constitution/CORE.md targets ~80 lines. AGENTS.md shrinks to ~40 lines (load order + guide table + pointer to constitution). Total always-resident budget: ~120 lines vs. the current ~155 in AGENTS.md alone before guides load.

Deduplication: delete from E2E-DELIVERY.md, not from AGENTS.md

guides/E2E-DELIVERY.md currently re-states mode declaration and escalation triggers. When these move to constitution/CORE.md, delete them from E2E-DELIVERY.md — not from both. The guide can reference: "Mode declaration and escalation triggers are in constitution/CORE.md (already resident — do not re-read)." This removes duplication without creating a hole.

Against further minimalism

There is a real risk of over-minimizing: removing rules from the always-resident context to save tokens, then watching agents violate them because they never loaded the relevant guide. The hard gates in particular (AGENTS.md lines 2337) have a known failure mode: agents skip them when they are on-demand. The existing decision to keep them always-resident is correct. Do not move them to on-demand guides. Token cost of 30 lines of hard-gate text is worth paying at every session.


Concrete File Layout Recommendation (Alpha)

packages/mosaic/framework/
  constitution/
    CORE.md              # ~80 lines: hard gates, mode declaration, block/done, escalation, seq-thinking req
    GUIDES.md            # Conditional guide loading table (extracted from AGENTS.md)
    SUBAGENT.md          # Model tier rules (extracted from AGENTS.md)
  defaults/
    AGENTS.md            # ~40 lines: load order + pointer to constitution/ + guide table ref
    USER.md              # Scrubbed placeholder (already done)
    STANDARDS.md         # Keep as-is
    TOOLS.md             # Keep as-is
    # SOUL.md — DELETED (generated by init only)
    # AUDIT-2026-02-17-*.md — DELETED (stale audit doc)
  templates/
    SOUL.md.template     # Remove {{PDA_PREFS}} and hardcoded "Jarvis"
    USER.md.template     # Already clean
    TOOLS.md.template    # Already exists
    agent/               # Keep as-is
  runtime/
    claude/
      RUNTIME.md         # Add: "Load constitution/CORE.md — law is there, not here"
      settings-overlays/
        # jarvis-loop.json — DELETED
        example-project-overlay.json  # Generic, token-substituted example
    codex/RUNTIME.md     # Same constitution reference addition
    pi/RUNTIME.md        # Same
    opencode/RUNTIME.md  # Same
  adapters/
    claude.md            # Add constitution reference; keep thin
    codex.md             # Same
    pi.md                # Same
    generic.md           # Same
  install.sh             # Rewrite PRESERVE_PATHS → FRAMEWORK_DIRS + PRESERVE_PATHS split
                         # Add migration v3: move defaults/SOUL.md → SOUL.md if user-edited

Migration Path (Alpha → Existing Installs)

Do not break existing deployments. The migration is:

  1. install.sh v3 migration: detect old defaults/SOUL.md with user edits (MD5 diff vs. shipped defaults/SOUL.md at install time). If edited, copy to ~/.config/mosaic/SOUL.md if that file does not already exist. Warn the user.
  2. Move Constitution content from AGENTS.md into constitution/CORE.md. Update AGENTS.md to reference it. Agents that load AGENTS.md still get the full law — they just get it via one more file read.
  3. The ~/.claude/CLAUDE.md thin pointer (mosaic/runtime/claude/CLAUDE.md) already says "read ~/.config/mosaic/AGENTS.md" — no change needed there.
  4. Ship constitution/ as a new directory. Existing installs get it on next upgrade. Existing AGENTS.md that is preserved (it's in SEED_ONCE) still works until the user runs mosaic upgrade — at that point the new AGENTS.md is seeded and the constitution directory appears.

Migration cost for existing users: one mosaic upgrade. No manual steps. No data loss.


Biggest Risk

The load-order indirection chain breaks silently across harnesses.

The current chain is: harness injects AGENTS.md → AGENTS.md says "read SOUL.md" → agent reads it. With the proposed change: harness injects AGENTS.md → AGENTS.md says "constitution/ is already resident (I was injected with it)" — but was it? If mosaic claude composes a --append-system-prompt that includes AGENTS.md but not constitution/CORE.md, the hard gates are silently absent.

This is not a hypothetical: defaults/README.md line 126 shows that mosaic claude uses --append-system-prompt "with composed runtime contract" but the composition logic is in the npm CLI (packages/mosaic/src/), not visible in the framework files. If the composer does not include constitution/CORE.md when composing, the law disappears from context with no error.

Mitigation: AGENTS.md must say "if constitution/CORE.md is not already in context, read it now" — making the Constitution self-bootstrapping, not injection-dependent. This is the same defensive pattern the current AGENTS.md uses for SOUL.md (line 11: "Read ~/.config/mosaic/SOUL.md"). The Constitution must not rely on the launcher getting the injection order right; it must be a file the agent is instructed to read regardless.


Single Strongest Recommendation

Extract the hard gates into constitution/CORE.md and instruct agents to self-load it from AGENTS.md — do not rely on the launcher to inject it. This one change makes the Constitution harness-agnostic by construction, eliminates the injection-order race, and gives you a clean file to upgrade without touching user-customized content. Every other improvement (sanitization, template generation, upgrade-safe overlays) is valuable but secondary. The Constitution's enforceability depends on agents reliably reading it — make that a file-read instruction, not a launcher implementation detail.