botpress-adkA guide to build AI bots with Botpress's Agent Development Kit (ADK)
Install via ClawdBot CLI:
clawdbot install yueranlu/botpress-adkA comprehensive guide for building AI bots with the Botpress Agent Development Kit (ADK).
adk CLI commands (init, dev, deploy, link)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.
This skill is your primary reference for building Botpress bots. When a user asks you to build something with the ADK:
src/ subdirectoryadk --help - For CLI commands not covered here, or adk --help for specific helpDecision 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 |
If the information in this skill isn't enough, fetch the corresponding GitHub reference file (links provided in each section) for more detailed specifications.
The ADK does NOT use traditional chatbot patterns. Don't create intents, entities, or dialog flows.
Instead of:
greet, orderPizza, checkStatus)@pizzaSize, @toppings)ADK uses:
execute() - The AI understands user intent naturally from instructionszai.extract() - Schema-based structured data extractionDocs: https://www.botpress.com/docs/adk/
GitHub: https://github.com/botpress/skills/tree/master/skills/adk
Before using the ADK, ensure the user has:
node --versionInstall the ADK CLI:
macOS & Linux:
curl -fsSL https://github.com/botpress/adk/releases/latest/download/install.sh | bash
Windows (PowerShell):
powershell -c "irm https://github.com/botpress/adk/releases/latest/download/install.ps1 | iex"
Verify installation:
adk --version
If installation fails, check https://github.com/botpress/adk/releases for manual download options.
Docs: https://www.botpress.com/docs/adk/quickstart
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/cli.md
Once the ADK CLI is installed, create a new bot:
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.
Docs: https://www.botpress.com/docs/adk/quickstart
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/cli.md
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.
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.).If you encounter errors when running adk dev or adk deploy:
Common error scenarios:
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
Docs: https://www.botpress.com/docs/adk/quickstart
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/cli.md
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:
adk link or adk dev. Add to .gitignore - contains environment-specific IDs that differ per developer@botpress/runtime dependency and scripts for dev, build, deployagent.json, .env, node_modules/, .botpress/Docs: https://www.botpress.com/docs/adk/project-structure
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/agent-config.md
The agent.config.ts file defines your bot's identity, AI models, state schemas, and integrations. Always start here when setting up a new bot.
import { defineConfig, z } from "@botpress/runtime";
export default defineConfig({
name: "my-support-bot",
description: "AI customer support assistant",
// AI models for different operations
defaultModels: {
autonomous: "openai:gpt-4o", // Used by execute() for conversations
zai: "openai:gpt-4o-mini" // Used by zai operations (cheaper, faster)
},
// Global bot state - shared across all conversations and users
bot: {
state: z.object({
maintenanceMode: z.boolean().default(false),
totalConversations: z.number().default(0)
})
},
// Per-user state - persists across all conversations for each user
user: {
state: z.object({
name: z.string().optional(),
tier: z.enum(["free", "pro"]).default("free"),
preferredLanguage: z.enum(["en", "es", "fr"]).default("en")
}),
tags: {
source: z.string(),
region: z.string().optional()
}
},
// Per-conversation state
conversation: {
state: z.object({
context: z.string().optional()
}),
tags: {
category: z.enum(["support", "sales", "general"]),
priority: z.enum(["low", "medium", "high"]).optional()
}
},
// Integrations your bot uses (ADK 1.9+ format)
dependencies: {
integrations: {
chat: { version: "chat@0.7.3", enabled: true },
slack: { version: "slack@2.5.5", enabled: true }
}
}
});
Available models:
openai:gpt-4o, openai:gpt-4o-mini, openai:gpt-4-turboanthropic:claude-3-5-sonnet, anthropic:claude-3-opusgoogle:gemini-1.5-pro, google:gemini-1.5-flashDocs: https://www.botpress.com/docs/adk/project-structure
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/agent-config.md
When to create an Action:
When NOT to use an Action (use a Tool instead):
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";
export const fetchUser = new Action({
name: "fetchUser",
description: "Retrieves user details from the database",
// Define input/output with Zod schemas for type safety
input: z.object({ userId: z.string() }),
output: z.object({ name: z.string(), email: z.string() }),
// IMPORTANT: Handler receives { input, client } - destructure input INSIDE the handler
async handler({ 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:
{ input, client } - must destructure input inside the handler.asTool()Docs: https://www.botpress.com/docs/adk/concepts/actions
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/actions.md
When to create a Tool:
The AI decides when to use tools based on:
description - Make this clear and specific about WHEN to use it.describe() fields - Help AI understand what parameters meanKey difference from Actions: Tools can destructure input directly; Actions cannot.
Location: src/tools/*.ts
import { Autonomous, z } from "@botpress/runtime";
export const searchProducts = new Autonomous.Tool({
name: "searchProducts",
// This description is critical - it tells the AI when to use this tool
description: "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 handler
handler: async ({ query, category }) => {
// Your search logic here
return { 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 appropriately
throw new Autonomous.ThinkSignal(
"No results found",
"No products found matching that query. Ask user to try different search terms."
);
Advanced Tool Properties:
export const myTool = new Autonomous.Tool({
name: "myTool",
description: "Tool description",
input: z.object({...}),
output: z.object({...}),
aliases: ["searchDocs", "findDocs"], // Alternative names
handler: async (input, ctx) => {
console.log(`Call ID: ${ctx.callId}`); // Unique call identifier
// ...
},
retry: async ({ attempt, error }) => {
if (attempt < 3 && error?.code === 'RATE_LIMIT') {
await new Promise(r => setTimeout(r, 1000 * attempt));
return true; // Retry
}
return false; // Don't retry
}
});
Docs: https://www.botpress.com/docs/adk/concepts/tools
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/tools.md
When to create a Conversation:
channel: "*" to handle all channels with one handlerKey decisions when building a conversation:
"*" for all, or specific channels like "slack.dm"execute({ tools: [...] })execute({ knowledge: [...] })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";
export const Chat = new Conversation({
// Which channels this handler responds to
channel: "chat.channel", // Or "*" for all, or ["slack.dm", "webchat.channel"]
// Per-conversation state (optional)
state: z.object({
messageCount: z.number().default(0)
}),
async handler({ message, state, conversation, execute, user }) {
state.messageCount += 1;
// Handle commands
if (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 knowledge
await execute({
// Instructions guide the AI's behavior and personality
instructions: `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 conversation
tools: [searchProducts, actions.createTicket.asTool()],
// Knowledge bases for RAG - AI will search these to ground responses
knowledge: [DocsKnowledgeBase],
model: "openai:gpt-4o",
temperature: 0.7,
iterations: 10 // Max tool call iterations
});
}
});
Handler Context:
message - User's message dataexecute - Run autonomous AI logicconversation - Conversation instance methods (send, startTyping, stopTyping)state - Mutable state (bot, user, conversation)client - Botpress API clienttype - Event classification (message, workflow_request)Execute Function Options:
await execute({
instructions: string | async function, // Required
tools: Tool[], // AI-callable tools
knowledge: Knowledge[], // Knowledge bases for RAG
exits: Exit[], // Structured exit handlers
model: string, // AI model to use
temperature: number, // 0-1, default 0.7
iterations: number, // Max tool calls, default 10
hooks: {
onBeforeTool: async ({ tool, input }) => { ... },
onAfterTool: async ({ tool, output }) => { ... },
onTrace: async (trace) => { ... }
}
});
Common channels: chat.channel, webchat.channel, slack.dm, slack.channel, discord.channel, whatsapp.channel, "*" (all)
Docs: https://www.botpress.com/docs/adk/concepts/conversations
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/conversations.md
When to create a Workflow:
When NOT to use a Workflow (handle in conversation instead):
Key workflow concepts:
state to track across stepsLocation: src/workflows/*.ts
import { Workflow, z } from "@botpress/runtime";
export const ProcessOrderWorkflow = new Workflow({
name: "processOrder",
description: "Processes customer orders",
timeout: "6h", // Max duration
schedule: "0 9 * * *", // Optional: run daily at 9am (cron syntax)
input: z.object({
orderId: z.string(),
conversationId: z.string() // Include this to message the user back!
}),
state: z.object({
currentStep: z.number().default(0),
processedItems: z.array(z.string()).default([])
}),
output: z.object({
success: z.boolean(),
itemsProcessed: z.number()
}),
async handler({ input, state, step, client, execute }) {
// State is passed as parameter, auto-tracked
state.currentStep = 1;
// IMPORTANT: Each step needs a unique, stable name (no dynamic names!)
const orderData = await step("fetch-order", async () => {
return await fetchOrderData(input.orderId);
});
// Steps can have retry logic
await step("process-payment", async () => {
return await processPayment(orderData);
}, { maxAttempts: 3 });
// To message the user from a workflow, use client.createMessage (NOT conversation.send)
await step("notify-user", async () => {
await client.createMessage({
conversationId: input.conversationId,
type: "text",
payload: { text: "Your order has been processed!" }
});
});
return {
success: true,
itemsProcessed: state.processedItems.length
};
}
});
// Start a workflow from a conversation or trigger
await ProcessOrderWorkflow.start({
orderId: "123",
conversationId: conversation.id // Always pass this if you need to message back
});
// Get or create with deduplication
const instance = await ProcessOrderWorkflow.getOrCreate({
key: `order-${orderId}`, // Prevents duplicate workflows
input: { orderId, conversationId }
});
Step Methods:
| Method | Purpose |
|--------|---------|
| step(name, fn) | Basic execution with caching |
| step.sleep(name, ms) | Pause for milliseconds |
| step.sleepUntil(name, date) | Pause until specific date |
| step.listen() | Wait for external events |
| step.progress(msg) | Update progress message |
| step.request(name, prompt) | Request user input (blocking) |
| step.executeWorkflow() | Start and await another workflow |
| step.waitForWorkflow(id) | Wait for existing workflow |
| step.map(items, fn) | Process array with concurrency |
| step.forEach(items, fn) | Execute on items without results |
| step.batch(items, fn) | Process in groups |
| step.fail(reason) | Mark workflow as failed |
| step.abort() | Stop immediately without failure |
Critical Rules:
this.stateconversationId for workflows that need to message usersDocs: https://www.botpress.com/docs/adk/concepts/workflows/overview
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/workflows.md
When to create a Table:
searchable: true)When NOT to use a Table (use State instead):
user.state or conversation.stateTables vs Knowledge Bases:
Location: src/tables/*.ts
CRITICAL RULES (violations will cause errors):
id column - it's created automatically as a numberOrdersTable, not Orders)import { Table, z } from "@botpress/runtime";
export const OrdersTable = new Table({
name: "OrdersTable", // Must end with "Table"
description: "Stores order information",
columns: {
// NO id column - it's automatic!
orderId: z.string(),
userId: z.string(),
status: z.enum(["pending", "completed", "cancelled"]),
total: z.number(),
createdAt: z.date(),
// Enable semantic search on a column:
notes: {
schema: z.string(),
searchable: true
}
}
});
CRUD operations:
// Create - id is auto-assigned
await OrdersTable.createRows({
rows: [{ orderId: "ord-123", userId: "user-456", status: "pending", total: 99.99, createdAt: new Date() }]
});
// Read with filters
const { rows } = await OrdersTable.findRows({
filter: { userId: "user-456", status: "pending" },
orderBy: "createdAt",
orderDirection: "desc",
limit: 10
});
// Get single row by id
const row = await OrdersTable.getRow({ id: 123 });
// Semantic search (on searchable columns)
const { rows } = await OrdersTable.findRows({
search: "delivery issue",
limit: 5
});
// Update - must include the id
await OrdersTable.updateRows({
rows: [{ id: 1, status: "completed" }]
});
// Upsert - insert or update based on key column
await OrdersTable.upsertRows({
rows: [{ orderId: "ord-123", status: "shipped" }],
keyColumn: "orderId"
});
// Delete by filter
await OrdersTable.deleteRows({ status: "cancelled" });
// Delete by IDs
await OrdersTable.deleteRowIds([123, 456]);
Advanced: Computed Columns:
columns: {
basePrice: z.number(),
taxRate: z.number(),
fullPrice: {
computed: true,
schema: z.number(),
dependencies: ["basePrice", "taxRate"],
value: async (row) => row.basePrice * (1 + row.taxRate)
}
}
Docs: https://www.botpress.com/docs/adk/concepts/tables
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/tables.md
When to create a Knowledge Base:
How RAG works in ADK:
execute(), the AI automatically searches relevant knowledgeChoosing a DataSource type:
Location: src/knowledge/*.ts
import { Knowledge, DataSource } from "@botpress/runtime";
// Website source - index via sitemap
const websiteSource = DataSource.Website.fromSitemap(
"https://docs.example.com/sitemap.xml",
{
id: "website-docs",
maxPages: 500,
maxDepth: 10,
filter: (ctx) => ctx.url.includes("/docs/") // Only index /docs/ pages
}
);
// Local files (development only - won't work in production)
const localSource = DataSource.Directory.fromPath("src/knowledge/docs", {
id: "local-docs",
filter: (path) => path.endsWith(".md")
});
// Table-based knowledge
const tableSource = DataSource.Table.fromTable(FAQTable, {
id: "faq-table",
transform: ({ row }) => `Question: ${row.question}\nAnswer: ${row.answer}`,
filter: ({ row }) => row.published === true
});
export const DocsKB = new Knowledge({
name: "docsKB",
description: "Product documentation and help articles",
sources: [websiteSource, localSource, tableSource]
});
// Use in conversations - AI will search this knowledge base
await execute({
instructions: "Answer based on the documentation",
knowledge: [DocsKB]
});
// Manually refresh knowledge base
await DocsKB.refresh(); // Smart refresh (only changed content)
await DocsKB.refresh({ force: true }); // Force full re-index
await DocsKB.refreshSource("website-docs", { force: true }); // Refresh specific source
Website Source Methods:
fromSitemap(url, options) - Parse XML sitemapfromWebsite(baseUrl, options) - Crawl from base URL (requires Browser integration)fromLlmsTxt(url, options) - Parse llms.txt filefromUrls(urls, options) - Index specific URLsDocs: https://www.botpress.com/docs/adk/concepts/knowledge
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/knowledge-bases.md
When to create a Trigger:
Common trigger patterns:
user.created β start onboarding workflowlinear:issueCreated β create record in tableworkflow.completed β send Slack messageFinding available events:
user.created, conversation.started, workflow.completed, etc.adk info --events to see available eventsLocation: src/triggers/*.ts
import { Trigger } from "@botpress/runtime";
export default new Trigger({
name: "onNewUser",
description: "Start onboarding when user created",
events: ["user.created"], // Can listen to multiple events
handler: async ({ event, client, actions }) => {
const { userId, email } = event.payload;
// Start an onboarding workflow
await OnboardingWorkflow.start({
userId,
email
});
}
});
// Integration events use format: integration:eventName
export const LinearTrigger = new Trigger({
name: "onLinearIssue",
description: "Handle Linear issue events",
events: ["linear:issueCreated", "linear:issueUpdated"],
handler: async ({ event, actions }) => {
if (event.type === "linear:issueCreated") {
await actions.slack.sendMessage({
channel: "#notifications",
text: `New issue: ${event.payload.title}`
});
}
}
});
Common Bot Events:
user.created, user.updated, user.deletedconversation.started, conversation.ended, message.createdworkflow.started, workflow.completed, workflow.failedbot.started, bot.stoppedCommon Integration Events:
slack:reactionAdded, slack:memberJoinedChannellinear:issueCreated, linear:issueUpdatedgithub:issueOpened, github:pullRequestOpenedintercom:conversationEvent, intercom:contactEventFind integration events: Run adk info
Docs: https://www.botpress.com/docs/adk/concepts/triggers
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/triggers.md
CRITICAL: The method depends on WHERE you're sending from:
| Context | Method | Why |
|---------|--------|-----|
| In Conversations | conversation.send() | Has conversation context |
| In Workflows/Actions | client.createMessage() | Needs explicit conversationId |
Common mistake: Using client.createMessage() in conversations. Always use conversation.send() instead.
The method depends on where you're sending from:
In conversations - Use conversation.send():
await conversation.send({ type: "text", payload: { text: "Hello!" } });
await conversation.send({ type: "image", payload: { imageUrl: "https://..." } });
await conversation.send({
type: "choice",
payload: {
text: "Pick one:",
choices: [
{ title: "Option A", value: "a" },
{ title: "Option B", value: "b" }
]
}
});
In workflows or actions - Use client.createMessage() with conversationId:
await client.createMessage({
conversationId: input.conversationId, // Must have this!
type: "text",
payload: { text: "Workflow complete!" }
});
All Message Types:
// Text
{ type: "text", payload: { text: "Hello!" } }
// Markdown
{ type: "markdown", payload: { text: "# Heading\n**Bold**" } }
// Image
{ type: "image", payload: { imageUrl: "https://..." } }
// Audio
{ type: "audio", payload: { audioUrl: "https://..." } }
// Video
{ type: "video", payload: { videoUrl: "https://..." } }
// File
{ type: "file", payload: { fileUrl: "https://...", title: "Document.pdf" } }
// Location
{ type: "location", payload: { latitude: 40.7128, longitude: -74.0060, address: "New York, NY" } }
// Card
{ type: "card", payload: {
title: "Product Name",
subtitle: "Description",
imageUrl: "https://...",
actions: [
{ action: "url", label: "View", value: "https://..." },
{ action: "postback", label: "Buy", value: "buy_123" }
]
}}
// Carousel
{ type: "carousel", payload: {
items: [
{ title: "Item 1", subtitle: "...", imageUrl: "...", actions: [...] },
{ title: "Item 2", subtitle: "...", imageUrl: "...", actions: [...] }
]
}}
// Choice (Quick Replies)
{ type: "choice", payload: {
text: "Select an option:",
choices: [
{ title: "Option 1", value: "opt1" },
{ title: "Option 2", value: "opt2" }
]
}}
// Dropdown
{ type: "dropdown", payload: {
text: "Select country:",
options: [
{ label: "United States", value: "us" },
{ label: "Canada", value: "ca" }
]
}}
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/messages.md
When to use Zai vs execute():
zai for specific, structured AI operations (extract data, classify, summarize)execute() for autonomous, multi-turn AI conversations with toolsZai is perfect for:
zai.extract)zai.check, zai.label)zai.summarize)zai.answer)zai.sort, zai.filter, zai.group)Zai operations are optimized for speed and cost - they use the zai model configured in agent.config.ts (typically a faster/cheaper model).
import { adk, z } from "@botpress/runtime";
// Extract structured data from text
const contact = await adk.zai.extract(
"Contact John at john@example.com, phone 555-0100",
z.object({
name: z.string(),
email: z.string(),
phone: z.string()
})
);
// Returns: { name: "John", email: "john@example.com", phone: "555-0100" }
// Check if text matches a condition (returns boolean)
const isSpam = await adk.zai.check(messageText, "is spam or promotional");
// Label text with multiple criteria
const labels = await adk.zai.label(customerEmail, {
spam: "is spam",
urgent: "needs immediate response",
complaint: "expresses dissatisfaction"
});
// Returns: { spam: false, urgent: true, complaint: true }
// Summarize content
const summary = await adk.zai.summarize(longDocument, {
length: 200,
bulletPoints: true
});
// Answer questions from documents (with citations)
const result = await adk.zai.answer(docs, "What is the refund policy?");
if (result.type === "answer") {
console.log(result.answer);
console.log(result.citations);
}
// Response types: "answer", "ambiguous", "out_of_topic", "invalid_question", "missing_knowledge"
// Rate items on 1-5 scale
const scores = await adk.zai.rate(products, "quality score");
// Sort by criteria
const sorted = await adk.zai.sort(tickets, "by urgency, most urgent first");
// Group items semantically
const groups = await adk.zai.group(emails, {
instructions: "categorize by topic"
});
// Rewrite text
const professional = await adk.zai.rewrite("hey wassup", "make it professional and friendly");
// Filter arrays
const activeUsers = await adk.zai.filter(users, "have been active this month");
// Generate text
const blogPost = await adk.zai.text("Write about AI in healthcare", {
length: 1000,
temperature: 0.7
});
// Patch code files
const patched = await adk.zai.patch(files, "add JSDoc comments to all functions");
Zai Configuration:
// Create configured instance
const preciseZai = adk.zai.with({
modelId: "best", // "best" | "fast" | custom model ID
temperature: 0.1
});
// Enable active learning
const learningZai = adk.zai.learn("sentiment-analysis");
Docs: https://www.botpress.com/docs/adk/zai/overview
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/zai-complete-guide.md
When to add an Integration:
Integration workflow:
adk search adk add @ http://localhost:3001/actions..() Making integration actions available to AI:
// Convert any integration action to an AI-callable tool
tools: [actions.slack.sendMessage.asTool()]
CLI commands:
adk search slack # Find integrations
adk add slack@latest # Add to project
adk add slack --alias my-slack # Add with custom alias
adk info slack --events # See available events
adk list # List installed integrations
adk upgrade slack # Update to latest
adk remove slack # Remove integration
Using integration actions:
import { actions } from "@botpress/runtime";
// Slack
await actions.slack.sendMessage({ channel: "#general", text: "Hello!" });
await actions.slack.addReaction({ channel: "C123", timestamp: "123", name: "thumbsup" });
// Linear
await actions.linear.issueCreate({ teamId: "123", title: "Bug report", description: "Details" });
const { items } = await actions.linear.issueList({
first: 10,
filter: { state: { name: { eq: "In Progress" } } }
});
// GitHub
await actions.github.createIssue({ owner: "org", repo: "repo", title: "Issue" });
// Browser (web scraping)
const results = await actions.browser.webSearch({ query: "Botpress docs", maxResults: 5 });
// Make integration actions available to AI as tools
await execute({ tools: [actions.slack.sendMessage.asTool()] });
Docs: https://www.botpress.com/docs/adk/managing-integrations
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/integration-actions.md
Understanding the state hierarchy - choose the right level:
| State Level | Scope | Use For |
|-------------|-------|---------|
| bot.state | Global, all users | Feature flags, counters, maintenance mode |
| user.state | Per user, all their conversations | User preferences, profile, tier |
| conversation.state | Per conversation | Context, message count, active workflow |
| workflow.state | Per workflow instance | Progress tracking, intermediate results |
State is automatically persisted - just modify it and it saves.
Access and modify state from anywhere in your bot:
import { bot, user, conversation } from "@botpress/runtime";
// Bot state - global, shared across all users
bot.state.maintenanceMode = true;
bot.state.totalConversations += 1;
// User state - per user, persists across conversations
user.state.name = "Alice";
user.state.tier = "pro";
user.state.preferredLanguage = "es";
// In handlers, state is passed as a parameter
async handler({ state }) {
state.messageCount += 1; // Auto-persisted
}
// Tags - simple string key-value pairs for categorization
user.tags.source = "website";
user.tags.region = "north-america";
conversation.tags.category = "support";
conversation.tags.priority = "high";
State Types:
Tags vs State:
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/tags.md
Access runtime services in any handler:
import { context } from "@botpress/runtime";
// Always available
const client = context.get("client"); // Botpress API client
const citations = context.get("citations"); // Citation manager
const cognitive = context.get("cognitive"); // LLM client
const logger = context.get("logger"); // Structured logger
const botId = context.get("botId"); // Current bot ID
const configuration = context.get("configuration"); // Bot config
// Conditionally available (use { optional: true })
const user = context.get("user", { optional: true });
const conversation = context.get("conversation", { optional: true });
const message = context.get("message", { optional: true });
const workflow = context.get("workflow", { optional: true });
const chat = context.get("chat", { optional: true }); // Conversation transcript
if (user) {
console.log(`User: ${user.id}`);
}
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/context-api.md
# 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
Docs: https://www.botpress.com/docs/adk/cli-reference
GitHub: https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/cli.md
execute()The execute() function is the core of ADK's AI capabilities. It runs an autonomous AI agent that can:
When to use execute():
Key parameters to configure:
instructions - Tell the AI who it is and how to behavetools - Give the AI capabilities (search, create, update, etc.)knowledge - Ground the AI in your documentationexits - Define structured output schemas for specific outcomesThe execute() function enables autonomous AI agent behavior:
import { Autonomous, z } from "@botpress/runtime";
// Define custom tool
const searchTool = new Autonomous.Tool({
name: "search",
description: "Search documentation",
input: z.object({ query: z.string() }),
output: z.string(),
handler: async ({ query }) => {
// Search implementation
return "results...";
}
});
// Define exit (structured response)
const AnswerExit = new Autonomous.Exit({
name: "Answer",
description: "Provide final answer to the user",
schema: z.object({
answer: z.string(),
confidence: z.number(),
sources: z.array(z.string())
})
});
// Execute AI with tools, knowledge, and exits
const result = await execute({
instructions: "Help the user with their request. Be helpful and concise.",
// Add tools
tools: [
searchTool,
actions.linear.issueCreate.asTool()
],
// Add knowledge bases
knowledge: [DocsKnowledgeBase, FAQKnowledgeBase],
// Define exits for structured outputs
exits: [AnswerExit],
// Model configuration
model: "openai:gpt-4o",
temperature: 0.7,
iterations: 10, // Max tool call iterations
// Hooks for monitoring
hooks: {
onBeforeTool: async ({ tool, input }) => {
console.log(`Calling ${tool.name}`, input);
return { input: { ...input, enhanced: true } }; // Modify input
},
onAfterTool: async ({ tool, output }) => {
console.log(`Result:`, output);
}
}
});
// Handle structured exit
if (result.is(AnswerExit)) {
console.log(result.output.answer);
console.log(result.output.sources);
}
| Error | Cause | Solution |
|-------|-------|----------|
| "Cannot destructure property" in Actions | Destructuring input directly in handler params | Use async handler({ input, client }) then const { field } = input inside |
| Table creation fails | Invalid table name or id defined | Remove id column, ensure name ends with "Table" |
| Integration action not found | Integration not installed or configured | Run adk list, add with adk add, configure in UI at localhost:3001 |
| Knowledge base not updating | KB not synced | Run adk kb sync --dev or adk kb sync --force |
| Workflow not resuming | Dynamic step names | Use stable, unique step names (no step(\item-${i}\)) |
| Types out of date | Generated types stale | Run adk dev or adk build to regenerate |
| Can't message user from workflow | Missing conversationId | Pass conversationId when starting workflow, use client.createMessage() |
| "user is not defined" | Accessing conversation context outside conversation | Use context.get("user", { optional: true }) |
| State changes not persisting | Creating new objects instead of modifying | Modify state directly: state.user.name = "Alice" |
| Tool not being used by AI | Poor description | Improve tool description, add detailed .describe() to inputs |
For more help: Run adk --help or check:
// In conversation - starting a workflow that needs to message back
await MyWorkflow.start({
conversationId: conversation.id, // Always include this!
data: "..."
});
// In workflow - messaging back to user
await client.createMessage({
conversationId: input.conversationId,
type: "text",
payload: { text: "Processing complete!" }
});
// In .env (never commit!)
API_KEY=sk-...
SLACK_TOKEN=xoxb-...
// In code
config: { apiKey: process.env.API_KEY }
// GOOD - Single step for batch
await step("process-all-items", async () => {
for (const item of items) {
await processItem(item);
}
});
// BAD - Dynamic names break resume
for (let i = 0; i < items.length; i++) {
await step(`process-${i}`, async () => { ... }); // Don't do this!
}
export default new Action({
handler: async ({ input }) => {
try {
// Action logic
return { success: true };
} catch (error) {
console.error("Action failed:", error);
throw new Error(`Failed to process: ${error.message}`);
}
}
});
handler: async ({ query }) => {
const results = await search(query);
if (!results.length) {
throw new Autonomous.ThinkSignal(
"No results",
"No results found. Ask the user to try different search terms."
);
}
return results;
}
export default new Conversation({
channels: ["slack.channel", "webchat.channel"],
handler: async ({ conversation }) => {
const channel = conversation.channel;
if (channel === "slack.channel") {
// Slack-specific handling (threading, mentions, etc.)
} else if (channel === "webchat.channel") {
// Webchat-specific handling
}
}
});
Base URL: https://www.botpress.com/docs/adk/
| Topic | URL |
|-------|-----|
| Introduction | https://www.botpress.com/docs/adk/introduction |
| Quickstart | https://www.botpress.com/docs/adk/quickstart |
| Project Structure | https://www.botpress.com/docs/adk/project-structure |
| Actions | https://www.botpress.com/docs/adk/concepts/actions |
| Tools | https://www.botpress.com/docs/adk/concepts/tools |
| Conversations | https://www.botpress.com/docs/adk/concepts/conversations |
| Workflows Overview | https://www.botpress.com/docs/adk/concepts/workflows/overview |
| Workflow Steps | https://www.botpress.com/docs/adk/concepts/workflows/steps |
| Tables | https://www.botpress.com/docs/adk/concepts/tables |
| Triggers | https://www.botpress.com/docs/adk/concepts/triggers |
| Knowledge Bases | https://www.botpress.com/docs/adk/concepts/knowledge |
| Managing Integrations | https://www.botpress.com/docs/adk/managing-integrations |
| Zai Overview | https://www.botpress.com/docs/adk/zai/overview |
| Zai Reference | https://www.botpress.com/docs/adk/zai/reference |
| CLI Reference | https://www.botpress.com/docs/adk/cli-reference |
Base URL: https://github.com/botpress/skills/tree/master/skills/adk/references
For detailed specifications beyond this guide, fetch the corresponding reference file:
| Topic | Reference File |
|-------|----------------|
| Actions | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/actions.md |
| Tools | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/tools.md |
| Workflows | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/workflows.md |
| Conversations | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/conversations.md |
| Tables | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/tables.md |
| Triggers | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/triggers.md |
| Knowledge Bases | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/knowledge-bases.md |
| Messages | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/messages.md |
| Agent Config | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/agent-config.md |
| CLI | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/cli.md |
| Integration Actions | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/integration-actions.md |
| Model Configuration | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/model-configuration.md |
| Context API | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/context-api.md |
| Tags | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/tags.md |
| Files | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/files.md |
| Zai Complete Guide | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/zai-complete-guide.md |
| Zai Agent Reference | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/zai-agent-reference.md |
| MCP Server | https://raw.githubusercontent.com/botpress/skills/master/skills/adk/references/mcp-server.md |
"I want to build a support bot that answers questions from our docs"
execute() with that knowledgechat integration for testing"I want the bot to create tickets in Linear when users report issues"
adk add linearactions.linear.issueCreate()execute() in your conversation"I need to run a daily sync job"
schedule: "0 9 *" (cron syntax)"I want to store user preferences"
agent.config.ts under user.stateuser.state.preferenceField = value"I need to react when a new user signs up"
user.created event"I want to store order data and search it"
id field, name ends with "Table")searchable: true on text columns you want to searchcreateRows, findRows, updateRows, deleteRowsThis skill provides comprehensive guidance for building Botpress bots using the ADK:
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:
Official Documentation: https://www.botpress.com/docs/adk/
GitHub Repository: https://github.com/botpress/adk
Skills Repository: https://github.com/botpress/skills
AI Usage Analysis
Analysis is being generated⦠refresh in a few seconds.
Captures learnings, errors, and corrections to enable continuous improvement. Use when: (1) A command or operation fails unexpectedly, (2) User corrects Clau...
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
Search and analyze your own session logs (older/parent conversations) using jq.
Typed knowledge graph for structured agent memory and composable skills. Use when creating/querying entities (Person, Project, Task, Event, Document), linking related objects, enforcing constraints, planning multi-step actions as graph transformations, or when skills need to share state. Trigger on "remember", "what do I know about", "link X to Y", "show dependencies", entity CRUD, or cross-skill data access.
Ultimate AI agent memory system for Cursor, Claude, ChatGPT & Copilot. WAL protocol + vector search + git-notes + cloud backup. Never lose context again. Vibe-coding ready.
Headless browser automation CLI optimized for AI agents with accessibility tree snapshots and ref-based element selection