Runs the same prompt through N parallel Claude agents and routes to
approved only if at least quorum of them agree. consensus is
ai_judge with redundancy: the right tool for
gates where one model's wrong answer is too expensive to accept.
Purpose#
Use consensus for high-stakes routing decisions: release approvals,
go/no-go gates, content moderation, irreversible actions. The cost is
linear in agentCount, but the reduction in tail-risk wrong answers is
substantial, and the per-agent reasoning cards in the UI make split
decisions inspectable.
Decision modes#
The validator.kind selects what the agents decide. The rest of this page
documents gate mode (the original behaviour); the other two modes share the
same fan-out and per-agent diversity but route differently:
validator.kind |
What it does | Transitions |
|---|---|---|
boolean / confidence |
Gate: each agent approves/rejects; route on a quorum. | approved / rejected |
answer (+ optional choices) |
Pick the best answer: each agent answers; the plurality wins (winningValue). |
decided / undecided |
most_consistent |
Pick the most consistent response: each agent writes a free-form response, then an aggregation step keeps the one most consistent with the rest (Universal Self-Consistency, USC). | decided / undecided |
In MCP mode on a subagent-capable host (Claude Code / Cowork), all three run their
fan-out on the user's subscription via handed-back subagents; most_consistent
does this in two rounds (generate, then aggregate).
YAML schema#
states:
review:
type: consensus # discriminator
label: Approve release? # required
model: claude-haiku-4-5 # required, must be in roscoe.yaml allow-list
prompt: | # required, auto-augmented with JSON envelope spec
Should we ship this release? Be skeptical.
agentCount: 3 # optional, 2-7, default 3
quorum: 2 # optional, >=1, must be <= agentCount, default 2
validator: # required — boolean OR confidence (no enum)
kind: boolean
maxRetries: 1 # optional, 0-3
on: # exactly two keys: approved | rejected
approved: ship
rejected: reviseConfiguration#
| Field | Type | Required | Default | Meaning |
|---|---|---|---|---|
type |
'consensus' |
yes | — | Discriminator. |
label |
string | yes | — | Human-readable name for the step. |
model |
string | yes | — | Model id; checked against roscoe.yaml at pre-flight. |
prompt |
string | yes | — | Question for each agent. Supports {{node.key}} interpolation. |
agentCount |
integer (2-7) | no | 3 |
How many parallel agents vote. Hard cap of 7. |
quorum |
integer (>= 1) | no | 2 |
Approvals needed for approved. Must be <= agentCount. |
validator |
boolean | confidence |
yes | — | Per-agent approval rule. enum is intentionally not supported. |
maxRetries |
integer (0-3) | no | 1 |
Per-agent retries on parse failure. Capped at 3. |
timeoutSeconds |
integer (1-2147483) | no | min(120s, defaults.timeoutMs) |
Per-agent deadline. When set it is authoritative (overrides both the 120s built-in default and the global defaults.timeoutMs). See below. |
maxIterations |
integer (1-50) | no | — | Per-run iteration cap when this node sits in a feedback loop. See the closed-loop feedback recipe. |
detectStall |
boolean | no | false |
Fail the run early if the consensus outcome is identical three times in a row. |
on |
transition map | yes | — | Must contain approved and rejected. |
Per-agent approval rule#
| Validator | An agent approves when… |
|---|---|
boolean |
its result is exactly true. |
confidence |
its score >= validator.threshold. |
enum is rejected at schema validation time: the per-agent approval rule
isn't well-defined for arbitrary route names.
Per-agent deadline#
Each agent has a hard 120-second deadline by default (capped further by your
roscoe.yaml defaults.timeoutMs if smaller). Set timeoutSeconds on the
node to override this with an authoritative per-agent deadline: it takes
precedence over both the 120s default and the global defaults.timeoutMs, so
it's the way to give agents more than 120s (the global cap alone can't, since
the deadline is the smaller of the two). Agents that time out or error count
as no-vote. They neither approve nor reject. With agentCount: 5, quorum: 3 and 2 timeouts, the run still completes if 3 of the remaining 3
approve.
Outputs#
consensus writes a ConsensusOutcome object into variables.<nodeId>:
| Key | Type | Notes |
|---|---|---|
outcome |
'approved' | 'rejected' | 'cancelled' |
Mirrors the chosen transition. 'cancelled' if the run was cancelled before the node settled. |
approvals |
number | Count of votes with verdict === 'approve'. |
agentCount |
number | Echo of the configured count. |
quorum |
number | Echo of the configured quorum. |
votes |
array of vote objects | One per agent; see below. |
Each vote contains agentIndex, status (completed / failed /
timed_out / cancelled), verdict (approve / reject / no_vote),
result, reasoning, attempts, and durationMs. cancelled appears on
an agent whose call was aborted because the run was cancelled mid-vote.
Transitions#
| Transition | When |
|---|---|
approved |
approvals >= quorum. |
rejected |
otherwise (including all-error scenarios). |
Both keys must be present in on. There are no other emitted transitions.
Execution mode notes#
In a server or web UI run, agents fan out in parallel against the configured LLM backend.
In an MCP run from Claude Code or Cowork, the fan-out is handed back to the
host: it runs the agentCount reviewers as independent subagents on your
Claude subscription and returns their verdicts via advance_consensus. Roscoe
tallies the quorum itself, with no API key required. See Running on your
Claude subscription.
For gate mode, that handback is a single round trip: Roscoe pauses at the
node and hands the prompt spec to Claude Code, which spawns the reviewers,
collects their verdicts, and reports them back in one advance_consensus
call that Roscoe tallies against the quorum.
sequenceDiagram
participant CC as Claude Code
participant GM as Roscoe MCP server
participant SA as N sub-agents
CC->>GM: start_workflow
GM-->>CC: pause at consensus node, pendingStep (prompt, agentCount, quorum)
CC->>SA: spawn agentCount independent reviewers
SA-->>CC: each agent returns a verdict
CC->>GM: advance_consensus(results)
GM->>GM: tally approvals against quorum
GM-->>CC: approved or rejected, run resumes
In an MCP run from Claude desktop chat, and in a standalone run, consensus
runs server-side against the configured LLM backend instead (a chat session
can't spawn subagents). That needs either ANTHROPIC_API_KEY set or the
claude CLI installed; if neither is available, the node fails before any
agent runs.
Worked example#
name: consensus
version: 1
description: Multi-agent gate where 3 reviewers must reach a 2-of-3 quorum.
initial: ask
states:
ask:
type: consensus
label: Is this idea worth shipping?
model: claude-haiku-4-5
prompt: |
Evaluate this product proposal:
"Add a feature where users can attach short voice memos (up to 30s) to
any item in their task list, transcribed automatically and searchable
alongside text notes."
Approve only if all four hold:
1. it solves a real, common user problem (not a niche edge case)
2. it can ship in under 4 weeks for a 3-person team
3. it doesn't add meaningful privacy/compliance burden
4. it's not better delivered as an integration with an existing tool
Be skeptical — reject if any single criterion is shaky.
agentCount: 3
quorum: 2
validator:
kind: boolean
maxRetries: 1
on:
approved: yes-end
rejected: no-end
yes-end:
type: end
label: Approved
outcome: success
message: Reviewer quorum agreed.
no-end:
type: end
label: Rejected
outcome: failure
message: Reviewer quorum did not agree.Common pitfalls#
quorum > agentCount. Rejected at schema validation time: the gate could never approve. Pick a quorum strictly between 1 andagentCount.- Using
enumvalidator. Unsupported for consensus. Usebooleanorconfidencewith a threshold. - Setting
quorum: 1. Equivalent to "any one agent approves". Almost always weaker than a single ai_judge call, since you're paying N× cost for a looser bar. - Long prompts ×
agentCount: 7. You're sending the same prompt seven times per run. For very large prompts, this gets expensive quickly. Start with the default of 3. - Missing API key for server-side consensus. When consensus runs
server-side (standalone, or driven from Claude desktop chat), it needs
ANTHROPIC_API_KEYset or theclaudeCLI on PATH, or the node fails before voting. Driven from Claude Code / Cowork the fan-out runs on your subscription and needs neither. - Treating no-vote as rejection. Errored or timed-out agents do not
count as "reject": they don't approve. With
agentCount: 5, quorum: 3, three timeouts means the gate fails because only two agents can approve, but the failure is a quorum miss, not a rejection.
Where to next#
- round_robin — pit candidates against each other instead of gating one proposal.
- Consensus voting — a copy-pasteable multi-agent gate recipe.
- Running on your Claude subscription — how the MCP handback keeps the fan-out on your plan.
- MCP integration — driving a run from Claude Code end to end.