forumkit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- forumkit-0.1.0/.github/workflows/ci.yml +39 -0
- forumkit-0.1.0/.github/workflows/publish.yml +19 -0
- forumkit-0.1.0/.gitignore +54 -0
- forumkit-0.1.0/CHANGELOG.md +26 -0
- forumkit-0.1.0/CONTRIBUTING.md +77 -0
- forumkit-0.1.0/LICENSE +21 -0
- forumkit-0.1.0/PKG-INFO +228 -0
- forumkit-0.1.0/README.md +180 -0
- forumkit-0.1.0/SECURITY.md +143 -0
- forumkit-0.1.0/docs/API.md +319 -0
- forumkit-0.1.0/docs/ARCHITECTURE.md +149 -0
- forumkit-0.1.0/docs/CONCEPTS.md +119 -0
- forumkit-0.1.0/docs/examples/simple_debate.py +108 -0
- forumkit-0.1.0/forumkit/__init__.py +53 -0
- forumkit-0.1.0/forumkit/base.py +312 -0
- forumkit-0.1.0/forumkit/forum_base.py +346 -0
- forumkit-0.1.0/forumkit/memory/__init__.py +8 -0
- forumkit-0.1.0/forumkit/memory/episodic.py +121 -0
- forumkit-0.1.0/forumkit/memory/manager.py +42 -0
- forumkit-0.1.0/forumkit/memory/semantic.py +125 -0
- forumkit-0.1.0/forumkit/memory/working.py +99 -0
- forumkit-0.1.0/forumkit/orchestration/__init__.py +6 -0
- forumkit-0.1.0/forumkit/orchestration/decision.py +77 -0
- forumkit-0.1.0/forumkit/orchestration/execution.py +79 -0
- forumkit-0.1.0/forumkit/registry.py +80 -0
- forumkit-0.1.0/forumkit/security/__init__.py +5 -0
- forumkit-0.1.0/forumkit/security/prompt_sanitizer.py +66 -0
- forumkit-0.1.0/forumkit/telemetry.py +75 -0
- forumkit-0.1.0/pyproject.toml +86 -0
- forumkit-0.1.0/tests/integration/test_debate_round.py +80 -0
- forumkit-0.1.0/tests/unit/test_base.py +265 -0
- forumkit-0.1.0/tests/unit/test_forum.py +256 -0
- forumkit-0.1.0/tests/unit/test_memory.py +205 -0
- forumkit-0.1.0/tests/unit/test_orchestration.py +147 -0
- forumkit-0.1.0/tests/unit/test_security.py +252 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.11"
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: |
|
|
22
|
+
pip install -e ".[dev]"
|
|
23
|
+
|
|
24
|
+
- name: Type check
|
|
25
|
+
run: mypy forumkit/ --strict
|
|
26
|
+
|
|
27
|
+
- name: Lint
|
|
28
|
+
run: ruff check forumkit/ tests/
|
|
29
|
+
|
|
30
|
+
- name: Run unit tests
|
|
31
|
+
run: pytest tests/unit/ -v --tb=short
|
|
32
|
+
|
|
33
|
+
- name: Run integration tests
|
|
34
|
+
run: pytest tests/integration/ -v --tb=short -m integration --maxfail=1
|
|
35
|
+
continue-on-error: true
|
|
36
|
+
env:
|
|
37
|
+
REDIS_URL: redis://localhost:6379
|
|
38
|
+
PG_DSN: postgresql://postgres:postgres@localhost:5432/test
|
|
39
|
+
QDRANT_URL: http://localhost:6333
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
deploy:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: '3.11'
|
|
15
|
+
- run: pip install build
|
|
16
|
+
- run: python -m build
|
|
17
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
18
|
+
with:
|
|
19
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Testing
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.coverage
|
|
26
|
+
htmlcov/
|
|
27
|
+
|
|
28
|
+
# Type checking
|
|
29
|
+
.mypy_cache/
|
|
30
|
+
.dmypy.json
|
|
31
|
+
dmypy.json
|
|
32
|
+
|
|
33
|
+
# IDEs
|
|
34
|
+
.vscode/
|
|
35
|
+
.idea/
|
|
36
|
+
*.swp
|
|
37
|
+
*.swo
|
|
38
|
+
*~
|
|
39
|
+
|
|
40
|
+
# Environment
|
|
41
|
+
.env
|
|
42
|
+
.venv
|
|
43
|
+
env/
|
|
44
|
+
venv/
|
|
45
|
+
|
|
46
|
+
# OS
|
|
47
|
+
.DS_Store
|
|
48
|
+
.AppleDouble
|
|
49
|
+
.LSOverride
|
|
50
|
+
*.log
|
|
51
|
+
|
|
52
|
+
# Project-specific
|
|
53
|
+
.venv/
|
|
54
|
+
dist/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-04-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Structured debate protocol: fan-out → challenge → rebuttal → consensus scoring → synthesize
|
|
11
|
+
- Consensus scoring with dissent tracking and unanimity anomaly detection
|
|
12
|
+
- Role-gated agent hierarchy: HEAD, LEAD, SENIOR, JUNIOR roles with LLM-calling restrictions
|
|
13
|
+
- Memory backends: Redis (working memory), PostgreSQL (episodic), Qdrant (semantic)
|
|
14
|
+
- Async orchestration with LiteLLM tool-calling wrapper
|
|
15
|
+
- Prompt injection defense: NFKC normalization + BiDi character stripping + pattern redaction
|
|
16
|
+
- 59 unit tests with full mypy --strict compliance
|
|
17
|
+
- Comprehensive API documentation and examples
|
|
18
|
+
- GitHub Actions CI/CD workflow (pytest + mypy + ruff)
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
- README with 5-minute quickstart
|
|
22
|
+
- API reference documenting all public types
|
|
23
|
+
- Concepts guide explaining the debate protocol
|
|
24
|
+
- Working example: simple_debate.py
|
|
25
|
+
|
|
26
|
+
[0.1.0]: https://github.com/vinitpai/forumkit/releases/tag/v0.1.0
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Contributing to forumkit
|
|
2
|
+
|
|
3
|
+
We welcome issues, discussions, and pull requests. This project is in alpha — your feedback shapes the direction.
|
|
4
|
+
|
|
5
|
+
## Code of Conduct
|
|
6
|
+
|
|
7
|
+
Be respectful, constructive, and assume good intent.
|
|
8
|
+
|
|
9
|
+
## Reporting Issues
|
|
10
|
+
|
|
11
|
+
- Use GitHub Issues for bugs, feature requests, documentation gaps
|
|
12
|
+
- Include Python version, OS, relevant error trace
|
|
13
|
+
- For security issues: email vinitpai@anthropic.com instead
|
|
14
|
+
|
|
15
|
+
## Pull Requests
|
|
16
|
+
|
|
17
|
+
1. Fork the repo
|
|
18
|
+
2. Create a branch: `git checkout -b feature/my-feature`
|
|
19
|
+
3. Write code following Google style guide (see [CLAUDE.md](CLAUDE.md))
|
|
20
|
+
4. Add/update tests (pytest required)
|
|
21
|
+
5. Run `ruff check --fix .` and `mypy . --strict`
|
|
22
|
+
6. Commit with clear message (see below)
|
|
23
|
+
7. Push and open PR with description of why (not just what)
|
|
24
|
+
|
|
25
|
+
### Commit Message Format
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
<type>: <subject>
|
|
29
|
+
|
|
30
|
+
<body (optional, explain why)>
|
|
31
|
+
|
|
32
|
+
<footer (optional)>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
```
|
|
39
|
+
feat: add timeout parameter to execute()
|
|
40
|
+
|
|
41
|
+
Previously, execute() had no way to enforce time limits. This adds
|
|
42
|
+
a configurable timeout_s parameter with exponential backoff on retry.
|
|
43
|
+
|
|
44
|
+
Fixes #123
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Testing
|
|
48
|
+
|
|
49
|
+
All new code requires tests:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Run tests
|
|
53
|
+
pytest tests/unit/ -v
|
|
54
|
+
|
|
55
|
+
# Run with coverage
|
|
56
|
+
pytest --cov=forumkit tests/unit/
|
|
57
|
+
|
|
58
|
+
# Type check
|
|
59
|
+
mypy forumkit/ --strict
|
|
60
|
+
|
|
61
|
+
# Lint
|
|
62
|
+
ruff check forumkit/
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Before Open Source
|
|
66
|
+
|
|
67
|
+
This project is currently private beta. Before we open source (v1.0), we need:
|
|
68
|
+
|
|
69
|
+
- [ ] Minimum 80% test coverage
|
|
70
|
+
- [ ] Zero mypy strict errors
|
|
71
|
+
- [ ] API documentation complete
|
|
72
|
+
- [ ] 2-3 working examples
|
|
73
|
+
- [ ] Security audit
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
Questions? Open a discussion or email the maintainer.
|
forumkit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vinit Pai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
forumkit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: forumkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Multi-agent debate framework with structured consensus scoring and dissent tracking
|
|
5
|
+
Project-URL: Homepage, https://github.com/vinitpai/forumkit
|
|
6
|
+
Project-URL: Repository, https://github.com/vinitpai/forumkit
|
|
7
|
+
Project-URL: Issues, https://github.com/vinitpai/forumkit/issues
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai-agents,consensus,debate,llm,multi-agent
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: aioredis>=2.0
|
|
19
|
+
Requires-Dist: asyncpg>=0.29
|
|
20
|
+
Requires-Dist: httpx>=0.27
|
|
21
|
+
Requires-Dist: litellm>=1.40
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: structlog>=24.0
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Requires-Dist: aioredis>=2.0; extra == 'all'
|
|
26
|
+
Requires-Dist: asyncpg>=0.29; extra == 'all'
|
|
27
|
+
Requires-Dist: httpx>=0.27; extra == 'all'
|
|
28
|
+
Requires-Dist: kafka-python>=2.0; extra == 'all'
|
|
29
|
+
Requires-Dist: minio>=7.0; extra == 'all'
|
|
30
|
+
Requires-Dist: neo4j>=5.0; extra == 'all'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: coverage>=7.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: mypy>=1.9; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-mock>=3.12; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
38
|
+
Provides-Extra: jepa
|
|
39
|
+
Requires-Dist: minio>=7.0; extra == 'jepa'
|
|
40
|
+
Requires-Dist: neo4j>=5.0; extra == 'jepa'
|
|
41
|
+
Provides-Extra: kafka
|
|
42
|
+
Requires-Dist: kafka-python>=2.0; extra == 'kafka'
|
|
43
|
+
Provides-Extra: memory
|
|
44
|
+
Requires-Dist: aioredis>=2.0; extra == 'memory'
|
|
45
|
+
Requires-Dist: asyncpg>=0.29; extra == 'memory'
|
|
46
|
+
Requires-Dist: httpx>=0.27; extra == 'memory'
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
|
|
49
|
+
# forumkit
|
|
50
|
+
|
|
51
|
+
The only Python multi-agent framework with a **structured debate protocol**: fan-out → challenge → rebuttal → consensus scoring.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from forumkit import ForumModeratorBase, ResearcherBase, AgentConfig, AgentRole
|
|
55
|
+
|
|
56
|
+
class MyAnalyst(ResearcherBase):
|
|
57
|
+
async def analyze(self, data):
|
|
58
|
+
# Independent analysis
|
|
59
|
+
return {"position": "...", "confidence": 0.8}
|
|
60
|
+
|
|
61
|
+
async def respond_to_challenge(self, challenge, context):
|
|
62
|
+
# Defend your position
|
|
63
|
+
return "Here's why I disagree..."
|
|
64
|
+
|
|
65
|
+
class MyForum(ForumModeratorBase):
|
|
66
|
+
def _synthesize(self, analyses, consensus, data):
|
|
67
|
+
# Domain-specific consensus logic
|
|
68
|
+
if consensus.agreement_pct >= 66:
|
|
69
|
+
return "accepted", {"action": "approve"}
|
|
70
|
+
return "conditional", {"action": "review"}
|
|
71
|
+
|
|
72
|
+
# Run a debate
|
|
73
|
+
forum = MyForum(config, researchers=[analyst1, analyst2, analyst3])
|
|
74
|
+
result = await forum.run_debate_round({"question": "Is X good?"})
|
|
75
|
+
|
|
76
|
+
print(f"Consensus: {result.consensus.agreement_pct}% agree")
|
|
77
|
+
print(f"Dissent: {result.consensus.strongest_dissent}")
|
|
78
|
+
print(f"Anomaly?: {result.consensus.unanimous_anomaly}") # Detects groupthink
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Why forumkit?
|
|
82
|
+
|
|
83
|
+
**The problem:** CrewAI, PraisonAI, AutoGen, and others assume agents reach consensus by averaging opinions. In reality, minority views often reveal blind spots.
|
|
84
|
+
|
|
85
|
+
**The solution:** forumkit enforces dissent:
|
|
86
|
+
- Every agent analyzes independently
|
|
87
|
+
- Agents actively challenge each other's positions
|
|
88
|
+
- Minority opinions are surfaced, not discarded
|
|
89
|
+
- Suspicious unanimity is flagged (`unanimous_anomaly` detection)
|
|
90
|
+
- Outcomes are tied to consensus strength, not just majority vote
|
|
91
|
+
|
|
92
|
+
**The result:** Better decisions with traceable dissent.
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
|
|
96
|
+
### Install
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install forumkit
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 5-minute example
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import asyncio
|
|
106
|
+
from forumkit import ForumModeratorBase, ResearcherBase, AgentConfig, AgentRole, PersonaContext
|
|
107
|
+
|
|
108
|
+
class Analyst(ResearcherBase):
|
|
109
|
+
async def analyze(self, data):
|
|
110
|
+
# Your LLM call here
|
|
111
|
+
return {
|
|
112
|
+
"persona_id": self.persona.persona_id,
|
|
113
|
+
"position": f"Analysis from {self.persona.name}",
|
|
114
|
+
"confidence": 0.75
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async def respond_to_challenge(self, challenge, context):
|
|
118
|
+
return "I maintain my position because..."
|
|
119
|
+
|
|
120
|
+
class DebateBoard(ForumModeratorBase):
|
|
121
|
+
def _synthesize(self, analyses, consensus, data):
|
|
122
|
+
synthesis = f"Consensus at {consensus.agreement_pct:.0f}%"
|
|
123
|
+
outcome = "approved" if consensus.agreement_pct >= 50 else "rejected"
|
|
124
|
+
return synthesis, outcome, None
|
|
125
|
+
|
|
126
|
+
async def main():
|
|
127
|
+
config = AgentConfig(
|
|
128
|
+
agent_id="board-1", team_id=1, role=AgentRole.HEAD,
|
|
129
|
+
model_alias="gpt-4"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Create 3 analysts with different personas
|
|
133
|
+
analysts = [
|
|
134
|
+
Analyst(AgentConfig(...), PersonaContext(
|
|
135
|
+
persona_id="p1", name="Optimist", system_prompt="..."
|
|
136
|
+
)),
|
|
137
|
+
# ... more analysts
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
board = DebateBoard(config, analysts)
|
|
141
|
+
result = await board.run_debate_round({"question": "Approve proposal X?"})
|
|
142
|
+
|
|
143
|
+
print(f"Outcome: {result.outcome}")
|
|
144
|
+
print(f"Agreement: {result.consensus.agreement_pct}%")
|
|
145
|
+
print(f"Dissent: {result.consensus.dissent_count} voices disagree")
|
|
146
|
+
print(f"Anomaly: {result.consensus.unanimous_anomaly}")
|
|
147
|
+
|
|
148
|
+
asyncio.run(main())
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Concepts
|
|
152
|
+
|
|
153
|
+
### The Forum Protocol
|
|
154
|
+
|
|
155
|
+
1. **Fan-out** — Every analyst independently evaluates the question
|
|
156
|
+
2. **Challenge** — Analysts see each other's views and attack weak points
|
|
157
|
+
3. **Rebuttal** — Each analyst responds to challenges
|
|
158
|
+
4. **Score** — Consensus metrics computed: agreement %, confidence, dissent count
|
|
159
|
+
5. **Synthesize** — Domain-specific outcome logic applied
|
|
160
|
+
|
|
161
|
+
### Key Types
|
|
162
|
+
|
|
163
|
+
- **BaseAgent** — Foundation for all agents. Role-gated: `HEAD` can delegate to children, `JUNIOR` cannot call LLM
|
|
164
|
+
- **ResearcherBase** — Expert with persona, implements `analyze()` and `respond_to_challenge()`
|
|
165
|
+
- **ForumModeratorBase** — Orchestrates the debate, computes consensus, applies domain logic
|
|
166
|
+
- **ConsensusScore** — Result includes `agreement_pct`, `dissent_count`, **`unanimous_anomaly`** (detects suspicious unanimity)
|
|
167
|
+
|
|
168
|
+
## Memory Backends
|
|
169
|
+
|
|
170
|
+
forumkit supports three memory types:
|
|
171
|
+
|
|
172
|
+
| Backend | Use | Backend |
|
|
173
|
+
|---------|-----|---------|
|
|
174
|
+
| **Working** | In-session cache | Redis (TTL) |
|
|
175
|
+
| **Episodic** | Long-term experience | PostgreSQL |
|
|
176
|
+
| **Semantic** | Vector embeddings | Qdrant |
|
|
177
|
+
|
|
178
|
+
All are optional. Use the facade:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from forumkit.memory import MemoryManager
|
|
182
|
+
|
|
183
|
+
memory = MemoryManager(
|
|
184
|
+
redis_url="redis://localhost:6379",
|
|
185
|
+
pg_url="postgresql://localhost/mydb",
|
|
186
|
+
qdrant_url="http://localhost:6333"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
await memory.initialize()
|
|
190
|
+
# Use: memory.working.set(), memory.episodic.store(), memory.semantic.search()
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Docs
|
|
194
|
+
|
|
195
|
+
- **[API Reference](docs/API.md)** — All public types and methods
|
|
196
|
+
- **[Concepts](docs/CONCEPTS.md)** — Architecture deep-dive
|
|
197
|
+
- **[Examples](docs/examples/)** — Working code samples
|
|
198
|
+
|
|
199
|
+
## Testing
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pytest tests/unit/ -v
|
|
203
|
+
pytest tests/integration/ -v # Requires Docker (Redis, PostgreSQL)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Security
|
|
207
|
+
|
|
208
|
+
forumkit includes prompt injection defense:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from forumkit.security import sanitize_for_prompt
|
|
212
|
+
|
|
213
|
+
clean = sanitize_for_prompt(user_input)
|
|
214
|
+
# Applies: NFKC normalize, strip BiDi/control chars, remove injection patterns
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Status
|
|
218
|
+
|
|
219
|
+
**v0.1.0 — Alpha**. API is stable; docs are being refined. Not yet open source (private beta).
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT
|
|
224
|
+
|
|
225
|
+
## Contact
|
|
226
|
+
|
|
227
|
+
Issues: GitHub Issues
|
|
228
|
+
Questions: vinitpai@anthropic.com
|
forumkit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# forumkit
|
|
2
|
+
|
|
3
|
+
The only Python multi-agent framework with a **structured debate protocol**: fan-out → challenge → rebuttal → consensus scoring.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from forumkit import ForumModeratorBase, ResearcherBase, AgentConfig, AgentRole
|
|
7
|
+
|
|
8
|
+
class MyAnalyst(ResearcherBase):
|
|
9
|
+
async def analyze(self, data):
|
|
10
|
+
# Independent analysis
|
|
11
|
+
return {"position": "...", "confidence": 0.8}
|
|
12
|
+
|
|
13
|
+
async def respond_to_challenge(self, challenge, context):
|
|
14
|
+
# Defend your position
|
|
15
|
+
return "Here's why I disagree..."
|
|
16
|
+
|
|
17
|
+
class MyForum(ForumModeratorBase):
|
|
18
|
+
def _synthesize(self, analyses, consensus, data):
|
|
19
|
+
# Domain-specific consensus logic
|
|
20
|
+
if consensus.agreement_pct >= 66:
|
|
21
|
+
return "accepted", {"action": "approve"}
|
|
22
|
+
return "conditional", {"action": "review"}
|
|
23
|
+
|
|
24
|
+
# Run a debate
|
|
25
|
+
forum = MyForum(config, researchers=[analyst1, analyst2, analyst3])
|
|
26
|
+
result = await forum.run_debate_round({"question": "Is X good?"})
|
|
27
|
+
|
|
28
|
+
print(f"Consensus: {result.consensus.agreement_pct}% agree")
|
|
29
|
+
print(f"Dissent: {result.consensus.strongest_dissent}")
|
|
30
|
+
print(f"Anomaly?: {result.consensus.unanimous_anomaly}") # Detects groupthink
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Why forumkit?
|
|
34
|
+
|
|
35
|
+
**The problem:** CrewAI, PraisonAI, AutoGen, and others assume agents reach consensus by averaging opinions. In reality, minority views often reveal blind spots.
|
|
36
|
+
|
|
37
|
+
**The solution:** forumkit enforces dissent:
|
|
38
|
+
- Every agent analyzes independently
|
|
39
|
+
- Agents actively challenge each other's positions
|
|
40
|
+
- Minority opinions are surfaced, not discarded
|
|
41
|
+
- Suspicious unanimity is flagged (`unanimous_anomaly` detection)
|
|
42
|
+
- Outcomes are tied to consensus strength, not just majority vote
|
|
43
|
+
|
|
44
|
+
**The result:** Better decisions with traceable dissent.
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install forumkit
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 5-minute example
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import asyncio
|
|
58
|
+
from forumkit import ForumModeratorBase, ResearcherBase, AgentConfig, AgentRole, PersonaContext
|
|
59
|
+
|
|
60
|
+
class Analyst(ResearcherBase):
|
|
61
|
+
async def analyze(self, data):
|
|
62
|
+
# Your LLM call here
|
|
63
|
+
return {
|
|
64
|
+
"persona_id": self.persona.persona_id,
|
|
65
|
+
"position": f"Analysis from {self.persona.name}",
|
|
66
|
+
"confidence": 0.75
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async def respond_to_challenge(self, challenge, context):
|
|
70
|
+
return "I maintain my position because..."
|
|
71
|
+
|
|
72
|
+
class DebateBoard(ForumModeratorBase):
|
|
73
|
+
def _synthesize(self, analyses, consensus, data):
|
|
74
|
+
synthesis = f"Consensus at {consensus.agreement_pct:.0f}%"
|
|
75
|
+
outcome = "approved" if consensus.agreement_pct >= 50 else "rejected"
|
|
76
|
+
return synthesis, outcome, None
|
|
77
|
+
|
|
78
|
+
async def main():
|
|
79
|
+
config = AgentConfig(
|
|
80
|
+
agent_id="board-1", team_id=1, role=AgentRole.HEAD,
|
|
81
|
+
model_alias="gpt-4"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Create 3 analysts with different personas
|
|
85
|
+
analysts = [
|
|
86
|
+
Analyst(AgentConfig(...), PersonaContext(
|
|
87
|
+
persona_id="p1", name="Optimist", system_prompt="..."
|
|
88
|
+
)),
|
|
89
|
+
# ... more analysts
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
board = DebateBoard(config, analysts)
|
|
93
|
+
result = await board.run_debate_round({"question": "Approve proposal X?"})
|
|
94
|
+
|
|
95
|
+
print(f"Outcome: {result.outcome}")
|
|
96
|
+
print(f"Agreement: {result.consensus.agreement_pct}%")
|
|
97
|
+
print(f"Dissent: {result.consensus.dissent_count} voices disagree")
|
|
98
|
+
print(f"Anomaly: {result.consensus.unanimous_anomaly}")
|
|
99
|
+
|
|
100
|
+
asyncio.run(main())
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Concepts
|
|
104
|
+
|
|
105
|
+
### The Forum Protocol
|
|
106
|
+
|
|
107
|
+
1. **Fan-out** — Every analyst independently evaluates the question
|
|
108
|
+
2. **Challenge** — Analysts see each other's views and attack weak points
|
|
109
|
+
3. **Rebuttal** — Each analyst responds to challenges
|
|
110
|
+
4. **Score** — Consensus metrics computed: agreement %, confidence, dissent count
|
|
111
|
+
5. **Synthesize** — Domain-specific outcome logic applied
|
|
112
|
+
|
|
113
|
+
### Key Types
|
|
114
|
+
|
|
115
|
+
- **BaseAgent** — Foundation for all agents. Role-gated: `HEAD` can delegate to children, `JUNIOR` cannot call LLM
|
|
116
|
+
- **ResearcherBase** — Expert with persona, implements `analyze()` and `respond_to_challenge()`
|
|
117
|
+
- **ForumModeratorBase** — Orchestrates the debate, computes consensus, applies domain logic
|
|
118
|
+
- **ConsensusScore** — Result includes `agreement_pct`, `dissent_count`, **`unanimous_anomaly`** (detects suspicious unanimity)
|
|
119
|
+
|
|
120
|
+
## Memory Backends
|
|
121
|
+
|
|
122
|
+
forumkit supports three memory types:
|
|
123
|
+
|
|
124
|
+
| Backend | Use | Backend |
|
|
125
|
+
|---------|-----|---------|
|
|
126
|
+
| **Working** | In-session cache | Redis (TTL) |
|
|
127
|
+
| **Episodic** | Long-term experience | PostgreSQL |
|
|
128
|
+
| **Semantic** | Vector embeddings | Qdrant |
|
|
129
|
+
|
|
130
|
+
All are optional. Use the facade:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from forumkit.memory import MemoryManager
|
|
134
|
+
|
|
135
|
+
memory = MemoryManager(
|
|
136
|
+
redis_url="redis://localhost:6379",
|
|
137
|
+
pg_url="postgresql://localhost/mydb",
|
|
138
|
+
qdrant_url="http://localhost:6333"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
await memory.initialize()
|
|
142
|
+
# Use: memory.working.set(), memory.episodic.store(), memory.semantic.search()
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Docs
|
|
146
|
+
|
|
147
|
+
- **[API Reference](docs/API.md)** — All public types and methods
|
|
148
|
+
- **[Concepts](docs/CONCEPTS.md)** — Architecture deep-dive
|
|
149
|
+
- **[Examples](docs/examples/)** — Working code samples
|
|
150
|
+
|
|
151
|
+
## Testing
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pytest tests/unit/ -v
|
|
155
|
+
pytest tests/integration/ -v # Requires Docker (Redis, PostgreSQL)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Security
|
|
159
|
+
|
|
160
|
+
forumkit includes prompt injection defense:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from forumkit.security import sanitize_for_prompt
|
|
164
|
+
|
|
165
|
+
clean = sanitize_for_prompt(user_input)
|
|
166
|
+
# Applies: NFKC normalize, strip BiDi/control chars, remove injection patterns
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Status
|
|
170
|
+
|
|
171
|
+
**v0.1.0 — Alpha**. API is stable; docs are being refined. Not yet open source (private beta).
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT
|
|
176
|
+
|
|
177
|
+
## Contact
|
|
178
|
+
|
|
179
|
+
Issues: GitHub Issues
|
|
180
|
+
Questions: vinitpai@anthropic.com
|