Packages
Multi-agent orchestration strategies for GenAgent. One ensemble process owns N sub-agents under a strategy (Solo, Switchboard, Pool, Pipeline, Supervisor, Debate, Consensus).
Current section
Files
Jump to
Current section
Files
guides/workflows/debate.md
# Debate workflow
## Topology
Two agents take turns arguing about a topic. The prompt hits the
first agent; that agent's response becomes the second agent's
prompt; the second agent's response becomes the first agent's next
prompt; and so on until they converge or a round cap is reached.
```
+-------+ +--------+ +-------+ +-------+
| E.* | -> | Debate | -----> | alice | -----> | bob |
+-------+ +--------+ +-------+ ^ +-------+
^ \_____|
|______________|
turn 1: "Topic X" -> alice -> "opener"
turn 2: "opener" -> bob -> "rebuttal"
turn 3: "rebuttal" -> alice -> "counter"
turn 4: "counter" -> bob -> "AGREED on Y" (converged)
final reply: transcript of all 4 turns, or last, or synthesized.
```
Each agent's backend session retains the full back-and-forth in its
own conversation memory, so turn N sees turns 1..N-1 in context.
The prompt wire format is just the previous agent's response text
(same convention as Pipeline), so the first turn's prompt is what
establishes the topic.
## When to reach for it
- You want two perspectives to interrogate each other (proposer vs
critic, bull vs bear, security vs feature-velocity).
- Running the same question through two different backends (Claude
+ GPT, say) surfaces disagreements you would have missed solo.
- Architecture and design decisions where the failure mode of a
single model is overconfidence.
For more than two agents or a moderator/synthesizer, Consensus
(planned) is the better fit. For parallel fan-out, use Supervisor.
## Config
```elixir
config :gen_agent_ensemble,
ensembles: [
[
name: "redis-vs-postgres",
strategy: GenAgentEnsemble.Strategies.Debate,
opts: [
agents: [
{"pro-redis", GenAgentEnsemble.Agents.Simple,
backend: GenAgent.Backends.Anthropic,
system: "You argue for Redis as the primary store. Be concrete, cite tradeoffs, respond directly to the other side's points."},
{"pro-postgres", GenAgentEnsemble.Agents.Anthropic,
backend: GenAgent.Backends.Anthropic,
system: "You argue for Postgres as the primary store. Same rules."}
],
rounds: 6,
converge: &String.contains?(&1, "AGREED"),
reply: :transcript
]
]
]
```
## Options
- `:agents` (required) -- exactly two `{name, module, opts}` specs.
Distinct names.
- `:first` (optional) -- which agent speaks first. Defaults to the
first entry in `:agents`.
- `:rounds` (optional) -- hard cap on total agent responses across
both sides. Defaults to 6 (three exchanges per side). Hitting the
cap ends the debate with whatever transcript has accumulated.
- `:converge` (optional) -- `(text -> boolean)`. Checked starting on
turn 2. Returning `true` ends the debate immediately. Default:
`fn _ -> false end` (round cap is the only limit).
- `:reply` (optional) -- how the final response is shaped:
* `:transcript` (default) -- all turns joined as
`"#{agent}:\\n#{text}"` separated by blank lines.
* `:last` -- just the last agent's response text.
* `{:synthesize, fun}` -- call `fun.(transcript)` where
transcript is `[{agent_name, text}, ...]` in order.
## Canonical workflow
### Sync run
```elixir
iex> E.ask!("redis-vs-postgres", "Should a new event-sourced audit log land in Redis Streams or a Postgres partitioned table?")
"pro-redis:
...
pro-postgres:
...
pro-redis:
...
pro-postgres:
AGREED on one point: ..."
```
One call, up to `rounds` backend calls in sequence.
### Async run with a meatier topic
```elixir
iex> {:ok, tok} = E.tell("redis-vs-postgres",
...> "Postgres 17 has logical replication with row filtering. Does that change the case for a dedicated CDC pipeline (Debezium/Kafka)?")
iex> E.await("redis-vs-postgres", tok, 120_000) |> E.puts()
```
### Inspect mid-debate
```elixir
iex> E.status("redis-vs-postgres")
{:ok, %{
session: "redis-vs-postgres",
strategy: GenAgentEnsemble.Strategies.Debate,
phase: %{awaiting: "pro-postgres", turns: 3, transcript_len: 3},
agents: ["pro-redis", "pro-postgres"],
first: "pro-redis",
rounds: 6,
queued: 0,
in_flight: 1,
pending_tokens: ["tok-7"]
}}
```
### Custom synthesis
```elixir
iex> E.start_link(
...> name: "code-review",
...> strategy: GenAgentEnsemble.Strategies.Debate,
...> opts: [
...> agents: [
...> {"optimist", GenAgentEnsemble.Agents.Simple,
...> backend: GenAgent.Backends.Anthropic,
...> system: "You find what's good about this code."},
...> {"skeptic", GenAgentEnsemble.Agents.Simple,
...> backend: GenAgent.Backends.Anthropic,
...> system: "You find what's concerning about this code."}
...> ],
...> rounds: 4,
...> reply: {:synthesize, fn transcript ->
...> concerns =
...> transcript
...> |> Enum.filter(fn {who, _} -> who == "skeptic" end)
...> |> Enum.map_join("\n", fn {_, text} -> "- #{text}" end)
...> "Concerns:\n" <> concerns
...> end}
...> ]
...> )
```
## Variations
- **Heterogeneous backends.** The point of Debate is that different
models have different blind spots; wire `pro-redis` to Claude and
`pro-postgres` to OpenAI, or run Haiku against Sonnet for cheap
cross-checking.
- **Asymmetric roles.** Nothing says both agents must argue. One
side can be "propose a design," the other "find five things that
will break in production" -- a code-review flow in disguise.
- **Structured verdict detection.** If you ask each agent to end its
response with `VERDICT: (AGREE|DISAGREE)`, the `:converge` function
can be `&String.contains?(&1, "VERDICT: AGREE")` for cheap
mechanical convergence detection.
## Gotchas
- **One debate at a time per ensemble.** Additional `tell`/`ask`
calls queue and run after the current debate finishes. If you
want parallelism, start multiple Debate ensembles with different
names.
- **Agents accumulate conversation state across runs.** A second
`ask!` on the same ensemble continues with both agents'
conversation history intact. If you want a fresh debate, restart
the ensemble (`E.stop("name")` then `start_link` again).
- **Round cap is turns, not exchanges.** `rounds: 6` means 6 agent
responses total, which is 3 exchanges per side. Set it to 4 if
you want a shorter back-and-forth.
- **Convergence skips the first turn.** The opener can't converge
against nothing. If the opener's text looks like agreement, the
`:converge` check still waits until turn 2.
- **Either agent dying halts the session.** Debate needs both
sides; there's no "recover with one speaker" mode.
- **`:reply :transcript` can get large.** Six turns at 1000 tokens
each is 6K tokens in the final response. Use `:last` or
`{:synthesize, ...}` if the full transcript isn't useful
downstream.