Skip to content
Bots

Discord bot in Python (discord.py 2)

discord.py 2 rules for agents: slash commands in cogs, range-limited options, 3-second responses, manual syncing, default intents and offline tests of the payload.

Verified install Not agent-tested v1.0.0 MIT updated pythondiscorduvpytest
Equips
  • 1 1 Rules
  • 1 1 Skills
  • – 0 MCP

Agents writing Discord bots in Python reach for prefix commands and the message content intent, sync the command tree on every start until Discord rate-limits them, and test nothing because everything seems to need a live gateway. This loadout sets the discord.py 2 conventions: slash commands in cogs, options described and limited in the schema, deferral for slow work, ephemeral error replies, syncing by hand to a dev guild, and the token only from the environment. A skill adds commands with logic in plain functions and offline tests of the exact payload Discord will receive. The verifier runs those tests on discord.py 2.7.1 and loads the cog without logging in.

What's in it

  • Slash commands in cogs; no message content intent
  • Options described and limited with app_commands.Range
  • Offline tests of logic and the command payload, no gateway needed
  • Sync by hand to a dev guild, never on every start
  • Token from the environment; default intents only

You'll need

  • Tools: uv, python3
  • Environment variables: DISCORD_TOKEN
Level 1 · automated

Verification

Verified install

Checked at commit 021470d · 9 passed, 0 failed, 1 not applicable

CheckResultWhat we found
Listinglisting.yaml matches the schema and declares every file in the folder. Pass
Schema valid; 7 files declared, none missing or extra
  • tested.status: untested (agent tests are separate from Level 1)
Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. Pass All 7 files parse; front matter valid
AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. Pass
CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
  • CLAUDE.md: matches the generator
  • GEMINI.md: matches the generator
Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. Pass
Correct paths for all 6 agents; layouts re-discovered
  • Claude Code: 6 files, 1 skill in .claude/skills/
  • Codex CLI: 5 files, 1 skill in .agents/skills/
  • Cursor: 5 files, 1 skill in .agents/skills/
  • Gemini CLI: 6 files, 1 skill in .agents/skills/
  • Antigravity CLI: 5 files, 1 skill in .agents/skills/
  • GitHub Copilot: 5 files, 1 skill in .agents/skills/
MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. Not applicable No MCP servers in this loadout
Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode or prompt-injection wording, and the listing's network flag is honest. Pass
Clean: no credentials, risky shell or hidden text
  • Scanner intake-precheck/1: verdict clean; network no (listing says yes)
ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. Pass 1 reference between files resolves for every agent
Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. Pass
7 files, 9.4 KB total; largest skills/discord-py-command/references/dice.py 2.2 KB
  • Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule
LicenceA recognised SPDX licence, with the licence text available. Pass MIT, by Agent Loadout; text in the repository LICENSE
Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. Pass
The discord-py-command skill's cog and tests run on discord.py 2.7.1 with no network: seven tests pass (logic, bad input, and the slash command payload with descriptions and ranges), the bot loads the cog offline, and ruff is clean
  • Applied 11 loadout files at every agent's paths
  • ✓ place the examples: bash -c mkdir -p cogs tests && touch cogs/__init__.py && R=.agents/skills/discord-py-command/references && cp $R/dice.py cogs/ && cp $R/bot.py . && cp $R/test_dice.py tests/ && echo placed
  • ✓ pytest (offline): uv run --no-project --python 3.12 --with discord.py==2.7.1 --with pytest==9.1.1 python -m pytest -q -p no:cacheprovider --rootdir . --import-mode importlib -o pythonpath=. tests
  • ✓ bot loads the cog without logging in: uv run --no-project --python 3.12 --with discord.py==2.7.1 python -c import asyncio, bot async def main(): async with bot.Bot() as b: await b.setup_hook() print('commands:', [c.name for c in b.tree.get_commands()]) asyncio.run(main())
  • ✓ no token without the environment: bash -c env -u DISCORD_TOKEN uv run --no-project --python 3.12 --with discord.py==2.7.1 python bot.py; echo "exit=$?"
  • ✓ ruff check: uvx ruff@0.17.1 check .
  • ✓ ruff format --check: uvx ruff@0.17.1 format --check .
  • Covers: Cog with app_commands, describe, Range limits and a per-user cooldown
  • Covers: Error handler: friendly cooldown message, logged exceptions, ephemeral replies
  • Covers: Command payload checked offline with to_dict(tree)
  • Covers: Default intents, token only from the environment, no sync on start

What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results ("tested with agent") are separate and stay untested until an agent actually runs.

Install

Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.

Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.

