A local, containerized Discord bot for the Goju Tech Talk (GTT) community. Answers questions grounded in an Obsidian knowledge base using RAG — local vector search for retrieval, Claude (Anthropic API) for opinionated, GTT-voiced responses.
Also includes an MCP server for personal use with Hermes Agent — see HERMES_SUPPORT.md.
┌─────────────────────┐ ┌─────────────────────┐
│ Obsidian vault │ │ Discord │
│ (markdown files) │ │ (bot account) │
└──────────┬──────────┘ └──────────┬──────────┘
│ mounted (ro) │ gateway
▼ ▼
╔══════════════════════════ Docker host (docker-compose) ══════════════════════════╗
║ ║
║ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ║
║ │ Indexer service │ │ Discord bot │ │ Ollama │ ║
║ │ - Watches vault │◄────►│ - discord.py │◄────►│ - nomic-embed │ ║
║ │ - Chunks + embeds │ - LlamaIndex │ └──────┬───────────┘ ║
║ └────────┬─────────┘ └────────┬─────────┘ │ ║
║ │ │ Anthropic API │ localhost:11434 ║
║ ▼ ▼ │ localhost:6333 ║
║ ┌──────────────────────────────┐ │ ║
║ │ Qdrant (vector DB) │ │ ║
║ │ - Persistent volume │ │ ║
║ └──────────────────────────────┘ │ ║
╚══════════════════════════════════════════════════════════════════════════════════╝
│
▼
┌──────────────────────┐
│ MCP server │
│ - gtt_mcp_server.py │
│ - Hermes Agent │
└──────────────────────┘
Indexing (on startup + on vault file changes)
- Indexer watches the
/vaultmount - Chunks markdown files respecting headers and wikilinks
- Calls Ollama →
nomic-embed-textembeddings - Stores vectors + metadata in Qdrant
Query (per Discord mention)
- User mentions
@GTT Botin Discord - Bot embeds the question (Ollama, local)
- Bot retrieves top-k chunks (Qdrant, local)
- Bot sends context + question to Claude via Anthropic API
- Bot posts answer back to Discord with source citations
/knowledge-base query (fully local, no API cost)
- User runs
/knowledge-base <query> - Bot embeds query and retrieves matching chunks (Ollama + Qdrant)
- Returns summary and raw chunks directly — no LLM involved
MCP query (via Hermes Agent — see HERMES_SUPPORT.md)
- Hermes Agent spawns
gtt_mcp_server.pyvia stdio - MCP server embeds query via Ollama (localhost:11434)
- Searches Qdrant (localhost:6333) with hybrid scoring
- Returns ranked chunks to Hermes for summarization
gtt-bot/
├── services/
│ ├── bot/ Discord bot (runs in Docker)
│ ├── indexer/ Vault indexer (runs in Docker)
│ └── mcp/ MCP server for Hermes Agent (runs natively)
│ ├── gtt_mcp_server.py
│ ├── requirements.txt
│ └── README.md
├── docker-compose.yml
├── README.md ← you are here
└── HERMES_SUPPORT.md Hermes Agent integration guide
| Command | Description | Cost |
|---|---|---|
@GTT Bot <question> |
Ask a question, get a GTT-voiced answer | Anthropic API |
/knowledge-base <query> |
Search the vault directly, returns raw chunks | Free (local) |
/knowledge-search <query> |
Search the vault, results in a private thread | Free (local) |
/thread-mode on/off |
Toggle thread replies on or off | Free (local) |
/status |
Show knowledge base size, uptime, config | Free (local) |
| Layer | Tech | Purpose |
|---|---|---|
| Answer generation | Claude (Anthropic API) | GTT-voiced responses |
| Embedding model | nomic-embed-text (Ollama) | Vault chunk embeddings |
| Vector DB | Qdrant | Persistent vector store |
| RAG pipeline | LlamaIndex | Chunking, retrieval |
| Discord client | discord.py | Bot integration |
| File watcher | watchdog | Auto-reindex on vault change |
| Containerization | Docker Compose | Multi-service orchestration |
| MCP server | httpx + MCP SDK | Hermes Agent integration |
- RAM: 16 GB minimum (32 GB recommended)
- Disk: ~5 GB for embedding model + vault index
- GPU: Optional — only used for Ollama embeddings
- Docker Desktop (or Docker Engine + Compose v2)
- A Discord bot token — Developer Portal → New Application → Bot → Reset Token
- Enable Privileged Gateway Intents:
MESSAGE CONTENT INTENT - Invite to your server via OAuth2 URL Generator with scopes
botandapplications.commands, permissionsSend Messages+Read Message History+Create Public Threads
- Enable Privileged Gateway Intents:
- An Anthropic API key — console.anthropic.com → API Keys
- An Obsidian vault (or any folder of markdown files)
cp .env.example .envEdit .env with your values:
DISCORD_TOKEN=your_bot_token_here
ANTHROPIC_API_KEY=your_anthropic_key_here
VAULT_PATH=/absolute/path/to/your/vault
# Lock the bot to specific servers (recommended)
ALLOWED_GUILDS=your_guild_id_here
To get your guild ID: enable Developer Mode in Discord (Settings → Advanced), then right-click your server name → Copy Server ID.
docker compose up -d --buildFirst run downloads images and builds containers. Takes a few minutes.
docker compose exec gtt-ollama ollama pull nomic-embed-textThen restart the indexer so it can build the vault index:
docker compose restart gtt-indexer# Bot connected
docker compose logs gtt-bot --tail=20
# expect: "Slash commands synced" and "Logged in as <bot-name>"
# Indexer built the vault
docker compose logs gtt-indexer --tail=20
# expect: "Index build complete"@GTT Bot what is DIF?
/knowledge-base repository lifetime reasoning
/status
All settings go in .env. See .env.example for the full list with descriptions.
| Variable | Default | Description |
|---|---|---|
DISCORD_TOKEN |
required | Bot token from Discord Developer Portal |
ANTHROPIC_API_KEY |
required | API key from console.anthropic.com |
VAULT_PATH |
required | Absolute path to your Obsidian vault |
EMBED_MODEL |
nomic-embed-text |
Ollama embedding model |
QDRANT_COLLECTION |
vault |
Qdrant collection name |
TOP_K |
5 |
Number of vault chunks retrieved per query |
ALLOWED_GUILDS |
(all) | Comma-separated guild IDs. Empty = allow all |
ALLOWED_CHANNELS |
(all) | Comma-separated channel IDs. Empty = allow all |
COOLDOWN_SECONDS |
30 |
Per-user cooldown for @mention (Anthropic API) |
COOLDOWN_LOCAL_SECONDS |
10 |
Per-user cooldown for /knowledge-base |
MAX_QUESTION_LENGTH |
500 |
Max characters per question |
USE_THREADS |
false |
Reply in threads instead of inline |
Set USE_THREADS=true in .env to default thread mode on for all guilds. Any member in an allowed server can also toggle it at runtime with /thread-mode on or /thread-mode off — this overrides the .env default for that server until the bot restarts.
The bot's knowledge base is an Obsidian vault of atomic notes. The indexer watches the vault folder and automatically reindexes when files change.
Notes follow a Zettelkasten structure with wikilinks ([[note-title]]) for connections. The bot retrieves the most relevant chunks for each question and uses them as context for the answer.
To update the knowledge base: add or edit markdown files in your vault folder. The indexer picks up changes within a few seconds. To force a full reindex:
docker compose restart gtt-indexerALLOWED_GUILDS— strongly recommended. Without it, anyone who discovers your bot's Client ID can invite it to their server and use your Anthropic API key..env— never commit this file. It's in.gitignoreby default.- Ports — Qdrant (6333) and Ollama (11434) are bound to
127.0.0.1and not accessible from outside the host. - Rate limiting — per-user cooldowns on both paths. Adjust
COOLDOWN_SECONDSandMAX_QUESTION_LENGTHto control API cost exposure.
The GTT vault can also be queried via Hermes Agent through an MCP (Model Context Protocol) server. This is a separate, personal interface — it does not affect the Discord bot.
See HERMES_SUPPORT.md for full setup instructions.
# Check bot logs
docker compose logs gtt-bot --tail=30
# Check indexer logs
docker compose logs gtt-indexer --tail=30
# Force reindex
docker compose restart gtt-indexer
# Rebuild after editing bot code
docker compose up --build gtt-bot -d
# Wipe vector store and reindex from scratch
docker compose down
docker volume rm gtt-bot_qdrant_data
docker compose up -d --build
# Pull a different embedding model
docker compose exec gtt-ollama ollama pull <model-name>If the bot doesn't respond to mentions, confirm MESSAGE CONTENT INTENT is enabled in the Developer Portal — without it, discord.py receives empty message content.
If /knowledge-base returns nothing, the indexer may not have run yet. Check its logs and restart if needed.
This is a proof-of-concept built for the GTT community. The knowledge base (vault) is separate from the bot code by design — the code is public, the knowledge base is yours to keep private or share as you choose.
To adapt this for your own community: replace the SYSTEM_PROMPT in services/bot/main.py with your own worldview, drop your markdown notes into the vault, and you have a bot that reflects your community's actual knowledge and voice.
