OpenCord
Discord bot that provisions Daytona sandboxes running OpenCode sessions. Each Discord thread gets its own isolated sandbox with full conversational context.
How It Works
- Mention the bot in an allowed channel
- Bot creates a Discord thread and provisions a Daytona sandbox
- OpenCode runs inside the sandbox, responding to messages in the thread
- Inactive threads pause their sandbox automatically; activity resumes them
- Conversational context is preserved across bot restarts
Setup
Prerequisites
- Bun installed
- A Discord bot application (see below)
- A Daytona account with API access
- An OpenCode API key
1. Create a Discord Bot
- Go to the Discord Developer Portal
- Create a new application
- Go to Bot and click Reset Token — save this as
DISCORD_TOKEN
- Enable Message Content Intent under Privileged Gateway Intents
- Go to OAuth2 > URL Generator, select the
bot scope with permissions: Send Messages, Create Public Threads, Send Messages in Threads, Read Message History
- Use the generated URL to invite the bot to your server
2. Get Your API Keys
- Daytona: Sign up at daytona.io and generate an API key from your dashboard
- OpenCode: Get an API key from opencode.ai
- GitHub Token (optional): A personal access token — enables authenticated
gh CLI inside sandboxes
3. Configure and Run
Environment Variables
Required
| Variable |
Description |
DISCORD_TOKEN |
Bot token from the Discord Developer Portal |
DAYTONA_API_KEY |
API key from your Daytona dashboard |
OPENCODE_ZEN_API_KEY |
API key from OpenCode |
Optional — Discord
| Variable |
Default |
Description |
ALLOWED_CHANNEL_IDS |
(empty) |
Comma-separated channel IDs where the bot listens. Empty = all channels |
DISCORD_CATEGORY_ID |
(empty) |
Restrict the bot to a specific channel category |
DISCORD_ROLE_ID |
(empty) |
Role ID that triggers the bot via @role mentions |
DISCORD_REQUIRED_ROLE_ID |
(empty) |
Role users must have to interact with the bot |
Optional — Storage & Runtime
| Variable |
Default |
Description |
DATABASE_PATH |
discord.sqlite |
Path to the local SQLite file |
GITHUB_TOKEN |
(empty) |
Injected into sandboxes for authenticated gh CLI |
OPENCODE_MODEL |
opencode/claude-sonnet-4-5 |
Model used inside OpenCode sessions |
Optional — Bot Behavior
| Variable |
Default |
Description |
SANDBOX_REUSE_POLICY |
resume_preferred |
resume_preferred or recreate |
SANDBOX_TIMEOUT_MINUTES |
30 |
Minutes of inactivity before pausing a sandbox |
PAUSED_TTL_MINUTES |
180 |
Minutes a paused sandbox lives before being destroyed |
RESUME_HEALTH_TIMEOUT_MS |
120000 |
Timeout (ms) when waiting for a sandbox to resume |
SANDBOX_CREATION_TIMEOUT |
180 |
Timeout (s) for sandbox creation |
TURN_ROUTING_MODE |
ai |
How the bot decides if a message needs a response: off, heuristic, or ai |
TURN_ROUTING_MODEL |
claude-haiku-4-5 |
Model used for AI turn routing |
Optional — Observability
| Variable |
Default |
Description |
LOG_LEVEL |
info |
debug, info, warn, or error |
LOG_PRETTY |
false |
Pretty-print JSON logs |
HEALTH_HOST |
0.0.0.0 |
Host for the health HTTP server |
HEALTH_PORT |
8787 |
Port for the health HTTP server |
Commands
| Command |
Description |
bun run dev |
Watch mode |
bun run start |
Production run |
bun run db:init |
Initialize/migrate database |
bun run typecheck |
TypeScript checks |
bun run build |
Bundle for deployment |
bun run check |
Typecheck + build |
Health Endpoints
GET /healthz — Liveness check (uptime, Discord status, active sessions)
GET /readyz — Readiness check (200 when Discord connected, 503 otherwise)
Architecture
Sessions are persisted in a local SQLite file. Sandbox filesystem (including OpenCode session state) survives pause/resume cycles via Daytona stop/start.