Some checks failed
ci/woodpecker/push/ci Pipeline failed
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>
189 lines
20 KiB
Markdown
189 lines
20 KiB
Markdown
# 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 23–37 plus the escalation triggers (lines 70–78) and block-vs-done (lines 80–87). 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 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.
|
||
4. **Merge `defaults/STANDARDS.md` into L1**, drop the "Master/slave" framing entirely (`defaults/STANDARDS.md` line 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.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 230–240 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`:
|
||
|
||
```bash
|
||
# 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 118–124).
|
||
- `FRAMEWORK_VERSION=2` + `.framework-version` stamp + a real `run_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
|
||
|
||
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 127–135: `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:26–35` 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 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) 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 89–110) 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: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.
|