Topic drift reminders and nudges now direct users to a specific
channel (configurable via redirect_channel). Both static templates
and LLM-generated redirects include the clickable channel mention.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changed if to elif so detected_game redirect only fires when
the topic_drift branch wasn't taken.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Parse display names with ': ' split to handle colons in names
- Reset cooldown to half instead of subtract-3 to reduce LLM call frequency
- Remove redundant message.guild check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace random-only proactive reply logic with LLM relevance check.
The bot now evaluates recent conversation context and user memory
before deciding to jump in, then applies reply_chance as a second
gate. Bump reply_chance values higher since the relevance filter
prevents most irrelevant replies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract _split_afterthought helper method
- Store cleaned content (no |||) in chat history to prevent LLM reinforcement
- Handle afterthought splitting in reaction-reply path too
- Log main_reply instead of raw response
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add triple-pipe afterthought splitting to chat replies so the bot can
send a follow-up message 2-5 seconds later, mimicking natural Discord
typing behavior. Update all 6 personality prompts with afterthought
instructions (~1 in 5 replies) and memory callback guidance so the bot
actively references what it knows about users. Enhance memory extraction
prompt to flag bold claims, contradictions, and embarrassing moments as
high-importance callback-worthy memories with a "callback" topic tag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use time.monotonic() at reaction time instead of stale message-receive timestamp
- Add excluded_channels config and filtering
- Truncate message content to 500 chars in pick_reaction
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a new cog that gives the bot ambient presence by reacting to
messages with contextual emoji chosen by the triage LLM. Includes
RNG gating and per-channel cooldown to keep reactions sparse and
natural.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix dirty-user flush race: discard IDs individually after successful save
- Escape LIKE wildcards in LLM-generated topic keywords for DB queries
- Anonymize absent-member aliases to prevent LLM de-anonymization
- Pass correct MIME type to vision model based on image file extension
- Use enumerate instead of list.index() in bcs-scan loop
- Allow bot @mentions with non-report intent to fall through to moderation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Warning flag now auto-expires after a configurable duration
(warning_expiration_minutes, default 30m). After expiry, the user must
be re-warned before a mute can be issued.
Messages that triggered moderation actions (warnings/mutes) are now
excluded from the LLM context window in both buffered analysis and
mention scans, preventing already-actioned content from influencing
future scoring. Uses in-memory tracking plus bot reaction fallback
for post-restart coverage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- LLM now evaluates messages against numbered server rules and reports
violated_rules in analysis output
- Warnings and mutes cite the specific rule(s) broken
- Rules extracted to prompts/rules.txt for prompt injection
- Personality prompts moved to prompts/personalities/ and compressed
(~63% reduction across all prompt files)
- All prompt files tightened: removed redundancy, consolidated Do NOT
sections, trimmed examples while preserving behavioral instructions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Queries Messages, AnalysisResults, and Actions tables to rank users by a
composite drama score (weighted avg toxicity, peak toxicity, and action rate).
Public command with configurable time period (7d/30d/90d/all-time).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace static random templates with LLM-generated redirect messages that
reference what the user actually said and why it's off-topic. Sass escalates
with higher strike counts. Falls back to static templates if LLM fails or
use_llm is disabled in config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The LLM returns note_update, reasoning, and worst_message with
anonymized names. These are now replaced with real display names
before storage, so user profiles no longer contain meaningless
User1/User2 references.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aliases now stored in UserState table instead of config.yaml. Adds
Aliases column (NVARCHAR 500), loads on startup, persists via flush.
New /bcs-alias slash command (view/set/clear) for managing nicknames.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds user_aliases config section mapping Discord IDs to known nicknames.
Aliases are anonymized and injected into LLM analysis context so it can
recognize when someone name-drops another member (even absent ones).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When @mentioned, fetch recent messages from ALL users in the channel
(up to 15 messages) instead of only the mentioner's messages. This lets
the bot understand debates and discussions it's asked to weigh in on.
Also update the personality prompt to engage with topics substantively
when asked for opinions, rather than deflecting with generic jokes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds LLM triage on bot @mentions to determine if the user is chatting
or reporting bad behavior. Only 'report' intents trigger the 30-message
scan; 'chat' intents skip the scan and let ChatCog handle it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge worktree: adds _extract_and_save_memories() method and fire-and-forget
extraction call after each chat reply. Combined with Task 4's memory
retrieval and injection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Display names like "Calm your tits" were causing the LLM to inflate toxicity
scores on completely benign messages. Usernames are now replaced with User1,
User2, etc. before sending to the LLM, then mapped back to real names in the
results.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert cogs/sentiment.py (1050 lines) into cogs/sentiment/ package:
- __init__.py (656 lines): core SentimentCog with new _process_finding()
that deduplicates the per-user finding loop from _process_buffered and
_run_mention_scan (~90 lines each → single shared method)
- actions.py: mute_user, warn_user
- topic_drift.py: handle_topic_drift
- channel_redirect.py: handle_channel_redirect, build_channel_context
- coherence.py: handle_coherence_alert
- log_utils.py: log_analysis, log_action, score_color
- state.py: save_user_state, flush_dirty_states
All extracted modules use plain async functions (not methods) receiving
bot/config as parameters. Named log_utils.py to avoid shadowing stdlib
logging. Also update CLAUDE.md with comprehensive project documentation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The conversation analysis was re-scoring old messages alongside new ones,
causing users to get penalized repeatedly for already-scored messages.
A "--- NEW MESSAGES ---" separator now marks which messages are new, and
the prompt instructs the LLM to score only those. Also fixes bot-mention
detection to require an explicit @mention in message text rather than
treating reply-pings as scans (so toxic replies to bot warnings aren't
silently skipped).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
last_offense_time was in-memory only — lost on restart, so the
offense_reset_minutes check never fired after a reboot. Now persisted
as LastOffenseAt FLOAT in UserState. On startup hydration, stale
offenses (and warned flag) are auto-cleared if the reset window has
passed. Bumped offense_reset_minutes from 2h to 24h.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gate mutes behind a prior warning — first offense always gets a warning,
mute only fires if warned_since_reset is True. Warned flag is persisted
to DB (new Warned column on UserState) and survives restarts.
Add post-warning escalation boost to drama_score: each high-scoring
message after a warning adds +0.04 (configurable) so sustained bad
behavior ramps toward the mute threshold instead of plateauing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from per-user message batching to per-channel conversation
analysis. The LLM now sees the full interleaved conversation with
relative timestamps, reply chains, and consecutive message collapsing
instead of isolated flat text per user.
Key changes:
- Fix gpt-5-nano temperature incompatibility (conditional temp param)
- Add mention-triggered scan: users @mention bot to analyze recent chat
- Refactor debounce buffer from (channel_id, user_id) to channel_id
- Replace per-message analyze_message() with analyze_conversation()
returning per-user findings from a single LLM call
- Add CONVERSATION_TOOL schema with coherence, topic, and game fields
- Compact message format: relative timestamps, reply arrows (→),
consecutive same-user message collapsing
- Separate mention scan tasks from debounce tasks
- Remove _store_context/_get_context (conversation block IS the context)
- Escalation timeout config: [30, 60, 120, 240] minutes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ignored_channels config to topic_drift section, supporting
channel names or IDs. General channel excluded from off-topic
warnings while still receiving full moderation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Slim down chat_roast.txt — remove anti-repetition rules that were
compensating for the local model (gpt-4o-mini handles this natively).
Remove disagreement detection from analysis prompt, tool schema, and
sentiment handler. Saves ~200 tokens per analysis call.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a separate llm_chat client so chat responses use a smarter model
(gpt-4o-mini) while analysis stays on the cheap local Qwen3-8B.
Falls back to llm_heavy if LLM_CHAT_MODEL is not set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The model dumps paraphrased context and style labels in [brackets]
before its actual roast. Instead of just removing bracket lines
(which leaves the preamble text), split on them and keep only the
last non-empty segment — the real answer is always last.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The model paraphrases injected metadata in unpredictable ways, so
targeted regexes can't keep up. Replace them with a single rule: any
[bracketed block] on its own line gets removed, since real roasts
never use standalone brackets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add frequency_penalty (0.8) and presence_penalty (0.6) to LLM chat
calls to discourage repeated tokens. Inject the bot's last 5 responses
into the system prompt so the model knows what to avoid. Strengthen
the roast prompt with explicit anti-repetition rules and remove example
lines the model was copying verbatim ("Real ___ energy", etc.).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the bot replies (proactive or mentioned), it now fetches the
user's drama tracker notes and their last ~10 messages in the channel.
Gives the LLM real context for personalized replies instead of
generic roasts on bare pings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Proactive replies used channel.send() which posted standalone messages
with no visual link to what triggered them. Now all replies use
message.reply() so the response is always attached to the source message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Send as a channel message instead of message.reply() so it doesn't
look like the bot is talking to itself.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reply to any message + @bot to have the bot read and respond to it.
Also picks up image attachments from referenced messages so users
can reply to a photo with "@bot roast this".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The prompt was scoreboard-only, so selfies got nonsensical stat-based
roasts. Now the LLM identifies what's in the image and roasts accordingly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Broader regex to strip leaked metadata even when the LLM drops
the "Server context:" prefix but keeps the content.
2. Skip sentiment analysis for messages that mention or reply to
the bot. Users interacting with the bot in roast/chat modes
shouldn't have those messages inflate their drama score.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When someone reacts to the bot's message, there's a 50% chance it
fires back with a reply commenting on their emoji choice, in
character for the current mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The local LLM was echoing back [Server context: ...] metadata lines
in its responses despite prompt instructions not to. Now stripped
via regex before sending to Discord.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>