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>
21 KiB
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 23–56), the steered-autonomy escalation triggers (lines 72–88), the mode declaration protocol (lines 59–68), subagent tier rules (lines 112–121), and the session-closure checklist (lines 148–155). 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.mdlines 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 lawdefaults/AGENTS.md— thin load-order dispatcher, no identitydefaults/USER.md— already scrubbed to "(not configured)" placeholdersdefaults/STANDARDS.md,defaults/TOOLS.md— machine-level, not personaltemplates/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 answersUSER.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 namesdefaults/AUDIT-2026-02-17-framework-consistency.md— move todocs/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 160–202) are the right mechanism. Migration v3 should:
- Move any user-edited content from the old
defaults/SOUL.mdintoSOUL.mdat the root (if SOUL.md does not already exist). - Delete
defaults/SOUL.md. - Warn if
defaults/AGENTS.mdhas 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 10–20 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:
- Does injection reach
constitution/CORE.md? (Yes ifAGENTS.mdloads it and AGENTS.md is injected.) - Does the RUNTIME.md contain any rule that contradicts CORE.md? (Audit:
grepfor escalation triggers, hard gate paraphrases — if found, delete and replace with reference.) - 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.mdline 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:
- Hard gates (the 13 bullets,
AGENTS.mdlines 23–37) — must be resident; violating these is catastrophic and silent. - Mode declaration (lines 59–68) — must be resident; it's the first response.
- Block vs. Done distinction (lines 80–88) — must be resident; determines whether agents stop prematurely.
- Escalation triggers (lines 72–79) — must be resident; determines when to interrupt humans.
- 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 112–121; load when spawning workers) - Conditional guide table → remain in
AGENTS.mdas 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 23–37) 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:
install.shv3 migration: detect olddefaults/SOUL.mdwith user edits (MD5 diff vs. shippeddefaults/SOUL.mdat install time). If edited, copy to~/.config/mosaic/SOUL.mdif that file does not already exist. Warn the user.- Move Constitution content from
AGENTS.mdintoconstitution/CORE.md. UpdateAGENTS.mdto reference it. Agents that load AGENTS.md still get the full law — they just get it via one more file read. - The
~/.claude/CLAUDE.mdthin pointer (mosaic/runtime/claude/CLAUDE.md) already says "read~/.config/mosaic/AGENTS.md" — no change needed there. - Ship
constitution/as a new directory. Existing installs get it on next upgrade. ExistingAGENTS.mdthat is preserved (it's in SEED_ONCE) still works until the user runsmosaic 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.