Add Slack platform adapter and comprehensive platform skills system

Major Features:
- Implemented full Slack channel-bot adapter with Socket Mode
- Added 35+ Mastodon platform skills across 7 categories
- Created constitution publishing system for Mastodon

Slack Adapter (NEW):
- Full PlatformAdapter implementation (1071 lines)
- Socket Mode for real-time events
- 16 channel-scoped governance skills
- User group management as channel "roles"
- Channel access control and management
- Message pinning and moderation
- Platform limitations documentation
- Comprehensive setup guide (SLACK_SETUP.md)

Mastodon Platform Skills (ENHANCED):
- Account Moderation (9 skills): suspend, silence, disable, mark sensitive, delete status
- Account Management (4 skills): approve, reject, delete data, create account
- Report Management (4 skills): assign, unassign, resolve, reopen
- Federation Management (4 skills): block/unblock domains, allow/disallow federation
- Security Management (4 skills): block IP/email domains
- Constitution Management (2 skills): publish constitution, update profile
- Documented web-only limitations (role management requires tootctl)

Constitution Publishing:
- Publish constitution as pinned Mastodon thread
- Automatic deprecation of previous versions
- Version control with change summaries
- Thread splitting for long documents
- CLI tool: scripts/publish_constitution.py
- Documentation: CONSTITUTION_PUBLISHING.md

Configuration & Integration:
- Added SlackConfig model with bot_token, app_token, channel_id
- Updated PlatformConfig to support type: slack
- Added slack-sdk>=3.33.0 dependency
- Bot.py now routes to Slack adapter
- Updated example config with Slack section

Documentation:
- SLACK_SETUP.md: Complete Slack setup guide
- PLATFORM_SKILLS.md: All 35+ Mastodon skills documented
- CONSTITUTION_PUBLISHING.md: Constitution publishing guide
- Updated README.md: Merged QUICKSTART, added Slack to supported platforms
- Updated PLATFORMS.md: Slack marked as implemented with examples
- Updated .gitignore: Added instance-specific state files

Security:
- All sensitive files properly gitignored
- Instance-specific state (.constitution_post_id) excluded
- Credentials properly handled in config

Breaking Changes: None

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Nathan Schneider
2026-02-10 22:46:48 -07:00
parent a0785f09cf
commit 54beddb420
16 changed files with 3675 additions and 489 deletions

View File