Download zip
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/AGENTS.md
curl -fsSL --create-dirs -o CLAUDE.md https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/CLAUDE.md
curl -fsSL --create-dirs -o .claude/skills/discord-py-command/SKILL.md https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/SKILL.md
curl -fsSL --create-dirs -o .claude/skills/discord-py-command/references/dice.py https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/dice.py
curl -fsSL --create-dirs -o .claude/skills/discord-py-command/references/bot.py https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/bot.py
curl -fsSL --create-dirs -o .claude/skills/discord-py-command/references/test_dice.py https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/test_dice.py
FileGoes to
AGENTS.mdAGENTS.md
CLAUDE.mdCLAUDE.md
skills/discord-py-command/SKILL.md.claude/skills/discord-py-command/SKILL.md
skills/discord-py-command/references/dice.py.claude/skills/discord-py-command/references/dice.py
skills/discord-py-command/references/bot.py.claude/skills/discord-py-command/references/bot.py
skills/discord-py-command/references/test_dice.py.claude/skills/discord-py-command/references/test_dice.py

Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.

Files

Rules AGENTS.md Rules for a discord.py 2 bot: layout, commands, interactions, syncing and secrets. 42 lines
Download Raw
# AGENTS.md: Discord bot (Python, discord.py 2)

## Layout
```
bot.py            creates the Bot, loads cogs in setup_hook, reads the token from the environment
cogs/<name>.py    one feature per cog: app commands plus an async setup(bot) function
tests/            pytest for command logic and the command payloads, no network
```
Run with `uv run python bot.py`. Tests: `uv run pytest`. Lint: `uv run ruff check` and `uv run ruff format --check`.

## Commands
- Slash commands (`app_commands`), not prefix commands that need the privileged message content intent.
- Every command and option has a description (`description=` and `@app_commands.describe`). Names are lowercase,
  1-32 characters.
- Constrain options in the schema: `app_commands.Range[int, 1, 20]`, `Range[str, 1, 200]`, `Choice` lists. Discord
  enforces these before your code runs.
- Keep logic in plain functions that take plain values, so tests do not need Discord objects. New commands: use the
  `discord-py-command` skill.

## Interactions
- Respond within 3 seconds. For slow work call `await interaction.response.defer()` first, then
  `await interaction.followup.send(...)`.
- Private replies: `ephemeral=True`. Errors go to a command `.error` handler (or `tree.on_error`): log the exception,
  reply with a short ephemeral message, never a traceback.
- Moderation commands set `@app_commands.default_permissions(...)` and re-check permissions in code.
- Cooldowns on costly commands: `@app_commands.checks.cooldown(rate, per, key=...)`.

## Syncing
- Do not call `tree.sync()` on every start; it is rate limited. Sync by hand, to a dev guild while developing
  (instant), and globally only for a release.
- After changing a command's name, options or descriptions, remind the user to sync.

## Token and intents
- `DISCORD_TOKEN` comes from the environment or a git-ignored `.env`. Never print, log or commit it. If it leaks, the
  user must reset it in the Developer Portal.
- `discord.Intents.default()` is enough for slash commands. `members`, `presences` and `message_content` are
  privileged; ask before enabling them.

## Dependencies
- `uv add "discord.py==2.7.1"` and commit `uv.lock`. Ask before adding anything else. Never pass user input to a
  shell, `eval` or a SQL string.
Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 10 lines
Download Raw
# CLAUDE.md

@AGENTS.md

## Claude Code notes
- Skills from this loadout live in `.claude/skills/`: `discord-py-command`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Discord bot in Python (discord.py 2)" v1.0.0 — https://loadout.jnw-it.com/loadouts/discord-bot-python/ -->
Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 10 lines
Download Raw
# GEMINI.md

@./AGENTS.md

## Gemini CLI notes
- Skills from this loadout live in `.agents/skills/`: `discord-py-command`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Discord bot in Python (discord.py 2)" v1.0.0 — https://loadout.jnw-it.com/loadouts/discord-bot-python/ -->
Skill skills/discord-py-command/SKILL.md Add a slash command as a tested cog. 24 lines
Download Raw
---
name: discord-py-command
description: Add a slash command to a discord.py 2 bot as a cog, with described and range-limited options, a cooldown, an error handler and offline tests of the logic and the command payload.
---

# Add a discord.py slash command

Examples: `references/dice.py` (cog), `references/bot.py` (entry point) and `references/test_dice.py` (tests).

1. Put the logic in a plain function that takes plain values and returns plain values (`roll(count, sides, rng)`).
   Inject randomness, clocks and HTTP clients so tests can control them.
2. Create or extend a cog in `cogs/<feature>.py`:
   - `@app_commands.command(name=..., description=...)`
   - `@app_commands.describe(...)` for every option
   - `app_commands.Range[...]` or `Choice` for limits
   - a cooldown for anything costly, and `@app_commands.default_permissions` for moderation
