12-Angry-Agent / core /models.py
Blu3Orange
feat: Introduce argument direction handling and enhance conviction mechanics for juror interactions
373ff24
"""Juror configuration and memory models."""
from dataclasses import dataclass, field
from typing import Literal
@dataclass
class JurorConfig:
"""Configuration for a single juror agent."""
# Identity
juror_id: str
seat_number: int
name: str
emoji: str # For display until sprites ready
# Personality (affects reasoning style)
archetype: str # "rationalist", "empath", "cynic", etc.
personality_prompt: str # Detailed persona prompt
# Behavior modifiers
stubbornness: float # 0.0-1.0, how hard to convince
volatility: float # 0.0-1.0, how much conviction swings
influence: float # 0.0-1.0, how persuasive to others
verbosity: float = 0.5 # 0.0-1.0, how long their arguments are
# Initial stance
initial_lean: str = "neutral" # "prosecution", "defense", "neutral", etc.
# Model configuration
model_provider: str = "gemini" # "gemini", "openai", "anthropic", "local"
model_id: str = "gemini-2.5-flash" # Specific model ID
temperature: float = 0.7
# Tools (future expansion)
tools: list[str] = field(default_factory=list)
# Memory
memory_window: int = 10 # How many turns to remember in detail
def is_player(self) -> bool:
"""Check if this is the player seat."""
return self.archetype == "player"
@dataclass
class ArgumentMemory:
"""Memory of a single argument heard."""
speaker_id: str
content_summary: str
argument_type: str
persuasiveness: float # How convincing it was to this juror
counter_points: list[str] = field(default_factory=list) # Thoughts against it
round_heard: int = 0
@dataclass
class JurorMemory:
"""Memory state for a single juror."""
juror_id: str
# Case understanding
case_summary: str = ""
key_evidence: list[str] = field(default_factory=list)
evidence_interpretations: dict[str, str] = field(default_factory=dict)
# Deliberation memory
arguments_heard: list[ArgumentMemory] = field(default_factory=list)
arguments_made: list[str] = field(default_factory=list)
# Compressed history (replaces old arguments_heard entries)
deliberation_summary: str = ""
# Relationships
opinions_of_others: dict[str, float] = field(default_factory=dict)
# Internal state
current_conviction: float = 0.5 # 0.0-1.0
conviction_history: list[float] = field(default_factory=list)
reasoning_chain: list[str] = field(default_factory=list)
doubts: list[str] = field(default_factory=list)
def get_current_vote(self) -> Literal["guilty", "not_guilty"]:
"""Get vote based on current conviction with hysteresis."""
# If no history, use simple threshold
if len(self.conviction_history) < 2:
return "guilty" if self.current_conviction > 0.5 else "not_guilty"
# Determine previous vote from previous conviction
prev_conviction = self.conviction_history[-2]
prev_vote_guilty = prev_conviction > 0.5
# Apply hysteresis thresholds to prevent flip-flopping
if prev_vote_guilty:
# Currently guilty - need to drop below 0.4 to flip
return "not_guilty" if self.current_conviction < 0.4 else "guilty"
else:
# Currently not guilty - need to rise above 0.6 to flip
return "guilty" if self.current_conviction > 0.6 else "not_guilty"
def add_argument(self, argument: ArgumentMemory) -> None:
"""Add a heard argument to memory."""
self.arguments_heard.append(argument)
def update_conviction(self, delta: float) -> None:
"""Update conviction score, clamping to valid range."""
self.current_conviction = max(0.0, min(1.0, self.current_conviction + delta))
self.conviction_history.append(self.current_conviction)
def get_recent_arguments(self, count: int = 5) -> list[ArgumentMemory]:
"""Get the most recent arguments heard."""
return self.arguments_heard[-count:] if self.arguments_heard else []