Add auto-polls to settle disagreements between users

LLM analysis now detects when two users are in a genuine
disagreement. When detected, the bot creates a native Discord
poll with each user's position as an option.

- Disagreement detection added to LLM analysis tool schema
- Polls last 4 hours with 1 hour per-channel cooldown
- LLM extracts topic, both positions, and usernames
- Configurable via polls section in config.yaml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 09:22:32 -05:00
parent 13a2030021
commit 622f0a325b
4 changed files with 113 additions and 0 deletions

View File

@@ -25,6 +25,8 @@ class SentimentCog(commands.Cog):
self._message_buffer: dict[tuple[int, int], list[discord.Message]] = {}
# Pending debounce timer tasks
self._debounce_tasks: dict[tuple[int, int], asyncio.Task] = {}
# Per-channel poll cooldown: {channel_id: last_poll_datetime}
self._poll_cooldowns: dict[int, datetime] = {}
async def cog_load(self):
self._flush_states.start()
@@ -245,6 +247,16 @@ class SentimentCog(commands.Cog):
if degradation and not config.get("monitoring", {}).get("dry_run", False):
await self._handle_coherence_alert(message, degradation, coherence_config, db_message_id)
# Disagreement poll detection
polls_config = config.get("polls", {})
if (
polls_config.get("enabled", False)
and result.get("disagreement_detected", False)
and result.get("disagreement_summary")
and not monitoring.get("dry_run", False)
):
await self._handle_disagreement_poll(message, result["disagreement_summary"], polls_config)
# Capture LLM note updates about this user
note_update = result.get("note_update")
if note_update:
@@ -543,6 +555,57 @@ class SentimentCog(commands.Cog):
))
self._save_user_state(message.author.id)
async def _handle_disagreement_poll(
self, message: discord.Message, summary: dict, polls_config: dict,
):
"""Create a Discord poll to settle a detected disagreement."""
ch_id = message.channel.id
cooldown_minutes = polls_config.get("cooldown_minutes", 60)
now = datetime.now(timezone.utc)
# Check per-channel cooldown
last_poll = self._poll_cooldowns.get(ch_id)
if last_poll and (now - last_poll) < timedelta(minutes=cooldown_minutes):
return
topic = summary.get("topic", "Who's right?")
side_a = summary.get("side_a", "Side A")
side_b = summary.get("side_b", "Side B")
user_a = summary.get("user_a", "")
user_b = summary.get("user_b", "")
# Build poll question
question_text = f"Settle this: {topic}"[:300]
# Build answer labels with usernames
label_a = f"{side_a} ({user_a})" if user_a else side_a
label_b = f"{side_b} ({user_b})" if user_b else side_b
duration_hours = polls_config.get("duration_hours", 4)
try:
poll = discord.Poll(
question=question_text,
duration=timedelta(hours=duration_hours),
)
poll.add_answer(text=label_a[:55])
poll.add_answer(text=label_b[:55])
await message.channel.send(poll=poll)
self._poll_cooldowns[ch_id] = now
await self._log_action(
message.guild,
f"**AUTO-POLL** | #{message.channel.name} | "
f"{question_text} | {label_a} vs {label_b}",
)
logger.info(
"Auto-poll created in #%s: %s | %s vs %s",
message.channel.name, topic, label_a, label_b,
)
except discord.HTTPException as e:
logger.error("Failed to create disagreement poll: %s", e)
def _save_user_state(self, user_id: int) -> None:
"""Fire-and-forget save of a user's current state to DB."""
user_data = self.bot.drama_tracker.get_user(user_id)