9-task step-by-step plan covering DB schema, LLM extraction tool, memory
retrieval/injection in chat, sentiment pipeline routing, background pruning,
and migration script.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Outlines persistent memory system for making the bot a real conversational
participant that knows people and remembers past interactions. Uses existing
UserNotes column for permanent profiles and a new UserMemory table for
expiring context with LLM-assigned lifetimes.
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>
Filter out non-dict entries from user_findings and handle non-dict
result to prevent 'str' object has no attribute 'setdefault' errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The triage LLM was blending context message content into its reasoning
for new messages (e.g., citing profanity from context when the new
message was just "I'll be here"). Added per-message [CONTEXT] tags
inline and strengthened the prompt to explicitly forbid referencing
context content in reasoning/scores.
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 display name "Calm your tits" was being factored into toxicity
scores. Updated the analysis prompt to explicitly instruct the LLM
to ignore all usernames/display names when scoring messages.
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>
When the LLM returns text instead of a tool call for conversation
analysis, try parsing the content as JSON before giving up. Also
log what the model actually returns on failure for debugging.
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>
gpt-5-nano and other newer models require max_completion_tokens
instead of max_tokens. The new parameter is backwards compatible
with older models.
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>
Lovable hammered friend with typos, strong nonsensical opinions,
random tangents, and overwhelming affection for everyone in chat.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New mode that gasses people up for their plays and takes using
gaming hype terminology, but reads the room and dials back to
genuine encouragement when someone's tilted or frustrated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add guidance for ~25% genuinely positive/hype responses
- Lean toward playful ribbing over pure negativity
- Reduce reply_chance from 35% to 20%
- Increase proactive_cooldown_messages from 5 to 8
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The model was inventing rankings and scoreboards from the drama score
metadata. Explicitly tell it not to make up stats it doesn't have.
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>
- Remove /no_think override from chat() so Qwen3 reasons before
generating responses (fixes incoherent word-salad replies)
- Analysis and image calls keep /no_think for speed
- Add varied roast style guidance (deadpan, sarcastic, blunt, etc.)
- Explicitly ban metaphors/similes in roast prompt
- Replace metaphor examples with direct roast examples
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>
Triage analysis runs on Qwen 8B (athena.lan) for free first-pass.
Escalation, chat, image roasts, and commands use GPT-4o via OpenAI.
Each tier gets its own base URL, API key, and concurrency settings.
Local models get /no_think and serialized requests automatically.
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>
- Default models: gpt-4o-mini (triage), gpt-4o (escalation)
- Remove Qwen-specific /no_think hacks
- Reduce timeout from 600s to 120s, increase concurrency semaphore to 4
- Support empty LLM_BASE_URL to use OpenAI directly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Raised sentence limit from 3 to 5 for english teacher mode
- Added instruction to list multiple corrections rapid-fire
- Roast mode reply chance: 10% -> 35%
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 vision model request was hanging indefinitely, freezing the bot.
The streaming loop had no timeout so if the model never returned
chunks, the bot would wait forever. Now times out after 2 minutes.
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>
Usage: ./scripts/announce.sh "message" [channel_name]
Fetches the bot token from barge, resolves channel by name,
and posts via the Discord API. Defaults to #general.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Insufferable grammar nerd that corrects spelling, translates slang
into proper English, and overanalyzes messages like literary essays.
20% proactive reply chance with relaxed moderation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a user replies to the bot's message, the original bot message
text is now included in the context sent to the LLM. This prevents
the LLM from misinterpreting follow-up questions like "what does
this even mean?" since it can see what message is being referenced.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
LLM was misinterpreting usernames as channel names because
the [Server context: ...] metadata format was never explained
in the system prompts. This caused nonsensical replies like
treating username "thelimitations" as "the limitations channel".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a BotSettings key-value table. The active mode is saved
when changed via /bcs-mode and restored on startup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Adds a server-wide mode system with /bcs-mode command.
- Default: current hall-monitor behavior unchanged
- Chatty: friendly chat participant with proactive replies (~10% chance)
- Roast: savage roast mode with proactive replies
- Chatty/roast use relaxed moderation thresholds
- 5-message cooldown between proactive replies per channel
- Bot status updates to reflect active mode
- /bcs-status shows current mode and effective thresholds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>