Observed since 20 Oct, 21:44 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Config-driven multi-agent debate council — add a reliability layer to any LLM pipeline
A reliability layer for LLM pipelines. Multiple agents with distinct personas debate a topic across iterative rounds; a judge synthesizes a final verdict.
Drop it into any agent to pressure-test a decision before committing to it.
Inspired by mshumer/llmcouncil and the Mixture of Agents research.
pip install agent-council
Requires Python ≥ 3.11.
This is the primary interface — use it inside your own agents and pipelines.
from agent_council import CouncilOrchestrator, MemberConfig, JudgeConfig
orchestrator = CouncilOrchestrator(
members=[
MemberConfig(
id="analyst",
name="The Analyst",
provider="anthropic",
model="claude-sonnet-4-6",
persona="Rigorous analytical thinker. Evidence-based, structured.",
),
MemberConfig(
id="skeptic",
name="The Skeptic",
provider="openai",
model="gpt-4o",
persona="Challenge every assumption. Surface hidden risks.",
),
],
judge=JudgeConfig(provider="anthropic", model="claude-opus-4-6"),
rounds=3,
early_exit_threshold=0.85,
)
session, verdict = await orchestrator.run("Should we adopt microservices?")
print(verdict.verdict) # synthesized conclusion
print(verdict.consensus_level) # ConsensusLevel.STRONG / MODERATE / WEAK / NONE
print(verdict.consensus_score) # float 0–1
print(verdict.key_agreements) # list[str]
print(verdict.dissenting_views) # list[str]
API keys are read from environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY) by default.
Callbacks fire as each member finishes — no waiting for a full round to complete. Both sync and async callbacks are supported.
def on_member(resp):
print(f"[{resp.member_name}] {resp.stance} ({resp.confidence:.0%} confident)")
async def on_round(round_):
await db.save_round(round_) # async is fine too
session, verdict = await orchestrator.run(
"Should we rewrite in Rust?",
on_member_response=on_member,
on_round_complete=on_round,
)
Override temperature, token limits, or use a custom Ollama endpoint:
from agent_council import (
CouncilOrchestrator, MemberConfig, JudgeConfig,
ProvidersConfig, AnthropicProviderConfig, OllamaProviderConfig,
)
orchestrator = CouncilOrchestrator(
members=[...],
judge=JudgeConfig(provider="anthropic", model="claude-opus-4-6"),
provider_configs=ProvidersConfig(
anthropic=AnthropicProviderConfig(temperature=0.5, max_tokens=1024),
ollama=OllamaProviderConfig(base_url="http://my-ollama:11434"),
),
)
@dataclass FinalVerdict:
topic: str
verdict: str
consensus_level: ConsensusLevel # STRONG / MODERATE / WEAK / NONE
consensus_score: float # 0–1
key_agreements: list[str]
dissenting_views: list[str]
rounds_completed: int
early_exit: bool
total_duration_seconds: float
@dataclass CouncilSession:
session_id: str
topic: str
started_at: datetime
rounds: list[DebateRound] # full transcript
verdict: FinalVerdict | None
@dataclass MemberResponse:
member_id, member_name, round_number: str / int
content: str # full response text
stance: str # one-line position summary
confidence: float # 0–1
changed_position: bool
LLMs can be confident and wrong. A single-shot response hides uncertainty and failure modes (hallucinations, missed trade‑offs, overfitting to prompt). Agent Council creates informed disagreement and then reconciles it:
What improves:
Programmatic snippet (minimal):
session, verdict = await CouncilOrchestrator(
members=[
MemberConfig(id="analyst", name="Analyst", provider="openrouter", model="anthropic/claude-3.5-sonnet"),
MemberConfig(id="skeptic", name="Skeptic", provider="openrouter", model="openai/gpt-4o"),
],
judge=JudgeConfig(provider="openrouter", model="anthropic/claude-3.5-sonnet"),
).run("Should we adopt microservices?")
print(verdict.verdict)
CLI:
export OPENROUTER_API_KEY=sk-or-...
council review "Remote work vs office?" -c config/council.yaml
HTTP (server):
pip install "agent-council[server]"
council serve
# POST http://127.0.0.1:8000/review {"topic":"Should we rewrite in Rust?"}
Round 1 — all members respond independently to the topic
Round 2..N — each member reads all peers' responses and may revise
→ early exit if consensus score ≥ threshold
Judge — reads full transcript, synthesizes final verdict
Consensus score = avg_confidence × (1 − changed_fraction)
Rewards both high confidence and stability across rounds.
Implement BaseModelAdapter (one method) and register it in the factory:
# agent_council/adapters/my_provider.py
from agent_council.adapters.base import BaseModelAdapter
class MyProviderAdapter(BaseModelAdapter):
async def complete(self, system: str, user: str) -> str:
# call your model here
...
# agent_council/adapters/__init__.py — add to build_adapter()
case "myprovider":
return MyProviderAdapter(member_cfg, provider_cfg)
For teams who prefer YAML over code:
# config/council.yaml
council:
debate_rounds: 3
early_exit_threshold: 0.85
members:
- id: "analyst"
name: "The Analyst"
provider: "anthropic"
model: "claude-sonnet-4-6"
persona: "Rigorous analytical thinker."
- id: "skeptic"
name: "The Skeptic"
provider: "openai"
model: "gpt-4o"
persona: "Challenge every assumption."
judge:
provider: "anthropic"
model: "claude-opus-4-6"
providers:
# Optional: use OpenRouter with OpenAI-compatible models
openrouter:
api_key_env: "OPENROUTER_API_KEY"
base_url: "https://openrouter.ai/api/v1"
max_tokens: 2048
temperature: 0.7
orchestrator = CouncilOrchestrator.from_config_file("config/council.yaml")
# Install with CLI support (included by default)
pip install agent-council
export ANTHROPIC_API_KEY=sk-ant-...
council review "Is Python the best language for data science?"
council review "Should we rewrite in Rust?" --config config/council.yaml
council review "Remote work vs office?" --no-rounds
pip install "agent-council[server]"
council serve # http://127.0.0.1:8000
| Method | Path | Description |
|---|---|---|
GET | /health | Liveness check |
POST | /review | Run debate, return full JSON result |
POST | /review/stream | Run debate, stream events via SSE |
Interactive docs at http://localhost:8000/docs.
SSE event stream (POST /review/stream):
data: {"event": "member_response", "data": {...}}
data: {"event": "round_complete", "data": {"round_number": 1, "consensus_score": 0.62}}
data: {"event": "verdict", "data": {...}}
Enable JSON traces to inspect every response, round, and the final verdict. Two ways:
from agent_council.tracing import TraceRecorder
rec = TraceRecorder()
session, verdict = await orchestrator.run("Your topic", trace=rec)
path = rec.save("traces/")
print("Trace saved to", path)
from agent_council.tracing import TraceRecorder
rec = TraceRecorder.from_session(session, verdict)
rec.save("traces/")
Each trace is a single JSON file containing per-member responses, round summaries, and the final verdict.
Control spend with a simple per‑call budget guard. Set these env vars:
COUNCIL_COST_PER_CALL_USD — estimated cost charged per model call.COUNCIL_MAX_BUDGET_USD — cap for the whole run; if exceeded and COUNCIL_BUDGET_HARD_STOP is true (default), the run stops.COUNCIL_BUDGET_HARD_STOP — set 0/false for soft cap.This wraps each provider adapter and enforces the budget before calling the model. For precise accounting, plug in your own adapter with exact token‑based pricing.
agent_council/
├── __init__.py # public API
├── orchestrator.py # CouncilOrchestrator — primary entry point
├── member.py # prompt construction + JSON parsing
├── debate.py # round loop + consensus scoring
├── judge.py # synthesis + FinalVerdict
├── config.py # Pydantic config schema
├── types.py # result dataclasses
├── server.py # FastAPI app (optional)
└── adapters/
├── base.py
├── anthropic_adapter.py
├── openai_adapter.py
└── ollama_adapter.py
MIT
pypistats · 56 observations shown. Missing days are unknown. Source category: overall: without mirrors.
| Date / source period | Value | Observed at |
|---|---|---|
| 2026-10-20 – 2026-10-20 | 6 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-19 – 2026-10-19 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-18 – 2026-10-18 | 4 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-17 – 2026-10-17 | 9 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-16 – 2026-10-16 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-15 – 2026-10-15 | 3 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-14 – 2026-10-14 | 5 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-13 – 2026-10-13 | 3 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-12 – 2026-10-12 | 4 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-11 – 2026-10-11 | 5 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-10 – 2026-10-10 | 6 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-09 – 2026-10-09 | 4 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-08 – 2026-10-08 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-06 – 2026-10-06 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-05 – 2026-10-05 | 4 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-04 – 2026-10-04 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-02 – 2026-10-02 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-10-01 – 2026-10-01 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-30 – 2026-09-30 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-29 – 2026-09-29 | 3 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-27 – 2026-09-27 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-26 – 2026-09-26 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-23 – 2026-09-23 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-22 – 2026-09-22 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-20 – 2026-09-20 | 7 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-18 – 2026-09-18 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-16 – 2026-09-16 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-14 – 2026-09-14 | 6 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-12 – 2026-09-12 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-09 – 2026-09-09 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-07 – 2026-09-07 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-04 – 2026-09-04 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-02 – 2026-09-02 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-09-01 – 2026-09-01 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-31 – 2026-08-31 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-28 – 2026-08-28 | 1 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-24 – 2026-08-24 | 8 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-23 – 2026-08-23 | 3 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-22 – 2026-08-22 | 6 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-21 – 2026-08-21 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-18 – 2026-08-18 | 3 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-17 – 2026-08-17 | 8 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-16 – 2026-08-16 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-14 – 2026-08-14 | 6 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-12 – 2026-08-12 | 5 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-10 – 2026-08-10 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-09 – 2026-08-09 | 3 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-06 – 2026-08-06 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-04 – 2026-08-04 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-08-02 – 2026-08-02 | 3 | 2026-10-21T19:53:20.2138527Z |
| 2026-07-31 – 2026-07-31 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-07-30 – 2026-07-30 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-07-27 – 2026-07-27 | 5 | 2026-10-21T19:53:20.2138527Z |
| 2026-07-25 – 2026-07-25 | 2 | 2026-10-21T19:53:20.2138527Z |
| 2026-07-24 – 2026-07-24 | 3 | 2026-10-21T19:53:20.2138527Z |
| 2026-07-23 – 2026-07-23 | 4 | 2026-10-21T19:53:20.2138527Z |
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.2.1 | 27 Feb 2026 | 9 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 20 Oct, 21:44 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:2c34cb6c2c4f8b7486d39f1b498c10b1630979500ebf9e7254b13498b31f2d84