Embedding into Another Bot¶
Dango's agent is a standard discord.py Cog. Drop ChatCog (and optionally
AdminCog) into an existing bot and it gains natural-language chat, web search,
and file access — while every slash command and prefix command you already have
keeps working untouched.
What you get¶
| Capability | How to enable | Notes |
|---|---|---|
| Chat | always on | History-aware natural-language replies |
| Web search | ENABLE_DUCKDUCKGO=on / ENABLE_BRAVE_SEARCH=on |
Agent decides when to search |
| Read URL | ENABLE_WEBSITE_TOOLS=on |
Agent reads links that appear in messages |
| Workspace | ENABLE_WORKSPACE=on |
Read-only access to a local folder (extra setup) |
| Custom commands & tools | write Python in custom/ |
Custom Commands & Tools |
Every toggle is read from the environment. Full list and per-feature behaviour: Environment Variables and Tools.
Install¶
uv add git+https://github.com/zhiro-labs/dango # uv
pip install git+https://github.com/zhiro-labs/dango # pip
Minimal integration¶
Dango reads environment variables at import time, so load_dotenv() must run
before the first dango import. Then load the Cogs in setup_hook:
# main.py
from dotenv import load_dotenv
load_dotenv() # MUST run before any dango import
import os
import discord
from discord.ext import commands
from dango import ChatCog, AdminCog, create_discord_workflow, RuntimeConfig
bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
@bot.event
async def setup_hook():
with open(os.getenv("CHAT_SYS_PROMPT_PATH", "config/chat_sys_prompt.txt"), encoding="utf-8") as f:
chat_system_prompt = f.read()
runtime_config = RuntimeConfig("config/runtime.yml")
discord_workflow = create_discord_workflow()
await bot.add_cog(ChatCog(bot, discord_workflow, chat_system_prompt, runtime_config))
await bot.add_cog(AdminCog(bot, runtime_config)) # optional — admin slash commands
await bot.tree.sync()
That is the whole integration. ChatCog handles incoming messages; AdminCog
adds admin slash commands (optional). Everything else is configuration.
Three things that will bite you
load_dotenv()before imports. dango resolves model/feature settings at import time. Importing before.envis loaded silently uses defaults.uv rundoes not load.env. You must callload_dotenv()yourself —uvonly injects shell-exported variables.- Register Cogs in
setup_hook, neveron_ready.setup_hookruns once before the gateway connects, so no events are missed.
Required intents¶
ChatCog needs message_content (to read messages) and members (for display
names). discord.Intents.all() covers these; to be explicit:
intents = discord.Intents(
guilds=True, guild_messages=True, dm_messages=True,
message_content=True, # required: read message text
members=True, # required: resolve display names
)
Configuration¶
The host bot owns its own configuration; dango only reads the variables it needs.
- Discord token is yours, not dango's.
DISCORD_BOT_TOKENbelongs to the host bot's config — dango never reads it. - Model + features come from the environment. See
Environment Variables for the complete list and
start from the repo's commented
.env.example. -
When does the bot respond? By default only when @mentioned. To answer every message in a channel, seed channel IDs on first run or add them at runtime:
# Seeds config/runtime.yml on first run only; later /removechannel edits are kept. runtime_config = RuntimeConfig("config/runtime.yml", default_channels=[CHANNEL_ID])Or let an admin run
/addchannelin the target channel after startup. -
Generated files to add to
.gitignore:
Workspace: extra setup when embedding¶
The standalone bot initialises the workspace automatically; an embedded bot must do it itself. Without this call the workspace system prompt is never generated and the agent has no knowledge of the files.
@bot.event
async def setup_hook():
if os.getenv("ENABLE_WORKSPACE") == "on":
from dango.utils import workspace_context
await workspace_context.init(
os.getenv("WORKSPACE_ROOT", "workspace"),
os.getenv("WORKSPACE_SYS_PROMPT_PATH", "config/workspace_sys_prompt.txt"),
)
# ... add_cog calls follow
init() reads the files, builds a topic-index prompt block, and caches it; the
cache invalidates automatically when the files change. See
Tools → Workspace for behaviour.
The objects you wire up¶
| Object | Role |
|---|---|
create_discord_workflow() |
Builds the Agno Workflow (fetch history → call agent → render tables → send). Call once. |
RuntimeConfig(path, default_channels=None) |
Allowed channels/users, history limit, timezone, activity. Pass the same instance to both Cogs. |
ChatCog(bot, workflow, system_prompt, runtime_config) |
Handles messages and runs the pipeline. |
AdminCog(bot, runtime_config) |
Admin slash commands (/addchannel, /settimezone, …), all require the Administrator permission. |
Full signatures: API Reference. The admin commands are listed under Slash Commands.
Add your own commands and tools¶
To extend the embedded agent with your own slash commands or LLM-callable tools —
including ones that are both at once — use the dango.extensions SDK and drop
Python files into a gitignored custom/ directory. See
Custom Commands & Tools.
Full example — Neko reminder bot¶
Neko is an event-reminder bot with its own slash commands (/event,
/event-list, /event-delete, …). Adding Dango lets users chat with it in plain
language while every existing command keeps working.
1. Install in the Neko project: uv add git+https://github.com/zhiro-labs/dango
2. Configure .env:
FAST_MODEL=google:gemini-2.0-flash
FAST_API_KEY=your_api_key_here
CHAT_SYS_PROMPT_PATH=config/chat_sys_prompt.txt
ENABLE_DUCKDUCKGO=on
3. Write the system prompt (config/chat_sys_prompt.txt). Describe the bot's
existing commands so the agent can point users to them — this is what turns chat
into a front door for your slash commands:
You are Neko, a Discord event reminder assistant. Guide users to the right command:
- Create an event → /event
- See upcoming events → /event-list
- Delete an event → /event-delete
- Set the reminder timezone → /set-reminder-timezone
Reply in a friendly, helpful tone.
4. Wire it into main.py using the minimal integration
pattern. Add the Dango pieces inside Neko's existing setup_hook (after its own
db.init_db() etc.), and pass default_channels=[YOUR_CHANNEL_ID] so Neko replies
to every message in your chosen channel.
The result: all original slash commands work unchanged, and users can now ask questions in plain English — the agent answers, suggests the right command, and searches the web when it helps.
Updating¶
uv add git+https://github.com/zhiro-labs/dango # uv
pip install --upgrade git+https://github.com/zhiro-labs/dango # pip
Restart the bot to pick up the new version.