Skip to content

bostie

bostie is a Discord library for Python whose gateway, HTTP client and voice engine run in Rust. You write the bot in typed, async Python.

Bostie, a Boston Terrier, welcoming you with open arms

Pre-alpha

bostie's API can still change. Slash, user and message commands, components, modals, gateway events, the cache, REST, webhooks, HTTP interactions, OAuth2, and voice playback and receiving work today.

import bostie
from bostie import ComponentContext, Context, ui, voice

bot = bostie.Bot()


@bot.command
async def play(ctx: Context, url: str) -> None:
    """Play a direct media URL in your voice channel."""
    vc = ctx.voice.current or await ctx.voice.connect()
    await vc.play(voice.Url(url))
    stop = ui.button("Stop", custom_id=stop_playing.id(guild_id=vc.guild_id))
    await ctx.respond(components=[ui.text(f"Playing <{url}>"), ui.row(stop)])


@bot.on_component("stop:{guild_id:int}")
async def stop_playing(ctx: ComponentContext, guild_id: int) -> None:
    if (vc := bot.voice.get(guild_id)) is not None:
        vc.stop()
    await ctx.update(components=[ui.text("Stopped.")])


bot.run()

/play joins the voice channel of the user who ran it, streams the URL and answers with a Stop button. The button's custom id carries the guild id, and @bot.on_component parses it back into an int, so the button still works after the bot restarts. bot.run() reads the token from the BOSTIE_TOKEN environment variable and sends the command to Discord when it changed.

Using an AI coding assistant?

Install the bostie skill to make your coding assistant better at bostie tasks. Copy the prompt and paste it into your coding assistant. More about the skill

Install the bostie skill
Install the bostie skill for my coding agent so it writes correct bostie code: Discord bots with slash commands, components, events, REST calls, voice and tests.

Use the Agent Skills installer with the bostie repository, https://github.com/nicokim/bostie:

    npx skills add nicokim/bostie --skill bostie --yes

If I ask for a global install instead, use:

    npx skills add nicokim/bostie --skill bostie --yes --global

Detect which agent or editor I am using. If it is one of these and I prefer the plugin path, install the bostie plugin instead, as https://bostie.dev/assistants/ shows:

    Claude Code: /plugin marketplace add nicokim/bostie, then /plugin install bostie@bostie
    Codex CLI: codex plugin marketplace add nicokim/bostie, then codex plugin add bostie@bostie
    GitHub Copilot CLI: copilot plugin marketplace add nicokim/bostie, then copilot plugin install bostie@bostie
    VS Code with GitHub Copilot: run "Chat: Install Plugin from Source" and enter nicokim/bostie
    Devin: devin plugins install nicokim/bostie#docs
    Factory Droid: droid plugin marketplace add nicokim/bostie, then droid plugin install bostie@bostie --scope user
    Qwen Code: qwen extensions install nicokim/bostie:bostie

Do not invent other skill names or install URLs.

Then add this line to the instructions file you read at the start of every session (AGENTS.md at the project root, or your global one for a global install), and create the file if needed:

    Before you write or change code that uses bostie, look at how the project is laid out, then load the bostie skill.

After installing, confirm that the bostie skill is available to you.

What it is built for

Voice

Discord has accepted only end-to-end encrypted calls (DAVE) since March 1, 2026. bostie's voice engine runs on Rust threads, outside the GIL: the 20 ms clock, decoding, mixing, Opus encoding, transport encryption and DAVE. Python only has to keep each sink's buffer filled, 0.2 seconds of audio by default. bostie plays files, URLs, HLS streams and your own PCM or Opus, mixes several sources, receives each speaker on their own and records calls.

Bots that survive Discord's changes

Unknown fields, enum values, component types and interaction types never break decoding. Every event, and every model decoded from a whole payload, keeps the raw JSON, and bot.http and raw event listeners reach what bostie does not model yet.

Types that match Discord's documentation

A field Discord always sends is never None, and a partial object has its own class: a member resolved from a command option is a ResolvedMember, which has no deaf or mute.

Commands from signatures

Options, choices, ranges and autocomplete come from type hints. Names, descriptions, option counts and choices are checked against Discord's limits when the decorator runs, not when Discord rejects the sync.

Tests without Discord

bostie.testing.FakeDiscord is a local gateway and REST API to run your real bot against.

Where to go next