# consensus node

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](/docs/nodes/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

```yaml
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: revise
```

## Configuration

| 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](/docs/recipes/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](/docs/running/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.

```mermaid
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

```yaml
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 and `agentCount`.
- **Using `enum` validator.** Unsupported for consensus. Use `boolean` or
  `confidence` with a threshold.
- **Setting `quorum: 1`.** Equivalent to "any one agent approves". Almost
  always weaker than a single [ai_judge](/docs/nodes/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_KEY` set or the `claude` CLI 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](/docs/nodes/round-robin) — pit candidates against each other
  instead of gating one proposal.
- [Consensus voting](/docs/recipes/consensus-voting) — a copy-pasteable
  multi-agent gate recipe.
- [Running on your Claude subscription](/docs/running/subscription) — how
  the MCP handback keeps the fan-out on your plan.
- [MCP integration](/docs/running/mcp) — driving a run from Claude Code end
  to end.
