The reusable Discord-bot framework extracted from Discoin — the runtime
that actually runs a bot. It is bot-agnostic: the same framework powers
Disco (the economy / NFT / game bot), Recycler (the ,clanker
containment bot) and any future bot you build, and it routes every bot's
AI through the Auren platform.
Nothing was removed when this was split out of Discoin — the framework was re-homed here and decoupled from the economy so it can be reused.
This repository ships three importable top-level packages. Every bot installs this framework and imports them unchanged:
| Package | What it provides |
|---|---|
core |
core.config.Config (env-driven settings), core.database (asyncpg pool + PgRow helpers) and the whole core.framework runtime |
core.framework |
The bot itself: FrameworkBot, prefix routing, the cog loader, the embedded FastAPI server, the agent-tools engine, the AI bridge (core.framework.ai), error tracking, the live dashboard, charts/render, graceful shutdown — and core.framework.run.run(), the shared entrypoint |
constants |
Shared UI colors + validation constants used across the runtime |
security |
The behavioural threat-scoring / detection engine |
database |
The shared data plane: asyncpg data-access layer (Database/PgDatabase + repos), the migration runner and schema.sql. Bots get a working DB out of the box; a bot can also inject its own via FrameworkBot(db_factory=...) |
What it deliberately does not ship: game cogs (each bot's features), the
domain services, shop catalogs, the dashboard frontend. Those belong to each
concrete bot. The data plane is bundled here because the runtime is built on
it; a bot that wants a slim, custom schema can still supply its own
database.Database via db_factory.
A bot built on the framework is small. It provides:
- A
cogs/package with its own features. - A
database/package exposing aDatabaseclass (its schema/repos). - A
auren.jsonmanifest declaring its identity, cog list (features) and settings. - A three-line
main.py. - A
Dockerfilethat installs this framework from its repo and runs the bot.
One declarative file at the repo root is the contract a bot satisfies. The
framework reads features (the cog list, which supersedes the old
bot_manifest.COGS) and settings at boot; the Auren control plane
reads the same file to deploy and manage the bot (see the managed-bots
platform spec). Phase 1 wires only the framework side — bots still run as
today.
settings field types map to a dynamic-UI control and a validator:
string · number · boolean · select (needs options) · secret
(masked, vault-bound) · discord_channel · discord_role. An unknown type
fails validation rather than rendering blank. Validate any manifest with:
python -m core.framework.manifest path/to/auren.jsonfrom core.framework.run import run_manifest
from bot_manifest import COGS, APP_NAME
if __name__ == "__main__":
# Boots from auren.json; falls back to the legacy cog list so the bot
# still starts as before if the manifest is ever missing or invalid.
run_manifest(fallback_cogs=COGS, fallback_app_name=APP_NAME)That's it. run_manifest() loads + validates the manifest, then runs
FrameworkBot off its features/identity with the same rate-limit-aware
startup retry loop and graceful SIGTERM/SIGINT draining as before.
FrameworkBot lazily imports the bot's own database.Database for its data
layer (or pass db_factory=...).
The loaded, validated settings are exposed as bot.settings — a typed view
that resolves each field from a control-plane override (Phase 2), then an
env var named after the field key, then the field's default:
wait = self.bot.settings.get("CLANK_ESCAPE_WAIT_MINUTES") # 8 by default
if "log_channel" in self.bot.settings:
channel_id = self.bot.settings["log_channel"]A bot booted from the legacy bot_manifest.COGS path (no manifest) still gets
a usable, empty bot.settings, so cog code is identical either way.
The legacy
run(cogs=COGS, app_name=APP_NAME)entrypoint still works unchanged;bot_manifest.pyis kept as the fallback the manifest supersedes.
FROM python:3.12-slim
WORKDIR /app
# Build + install the framework straight from the Framework repo:
RUN pip install "git+https://github.com/HiLleywyn/Framework.git@main"
COPY requirements.txt .
RUN pip install -r requirements.txt # the bot's own extra deps
COPY . .
CMD ["python", "main.py"]Every AI feature in every bot goes through core.framework.ai. When the bot
sets AUREN_AI_BASE_URL (and the tenant token AUREN_AI_API_KEY), the
AI client routes all chat completions through the Auren proxy gateway
— the platform's AI controller, OpenAI-compatible at /v1/chat/completions
with tenant auth, billing and risk checks — instead of calling OpenRouter
directly. Flip those env vars and a whole bot's AI surface (e.g. the
Recycler ,clanker AI) moves onto Auren with no code changes.
| Env var | Meaning |
|---|---|
AUREN_AI_BASE_URL |
Base URL of the Auren deployment. Empty → call OpenRouter directly. |
AUREN_AI_API_KEY |
Per-bot Auren tenant token. |
AUREN_BOT_ID |
Stable id Auren uses to attribute this bot's AI usage. |
APP_NAME |
Human name of the bot (presence + logs). |
The split that makes this reusable:
- Cog registry decoupled —
FrameworkBot(cogs=[...]); no bot's cog list is baked into the framework. - Data layer injected — the framework lazily imports each bot's
database.Database(or takesdb_factory=); no economy schema baked in. - API server optional — the embedded HTTP/dashboard server only starts if
the bot ships an
api.v2app; minimal bots skip it. - Economy-free config —
core.configtolerates a missing economyconfigs.items_config, so a bot with no shop still boots. - AI controller pluggable — one env var routes AI through Auren.
{ "manifest_version": "1", "bot": { "slug": "recycler", "name": "Recycler", "version": "1.0.0" }, "channels": ["discord"], "runtime": { "dockerfile": "Dockerfile", "entrypoint": "main.py" }, "credentials": [ { "key": "DISCORD_TOKEN", "label": "Bot token", "secret": true } ], "provision": { "database": "postgres" }, "settings": { "groups": [ { "id": "clanktank", "label": "Clanktank", "fields": [ { "key": "CLANK_ESCAPE_WAIT_MINUTES", "type": "number", "label": "Reflection wait (minutes)", "default": 8, "min": 1, "max": 120 } ]} ]}, "features": ["cogs.clanktank"] }