Skip to content

feat: agent initial#626

Draft
garrappachc wants to merge 13 commits into
masterfrom
feat/discord-ai-agent
Draft

feat: agent initial#626
garrappachc wants to merge 13 commits into
masterfrom
feat/discord-ai-agent

Conversation

@garrappachc
Copy link
Copy Markdown
Member

@garrappachc garrappachc commented May 20, 2026

Summary

  • Adds a Claude-powered AI agent that answers player and admin questions in Discord by querying MongoDB
  • Agent core lives in `src/agent/` (session management, query execution, system prompt) — interface-agnostic so new adapters (Slack, web chat, etc.) can be added without touching the core
  • Discord adapter auto-loaded as a Fastify plugin; silently disabled when `DISCORD_BOT_TOKEN` or `ANTHROPIC_API_KEY` are missing
  • CLI adapter at `agent/cli.ts` (`pnpm agent:cli`) for local testing without Discord
  • Admin panel Discord page extended with agent status (enabled/disabled + reasons) and per-guild channel selection

What the agent can do

  • Answer natural language questions about players, games, and stats by generating and executing MongoDB queries
  • Handles DPM, K/D, HPM (medic), game history, player lookups, map stats, etc.
  • Responds in the language of the question (Polish, English, etc.)
  • Pushes back on vulgar or off-topic messages with a dry dismissal
  • Links players and games back to the website
  • Per-channel enable/disable — only responds where configured

Session scoping

Conversations are grouped into sessions so the agent retains context across follow-up questions:

  • Discord thread — all messages in the same thread share one session (session key = thread channel ID). Threads are gated by their parent channel's enabled-channel config.
  • Reply chain — when a user replies to a bot message, the same session continues. Bot reply IDs are tracked in memory; if the chain spans a restart, the reply starts a fresh session (threads survive restarts via DB).
  • New top-level mention — always starts a fresh session.

Sessions are persisted in MongoDB (`agent.sessions` collection) and expire automatically after 30 days of inactivity.

Cost controls

Token usage (input + output) is tracked per day in MongoDB (`agent.tokenusage` collection) and survives app restarts. A configurable daily token budget can be set via the `agent.daily_token_budget` configuration key (integer, `null` = unlimited). When the daily limit is reached the bot replies with a friendly message instead of calling the API. Usage resets at midnight UTC.

Configuration required

  • `ANTHROPIC_API_KEY` env var (new, optional — agent disabled if absent)
  • Discord bot must have Message Content Intent and Server Members Intent enabled in the Discord developer portal
  • Enable agent per-channel in Admin → Discord → guild config
  • Optionally set `agent.daily_token_budget` to cap daily API spend

Test plan

  • `pnpm agent:cli` starts a REPL and answers questions against the DB
  • Agent responds to @mentions only in configured channels
  • Agent is silent in unconfigured channels
  • Admin panel shows "Agent enabled" when both keys are set and Discord is connected
  • Admin panel shows specific missing-key reason(s) when disabled
  • Channel multi-select saves and reloads correctly
  • Saving guild config with no channels selected clears agent access for that guild
  • Vulgar messages get a dismissal, not a DB query
  • Reply chain: replying to a bot message continues the same conversation
  • Thread: all messages in a thread share one session; thread is gated by parent channel config
  • Daily budget: set `agent.daily_token_budget` to a low value, exhaust it, verify bot replies with limit message
  • Session history survives app restart (ask a question, restart, ask a follow-up referencing the first answer)

🤖 Generated with Claude Code

- Agent core in src/agent/ (create-session, execute-query, system-prompt)
- Discord plugin auto-loaded at startup; no-ops when keys are missing
- CLI entrypoint at agent/cli.ts for local testing (pnpm agent:cli)
- Admin panel shows agent enabled/disabled status with reasons
- Per-guild agent channel selection via multi-select in Discord config
- agent.channels configuration key stores enabled guild+channel pairs
- Fix pre-existing bug in set-multi.ts (missing $set operator + events)
- Requires ANTHROPIC_API_KEY env var and MessageContent/GuildMembers Discord intents

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

Playwright test results

passed  199 passed

Details

stats  199 tests across 29 suites
duration  7 minutes, 51 seconds
commit  37edc1a

garrappachc and others added 3 commits May 27, 2026 17:39
Track Anthropic API token usage per day in MongoDB so the counter
survives restarts. Expose an agent.daily_token_budget configuration
key (null = unlimited); when the daily limit is reached the bot replies
with a friendly message instead of calling the API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Store per-channel conversation history in the agent.sessions collection
so sessions survive app restarts. History is loaded from DB on first
message after startup and saved after each answer. Sessions expire
automatically after 30 days of inactivity via a TTL index.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of one session per channel (which mixed all conversations),
sessions are now scoped to:
- A Discord thread: thread channel ID is the session key
- A reply chain: the root message ID is the session key, tracked via
  an in-memory map of bot reply IDs → session key

For threads, the enabled-channel check falls back to the parent channel
ID so existing admin configuration still applies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@garrappachc garrappachc changed the title feat: add Discord AI agent with admin panel configuration feat: agent initial May 27, 2026
garrappachc and others added 9 commits May 27, 2026 19:14
After each normally-ended game the agent reviews player performance and
adjusts skill values where the evidence clearly warrants it. The review
runs as a scheduled task 3 minutes after game:ended (to allow logs.tf
data to be fetched) and uses the logs stats (DPM/K-D for DPS, HPM/ubers
for medic) when available, falling back to score-only when they are not.

Changes are written to skillHistory with actor='bot'. A consolidated
Discord embed is posted to the admin-notifications channel listing every
adjustment with a one-sentence reasoning and an overall game summary.

The feature is opt-in via the agent.skill_supervisor configuration key
(boolean, default false).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndler

- Move chat system prompt to src/agent/prompts/chat.ts
- Move skill review prompt and builder to src/agent/prompts/skill-review.ts
- Extract shared tools to src/agent/tools.ts (queryTools, submitReviewTool)
- Parameterize createSession with systemPrompt and tools instead of hardcoding
- reviewGameSkills now uses createSession for consistency, returning JSON text
- Replace void IIFE in discord agent plugin with named handleMessage function
- Remove unused 'agent' script from package.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add IconRobot icon from Tabler Icons
- Add Agent page under /admin/agent showing enabled/disabled status
  and configuration for skill_supervisor and daily_token_budget
- Register Agent in admin sidebar navigation (Configuration section)
- Remove AgentStatus from Discord page (moved to Agent page)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude Opus 4.7 costs $5/MTok input and $25/MTok output.
Link to official pricing page with inline examples for 10k and 1M tokens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Break the dense single line into three distinct items:
description, rate, and a bulleted example list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wraps the agent channels select in a disabled fieldset with reduced
opacity and swaps the hint text to explain why it is unavailable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each ask appends { at, inputTokens, outputTokens } to the
tokenUsage array on the session document via $push.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant