Core Responsibilities
- Multi-channel chat - Web UI and Telegram bot integration
- Job creation - Generates job branches and triggers GitHub Actions
- Webhook handling - Receives notifications from GitHub, Telegram, external services
- Cron scheduling - Runs recurring tasks via
node-cron - Authentication - NextAuth v5 for web UI, API keys for external callers
- LLM integration - Real-time chat responses using multiple providers
Deployment Model
The Event Handler runs as a long-lived Docker container:- Installs the published
thepopebotnpm package - User project files (
config/,skills/,.env,data/) are volume-mounted at/app - Runs
server.jsvia PM2 process manager - Sits behind Traefik reverse proxy for HTTPS
All event handler logic lives in the npm package (
api/, lib/, config/). The user’s project contains only configuration files and thin Next.js wiring that imports from thepopebot/*.API Endpoints
All routes are served through a catch-all route (app/api/[...thepopebot]/route.js) that re-exports handlers from thepopebot/api.
Routes Reference
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/api/ping | GET | None | Health check, returns {"message": "Pong!"} |
/api/create-job | POST | x-api-key | Generic webhook for job creation |
/api/telegram/webhook | POST | Webhook secret | Telegram bot webhook |
/api/telegram/register | POST | x-api-key | Register Telegram webhook URL |
/api/github/webhook | POST | Webhook secret | GitHub Actions notifications |
/api/jobs/status | GET | x-api-key | Check job status |
Authentication Patterns
/api routes - External callers only
/api routes - External callers only
API routes authenticate via
x-api-key header or webhook secrets (Telegram, GitHub). API keys are stored in SQLite and managed through the admin UI — they are NOT environment variables.Server Actions - Browser UI only
Server Actions - Browser UI only
All authenticated browser-to-server calls use Next.js Server Actions (
'use server' functions) with requireAuth() session validation. Never use /api fetch calls from browser UI.Chat streaming - Dedicated route
Chat streaming - Dedicated route
The AI SDK’s
DefaultChatTransport requires an HTTP endpoint. Chat has its own route handler at /stream/chat with session auth (outside /api).| Caller | Mechanism | Auth | Location |
|---|---|---|---|
| External (cURL, GitHub Actions, Telegram) | /api route | x-api-key or webhook secret | api/index.js |
| Browser UI (data/mutations) | Server Action | requireAuth() session | lib/chat/actions.js |
| Browser UI (chat streaming) | Dedicated route | auth() session | lib/chat/api.js |
Example: Create a Job via Webhook
Directory Structure
The Event Handler code lives in the npm package:Job Creation Flow
When a job is created (via chat, webhook, or cron), the Event Handler:-
Generates job metadata
- Creates UUID for the job
- Uses LLM to generate a descriptive title (structured output prevents token leaks)
- Builds
job.config.jsonwith title, description, and optional LLM overrides
-
Creates GitHub branch
- Gets main branch SHA and tree SHA via GitHub API
- Creates tree entry for
logs/{uuid}/job.config.json - Creates commit with message
🤖 Agent Job: {title} - Creates
job/{uuid}branch pointing to commit
-
GitHub Actions takes over
- Branch creation triggers
run-job.yml - Workflow reads job config, collects secrets, launches Docker agent
- Branch creation triggers
Action Dispatch System
Both cron jobs and webhook triggers use a shared dispatch system (lib/actions.js). Every action has a type field:
Action Types
| Type | Uses LLM | Runtime | Cost | Use When |
|---|---|---|---|---|
agent | Yes | Minutes to hours | LLM API + GitHub Actions | Task needs to think |
command | No | Milliseconds to seconds | Free (event handler) | Task just needs to do |
webhook | No | Milliseconds to seconds | Free (event handler) | Call external service |
Cron Jobs
Defined inconfig/CRONS.json, loaded at startup via node-cron:
schedule- Cron expression (usesnode-cronsyntax)type-agent(creates job),command(runs shell command), orwebhook(HTTP request)enabled- Set tofalseto disable- LLM overrides - Optional
llm_providerandllm_modelfor agent-type jobs
Webhook Triggers
Defined inconfig/TRIGGERS.json, fire actions when specific endpoints are hit:
Template Variables
Triggers support template tokens injob and command strings:
{{body}}- Full request body as JSON{{body.field}}- Specific field from body{{query}}- Query params as JSON{{query.field}}- Specific query param{{headers}}- Request headers as JSON{{headers.field}}- Specific header
LLM Providers
The Event Handler supports multiple LLM providers for chat:| Provider | LLM_PROVIDER | Default Model | Required Env |
|---|---|---|---|
| Anthropic | anthropic (default) | claude-sonnet-4-20250514 | ANTHROPIC_API_KEY |
| OpenAI | openai | gpt-4o | OPENAI_API_KEY |
google | gemini-2.5-pro | GOOGLE_API_KEY | |
| Custom | custom | — | OPENAI_BASE_URL, CUSTOM_API_KEY |
Chat LLM config (
.env) is separate from agent job config (GitHub variables). They use the same variable names but can hold different values. For example, chat can use Sonnet while jobs use Opus.Channel Adapters
The Event Handler uses a channel adapter pattern to normalize messages from different platforms:- Message format (text, attachments, metadata)
- Thread IDs (for conversation continuity)
- Acknowledgements (typing indicators, read receipts)
- Response delivery
Database
SQLite via Drizzle ORM atdata/thepopebot.sqlite:
- API keys - Hashed, timing-safe comparison
- Messages - Thread-based storage for chat history
- Notifications - Job completion/failure notifications
- Workspaces - Code workspace metadata
Next Steps
Docker Agent
Learn how jobs execute in isolated containers
Skills System
Extend your agent with custom skills