multi-agent review · supervised · auditable
Many agents.
One review standard.
revmux is launched by your coding agent rather than typed by you. The shipped skill works out what is under review, writes the context to disk, runs revmux and reads the report back. A branch, a pull request, a design document or a filed issue all go in the same way, and come back as one structured answer several supervised models have cross-checked.
1
2
3
4
5
6
7
8
9s reading app/pipeline/find.go
3
4
5
6
7
8
9
0
1s checking the roster resolution path
8
9
0
1
2
3
4
5
6s stalled 2m0s, relaunched
01 / control
Every agent is visible, and every phase recoverable
Each model runs as a subprocess revmux owns, so it can be watched, timed, killed and relaunched. You see what every agent is doing and what it has spent, an agent that goes quiet is restarted without your help, and the whole run is on disk afterwards. A subprocess does not make the model faster; it makes the run something you can hold on to.
A stall is caught and relaunched
A finder with nothing on its output for the idle timeout is killed and retried once. If the second attempt fails too, the run carries on without it: the report names the missing source and synthesis is told the real count, so the other agents' work still reaches you. Only a run where every source failed is an error.
One review standard, not one per session
A review assembled ad hoc varies with the prompt and whatever context was left in the session. Here the roster, the lenses, the severity bar and the three stages are files. A contributor gets the review the maintainer would have run, and the maintainer can check which lens text produced it.
The right model for each role
A broad bug pass, an adversarial second opinion, synthesis and verification favour different
models, effort levels and costs. Every agent and both stages select a binary, a model and an
effort in one model: string, and the resolved runner for every process is recorded.
Auditable by agents, not only by people
Each run keeps the composed prompt every agent received, the verbatim output it returned, the findings after every stage and revmux's own decisions about stalls and retries. A person reads the report. An agent can read the whole run and propose edits to the lens text behind it.
02 / the run
Three fixed stages
Only the roster and the severity bar vary between review shapes. Everything else is configuration.
find
synthesize
verify
--no-synthesis passes findings through with attribution intact. --no-verify marks every finding
unverified rather than claiming it was checked.
find
The profile's roster runs in parallel, each agent composing one or more lenses. Launch is staggered: agent one goes first and the rest are released once it produces its first output.
synthesize
One model call merges every source, dedupes on (file, line ±2), boosts
confidence where distinct sources corroborate, splits out open questions and pre-existing
issues, and drops weak singletons.
verify
Parallel verifiers grouped by directory, each seeing only its own group so it cannot anchor on a neighbour. Every finding comes back confirmed, refined, rejected, immaterial or pre-existing.
03 / on screen
Watch it, or read the log
A status table with one row per supervised process, a combined chronological pane and a tab per agent, then a findings browser when the report is in. Press i and the same panes show the inputs the round was pointed at.
scope.md,
goal.md, profile.md and every context file, markdown rendered as a
document and other text shown as it is, so the context the agents were given is one keystroke
away from the run they are doing.
the counting
rule
A source is a process. An agent carrying two lenses that flags the same issue under both is still one source: it cannot corroborate itself.
The cross-source confidence boost counts distinct processes, never lenses and never tags. revmux stamps the attribution itself once the model's output is parsed, and no schema exposes the field to the model, so an agent cannot name itself twice. Codex runs as a peer alongside the lens agents rather than as a second pass, because a reviewer who sees the first reviewer's findings anchors on them and the boost then measures nothing.
04 / context
Review context arrives as a task round
The subject is whatever the scope describes: a branch, a pull request, an implementation plan,
a design document, or an issue somebody filed. revmux performs no scope detection, no git
operations and no PR fetching, and it modifies no source. The caller writes the round's
input/ and passes its name. Variables expand to the paths of those files, never to
their contents, so no prompt is bloated by a large scope.
.revmux/tasks/pr-123/ a task: one subject, as many rounds as it takes
├── task.md optional anchors: description, url, branch, base
├── 01-initial/ a round
│ ├── input/ caller-written, the only channel context travels through
│ │ ├── scope.md {{SCOPE}} required
│ │ ├── goal.md {{GOAL}} optional
│ │ ├── profile.md {{PROFILE}} optional, the project's own conventions
│ │ └── context/ {{CONTEXT}} optional, any number of files
│ └── ... revmux-written artifacts
└── 02-after-fix/ the next round, with its own input/
Both names are caller-chosen and semantic. A round that has already run is an error rather than an overwrite, because a round that went badly is exactly the one worth keeping. Prior rounds are injected into every composed prompt with their own instruction to re-evaluate independently, so round two knows what round one reported without being told to agree with it.
revmux new
prints every
path you write
to, so nothing
joins one by
hand
$ scope=$(revmux new --task pr-123 --run 01-initial | jq -r .scope) $ cat > "$scope" <<'EOF' Review the changes on this branch against master. Diff command: git diff master...HEAD EOF $ revmux --task pr-123 --run 01-initial > findings.json 16:02:11 bugs+impl started [bugs, impl] 16:04:02 docs+tests retrying: agent docs+tests stalled 16:05:12 bugs+impl done, 6 findings 16:09:03 ── complete ── 16:09:03 6m52s, sources 4/4, degraded none 16:09:03 6 findings: 1 major, 5 minor
05 / the archive
Enough to audit the review without re-running it
Every run writes its artifacts into its own round directory, beside the input/ it
was pointed at. One round read in isolation shows both what was reviewed and what came back.
A failed archive write fails the run: a report next to a half-written archive reads as complete.
which lens text
raised this?
did synthesis
drop something
real?
.revmux/tasks/pr-123/02-after-fix/ ├── input/ what this round was reviewed against ├── manifest.json roster, prompt provenance and hashes, requested vs actual model ├── prompts/ │ ├── agents/ the composed prompt each agent received, post-substitution │ └── stages/ synthesis.md, verify-app-pipeline.md, ... ├── stages/ 1-found.json, 2-synthesized.json, 3-verified.json ├── events.jsonl revmux's own decisions: stalls, retries, degrades, transitions ├── agents/ verbatim tees, one file per attempt └── report.md, findings.json
manifest.json doubles as the marker claiming the round. It is created exclusively
as the run starts, which is both how an already-run round is detected and how a real round is
told from a directory somebody left behind. Read the archive rules,
or turn the corpus into numbers with
revmux stats: what each agent and each lens produced, and how many findings the pipeline dropped between
stages.
06 / profiles
Any agents, any models, any lenses
A profile is a file. Its front matter names the roster, and its body is the shared preamble and the severity bar. Each entry picks the lenses it carries and, if it wants one, its own binary, model and effort. Synthesis and verification choose separately. Nothing about the shape is fixed, so a review is composed rather than selected from a menu.
.revmux/prompts/
profiles/
release.md
then --profile release
---
description: pre-release pass over the payment path
model: claude/opus:high
agents:
- {name: money, lenses: [bugs, impl, payments], color: red}
- {name: contracts, lenses: [architecture, docs], color: cyan}
- {name: peer, lenses: [adversarial], model: codex/gpt-5.6-sol:xhigh}
- {name: second, lenses: [bugs], model: codex/gpt-5.6-sol:high}
stages:
synthesis: claude/opus:high
verify: claude/sonnet:low
---
Everything below the front matter is the preamble every agent in this
roster receives, including what counts as critical, major and minor here.
Four sources, two vendors, four different jobs, and a synthesis model stronger than the
verifier that follows it. payments is not a shipped lens: it is
.revmux/lenses/payments.md, written by whoever knows what goes wrong in that code.
A lens is a file of instructions and nothing else, so a project can have as many as it has
subjects worth a dedicated pass, and a profile can have as many agents as it is willing to pay
for.
eight ship, as starting points rather than as the menu
| profile | roster |
|---|---|
| comprehensive |
bugs+impl, arch+quality, docs+tests on claude, plus an
adversarial codex peer. The default.
|
| focused | one bugs agent plus the codex peer, for a small or time-boxed change |
| final | bugs+impl plus the codex peer, nothing below major reported |
| claude-only | the same four lens splits on claude, for a machine with no codex |
| codex-only | the same splits on codex, both stages with them, no claude anywhere |
| grill-me | two lens splits run once on claude and once on codex, every agent reading against the change |
| expert | two agents at the highest effort, each carrying all eight code lenses, both stages on fable. For when the answer matters more than the wall clock. |
| triage | a four-way panel over a filed item rather than a diff: grounding, the case for, the case against, and cost |
thirteen lenses
ship: eight read
a change, five
read a filed
item
A lens is a file of instructions and nothing else: no executor, no output format, no mention of
a prior round. bugs covers correctness, impl goal fit,
architecture conventions and organization, quality style and
over-engineering, docs and comments the documentation a change leaves
stale, tests whether a defect can hide, and adversarial attacks the
change looking for what a sympathetic reader would accept.
Prompt text resolves per file across three layers: ./.revmux/, then
~/.config/revmux/, then the embedded defaults. Overriding one lens does not orphan
the others, and adding a file is how the set grows.
Every lens and key is in the reference.
07 / the blueprint
The review standard lives in the repository
What a project actually cares about, its conventions, what counts as major, the mistakes it
keeps repeating, usually lives in a maintainer's head and reaches contributors one review
comment at a time. revmux init writes it to disk instead.
commit it, and
every clone
reviews by the
same rules
$ revmux init # writes whatever currently resolved, ready to edit
.revmux/
├── config runtime knobs, shipped commented out
├── lenses/
│ ├── bugs.md the shipped lens, now yours to edit
│ └── payments.md a lens only this project needs
└── prompts/
├── profiles/release.md the roster above
└── synthesis.md verify.md
Checked in, that directory is the project's review, versioned and diffable like the rest of the code. A contributor who clones the repository and runs revmux gets the review the maintainer would have run, before the maintainer ever opens the pull request. A maintainer reading a finding can see which lens text raised it rather than taking it on faith. A review that missed something is fixed by editing a file, once, and every later review has it.
It cuts both ways: that text becomes the instructions a headless agent with a shell executes,
so .revmux/ is code and deserves the same look as a Makefile before
you run it on a branch you did not write. Running revmux from outside the tree never loads it.
08 / output
Findings to stdout, everything else to the tty
JSON is the default because the usual reader is the agent that launched the run, which decides
what to fix, what to argue with and what to put in front of you. --markdown is for
when a person is the one reading. The terminal UI renders to the tty and progress lines go to
stderr, so revmux --task pr-123 --run 02-after-fix > findings.json works with the
display running.
{
"scope": {"task": "pr-123", "run": "02-after-fix", "scope_path": "..."},
"sources": {
"expected": 4, "reported": 3, "degraded": ["docs+tests"],
"agents": [{"name": "bugs+impl", "lenses": ["bugs", "impl"],
"executor": "claude", "requested_model": "opus",
"actual_model": "claude-opus-5", "effort": "high",
"tokens": 48210, "raised": 6, "degraded": false}]
},
"findings": [
{"id": "f1", "file": "app/pipeline/find.go", "line": 88,
"severity": "major", "confidence": 90,
"title": "...", "body": "...", "fix": "...",
"sources": ["bugs+impl", "codex"], "lenses": ["bugs", "adversarial"],
"verdict": "confirmed"}
],
"open_questions": [], "pre_existing": [], "immaterial": [],
"stats": {"duration_ms": 333000, "tokens": 184920, "stages": [...]}
}
exit codes
0 nothing found
1 findings above the confidence floor
2 tool error
1 is a normal outcome. Callers script against these.
sources holds agent names and is the only input to the confidence boost.
lenses holds the lens names that raised the finding and is informational: it answers why
something was reported, never how many independently agreed. Empty lists are emitted as arrays, so a
caller can index into them without a nil check.
09 / the caller
Ask in words, and the skill does the rest
To the agent, revmux is a black box: context in, a verified report out. The skill is what fills
that box. It resolves what is being reviewed, runs the git commands, writes the round's
input/, launches revmux, reads the JSON back, and opens a new round after fixes.
You never compose a command, and the review does not have to be code: a plan, a design
document, a proposal or a filed issue goes in the same way.
Plugin install
/plugin marketplace add umputun/revmux
/plugin install revmux@revmux
Then ask for a review in words: revmux this branch, revmux pr 123, re-review after fixes.
Copy the skill
cp -r plugins/codex/skills/revmux ~/.codex/skills/revmux
Same reference material and the same scripts. Both trees carry preflight.sh,
task-state.sh, launch-revmux.sh and analyze-corpus.py.
review a change,
or triage what
somebody filed
Triage is the other job. Point the skill at an issue, a proposal or a
discussion and it gathers the item, its thread and the author's history into
context/, then runs a four-way panel over them: is the claim true of the code as
it stands, how were comparable asks decided here before, the strongest honest case for, the
strongest case against, and what the work would reach into. The severities rate how much a
point bears on the decision rather than what breaks at runtime, and what comes back is
arguments for a maintainer to weigh. revmux decides nothing, and neither does the skill: it
puts the six questions to you with the case behind each.
Asked for a pull request instead, the skill fetches the head into a throwaway worktree, points
--workdir at it while running from the main checkout, and removes both afterwards.
An agent's shell has no tty, so launch-revmux.sh opens the run with its TUI in a
terminal overlay when you want to watch it happen.
10 / install
Brew it, or take a binary
revmux drives the model CLIs as subprocesses, so whichever ones the profile names must already
be installed and authenticated. The shipped preflight.sh answers that for any
profile and any invocation.
MIT
no telemetry
no network of
its own
brew install umputun/apps/revmux # macOS # linux, from a release: .tar.gz, .deb and .rpm for amd64 and arm64 dpkg -i revmux_<version>_linux_amd64.deb # or with a go toolchain, installed as `app` go install github.com/umputun/revmux/app@latest
Full install notes, including building from a clone and how authentication is passed to the model CLIs.