""" Action Primitives for Agentic Governance. These are the low-level operations that the AI agent can orchestrate to implement governance processes. Each primitive is simple and composable, allowing the agent to flexibly implement constitutional procedures. """ from datetime import datetime, timedelta from typing import Dict, Any, Optional, List from sqlalchemy.orm import Session import json from ..db import queries from ..db.models import GovernanceProcess, Action class GovernancePrimitives: """ Provides primitive operations for governance actions. These are called by the AI agent to implement constitutional procedures. """ def __init__(self, db_session: Session): self.db = db_session # Storage primitives def store_record( self, record_type: str, data: Dict[str, Any], actor: str, reasoning: Optional[str] = None, citation: Optional[str] = None, ) -> int: """ Store a governance record (generic storage primitive). Args: record_type: Type of record (e.g., "proposal", "vote", "decision") data: Record data as dictionary actor: Who created this record reasoning: Bot's reasoning for creating this record citation: Constitutional citation Returns: Record ID """ action = queries.create_action( session=self.db, action_type=f"store_{record_type}", actor=actor, data=data, bot_reasoning=reasoning, constitutional_citation=citation, ) return action.id def query_records( self, record_type: Optional[str] = None, criteria: Optional[Dict[str, Any]] = None, limit: int = 50, ) -> List[Dict[str, Any]]: """ Query governance records (generic retrieval primitive). Args: record_type: Type of record to query (None for all) criteria: Filter criteria (e.g., {"status": "active"}) limit: Maximum number of results Returns: List of matching records as dictionaries """ actions = queries.get_recent_actions( session=self.db, limit=limit, action_type=record_type ) if criteria: # Filter by criteria in data field filtered = [] for action in actions: if action.data and all( action.data.get(k) == v for k, v in criteria.items() ): filtered.append(action.to_dict()) return filtered return [action.to_dict() for action in actions] # Process primitives def create_process( self, process_type: str, creator: str, deadline_days: int, constitutional_basis: str, initial_state: Optional[Dict[str, Any]] = None, mastodon_thread_id: Optional[str] = None, ) -> int: """ Create a new governance process. Args: process_type: Type of process (e.g., "standard_proposal") creator: Who initiated the process deadline_days: Days until deadline constitutional_basis: Constitutional citation initial_state: Initial state data mastodon_thread_id: Link to Mastodon thread Returns: Process ID """ deadline = datetime.utcnow() + timedelta(days=deadline_days) process = queries.create_process( session=self.db, process_type=process_type, creator=creator, constitutional_basis=constitutional_basis, deadline=deadline, state_data=initial_state or {}, mastodon_thread_id=mastodon_thread_id, ) # Log the action queries.create_action( session=self.db, action_type="process_created", actor="bot", data={ "process_id": process.id, "process_type": process_type, "creator": creator, "deadline": deadline.isoformat(), }, constitutional_citation=constitutional_basis, ) return process.id def update_process_state( self, process_id: int, state_updates: Dict[str, Any], actor: str = "bot" ) -> bool: """ Update the state of a governance process. Args: process_id: ID of process to update state_updates: Dictionary of state updates to merge actor: Who is updating the state Returns: True if successful """ try: process = queries.update_process_state( session=self.db, process_id=process_id, state_data=state_updates ) # Log the action queries.create_action( session=self.db, action_type="process_updated", actor=actor, data={"process_id": process_id, "updates": state_updates}, ) return True except Exception as e: return False def complete_process( self, process_id: int, outcome: str, reasoning: str ) -> bool: """ Mark a governance process as completed. Args: process_id: ID of process to complete outcome: Outcome description (e.g., "passed", "failed") reasoning: Explanation of outcome Returns: True if successful """ try: process = queries.complete_process( session=self.db, process_id=process_id, outcome=outcome ) # Log the action queries.create_action( session=self.db, action_type="process_completed", actor="bot", data={"process_id": process_id, "outcome": outcome}, bot_reasoning=reasoning, ) return True except Exception as e: return False # Calculation primitives def calculate(self, expression: str, variables: Dict[str, Any]) -> Any: """ Safely evaluate a mathematical expression. Args: expression: Math expression (e.g., "agree > disagree") variables: Variable values (e.g., {"agree": 10, "disagree": 3}) Returns: Result of calculation """ # Safe evaluation using eval with restricted globals allowed_names = { "abs": abs, "max": max, "min": min, "sum": sum, "len": len, } allowed_names.update(variables) try: result = eval(expression, {"__builtins__": {}}, allowed_names) return result except Exception as e: raise ValueError(f"Invalid expression: {expression} - {e}") def count_votes( self, process_id: int ) -> Dict[str, int]: """ Count votes for a governance process. Args: process_id: ID of process to count votes for Returns: Dictionary with vote counts """ process = queries.get_process(self.db, process_id) if not process: return {} # Get votes from process state votes = process.state_data.get("votes", {}) # Count by type counts = {"agree": 0, "disagree": 0, "abstain": 0, "block": 0} for voter, vote_data in votes.items(): vote_type = vote_data.get("vote", "").lower() if vote_type in counts: counts[vote_type] += 1 return counts def check_threshold( self, counts: Dict[str, int], threshold_type: str ) -> bool: """ Check if vote counts meet a threshold. Args: counts: Vote counts dictionary threshold_type: Type of threshold to check Returns: True if threshold is met """ agree = counts.get("agree", 0) disagree = counts.get("disagree", 0) block = counts.get("block", 0) if threshold_type == "simple_majority": return agree > disagree elif threshold_type == "3x_majority": return agree >= (disagree * 3) elif threshold_type == "with_blocks": # Require 9x more agree than disagree+block return agree >= ((disagree + block) * 9) elif threshold_type == "supermajority_2/3": total = agree + disagree if total == 0: return False return (agree / total) >= (2 / 3) elif threshold_type == "admin_approval": # Admin decision model - no voting threshold # This type means admin must approve, not vote counting return False # Requires manual admin approval else: raise ValueError(f"Unknown threshold type: {threshold_type}") # Scheduling primitives def schedule_reminder( self, when: datetime, message: str, recipient: Optional[str] = None ) -> int: """ Schedule a reminder for a future time. Args: when: When to send reminder message: Reminder message recipient: Who to remind (None for broadcast) Returns: Reminder ID """ action = queries.create_action( session=self.db, action_type="reminder_scheduled", actor="bot", data={ "scheduled_for": when.isoformat(), "message": message, "recipient": recipient, "status": "pending", }, ) return action.id def get_pending_reminders(self) -> List[Dict[str, Any]]: """ Get reminders that are due. Returns: List of reminder dictionaries """ actions = queries.get_recent_actions( session=self.db, limit=100, action_type="reminder_scheduled" ) now = datetime.utcnow() due_reminders = [] for action in actions: if action.data.get("status") == "pending": scheduled_time = datetime.fromisoformat( action.data["scheduled_for"] ) if scheduled_time <= now: due_reminders.append(action.to_dict()) return due_reminders def mark_reminder_sent(self, reminder_id: int) -> bool: """ Mark a reminder as sent. Args: reminder_id: ID of reminder action Returns: True if successful """ action = queries.get_action(self.db, reminder_id) if action and action.data: action.data["status"] = "sent" self.db.commit() return True return False # Reversal primitives def reverse_action( self, action_id: int, actor: str, reason: str ) -> bool: """ Reverse a previous action. Args: action_id: ID of action to reverse actor: Who is reversing the action reason: Reason for reversal Returns: True if successful """ try: queries.reverse_action( session=self.db, action_id=action_id, reversing_actor=actor, reason=reason, ) return True except Exception as e: return False def is_action_reversed(self, action_id: int) -> bool: """ Check if an action has been reversed. Args: action_id: ID of action to check Returns: True if action is reversed """ action = queries.get_action(self.db, action_id) return action.status == "reversed" if action else False def create_primitives(db_session: Session) -> GovernancePrimitives: """Factory function to create primitives instance""" return GovernancePrimitives(db_session)