Initial commit: Platform-agnostic governance bot

Govbot is an AI-powered governance bot that interprets natural language
constitutions and facilitates collective decision-making across social
platforms.

Core features:
- Agentic architecture with constitutional reasoning (RAG)
- Platform-agnostic design (Mastodon, Discord, Telegram, etc.)
- Action primitives for flexible governance processes
- Temporal awareness for multi-day proposals and voting
- Audit trail with constitutional citations
- Reversible actions with supermajority veto
- Works with local (Ollama) and cloud AI models

Platform support:
- Mastodon: Full implementation with streaming, moderation, and admin skills
- Discord/Telegram: Platform abstraction ready for implementation

Documentation:
- README.md: Architecture and overview
- QUICKSTART.md: Getting started guide
- PLATFORMS.md: Platform implementation guide for developers
- MASTODON_SETUP.md: Complete Mastodon deployment guide
- constitution.md: Example governance constitution

Technical stack:
- Python 3.11+
- SQLAlchemy for state management
- llm CLI for model abstraction
- Mastodon.py for Mastodon integration
- Pydantic for configuration validation

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Nathan Schneider
2026-02-06 17:09:26 -07:00
commit fbc37ecb8f
27 changed files with 6004 additions and 0 deletions

172
tests/test_basic.py Normal file
View File

@@ -0,0 +1,172 @@
"""
Basic tests for Govbot functionality.
These tests demonstrate the core features without requiring Mastodon.
"""
import pytest
from pathlib import Path
import sys
import tempfile
import shutil
# Add src to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from src.govbot.db.models import init_db, get_session
from src.govbot.agent import GovernanceAgent
from src.govbot.governance.constitution import ConstitutionalReasoner
from src.govbot.governance.primitives import GovernancePrimitives
@pytest.fixture
def test_db():
"""Create a temporary test database"""
temp_dir = tempfile.mkdtemp()
db_path = Path(temp_dir) / "test.db"
engine = init_db(str(db_path))
session = get_session(engine)
yield session
session.close()
shutil.rmtree(temp_dir)
@pytest.fixture
def constitution_path():
"""Get path to test constitution"""
return str(Path(__file__).parent.parent / "constitution.md")
def test_constitution_loading(constitution_path):
"""Test that constitution loads and parses correctly"""
reasoner = ConstitutionalReasoner(constitution_path)
assert reasoner.constitution_text
assert len(reasoner.sections) > 0
# Test section listing
sections = reasoner.list_sections()
assert any("Proposal" in s for s in sections)
def test_constitutional_query(constitution_path):
"""Test querying the constitution"""
reasoner = ConstitutionalReasoner(constitution_path)
result = reasoner.query("What are the rules for standard proposals?")
assert "answer" in result
assert "citations" in result
assert len(result["citations"]) > 0
def test_primitives_store_and_query(test_db):
"""Test basic primitive operations"""
primitives = GovernancePrimitives(test_db)
# Store a record
record_id = primitives.store_record(
record_type="test_record",
data={"foo": "bar", "count": 42},
actor="test_user",
reasoning="Testing storage",
)
assert record_id > 0
# Query it back
records = primitives.query_records(record_type="store_test_record")
assert len(records) > 0
def test_create_process(test_db):
"""Test creating a governance process"""
primitives = GovernancePrimitives(test_db)
process_id = primitives.create_process(
process_type="test_proposal",
creator="test_user",
deadline_days=6,
constitutional_basis="Article 3, Section 1",
initial_state={"proposal_text": "Test proposal"},
)
assert process_id > 0
# Verify it was created
from src.govbot.db import queries
process = queries.get_process(test_db, process_id)
assert process is not None
assert process.process_type == "test_proposal"
assert process.creator == "test_user"
def test_vote_counting(test_db):
"""Test vote counting and threshold checking"""
primitives = GovernancePrimitives(test_db)
# Create a process
process_id = primitives.create_process(
process_type="test_proposal",
creator="alice",
deadline_days=6,
constitutional_basis="Article 3",
initial_state={"votes": {}},
)
# Cast some votes
primitives.update_process_state(
process_id,
{
"votes.alice": {"vote": "agree"},
"votes.bob": {"vote": "agree"},
"votes.charlie": {"vote": "disagree"},
},
)
# Count votes
counts = primitives.count_votes(process_id)
assert counts["agree"] == 2
assert counts["disagree"] == 1
# Check threshold
passed = primitives.check_threshold(counts, "simple_majority")
assert passed is True
def test_agent_proposal_creation(test_db, constitution_path):
"""Test agent creating a proposal from natural language"""
agent = GovernanceAgent(test_db, constitution_path, model=None)
result = agent.process_request(
request="I propose we have weekly meetings",
actor="test_user",
)
assert result["success"] is True
assert result["process_id"] is not None
assert "proposal" in result["response"].lower()
def test_proposal_interpretation(constitution_path):
"""Test interpreting different proposal types"""
reasoner = ConstitutionalReasoner(constitution_path)
# Standard proposal
info = reasoner.interpret_proposal("We should update the guidelines")
assert "proposal_type" in info
assert info["discussion_period_days"] >= 2
# Urgent proposal
info = reasoner.interpret_proposal("URGENT: We need to address this spam attack")
# Should recognize urgency (though AI interpretation may vary)
assert "proposal_type" in info
if __name__ == "__main__":
# Run tests
pytest.main([__file__, "-v"])