Implement working Mastodon bot with proposal system

Major Features:
- Mastodon integration with polling-based listener (streaming unreliable)
- Claude AI integration via llm CLI with API key support
- Public proposal announcements with voting
- Markdown stripping for Mastodon plain text
- Thread-aware voting system

Configuration:
- Added requirements.txt with all dependencies
- API key configuration in config.yaml (not streamed keys)
- Support for multiple Claude models via llm-anthropic

Platform Adapter (Mastodon):
- Polling notifications every 5 seconds (more reliable than streaming)
- Notification ID tracking to prevent re-processing on restart
- Markdown stripping for clean plain text output
- Vote thread matching via announcement IDs

Agent & Governance:
- Conversational tone (direct, concise, not legalistic)
- Proposal creation with AI-generated titles and descriptions
- Public announcements for proposals with all details
- Vote casting with automatic proposal detection from threads
- Constitutional reasoning for governance decisions

Bot Features:
- Long message splitting into threaded posts
- Public proposal announcements separate from user replies
- Announcement includes: title, proposer, description, deadline, voting instructions
- Vote tracking linked to proposal announcement threads

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Nathan Schneider
2026-02-06 22:26:42 -07:00
parent b636a805f9
commit 5fe22060e1
6 changed files with 351 additions and 39 deletions

View File

@@ -27,11 +27,13 @@ class AIConfig(BaseModel):
default_model: Optional[str] = Field(
None,
description="Default LLM model to use (e.g., 'llama3.2' for Ollama, 'gpt-4' for OpenAI)",
description="Default LLM model to use (e.g., 'gpt-4o-mini', 'claude-opus-4-6')",
)
fallback_model: Optional[str] = Field(None, description="Fallback model if default fails")
temperature: float = Field(0.7, description="LLM temperature for responses")
max_tokens: Optional[int] = Field(None, description="Maximum tokens for LLM responses")
openai_api_key: Optional[str] = Field(None, description="OpenAI API key (for GPT models)")
anthropic_api_key: Optional[str] = Field(None, description="Anthropic API key (for Claude models)")
class GovernanceConfig(BaseModel):