CookbookAgents
Multi-agent debate
Three agents take positions, a quorum decides. Built on consensus + a shared blackboard.
This recipe runs N agents in parallel on the same question, each taking a different stance, then asks a coordinator to reach a quorum decision. The shared blackboard lets agents see each other's reasoning across rounds.
Primitives used
agent()per stanceblackboard()for shared typed state across agentscreateConsensus()from@use-crux/core/agentfor parallel voting + quorum
When to reach for this pattern
- A question has multiple defensible answers and you want diverse takes before committing
- You need higher-confidence answers than a single LLM call provides
- The marginal cost of N parallel calls is acceptable for the quality lift
Full code
import { agent, blackboard, createConsensus } from "@use-crux/core/agent";
import { prompt } from "@use-crux/core";
import { generate } from "@use-crux/ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const VoteSchema = z.object({
position: z.enum(["ship", "hold", "rework"]),
reasoning: z.string(),
confidence: z.number().min(0).max(1),
});
// Shared state across the parallel agents
const debateBoard = blackboard({
id: "release-debate",
schema: z.object({
proposal: z.string(),
risks: z.array(z.string()).default([]),
}),
});
const optimist = agent({
id: "optimist",
prompt: prompt({
use: [debateBoard],
input: z.object({ proposal: z.string() }),
output: VoteSchema,
system:
"You favor shipping. Argue for ship unless there is a clear blocker.",
prompt: ({ input }) => input.proposal,
}),
});
const skeptic = agent({
id: "skeptic",
prompt: prompt({
use: [debateBoard],
input: z.object({ proposal: z.string() }),
output: VoteSchema,
system: "You favor caution. Identify risks. Lean toward hold or rework.",
prompt: ({ input }) => input.proposal,
}),
});
const pragmatist = agent({
id: "pragmatist",
prompt: prompt({
use: [debateBoard],
input: z.object({ proposal: z.string() }),
output: VoteSchema,
system: "You weigh shipping value vs risk. No bias either way.",
prompt: ({ input }) => input.proposal,
}),
});
const consensus = createConsensus({
executor: (agent, opts) => generate(agent.prompt, opts),
});
await debateBoard.patch({
proposal: "Release auth v2 next Tuesday",
risks: [],
});
const decision = await consensus({
id: "multi-agent-debate-consensus-1",
agents: [optimist, skeptic, pragmatist],
model: openai("gpt-4o"),
input: { proposal: "Release auth v2 next Tuesday" },
extract: (r) => r.output.position, // pull the vote value from each agent's result
quorum: "majority", // 'majority' | 'unanimous' | number, number = at least N votes
});
decision.result; // 'ship' | 'hold' | 'rework', the winning position
decision.votes; // { ship: 2, hold: 1 }, breakdown by value
decision.agreement; // 0.67, winner count / total
decision.details; // AgentResult[], every agent's full result with reasoningHow it works
- Three agents, three stances. Each has a system message that tilts its prior. Same input schema, same output schema (
VoteSchema). - The blackboard is shared. All three see the same
proposaland accumulatingrisks. Because the prompt usesuse: [debateBoard], Crux also exposes focused blackboard tools so agents can surface risks for the next round. createConsensus()runs them in parallel. All threegenerate()calls happen concurrently; you wait for the slowest.extractpulls the vote value from each agent's result; consensus tallies them and applies thequorumrule. If quorum isn't met,consensus()throwsConsensusErrorwith the vote breakdown: the caller decides whether to escalate, retry with different agents, or fall back.
Variations
Multi-round debate
Loop the consensus call N times, letting agents see prior rounds via the blackboard. Catch ConsensusError to detect missed quorums and append risks before retrying:
import { ConsensusError } from "@use-crux/core/agent";
for (let round = 0; round < 3; round++) {
try {
return await consensus({
/* ... */
});
} catch (err) {
if (!(err instanceof ConsensusError)) throw err;
// Append risks surfaced from each agent's reasoning to the board for the next round
await debateBoard.patch({
risks: [...board.risks, ...extractRisksFromAgents(err)],
});
}
}Scoring instead of voting
Replace the discriminator vote with a numeric score, then reduce by averaging or median. Useful when "which option" is less interesting than "how good is this one option."