3. Respond within 3 seconds; defer first if the work may be slow.
4. Add a `.error` handler: cooldowns get a friendly ephemeral message; anything else is logged and gets
   "Something went wrong."
5. Export `async def setup(bot)` and add the module to the extensions list in `bot.py`.
6. Tests, with no network: the logic function (including bad input), and the payload Discord will receive:
   `bot.add_cog(...)`, then `c.to_dict(bot.tree)` for each command, checking names, descriptions and limits.
7. Run `uv run pytest`, `uv run ruff check` and `uv run ruff format --check`. Remind the user to sync commands.
Skill file skills/discord-py-command/references/dice.py Cog with a validated, rate-limited command. 62 lines
Download Raw
"""Dice cog: one slash command with validated options, a cooldown and logic that can be tested offline."""

import logging
import random

import discord
from discord import app_commands
from discord.ext import commands

log = logging.getLogger(__name__)

MAX_DICE = 20


def roll(count: int, sides: int, rng: random.Random) -> list[int]:
    """Pure logic, no Discord objects: easy to test."""
    if not 1 <= count <= MAX_DICE:
        raise ValueError(f"count must be 1-{MAX_DICE}")
    if not 2 <= sides <= 1000:
        raise ValueError("sides must be 2-1000")
    return [rng.randint(1, sides) for _ in range(count)]


def format_roll(results: list[int], sides: int) -> str:
    total = sum(results)
    shown = ", ".join(map(str, results)) if len(results) <= 10 else f"{len(results)} dice"
    return f"Rolled {len(results)}d{sides}: {shown} (total {total})"


class Dice(commands.Cog):
    def __init__(self, bot: commands.Bot, rng: random.Random | None = None) -> None:
        self.bot = bot
        self.rng = rng or random.Random()

    @app_commands.command(name="roll", description="Roll dice, for example 3 six-sided dice")
    @app_commands.describe(count="How many dice (1-20)", sides="Sides per die (2-1000)")
    @app_commands.checks.cooldown(1, 3.0, key=lambda i: i.user.id)
    async def roll_command(
        self,
        interaction: discord.Interaction,
        count: app_commands.Range[int, 1, MAX_DICE] = 1,
        sides: app_commands.Range[int, 2, 1000] = 6,
    ) -> None:
        results = roll(count, sides, self.rng)
        await interaction.response.send_message(format_roll(results, sides))

    @roll_command.error
    async def roll_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError) -> None:
        if isinstance(error, app_commands.CommandOnCooldown):
            message = f"Slow down: try again in {error.retry_after:.0f}s."
        else:
            log.exception("roll failed", exc_info=error)
            message = "Something went wrong."
        if interaction.response.is_done():
            await interaction.followup.send(message, ephemeral=True)
        else:
            await interaction.response.send_message(message, ephemeral=True)


async def setup(bot: commands.Bot) -> None:
    await bot.add_cog(Dice(bot))
Skill file skills/discord-py-command/references/bot.py Entry point with default intents and manual sync. 41 lines
Download Raw
"""Entry point: minimal intents, cogs loaded in setup_hook, token from the environment, no sync on start."""

import asyncio
import logging
import os

import discord
from discord.ext import commands

EXTENSIONS = ["cogs.dice"]


class Bot(commands.Bot):
    def __init__(self) -> None:
        # Slash commands need no privileged intents. Do not enable message_content or members unless required.
        super().__init__(command_prefix=commands.when_mentioned, intents=discord.Intents.default())

    async def setup_hook(self) -> None:
        for ext in EXTENSIONS:
            await self.load_extension(ext)
        guild_id = os.environ.get("DISCORD_DEV_GUILD_ID")
        if os.environ.get("SYNC_COMMANDS") == "1" and guild_id:
            # Sync by hand, to one dev guild: instant, and avoids the global sync rate limit.
            guild = discord.Object(id=int(guild_id))
            self.tree.copy_global_to(guild=guild)
            synced = await self.tree.sync(guild=guild)
            logging.info("synced %d commands to guild %s", len(synced), guild_id)


async def main() -> None:
    token = os.environ.get("DISCORD_TOKEN")
    if not token:
        raise SystemExit("DISCORD_TOKEN is not set (put it in .env, never in code)")
    discord.utils.setup_logging()
    async with Bot() as bot:
        await bot.start(token)


if __name__ == "__main__":
    asyncio.run(main())
Skill file skills/discord-py-command/references/test_dice.py Offline tests of logic and payload. 40 lines
Download Raw
import asyncio
import random

import discord
import pytest
from discord.ext import commands

from cogs.dice import Dice, format_roll, roll


def test_roll_is_deterministic_with_a_seeded_rng():
    assert roll(3, 6, random.Random(42)) == roll(3, 6, random.Random(42))
    assert all(1 <= r <= 6 for r in roll(20, 6, random.Random(1)))


