A guide to build AI bots with Botpress's Agent Development Kit (ADK)
version
1.0.0
author
yueranlu
tags
["botpress","adk","chatbot","ai","typescript"]
homepage
https://github.com/botpress/adk
Botpress ADK Development Guide
A comprehensive guide for building AI bots with the Botpress Agent Development Kit (ADK).
When to Use
User asks to build a Botpress bot or chatbot
User mentions ADK, Agent Development Kit, or Botpress
User wants to create actions, tools, workflows, conversations, tables, triggers, or knowledge bases
User needs help with adk CLI commands (init, dev, deploy, link)
User has ADK-related errors or needs troubleshooting
User asks about bot configuration, state management, or integrations
Quick Reference
The ADK is a convention-based TypeScript framework where file structure maps directly to bot behavior.
Your role: Guide users through the entire bot development lifecycle - from project setup to deployment. Use the patterns and code examples in this skill to write correct, working ADK code.
Key principle: In ADK, where you put files matters. Each component type has a specific src/ subdirectory, and files are auto-discovered based on location.
How to Use This Skill
This skill is your primary reference for building Botpress bots. When a user asks you to build something with the ADK:
Identify what they need - Is it a new bot, a feature (action, tool, workflow), data storage (table), or event handling (trigger)?
Check the correct directory - Each component type goes in a specific src/ subdirectory
Use the patterns below - Follow the code examples exactly, they represent the correct ADK conventions
Run adk --help - For CLI commands not covered here, or adk <command> --help for specific help
Decision Guide - What Component to Create:
User Wants To...
Create This
Location
Handle user messages
Conversation
src/conversations/
Add a function the AI can call
Tool
src/tools/
Add reusable business logic
Action
src/actions/
Run background/scheduled tasks
Workflow
src/workflows/
Store structured data
Table
src/tables/
React to events (user created, etc.)
Trigger
src/triggers/
Give AI access to docs/data
Knowledge Base
src/knowledge/
Connect external service (Slack, etc.)
Integration
adk add <name>
If the information in this skill isn't enough, fetch the corresponding GitHub reference file (links provided in each section) for more detailed specifications.
Important: ADK is AI-Native
The ADK does NOT use traditional chatbot patterns. Don't create intents, entities, or dialog flows.
Instead of:
Defining intents (greet, orderPizza, checkStatus)
Training entity extraction (@pizzaSize, @toppings)
Manually routing to intent handlers
ADK uses:
execute() - The AI understands user intent naturally from instructions
Tools - AI autonomously decides when to call your functions
zai.extract() - Schema-based structured data extraction
Knowledge bases - RAG for grounding responses in your docs
adk init my-bot # Create project (choose "Hello World" template for beginners)cd my-bot
npm install # Or bun/pnpm/yarn
adk login # Authenticate with Botpress Cloud
adk add chat # Add the chat integration for testing
adk dev # Start dev server with hot reload
adk chat # Test in CLI (run in separate terminal)
adk deploy # Deploy to production when ready
The visual console at http://localhost:3001/ lets you configure integrations and test the bot.
IMPORTANT: Your bot must be linked to Botpress Cloud and deployed for it to work. The ADK runs locally during development but the bot itself lives in Botpress Cloud.
The Correct Order: Link → Dev → Deploy
Follow this order to get your bot working:
# 1. LINK - Connect your project to Botpress Cloud (creates agent.json)
adk link# 2. DEV - Start the development server (hot reload, testing)
adk dev
# 3. DEPLOY - Push to production when ready
adk deploy
Step-by-step:
adk link - Links your local project to a bot in Botpress Cloud. This creates agent.json with your workspace and bot IDs. Run this first before anything else.
adk dev - Starts the local development server with hot reloading. Opens the dev console at http://localhost:3001 where you can configure integrations and test your bot. Use adk chat in a separate terminal to test.
adk deploy - Deploys your bot to production. Run this when you're ready for your bot to be live and accessible through production channels (Slack, WhatsApp, webchat, etc.).
Troubleshooting Errors
If you encounter errors when running adk dev or adk deploy:
Check the logs - Look at the terminal output or the logs panel in the dev console at http://localhost:3001
Copy the error message - Select and copy the full error message from the logs
Ask for help - Paste the error back to the AI assistant and ask it to help fix the issue
Common error scenarios:
Integration configuration errors: Usually means an integration needs to be configured in the UI at localhost:3001
Type errors: Often caused by incorrect imports or schema mismatches
Deployment failures: May indicate missing environment variables or invalid configuration
Example workflow for fixing errors:
1. Run `adk dev` or `adk deploy`
2. See error in terminal/logs
3. Copy the error message
4. Tell the AI: "I got this error when running adk dev: [paste error]"
5. The AI will help diagnose and fix the issue
Critical rule: File location determines behavior. Place components in the correct src/ subdirectory or they won't be discovered.
my-bot/
├── agent.config.ts # Bot configuration: name, models, state schemas, integrations
├── agent.json # Workspace/bot IDs (auto-generated by adk link/dev, add to .gitignore)
├── package.json # Node.js dependencies and scripts (dev, build, deploy)
├── tsconfig.json # TypeScript configuration
├── .env # API keys and secrets (never commit!)
├── .gitignore # Should include: agent.json, .env, node_modules/, .botpress/
├── src/
│ ├── conversations/ # Handle incoming messages → use execute() for AI responses
│ ├── workflows/ # Background processes → use step() for resumable operations
│ ├── actions/ # Reusable functions → call from anywhere with actions.name()
│ ├── tools/ # AI-callable functions → AI decides when to invoke these
│ ├── tables/ # Data storage → auto-synced to cloud, supports semantic search
│ ├── triggers/ # Event handlers → react to user.created, integration events, etc.
│ └── knowledge/ # RAG sources → index docs, websites, or tables for AI context
└── .botpress/ # Auto-generated types (never edit manually)
Key Configuration Files:
agent.config.ts - Primary configuration defining bot metadata, AI models, state schemas, and integrations (you edit this)
agent.json - Links agent to workspace/bot IDs. Auto-generated by adk link or adk dev. Add to .gitignore - contains environment-specific IDs that differ per developer
package.json - Node.js config with @botpress/runtime dependency and scripts for dev, build, deploy
tsconfig.json - TypeScript configuration for the project
.env - Environment variables for API keys and secrets (never commit!)
.gitignore - Should include: agent.json, .env, node_modules/, .botpress/
You need reusable logic that will be called from multiple places (workflows, conversations, triggers)
You're wrapping an external API or database operation
You want testable, composable business logic
You need to call integration APIs (Slack, Linear, etc.) with custom logic
When NOT to use an Action (use a Tool instead):
You want the AI to decide when to call it autonomously
The function should be available during execute()
Actions are not directly callable by the AI - convert them to tools with .asTool() if the AI needs to use them.
Location:src/actions/*.ts
import { Action, z } from"@botpress/runtime";
exportconst fetchUser = newAction({
name: "fetchUser",
description: "Retrieves user details from the database",
// Define input/output with Zod schemas for type safetyinput: z.object({ userId: z.string() }),
output: z.object({ name: z.string(), email: z.string() }),
// IMPORTANT: Handler receives { input, client } - destructure input INSIDE the handlerasynchandler({ input, client }) {
const { user } = await client.getUser({ id: input.userId });
return { name: user.name, email: user.tags.email };
}
});
Calling actions:
import { actions } from"@botpress/runtime";
const userData = await actions.fetchUser({ userId: "123" });
// To make an action callable by the AI, convert it to a tool:tools: [actions.fetchUser.asTool()]
Key Rules:
Handler receives { input, client } - must destructure input inside the handler
Cannot destructure input fields directly in parameters
Can call other actions, integration actions, access state
You want the AI to autonomously decide when to use this function
The function retrieves information the AI needs (search, lookup, fetch)
The function performs actions on behalf of the user (create ticket, send message)
You're building capabilities the AI should have during conversations
The AI decides when to use tools based on:
The tool's description - Make this clear and specific about WHEN to use it
The input schema's .describe() fields - Help AI understand what parameters mean
The conversation context and user's intent
Key difference from Actions: Tools can destructure input directly; Actions cannot.
Location:src/tools/*.ts
import { Autonomous, z } from"@botpress/runtime";
exportconst searchProducts = newAutonomous.Tool({
name: "searchProducts",
// This description is critical - it tells the AI when to use this tooldescription: "Search the product catalog. Use when user asks about products, availability, pricing, or wants to browse items.",
input: z.object({
query: z.string().describe("Search keywords"),
category: z.string().optional().describe("Filter by category")
}),
output: z.object({
products: z.array(z.object({ id: z.string(), name: z.string(), price: z.number() }))
}),
// Unlike actions, tools CAN destructure input directly in the handlerhandler: async ({ query, category }) => {
// Your search logic herereturn { products: [] };
}
});
Using ThinkSignal: When a tool can't complete but you want to give the AI context:
import { Autonomous } from"@botpress/runtime";
// Inside handler - AI will see this message and can respond appropriatelythrownewAutonomous.ThinkSignal(
"No results found",
"No products found matching that query. Ask user to try different search terms."
);
Every bot needs at least one conversation handler to respond to users
Create separate handlers for different channels if they need different behavior
Use channel: "*" to handle all channels with one handler
Key decisions when building a conversation:
Which channels? - Specify "*" for all, or specific channels like "slack.dm"
What tools does the AI need? - Pass them to execute({ tools: [...] })
What knowledge should ground responses? - Pass to execute({ knowledge: [...] })
What instructions guide the AI? - Define personality, rules, and context
The execute() function is the heart of ADK - it runs autonomous AI logic with your tools and knowledge. Most conversation handlers will call execute().
Location:src/conversations/*.ts
import { Conversation, z } from"@botpress/runtime";
exportconstChat = newConversation({
// Which channels this handler responds tochannel: "chat.channel", // Or "*" for all, or ["slack.dm", "webchat.channel"]// Per-conversation state (optional)state: z.object({
messageCount: z.number().default(0)
}),
asynchandler({ message, state, conversation, execute, user }) {
state.messageCount += 1;
// Handle commandsif (message?.payload?.text?.startsWith("/help")) {
await conversation.send({
type: "text",
payload: { text: "Available commands: /help, /status" }
});
return;
}
// Let the AI handle the response with your tools and knowledgeawaitexecute({
// Instructions guide the AI's behavior and personalityinstructions: `You are a helpful customer support agent for Acme Corp.
User's name: ${user.state.name || "there"}
User's tier: ${user.state.tier}
Be friendly, concise, and always offer to help further.`,
// Tools the AI can use during this conversationtools: [searchProducts, actions.createTicket.asTool()],
// Knowledge bases for RAG - AI will search these to ground responsesknowledge: [DocsKnowledgeBase],
model: "openai:gpt-4o",
temperature: 0.7,
iterations: 10// Max tool call iterations
});
}
});
# Project Lifecycle
adk init <name> # Create new project
adk login # Authenticate with Botpress
adk dev # Start dev server (hot reload)
adk dev --port 3000 # Custom port
adk chat # Test in CLI
adk build # Build for production
adk deploy # Deploy to Botpress Cloud
adk deploy --env production # Deploy to specific environment# Integration Management
adk add <integration> # Add integration
adk add slack@2.5.5 # Add specific version
adk add slack --alias my-slack # Add with alias
adk remove <integration> # Remove integration
adk search <query> # Search integrations
adk list # List installed integrations
adk list --available # List all available
adk info <name> # Integration details
adk info <name> --events # Show available events
adk upgrade <name> # Update integration
adk upgrade # Interactive upgrade all# Knowledge & Assets
adk kb sync --dev # Sync knowledge bases
adk kb sync --prod --force # Force re-sync production
adk assets sync# Sync static files# Advanced
adk run <script.ts> # Run TypeScript script
adk mcp # Start MCP server
adk link --workspace ws_123 --bot bot_456 # Link to existing bot# Utilities
adk self-upgrade # Update CLI
adk telemetry --disable# Disable telemetry
adk --help# Full CLI help
adk <command> --help# Help for specific command
// In conversation - starting a workflow that needs to message backawaitMyWorkflow.start({
conversationId: conversation.id, // Always include this!data: "..."
});
// In workflow - messaging back to userawait client.createMessage({
conversationId: input.conversationId,
type: "text",
payload: { text: "Processing complete!" }
});
2. Use Environment Variables for Secrets
// In .env (never commit!)API_KEY=sk-...
SLACK_TOKEN=xoxb-...
// In codeconfig: { apiKey: process.env.API_KEY }
3. Keep Step Names Stable in Workflows
// GOOD - Single step for batchawaitstep("process-all-items", async () => {
for (const item of items) {
awaitprocessItem(item);
}
});
// BAD - Dynamic names break resumefor (let i = 0; i < items.length; i++) {
awaitstep(`process-${i}`, async () => { ... }); // Don't do this!
}
handler: async ({ query }) => {
const results = awaitsearch(query);
if (!results.length) {
thrownewAutonomous.ThinkSignal(
"No results",
"No results found. Ask the user to try different search terms."
);
}
return results;
}
Testing & Deployment - Local testing and cloud deployment
Common Patterns - Best practices and troubleshooting
Core Principle: The ADK is a convention-based framework where file location determines behavior. Place components in the correct src/ subdirectory and they automatically become bot capabilities.
When to use this skill:
User wants to create a new Botpress bot
User asks how to add actions, tools, workflows, conversations, tables, knowledge bases, or triggers
User needs help with integrations (Slack, Linear, GitHub, etc.)
User wants to understand ADK patterns and best practices
User has errors or needs troubleshooting
User asks about CLI commands, configuration, or deployment