Skip to content

muriel v0.14.0 — a second body, and a broken mount

Latest

Choose a tag to compare

@andyed andyed released this 07 Aug 14:20
· 22 commits to main since this release

muriel v0.14.0 — a second body, and a broken mount

Released 2026-08-07.

Two things ship in this release, and the smaller one is the urgent one.

If you installed muriel from a checkout with install.sh, your install is probably broken right now, and it looks fine. Re-run ./install.sh — it will tell you. Details in Headline 1.

The feature is a design jury: muriel gains a second body. Until now it had one critic — muriel-critique — playing juror, chair, and bailiff at once, and no ranking apparatus at all. Compare mode could produce three plausible directions and nothing in the skill knew how to choose between them. Three moves carry the throughline:

  1. Two bodies, two aggregation rules. Defect finding takes the union at max severity. Direction selection uses comparative judgement with splits reported rather than averaged. These are different questions and they were being answered by one mechanism.
  2. Seats differ by loss function and evidence access, not by persona. N copies of one critic with different adjectives share priors and produce correlated errors. Diversity has to be structural or it is decorative.
  3. Disagreement is the output. Where the panel splits is the unsettled design decision, which is muriel's entire stated remit. Averaging five rankings destroys the only thing a panel uniquely produces.

Headline 1 — the mount that could not heal

install.sh symlinks a dev checkout into ~/.claude/ so edits show up live. It had one shape of bug in two places, and the bug was invisible by construction.

The existence check was:

if [ -L "$dst" ] || [ -d "$dst" ]; then
  echo "✓ already exists — leaving alone"

-L is true for a dangling symlink. -d is true for a stale directory. So a mount pointing at a path that no longer existed reported success, forever. The script could only ever add; it never verified.

Two consequences, both found on the author's own machine after months:

  • ~/.claude/agents/muriel-critique.md still pointed at <repo>/agents/muriel-critique.md, a path that stopped existing at the plugin migration. No muriel subagent resolved at all. Every individual -L check passed.
  • ~/.claude/skills/muriel was a real directory of per-item symlinks, assembled before references/ existed. Every link in it resolved correctly, which is why it looked healthy — but there was no references entry, so all seven deep references SKILL.md links were unreachable from the running skill. SKILL.md was pointing at documents the skill could not open.

The per-file agent loop had the same add-only shape, so a seat added later never appeared and a seat removed left a dangling link behind.

The fix is one directory symlink per mount. That is the only shape that cannot rot: a new channel, reference, or jury seat appears in the live install the moment it lands in the checkout, with no re-run.

For agents this works because Claude Code scans ~/.claude/agents/ recursively and takes a subagent's identity from its name: frontmatter rather than its path. One ~/.claude/agents/muriel mount registers muriel-critique and all five seats, including any added later. It is a subdirectory rather than the directory itself because ~/.claude/agents/ is a shared namespace.

mount_dir now checks where a mount actually points:

  • points at the current checkout → leave it
  • a symlink pointing anywhere else, including nowhere → repoint it, because a symlink at our own mount point is ours to manage
  • a real file or directory → report and stop, until you re-run with --repair, which moves it to muriel.bak-<timestamp> and then links

Nothing is deleted, ever. Legacy per-file agent links are retired only when they resolve into the checkout; anything else is left alone.

If you are on the /plugin install path, none of this affected you.


Headline 2 — the jury

Jurisdiction: the panel may not rule on facts

A jury on a measurable fact is theater, and worse than theater — it is corrosive. If a seat can vote "the 4:1 caption is fine, it's decorative," the 8:1 floor is dead.

muriel.contrast, muriel.devibe, and the impeccable pre-scan produce findings of fact. They enter the panel as stipulated evidence, never as motions. The chair's first duty is kicking measurable questions back to the compute tools; a ballot claim a tool could have settled is struck and logged as noise against the seat that made it.

The jury's jurisdiction is only the genuinely contested: composition, hierarchy, reading order, direction selection, brand voice, whether an encoding earns its complexity.

Two bodies

Defect finding — worst critic wins. Union of findings, max severity, no vote. The loss is asymmetric: a false positive costs one wasted fix, a false negative ships. Nobody gets to vote a defect away.

One exception, and it is the mechanism by which the panel can acquit. When two seats' fixes on the same target are mutually exclusive, that is a split, not two defects. On a Tufte small-multiples grid the Squinter fires on "no mass dominates" and the Forger fires on "equal-weight tiles are template stock" while muriel-critique endorses small multiples as correct. Without a route to the split report, union-max ships both and the chair — which breaks no ties by preference — cannot say the form is right.