@pytest.mark.parametrize(("count", "sides"), [(0, 6), (21, 6), (1, 1), (1, 1001)])
def test_roll_rejects_out_of_range(count, sides):
    with pytest.raises(ValueError):
        roll(count, sides, random.Random(0))


def test_format_roll_summarises_many_dice():
    assert format_roll([1, 2, 3], 6) == "Rolled 3d6: 1, 2, 3 (total 6)"
    assert format_roll([1] * 12, 4) == "Rolled 12d4: 12 dice (total 12)"


def test_command_payload_registers_offline():
    async def build():
        bot = commands.Bot(command_prefix=commands.when_mentioned, intents=discord.Intents.default())
        await bot.add_cog(Dice(bot))
        return [c.to_dict(bot.tree) for c in bot.tree.get_commands()]

    (payload,) = asyncio.run(build())
    assert payload["name"] == "roll"
    options = {o["name"]: o for o in payload["options"]}
    assert options["count"]["description"] == "How many dice (1-20)"
    assert (options["count"]["min_value"], options["count"]["max_value"]) == (1, 20)
    assert options["sides"]["max_value"] == 1000
    assert not options["count"]["required"]

All files: source zip · Licence MIT · By Agent Loadout · First listed

Installing and testing

How do I install Discord bot in Python (discord.py 2) in Claude Code?

From your project root, put AGENTS.md, CLAUDE.md, skills/discord-py-command/SKILL.md at .claude/skills/discord-py-command/SKILL.md, skills/discord-py-command/references/dice.py at .claude/skills/discord-py-command/references/dice.py, skills/discord-py-command/references/bot.py at .claude/skills/discord-py-command/references/bot.py, skills/discord-py-command/references/test_dice.py at .claude/skills/discord-py-command/references/test_dice.py. The "Claude Code" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/discord-bot-python/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Discord bot in Python (discord.py 2) in Codex CLI?

From your project root, put AGENTS.md, skills/discord-py-command/SKILL.md at .agents/skills/discord-py-command/SKILL.md, skills/discord-py-command/references/dice.py at .agents/skills/discord-py-command/references/dice.py, skills/discord-py-command/references/bot.py at .agents/skills/discord-py-command/references/bot.py, skills/discord-py-command/references/test_dice.py at .agents/skills/discord-py-command/references/test_dice.py. The "Codex CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/discord-bot-python/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Discord bot in Python (discord.py 2) in Cursor?

From your project root, put AGENTS.md, skills/discord-py-command/SKILL.md at .agents/skills/discord-py-command/SKILL.md, skills/discord-py-command/references/dice.py at .agents/skills/discord-py-command/references/dice.py, skills/discord-py-command/references/bot.py at .agents/skills/discord-py-command/references/bot.py, skills/discord-py-command/references/test_dice.py at .agents/skills/discord-py-command/references/test_dice.py. The "Cursor" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/discord-bot-python/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Discord bot in Python (discord.py 2) in Gemini CLI?

From your project root, put AGENTS.md, GEMINI.md, skills/discord-py-command/SKILL.md at .agents/skills/discord-py-command/SKILL.md, skills/discord-py-command/references/dice.py at .agents/skills/discord-py-command/references/dice.py, skills/discord-py-command/references/bot.py at .agents/skills/discord-py-command/references/bot.py, skills/discord-py-command/references/test_dice.py at .agents/skills/discord-py-command/references/test_dice.py. The "Gemini CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/discord-bot-python/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Discord bot in Python (discord.py 2) in Antigravity CLI?

From your project root, put AGENTS.md, skills/discord-py-command/SKILL.md at .agents/skills/discord-py-command/SKILL.md, skills/discord-py-command/references/dice.py at .agents/skills/discord-py-command/references/dice.py, skills/discord-py-command/references/bot.py at .agents/skills/discord-py-command/references/bot.py, skills/discord-py-command/references/test_dice.py at .agents/skills/discord-py-command/references/test_dice.py. The "Antigravity CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/discord-bot-python/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Discord bot in Python (discord.py 2) in GitHub Copilot?

From your project root, put AGENTS.md, skills/discord-py-command/SKILL.md at .agents/skills/discord-py-command/SKILL.md, skills/discord-py-command/references/dice.py at .agents/skills/discord-py-command/references/dice.py, skills/discord-py-command/references/bot.py at .agents/skills/discord-py-command/references/bot.py, skills/discord-py-command/references/test_dice.py at .agents/skills/discord-py-command/references/test_dice.py. The "GitHub Copilot" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/discord-bot-python/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

Has Discord bot in Python (discord.py 2) been tested?

It passed the Level 1 automated install checks on 2026-10-17: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.

More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.