Skip to content

Instantly share code, notes, and snippets.

@timcunningham
Last active March 23, 2026 14:55
Show Gist options
  • Select an option

  • Save timcunningham/65378067333a327ff32c0e7fb3f935f8 to your computer and use it in GitHub Desktop.

Select an option

Save timcunningham/65378067333a327ff32c0e7fb3f935f8 to your computer and use it in GitHub Desktop.
WCP Snark Bot

ShowBot — The Working Code Podcast Discord Bot

Quick Start Guide

ShowBot is a snarky, achievement-wielding podcast search bot that knows everything the hosts have ever said across 252+ episodes. Here's how to use it.

Chat Mode (#showbot channel)

Just type naturally in the #showbot channel. No commands needed.

You: What is Tim's favorite database?
ShowBot: PostgreSQL, and if you didn't already know that, have you even
         been listening? [snarky answer with episode links]

ShowBot remembers what you were talking about, so follow-ups work:

You: tell me more
ShowBot: [continues the conversation with context]

Slash Commands (work in any channel)

Command What it does
/showbot Quick start guide and documentation link
/ask question: Search transcripts — raw results, no LLM summary
/summarize question: Search + AI summary with evidence cards
/hosts Show host stats and attendance
/attendance Host attendance report with visual bars
/attendance host: Carol Detailed view — which episodes a host missed
/episodes Browse all episodes (paginated, sent to DMs)
/episode 251 AI-generated summary of a specific episode (sent to DMs)
/profile Your ShowBot stats, level, XP, and achievements
/badges View the punk zine badge art for achievements you've earned (DMs)
/temperature set value: Set your personal LLM temperature (0.0–2.0)
/temperature show See your current temperature setting
/temperature reset Go back to the default (0.7)
/serious Toggle serious mode on/off — no snark, just facts (per-user)
/scrape Export text channel history to JSONL for RAG (admin only)

Channel History Scraper

So you want ShowBot to learn from the channel chatter? Bold move, crawler. Here's how it works.

Who can use it: Server managers only. If you don't have the Manage Server permission, ShowBot will laugh at you.

What it does: Grabs every human message from eligible text channels and dumps them into discord_history.jsonl — one JSON record per message with author, content, timestamp, and channel info.

What it skips:

  • #secret, #patrons-only, #showbot — off limits, don't even try
  • Voice channels — ShowBot can't hear you
  • Bot messages — nobody wants to train on bot spam
  • Empty messages — nothing to see here
  • Channels ShowBot can't access — no permissions, no data

Incremental updates: ShowBot remembers the last message it scraped per channel. Run /scrape again and it only grabs what's new. No duplicate messages, no wasted API calls. It's smarter than it looks.

The output: discord_history.jsonl in the bot directory. Each line is a JSON object:

{"id": "123", "channel": "general", "author": "Tim", "content": "PostgreSQL forever", "created_at": "2025-01-15T10:30:00Z", "reply_to": null}

Discord RAG Ingest Pipeline

Once you've scraped the channels, you need to feed that data into ShowBot's brain. Two commands, zero thinking required.

Step 1: Scrape (from Discord slash command or CLI)

python scrape_discord.py    # CLI version — connects, scrapes, disconnects

Step 2: Ingest into RAG

python ingest_discord.py            # embed and merge into rag_index.json
python ingest_discord.py --dry-run  # preview without modifying anything

What happens behind the curtain:

  1. Messages are grouped into conversation chunks (15+ minute gap = new conversation)
  2. Tiny conversations (< 50 chars) get thrown in the trash where they belong
  3. Each chunk gets embedded with the same model as podcast transcripts (all-MiniLM-L6-v2)
  4. Discord segments are merged into the existing RAG index alongside the 54,800+ podcast segments
  5. Search results from Discord show as Discord [epdiscord_YYYY-MM-DD] #channel-name

Re-running is safe. The ingest strips out old Discord segments before merging new ones. It's idempotent, crawler. Run it as many times as you want.

The numbers: 100,000+ Discord messages become ~27,000 conversation segments after chunking. Combined with podcast transcripts, ShowBot now searches across 82,000+ segments. It knows what you said. All of it.

Source-Aware Search

ShowBot knows the difference between what the hosts said on the podcast and what you nerds typed in Discord. Ask about "discord" or "the channel" and it filters to Discord-only segments. Ask a normal question and it sticks to podcast transcripts — because let's be honest, the hosts' opinions carry more weight than your hot takes in #general. If the transcripts come up empty, Discord results sneak in as a fallback, like that friend who shows up uninvited but at least brings snacks.

Keywords that trigger Discord-only search: discord, channel, said in chat, in the channel, in discord, on discord, chat history, general channel

Asking Good Questions

Works great:

  • "What does Ben think about ColdFusion?"
  • "Has Carol talked about testing?"
  • "What topics come up the most?"
  • "Tell me about Tim and PostgreSQL"

Works with counting:

  • "How many times has Ben mentioned ColdFusion?"
  • "How many episodes has Carol missed?"
  • "How often does Tim talk about databases?"

Follow-ups work (sort of):

  • Ask a question, then say "tell me more" or "what about Tim?"
  • ShowBot only uses your prior questions as a fallback when the current query finds nothing on its own
  • Memory is RAM-only — bot restart wipes the slate clean

Host filtering:

  • Mention a host name and ShowBot prioritizes their own words
  • "What does Ben think about..." searches Ben's segments first

Achievements

ShowBot tracks your activity and awards achievements — Dungeon Crawler Carl style. Achievements are sent as private DMs with punk zine badge art.

How to earn them:

  • Ask your first question (First Blood)
  • Ask about PostgreSQL (Tim's Favorite)
  • Ask about ColdFusion (Ben's Ride or Die)
  • Ask about testing (Carol Approves)
  • Ask about architecture (Adam's Spirit Animal)
  • Mention all four hosts (Full Party Wipe)
  • Ask a counting question (Bean Counter)
  • Ask a follow-up (Tell Me More)
  • Stump the bot (Stumped The Bot)
  • Ask after midnight (Night Owl)
  • Ask 3 questions in 2 minutes (Machine Gun Questions)
  • Ask the same question twice (Groundhog Day)
  • Trigger the fallacy detector (Logic Police Bait)
  • Attack a host personally (Chose Violence)
  • Hit 10/25/50/100 questions (Regular/Obsessed/Touched Grass?/Final Boss)
  • Trigger specific fallacies (unique DCC-style achievement per fallacy type)

Use /profile to see your stats and /badges to view your earned badge art.

Fallacy Detection

ShowBot detects 15 types of logical fallacies and calls them out conversationally:

Ad Hominem, Straw Man, False Dilemma, Hasty Generalization, Slippery Slope, Appeal to Authority, Appeal to Popularity, Appeal to Ignorance, Circular Reasoning, False Cause, Red Herring, Tu Quoque, False Analogy, No True Scotsman, Loaded Question

Each fallacy type has its own DCC-style achievement with rambling, ominous 80s/90s nostalgia text.

Warning: Personal attacks on hosts (Ad Hominem) trigger ShowBot's nuclear defense mode. You've been warned.

Spellcheck

ShowBot checks your spelling:

  • 1-2 typos: quiet correction, still answers
  • 3+ typos: refuses to answer until you fix your spelling

Tech terms (postgresql, coldfusion, kubernetes, etc.) and contractions (don't, isn't, etc.) are whitelisted.

Serious Mode

Look, not everyone wants to be roasted by a podcast bot. Some people just want the damn answer. Use the /serious slash command and ShowBot drops the snark, the sass, and the personality for your entire session. You get clean, factual, cited answers — like talking to a bot that hasn't been raised by four opinionated developers.

The command works in any channel — just type /serious and Discord will show it in the autocomplete. ShowBot confirms the toggle privately (only you see it).

/serious  → "Serious mode on. Straight answers, no snark."

You: What does Ben think about TypeScript?
ShowBot: Ben has discussed TypeScript across several episodes. In episode 142,
         he acknowledged it caught a real bug in his code. He sees value in it
         for larger projects but considers the overhead unnecessary for smaller
         ones.

/serious  → "Serious mode off. The snark is back, baby."

It's a per-user toggle — your serious mode doesn't affect anyone else's experience. Works across chat and /summarize. Use /serious again to bring back the chaos.

Temperature Control

You know how some days you want the bot to give you a straight, predictable answer and other days you want it to go full chaos goblin? Temperature controls that. It's your setting — it doesn't affect anyone else's queries, so go wild.

/temperature set value:0.0    → Deterministic robot mode. Same question = same answer. Boring but reliable.
/temperature set value:0.7    → The default. Balanced like a well-specced character build.
/temperature set value:1.5    → Creative mode. ShowBot starts improvising. Results may vary. A lot.
/temperature show             → Check what you're running at.
/temperature reset            → Back to 0.7. The coward's choice. (Just kidding. Mostly.)

Range: 0.0 to 2.0. Anything outside that range gets rejected because ShowBot has some self-preservation instincts.

What it affects: All LLM-powered responses to your questions — chat in #showbot and /summarize. It does NOT affect episode summaries (those stay at 0.3 because facts shouldn't be creative).

Persistence: Your setting sticks around as long as ShowBot is running. Bot restart = back to defaults. Think of it like a temporary enchantment, not a permanent stat boost.

Easter Eggs

Trigger What happens
pew pew ASCII fireworks display
i'm struggling Genuine heartfelt affirmation (snark off)
heart check Same — ShowBot drops everything to be kind

Source Links

Every response includes clickable episode links that scroll directly to the quoted text on workingcode.dev using text fragments. Click a source link to see exactly what the hosts said.

Tips & Tricks

  1. Be specific — "What does Ben think about TypeScript?" works better than "TypeScript"
  2. Use host names — ShowBot prioritizes that host's own words when you mention them
  3. Follow up — short messages like "tell me more" or "what about Carol?" use conversation context
  4. Counting questions — start with "how many" or "how often" for real data, not LLM guesses
  5. Attendance — ask "how many episodes has X missed?" for accurate speaker-based stats
  6. Challenge it — try logical fallacies and bad takes, ShowBot will call you out
  7. Collect achievements — check /profile to see what you've unlocked
  8. After midnight — you'll get a special Night Owl achievement
  9. Rapid fire — ask 3 questions in 2 minutes for the Machine Gun achievement
  10. Be nice — or don't. ShowBot can take it. But the hosts are off limits.

How ShowBot Works: A Technical Deep Dive (For Nerds, By Nerds)

Because of course you want to know how it works. You're a Working Code listener — you can't just use something without peeking under the hood.

The 30-Second Version

You type a question
    ↓
Two search engines fight over who finds the best transcript matches
    ↓
A re-ranker picks the winner
    ↓
An LLM reads the evidence and writes a snarky summary
    ↓
You get an answer with links to the actual transcript

The Architecture

┌─────────────────────────────────────────────────────────┐
│  Discord (you, asking about Ben's ColdFusion obsession) │
└──────────────────────────┬──────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│  Discord Bot (disnake)                                  │
│  - Listens in #showbot channel                          │
│  - Slash commands in all channels                       │
│  - Tracks conversation history (last 10 messages)       │
│  - Detects which host you're asking about               │
│  - Spellcheck, fallacy detection, achievements          │
└──────────────────────────┬──────────────────────────────┘
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
┌──────────────────┐ ┌──────────┐ ┌──────────────────┐
│ Semantic Search   │ │  BM25    │ │ Host Detection   │
│ (Embeddings)      │ │ (Keywords)│ │ "about Ben?" →   │
│                   │ │          │ │ search Ben first  │
│ all-MiniLM-L6-v2 │ │ rank-bm25│ │                  │
│ 384-dim vectors   │ │          │ │                  │
└────────┬─────────┘ └────┬─────┘ └──────────────────┘
         │                │
         └───────┬────────┘
                 ▼
┌─────────────────────────────────────────────────────────┐
│  Cross-Encoder Re-Ranker                                │
│  ms-marco-MiniLM-L-6-v2                                 │
│                                                         │
│  Takes ~40 candidates from both searches                │
│  Scores each one against your actual question            │
│  Keeps the top 8                                        │
└──────────────────────────┬──────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│  LLM (Mistral Nemo 12B via Ollama)                      │
│                                                         │
│  Gets: system prompt (personality) + conversation       │
│        history + 8 evidence segments + your question    │
│                                                         │
│  Returns: a snarky, cited summary                       │
└──────────────────────────┬──────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│  Response Builder                                       │
│  - Adds clickable episode links (workingcode.dev)       │
│  - Text fragment URLs scroll to the exact quote         │
│  - Checks for achievements, fallacies, spelling         │
│  - DMs achievements with punk zine badge art            │
└─────────────────────────────────────────────────────────┘

The Data Pipeline

Before the bot can answer anything, we had to feed it the entire podcast:

252 episodes of audio
    ↓
Whisper transcription → JSON transcripts with speaker labels
    ↓
54,800+ segments (speaker + text chunks)
    ↓
Sentence-transformers encodes each segment → 384-dim embeddings
    ↓
rag_index.json (15 MB) + rag_embeddings.npy (40 MB)
    ↓
BM25 index built at startup from segment text
    ↓
Host attendance computed from speaker metadata

Weekly Updates

New episodes are added weekly with one command:

python update_index.py

This checks the RSS feed, scrapes any new transcripts from workingcode.dev, and rebuilds the full index in ~4 minutes.

Why Two Search Methods?

Because neither one is good enough alone. Seriously.

Search Type Good At Bad At
Semantic (embeddings) "What does Tim think about databases?" → finds segments about database opinions even if they don't say "database" Finding specific names like "PostgreSQL" when the query says "favorite database"
BM25 (keywords) "PostgreSQL" → finds every mention of that exact word Understanding that "favorite database" and "I love Postgres" are related

We run both, merge the results, and let the cross-encoder sort out who actually won.

Why a Cross-Encoder?

The embedding search is fast but dumb — it encodes the question and segments separately and compares vectors. The cross-encoder is slow but smart — it reads the question and each candidate together and decides "does this actually answer the question?"

Think of it like this:

  • Embedding search: speed dating. Quick vibes check on 54,000 segments.
  • Cross-encoder: the actual date. Deep conversation with the top 40 candidates.

The LLM Situation

We use Mistral Nemo 12B running locally via Ollama. It's connected through a Cloudflare Tunnel so the Discord bot (which could run on a cheap cloud server) can reach Ollama on a home machine.

Why not GPT-4 or Claude? Because:

  1. Free is better than not free
  2. The bot runs 24/7 — API costs add up
  3. 12B parameters is actually plenty when you're just summarizing evidence, not reasoning about quantum physics
  4. We like running things locally. We're that kind of podcast.

Conversation Memory

ShowBot keeps a buffer of the last 10 messages per user — tracked by your Discord user ID, not by channel. But here's the thing: it doesn't always use them.

ShowBot's memory is a fallback system, not a conversation engine. Here's what actually happens when you ask a question:

  1. ShowBot searches the transcripts using your question as-is
  2. Only if that search comes back empty does it pull in your previous question, glue it to your current one, and retry the search
  3. If that enriched search finds results, it passes your conversation history to the LLM so it can write a contextual answer
  4. If your first search already found good results? Your history sits there unused. ShowBot doesn't even tell the LLM about your prior conversation.

So "tell me more" works — but only because "tell me more" by itself returns zero search results, which triggers the fallback that prepends your last real question. It's not intelligence. It's a safety net that happens to look like memory.

The limitations — and they're real, crawler:

  • It's a fallback, not a feature. If your follow-up question happens to match transcript segments on its own, ShowBot answers without any conversation context. It doesn't know or care what you asked before.
  • 10 messages. That's it. Message eleven pushes message one into the void. Like a temp buff that expired while you were still reading the tooltip.
  • Memory is RAM-only. When ShowBot restarts, your entire conversation history vanishes. No database. No save file. No continue screen. You're starting a fresh run every time the bot reboots.
  • It's per-user, not per-channel. If you ask something in #showbot and then use /summarize in #general, it's all the same thread to ShowBot.
  • Follow-ups only work if there's context. If ShowBot just restarted and you waltz in with "tell me more" — more about what? ShowBot doesn't know you. You're a stranger now. Start over.

It's not a chat agent — it's more like a party member with amnesia who occasionally gets a flashback when nothing else works, then blacks out again when you return to town.

Counting & Attendance

When ShowBot detects a "how many" question, it bypasses the LLM entirely and does real counting:

  • Keyword counting: scans all 54,800+ segments for exact matches, per-host breakdown, top episodes
  • Attendance tracking: pre-computed at startup from transcript speaker metadata — who was in which episode, who missed what

This means "how many episodes has Carol missed?" gives you an exact number (73), not an LLM guess.

Achievement System

Achievements persist in SQLite (achievements.db) across restarts. The system tracks:

  • Earned achievements per user (never repeat)
  • Question counts, typo counts, fallacy counts
  • Host mention tracking
  • Question history for repeat detection

Each achievement has 20+ random DCC-style text variants (Dungeon Crawler Carl narrator voice — rambling, ominous, full of 80s/90s nostalgia). Badge art is generated in WP Punks punk zine style and attached to DMs.

The Source Links

Every response includes clickable episode links that use text fragments to scroll directly to the quoted text on workingcode.dev. Chrome, Edge, and Firefox all support this.

A link looks like:

https://workingcode.dev/episodes/108-2022-year-in-review/#:~:text=other%20than%20that%2C%20it%27s%20a%20great%20database

Click it and your browser highlights the exact words the bot is referencing. Trust but verify.

The Stack

Component Technology Why
Bot framework disnake 2.x discord.py fork with better slash command support
Embeddings sentence-transformers (all-MiniLM-L6-v2) Fast, small (80MB), good enough
Keyword search rank-bm25 Classic IR algorithm, zero dependencies
Re-ranker cross-encoder (ms-marco-MiniLM-L-6-v2) Best accuracy-per-MB in the business
LLM Mistral Nemo 12B (Q4 quantized) Smart enough to be snarky, small enough to run at home
LLM server Ollama One command to run any model
Tunnel Cloudflare Tunnel Free, connects cloud bot to home LLM
Transcript data NumPy + JSON 55 MB total. Fits in RAM. No database needed
Achievements SQLite Zero-config persistence for user data
Spellcheck pyspellchecker Lightweight, customizable dictionary
Badge art DALL-E 3 generated WP Punks punk zine sticker aesthetic

Total RAM usage: ~1.5 GB for the bot + models (no LLM). The LLM runs separately on whatever machine has the GPU (or CPU patience).

What It Can't Do

  • It can't make stuff up. Every answer comes from actual transcript evidence. If the hosts never talked about it, ShowBot says so.
  • It sometimes gets sentiment wrong. Sarcasm in transcripts is hard. When Adam says "oh GREAT, another framework" — is that genuine or sarcastic? The LLM guesses. Sometimes it guesses wrong.
  • Episode summaries are AI-generated. They're based on transcript excerpts, not human curation. Take them as a starting point, not gospel.

Want to Run Your Own?

The whole thing is open source. You need:

  • Python 3.9+
  • A Discord bot token
  • The transcript data (rag_index.json + rag_embeddings.npy)
  • Ollama with Mistral Nemo 12B (for summaries) — or it works without for search-only mode
  • About 30 minutes and a tolerance for reading startup logs
pip install -r requirements.txt
cp .env.example .env
# Edit .env with your Discord token
python discord_bot.py

Weekly Episode Updates

python update_index.py          # Check RSS, scrape new transcripts, rebuild index
python update_index.py --dry-run  # Preview what would be updated

That's it. No Kubernetes. No microservices. Ben would be proud.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment