Multi-agent debate framework with structured consensus scoring and dissent tracking
Project description
forumkit
The only Python multi-agent framework with a structured debate protocol: fan-out → challenge → rebuttal → consensus scoring.
from forumkit import ForumModeratorBase, ResearcherBase, AgentConfig, AgentRole
class MyAnalyst(ResearcherBase):
async def analyze(self, data):
# Independent analysis
return {"position": "...", "confidence": 0.8}
async def respond_to_challenge(self, challenge, context):
# Defend your position
return "Here's why I disagree..."
class MyForum(ForumModeratorBase):
def _synthesize(self, analyses, consensus, data):
# Domain-specific consensus logic
if consensus.agreement_pct >= 66:
return "accepted", {"action": "approve"}
return "conditional", {"action": "review"}
# Run a debate
forum = MyForum(config, researchers=[analyst1, analyst2, analyst3])
result = await forum.run_debate_round({"question": "Is X good?"})
print(f"Consensus: {result.consensus.agreement_pct}% agree")
print(f"Dissent: {result.consensus.strongest_dissent}")
print(f"Anomaly?: {result.consensus.unanimous_anomaly}") # Detects groupthink
Why forumkit?
The problem: CrewAI, PraisonAI, AutoGen, and others assume agents reach consensus by averaging opinions. In reality, minority views often reveal blind spots.
The solution: forumkit enforces dissent:
- Every agent analyzes independently
- Agents actively challenge each other's positions
- Minority opinions are surfaced, not discarded
- Suspicious unanimity is flagged (
unanimous_anomalydetection) - Outcomes are tied to consensus strength, not just majority vote
The result: Better decisions with traceable dissent.
Quick Start
Install
pip install forumkit
5-minute example
import asyncio
from forumkit import ForumModeratorBase, ResearcherBase, AgentConfig, AgentRole, PersonaContext
class Analyst(ResearcherBase):
async def analyze(self, data):
# Your LLM call here
return {
"persona_id": self.persona.persona_id,
"position": f"Analysis from {self.persona.name}",
"confidence": 0.75
}
async def respond_to_challenge(self, challenge, context):
return "I maintain my position because..."
class DebateBoard(ForumModeratorBase):
def _synthesize(self, analyses, consensus, data):
synthesis = f"Consensus at {consensus.agreement_pct:.0f}%"
outcome = "approved" if consensus.agreement_pct >= 50 else "rejected"
return synthesis, outcome, None
async def main():
config = AgentConfig(
agent_id="board-1", team_id=1, role=AgentRole.HEAD,
model_alias="gpt-4"
)
# Create 3 analysts with different personas
analysts = [
Analyst(AgentConfig(...), PersonaContext(
persona_id="p1", name="Optimist", system_prompt="..."
)),
# ... more analysts
]
board = DebateBoard(config, analysts)
result = await board.run_debate_round({"question": "Approve proposal X?"})
print(f"Outcome: {result.outcome}")
print(f"Agreement: {result.consensus.agreement_pct}%")
print(f"Dissent: {result.consensus.dissent_count} voices disagree")
print(f"Anomaly: {result.consensus.unanimous_anomaly}")
asyncio.run(main())
Concepts
The Forum Protocol
- Fan-out — Every analyst independently evaluates the question
- Challenge — Analysts see each other's views and attack weak points
- Rebuttal — Each analyst responds to challenges
- Score — Consensus metrics computed: agreement %, confidence, dissent count
- Synthesize — Domain-specific outcome logic applied
Key Types
- BaseAgent — Foundation for all agents. Role-gated:
HEADcan delegate to children,JUNIORcannot call LLM - ResearcherBase — Expert with persona, implements
analyze()andrespond_to_challenge() - ForumModeratorBase — Orchestrates the debate, computes consensus, applies domain logic
- ConsensusScore — Result includes
agreement_pct,dissent_count,unanimous_anomaly(detects suspicious unanimity)
Memory Backends
forumkit supports three memory types:
| Backend | Use | Backend |
|---|---|---|
| Working | In-session cache | Redis (TTL) |
| Episodic | Long-term experience | PostgreSQL |
| Semantic | Vector embeddings | Qdrant |
All are optional. Use the facade:
from forumkit.memory import MemoryManager
memory = MemoryManager(
redis_url="redis://localhost:6379",
pg_url="postgresql://localhost/mydb",
qdrant_url="http://localhost:6333"
)
await memory.initialize()
# Use: memory.working.set(), memory.episodic.store(), memory.semantic.search()
Docs
- API Reference — All public types and methods
- Concepts — Architecture deep-dive
- Examples — Working code samples
Testing
pytest tests/unit/ -v
pytest tests/integration/ -v # Requires Docker (Redis, PostgreSQL)
Security
forumkit includes prompt injection defense:
from forumkit.security import sanitize_for_prompt
clean = sanitize_for_prompt(user_input)
# Applies: NFKC normalize, strip BiDi/control chars, remove injection patterns
Status
v0.1.0 — Alpha. API is stable; docs are being refined. Not yet open source (private beta).
License
MIT
Contact
Issues: GitHub Issues Questions: vinitpai@anthropic.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file forumkit-0.1.0.tar.gz.
File metadata
- Download URL: forumkit-0.1.0.tar.gz
- Upload date:
- Size: 35.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9107a1d9dab1ee9a2bc0e220d9cd0eedeac81a1f812e4fb3c4827fb5102e6ad0
|
|
| MD5 |
ff1173c63d9e9b41b928952c76843fe0
|
|
| BLAKE2b-256 |
0cd8a22041a78757612b35efb3264efe13ddcd6063fdd36eb9fdf0ba8895a7c4
|
File details
Details for the file forumkit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: forumkit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d1f08f782d80d7abb237869bf2b8c10250d34d8b3b3b4b8b18752f89565d3262
|
|
| MD5 |
e4c766746be4909689649bf7a766dfee
|
|
| BLAKE2b-256 |
59915584213ae0b1d9ad61c042f653568bf93b67114f615e66086aba675b5ce0
|