OrchestratorAgent — Multi-Party Conferencing (confer)¶
Feature: FEAT-223
Spec: sdd/specs/orchestratoragent-multiparty.spec.md
What it is¶
OrchestratorAgent.confer() is a deterministic alternative to the
LLM-driven ask() ReAct loop. Instead of letting an LLM pick which
specialists to call, confer() asks every selected specialist the same
question, cross-pollinates their answers anonymously, and lets each agent
vote with a confidence score. The consensus is resolved by a
confidence-weighted tally — there is no extra LLM synthesis step.
confer() is purely additive: it does not change the behavior of ask().
How it works¶
- Round-0 (independent) — the question is broadcast in parallel
(
asyncio.gather) to every selected specialist; each returns one answer. - Round-k (cross-pollinate + vote) — every agent sees an anonymous
block (
Answer A,Answer B, …; authors are never named, to avoid authority bias) and votes viaask(structured_output=PeerVote). A vote carries thechosen_label(which answer it keeps — possibly its own), arevised_answer, aconfidence(0–100), and arationale. - Aggregation —
scores[label] += confidence; the label with the highest summed confidence wins (ties break deterministically to the lowest label). The winning agent'srevised_answerbecomes the new candidate. - Convergence — rounds repeat until the winning agent is stable across two
consecutive rounds (
until_convergence=True) ormax_roundsis reached.
Every round is persisted to the orchestrator's ExecutionMemory for audit.
Usage¶
from parrot.bots.flows.agents import OrchestratorAgent
orchestrator = OrchestratorAgent(name="panel")
await orchestrator.add_agent_by_name("data_specialist")
await orchestrator.add_agent_by_name("policy_specialist")
await orchestrator.add_agent_by_name("risk_specialist")
msg = await orchestrator.confer(
"Should we approve this transaction?",
agents=None, # None = all specialists; or pass a subset of names
max_rounds=3, # hard cap on vote rounds
until_convergence=True, # stop early when the winner stabilizes
)
print(msg.content) # final answer (winner's revised answer)
result = msg.structured_output # ConferenceResult
print(result.winner_agent, result.confidence_score, result.converged)
for rnd in result.rounds: # full audit trail
print(rnd.round_index, list(rnd.votes.keys()))
The returned AIMessage has:
content— the final answer (the winning agent'srevised_answer),structured_output— aConferenceResult(is_structured=True) with the winner, aggregated confidence, everyConferenceRound, the vote breakdown, and aconvergedflag.
Cost & latency¶
confer() makes N × (1 + rounds) LLM calls, where N is the number of
specialists on the panel and rounds is the number of vote rounds actually
run (≤ max_rounds):
- 1 broadcast call per specialist (Round-0), plus
- 1 vote call per specialist per round.
For example, 3 specialists over 3 rounds ≈ 3 × (1 + 3) = 12 calls. Use the
agents argument to narrow the panel, and max_rounds / until_convergence
to bound the number of rounds, when cost or latency matters.
Graceful degradation¶
If a specialist cannot emit a structured PeerVote (e.g. a provider that does
not support structured output), its vote is normalized from text: the agent
keeps its own answer at a neutral confidence (50) and a warning is logged. The
round never fails because one specialist could not produce structured output.