Direction selection — the jury proper. Comparative judgement, splits reported. No worst-case rule, because there is no "worst" when ranking viable options. Separation and concordance are defined as raw proportions the chair can compute by counting, not as fitted statistics nothing in muriel computes.

The seats

Five, each a subagent. Ballots are sealed in round 1 and option order is randomized per seat with a named digest, because LLM judges have strong position bias and a fallback derived from option ids alone makes that bias correlate perfectly across the panel while still emitting a valid-looking ballot.

  • Squinter — heavy Gaussian blur. Judges focal dominance, reading order, figure/ground. Denied the sharp render and all text.
  • Thumbnail — 1/8 scale and 16 px. Not a smaller Squinter: blur preserves position and area while stripping high frequency, downscale destroys sub-pixel features outright and aliasing invents structure. An artifact can pass hierarchy cleanly and die at 16 px.
  • Stranger — one read, brief withheld, three questions. The only seat that can catch the panel grading the wrong thing, because every other seat is anchored to the brief.
  • Forger — asserts it can rebuild the artifact from a generic template, then actually attempts it and reports the residue. Converts the anti-slop check from rubric to proof obligation.
  • Pedant — text only, composition withheld. Unglamorous, highest hit rate; most shipped defects are a missing unit, not a broken layout.

Two limits are stated in the seat cards rather than left to be discovered. The Stranger is not a naive eye — it shares a base model with every other seat, so isolation removes brief anchoring and nothing about genre priors; its ballot carries a mandatory Prior strength line so the chair can weight it. The Pedant's denial is real only on source artifacts — muriel has no OCR path returning strings, so on rasters it either takes chair-supplied text or records denial partial.

The Stranger enforces its own denial: it audits its invocation for briefing material and aborts rather than filing a contaminated ballot. A Stranger that has read the brief launders the chair's assumption into an independent-looking vote, which is the exact failure the seat exists to prevent.

The ledger

Severity inflation is what kills harsh-critic systems. If brutality is rewarded, everything becomes CRITICAL, the scale collapses, and under any-CRITICAL→FAIL the panel fails everything and carries zero bits. Harshness is also cheap and usually unfalsifiable — "this lacks conviction" can never be wrong.

So each finding emits a record scored against what happened next: hit (fixed, re-render confirms), miss (dismissed with a stated reason), noise (nobody could act on it). A seat running high noise gets its severities discounted automatically.

precision = hit / (hit + noise)miss is recorded without penalty and reported separately as dismissal_rate. Including it would weigh "the human disagreed" identically to slop, which would mechanically silence the Stranger, whose entire job is producing findings teams dismiss.


muriel.squint

The compute tool behind two seats. python -m muriel squint <image> writes a blur ladder plus thumbnail companions.

Sigma is derived from a measured constant rather than a rule of thumb. An isolated feature of width d retains half its contrast at d ≈ 2.1σ — measured against the installed Pillow on every test run, so the tool and the seat definition cannot drift apart silently.

The first ladder shipped in this work did not discriminate: on the block-composed artifacts the Squinter exists to judge, light and heavy correlated at r = 0.985. Three levels, one picture. The blur only bit in the 20–90 px band — on text — which is not what the seat is for. The test suite asserted only monotone luminance variance, which is true of any blur ladder on any input and could never have caught it.

The ladder is now derived against the focal mass fraction it is meant to threshold, and tests/test_squint.py carries a two-fixture discrimination test — one dominant mass against nine equal tiles — that fails on the old constants.


Upgrading

cd ~/Documents/dev/muriel && git pull && ./install.sh

If it reports a legacy mount, re-run with --repair. Your previous mount is moved aside, not deleted.

Then confirm:

ls -la ~/.claude/skills/muriel ~/.claude/agents/muriel

Both should be symlinks into the checkout.

Known gaps

  • The ledger has no sink. The record shape is fixed and nothing writes it, so no seat is scored yet. Coordination with Session Cartographer's skill_outcome event is the open design question.
  • Auto-convene is unsolved. A jury is roughly 4–6× a single critique per seat per option; the trigger surface is the same unsolved problem as muriel-critique's auto-invoke gate.
  • A jury and muriel-critique are mutually exclusive. The generalist overlaps four of five seats, so running both double-counts by construction. The chair must pick one.