@@ -61,7 +61,8 @@ class GovernanceAgent:
request: str,
actor: str,
context: Optional[Dict[str, Any]] = None,
platform_skills: Optional[List[Dict[str, Any]]] = None
platform_skills: Optional[List[Dict[str, Any]]] = None,
platform_limitations: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Process a governance request using agentic interpretation.
@@ -80,6 +81,7 @@ class GovernanceAgent:
actor: Who made the request
context: Optional context (thread ID, etc.)
platform_skills: List of available platform-specific skills
platform_limitations: Information about what's not possible via API
Returns:
Response dictionary with action taken and audit trail
@@ -106,7 +108,8 @@ class GovernanceAgent:
memory=memory_context,
actor=actor,
context=context,
platform_skills=platform_skills
platform_skills=platform_skills,
platform_limitations=platform_limitations
)
# Step 5: Execute the decision
@@ -225,7 +228,8 @@ Return your analysis as JSON:
memory: Dict[str, Any],
actor: str,
context: Optional[Dict[str, Any]],
platform_skills: Optional[List[Dict[str, Any]]] = None
platform_skills: Optional[List[Dict[str, Any]]] = None,
platform_limitations: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Use LLM to decide what action to take.
@@ -247,10 +251,34 @@ Return your analysis as JSON:
platform_skills_info = "\n\nAVAILABLE PLATFORM SKILLS:\n"
for skill in platform_skills:
params_str = ", ".join([f"{p['name']}: {p['type']}" for p in skill.get('parameters', [])])
category = skill.get('category', 'general')
platform_skills_info += f"- {skill['name']}({params_str}): {skill['description']}\n"
platform_skills_info += f" Category: {category}\n"
if skill.get('constitutional_authorization'):
platform_skills_info += f" Authorization: {skill['constitutional_authorization']}\n"
# Format platform limitations for the prompt
platform_limitations_info = ""
if platform_limitations:
platform_limitations_info = "\n\nPLATFORM LIMITATIONS:\n"
if 'web_interface_only' in platform_limitations:
platform_limitations_info += "\nActions requiring web interface (NOT available via bot):\n"
for key, desc in platform_limitations['web_interface_only'].items():
platform_limitations_info += f"- {key}: {desc}\n"
if 'not_possible' in platform_limitations:
platform_limitations_info += "\nActions not possible via any API:\n"
for key, desc in platform_limitations['not_possible'].items():
platform_limitations_info += f"- {key}: {desc}\n"
if 'limitations' in platform_limitations:
platform_limitations_info += "\nGeneral limitations to be aware of:\n"
for key, desc in platform_limitations['limitations'].items():
platform_limitations_info += f"- {key}: {desc}\n"
platform_limitations_info += "\nIMPORTANT: If a user requests something that requires web interface or is not possible via API, explain this limitation clearly and provide the web interface URL if applicable."
prompt = f"""You are a governance bot interpreting a community constitution.
IMPORTANT: When generating the "response" field, use newline characters (\\n) for line breaks:
@@ -269,7 +297,7 @@ CURRENT MEMORY STATE:
{json.dumps(memory, indent=2)}
ACTOR: {actor}
{platform_skills_info}
{platform_skills_info}{platform_limitations_info}
Based on the constitution and current state, decide what action to take.
IMPORTANT AUTHORITY CHECK:

View File

@@ -25,6 +25,7 @@ from .agent import GovernanceAgent
from .scheduler import GovernanceScheduler
from .platforms.base import PlatformAdapter, PlatformMessage, MockPlatformAdapter
from .platforms.mastodon import MastodonAdapter
from .platforms.slack import SlackAdapter
# Configure logging
@@ -125,12 +126,14 @@ class Govbot:
if platform_type == "mastodon":
return MastodonAdapter(self.config.platform.mastodon.model_dump())
elif platform_type == "slack":
return SlackAdapter(self.config.platform.slack.model_dump())
elif platform_type == "mock":
return MockPlatformAdapter({})
else:
raise ValueError(
f"Unknown platform type: {platform_type}. "
f"Supported: mastodon, mock"
f"Supported: mastodon, slack, mock"
)
def run(self):
@@ -199,6 +202,7 @@ class Govbot:
# Get available platform skills
platform_skills_list = []
platform_limitations = None
try:
platform_skills = self.platform.get_skills()
# Convert PlatformSkill objects to dicts for the agent
@@ -214,6 +218,10 @@ class Govbot:
"constitutional_authorization": skill.constitutional_authorization,
}
platform_skills_list.append(skill_dict)
# Get platform limitations if available
if hasattr(self.platform, 'get_platform_limitations'):
platform_limitations = self.platform.get_platform_limitations()
except Exception as e:
logger.warning(f"Could not get platform skills: {e}")
@@ -222,6 +230,7 @@ class Govbot:
actor=f"@{message.author_handle}",
context=context,
platform_skills=platform_skills_list if platform_skills_list else None,
platform_limitations=platform_limitations,
)
# Check if we need to execute a platform skill

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,15 @@ class MastodonConfig(BaseModel):
bot_username: str = Field("govbot", description="Bot's Mastodon username")
class SlackConfig(BaseModel):
"""Slack workspace configuration"""
bot_token: str = Field(..., description="Slack bot user OAuth token (xoxb-...)")
app_token: str = Field(..., description="Slack app-level token for Socket Mode (xapp-...)")
channel_id: str = Field(..., description="Channel ID for governance (e.g., C0123456789)")
bot_user_id: Optional[str] = Field(None, description="Bot user ID (will be auto-detected)")
class AIConfig(BaseModel):
"""AI model configuration"""
@@ -58,8 +67,9 @@ class GovernanceConfig(BaseModel):
class PlatformConfig(BaseModel):
"""Platform selection and configuration"""
type: str = Field(..., description="Platform type: mastodon, discord, telegram, mock")
type: str = Field(..., description="Platform type: mastodon, slack, discord, telegram, mock")
mastodon: Optional[MastodonConfig] = Field(None, description="Mastodon configuration")
slack: Optional[SlackConfig] = Field(None, description="Slack configuration")
# Future platforms:
# discord: Optional[DiscordConfig] = None
# telegram: Optional[TelegramConfig] = None
@@ -117,7 +127,7 @@ def create_example_config(output_path: str = "config/config.example.yaml"):
"""
example_config = {
"platform": {
"type": "mastodon", # or "discord", "telegram", "mock"
"type": "mastodon", # or "slack", "discord", "telegram", "mock"
"mastodon": {
"instance_url": "https://your-mastodon-instance.social",
"client_id": "your_client_id_here",
@@ -125,6 +135,12 @@ def create_example_config(output_path: str = "config/config.example.yaml"):
"access_token": "your_access_token_here",
"bot_username": "govbot",
},
# Slack example:
# "slack": {
# "bot_token": "xoxb-your-bot-token-here",
# "app_token": "xapp-your-app-token-here",
# "channel_id": "C0123456789",
# },
# Discord example (for future use):
# "discord": {
# "token": "your_discord_bot_token",