Post 021


The Concept

BD-1 is a custom Discord bot powered by Anthropic's Claude API. It lives in the Alliance Fleet Discord server and serves as an interactive knowledge assistant. Answering questions about the homelab infrastructure, study material, and project documentation using a Git-backed knowledge pipeline.

The constraint: hard $10/month budget cap on API costs. That constraint shaped every design decision.


Architecture

Discord ←→ BD-1 (Node.js) ←→ Anthropic API (Claude Haiku/Sonnet)
                │
                ├── SQLite (conversation state, usage tracking)
                ├── PM2 (process management)
                └── Git knowledge repo (synced from GitHub)
Host:    Stinger Mantis VM (Node-B)
Runtime: Node.js 20+
Bot:     892 lines of JavaScript
DB:      SQLite (local, no external dependencies)
Process: PM2 (auto-restart, log rotation)
Budget:  $10/month hard cap (Anthropic API)

The Knowledge Pipeline

BD-1 doesn't just answer generic questions, it has context about the entire Alliance Fleet. This comes from a Git-backed knowledge system:

Source of Truth

A private GitHub repository contains the master knowledge document, a 1,128-line v3 export covering:

  • Full homelab topology (nodes, VMs, containers, IPs)
  • All 25+ services with codenames and configurations
  • Network architecture (4 VLANs, firewall rules)
  • AI agent configurations
  • Career context and certification progress

Sync Mechanism

The knowledge repo is cloned to /opt/bd1-knowledge on the Stinger Mantis VM. A fine-grained read-only GitHub Personal Access Token (PAT) provides access:

# Initial clone
git clone https://<PAT>@github.com/timanlemvo/bd1-knowledge.git /opt/bd1-knowledge

# Scheduled sync (cron or n8n)
cd /opt/bd1-knowledge && git pull

BD-1 reads the knowledge files at startup and includes relevant sections in the system prompt when responding to queries. This means updating the bot's knowledge is a git push - no code changes, no redeployment.


Budget Management

The $10/month hard cap is enforced in code. BD-1 tracks token usage per message and maintains a running monthly total in SQLite:

// Simplified budget tracking
const MONTHLY_BUDGET_CENTS = 1000; // $10.00
const HAIKU_INPUT_COST = 0.025;    // per 1K tokens
const HAIKU_OUTPUT_COST = 0.125;   // per 1K tokens

async function checkBudget(estimatedTokens) {
    const currentMonth = new Date().toISOString().slice(0, 7);
    const spent = await db.get(
        'SELECT SUM(cost_cents) as total FROM usage WHERE month = ?',
        [currentMonth]
    );

    if ((spent?.total || 0) + estimatedCost > MONTHLY_BUDGET_CENTS) {
        return { allowed: false, remaining: MONTHLY_BUDGET_CENTS - (spent?.total || 0) };
    }
    return { allowed: true, remaining: MONTHLY_BUDGET_CENTS - (spent?.total || 0) };
}

Model Routing

Most queries use Claude Haiku (cheapest, fastest). Complex questions or those requiring deeper reasoning are routed to Claude Sonnet. The routing decision is based on message length and detected complexity:

function selectModel(message) {
    const wordCount = message.split(' ').length;
    const hasCodeBlock = message.includes('```');
    const isComplex = wordCount > 100 || hasCodeBlock;

    return isComplex ? 'claude-sonnet-4-20250514' : 'claude-haiku-4-5-20251001';
}

Haiku handles ~90% of requests. Sonnet is reserved for technical deep-dives. This keeps costs well under the cap.


SQLite Schema

CREATE TABLE conversations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id TEXT NOT NULL,
    channel_id TEXT NOT NULL,
    message TEXT NOT NULL,
    response TEXT NOT NULL,
    model TEXT NOT NULL,
    input_tokens INTEGER,
    output_tokens INTEGER,
    cost_cents REAL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE usage (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    month TEXT NOT NULL,
    cost_cents REAL NOT NULL,
    model TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_usage_month ON usage(month);
CREATE INDEX idx_conversations_user ON conversations(user_id, created_at DESC);

Process Management with PM2

# Start BD-1 with PM2
pm2 start /opt/bd1/index.js --name bd1

# Auto-restart on crash
pm2 save
pm2 startup

# Log rotation (prevent disk fill)
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

PM2 provides:

  • Automatic restart on crash
  • Log management with rotation
  • Memory/CPU monitoring
  • Startup script generation (survives reboots)

Deployment Issues Resolved

Node.js Version

The Stinger Mantis VM shipped with Node.js 16, which doesn't support the Anthropic SDK's modern JavaScript features. Upgraded to Node.js 20:

curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs

Locale Fix

Internationalization errors from the Anthropic SDK due to missing locale data:

apt-get install -y locales
sed -i 's/# en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen
locale-gen
export LANG=en_US.UTF-8

Wazuh Agent Enrollment

After deploying BD-1's VM, enrolled it as Wazuh agent #10 for security monitoring:

/tmp/install-wazuh-agent.sh "Stinger-Mantis"

What It Does

BD-1 responds to mentions and slash commands in the Alliance Fleet Discord:

  • Infrastructure queries - "What's the IP for Grafana?" → Answers from knowledge base
  • Study assistance - "Explain Azure RBAC" → Claude generates explanation with homelab analogies
  • Troubleshooting - "Why would NPM return 502?" → Contextual answer knowing the Alliance Fleet topology
  • Project status - "What's left on the SIEM project?" → References the master knowledge doc

Cost Results

Month Messages Haiku Calls Sonnet Calls Total Cost
Feb 2026 ~200 ~180 ~20 $3.47
Mar 2026 ~150 ~140 ~10 $2.12

Well under the $10 cap. Haiku is genuinely cheap for Discord-length interactions.


Related: Post 022 - K-2SO Architecture | Post 024 - Discord as an Ops Console