The ChatBotKit Agent SDK provides a powerful framework for building autonomous AI agents that can execute complex tasks using custom tools while leveraging the full capabilities of the ChatBotKit platform. This comprehensive guide covers tool creation, execution modes, integration patterns, and best practices for building intelligent agents.
Key Features
- Custom tool integration with type-safe schemas
- Dual execution modes (complete and execute)
- Built-in task planning and progress tracking
- Full ChatBotKit platform integration
- Parallel tool execution
- Streaming events and real-time feedback
- Error handling and recovery
Getting Started
Installation
npm install @chatbotkit/agent @chatbotkit/sdk # or yarn add @chatbotkit/agent @chatbotkit/sdkshell
Prerequisites
- Node.js 20.x or higher
- A ChatBotKit API key
- Basic understanding of async/await patterns
- Familiarity with Zod schema validation
Authentication
Authentication is handled through the ChatBotKit SDK client, which should be initialized with your API key.
import { ChatBotKit } from '@chatbotkit/sdk'; import { complete, execute } from '@chatbotkit/agent'; // Initialize the client const client = new ChatBotKit({ secret: process.env.CHATBOTKIT_API_TOKEN }); // Use with agent functions const stream = complete({ client, // ... other options });javascript
Best practices for API key management:
- Store API keys in environment variables
- Never commit API keys to version control
- Use different keys for development and production
- Rotate keys periodically for security
Tool Creation
Basic Tool Definition
Tools are the building blocks of agent capabilities. Each tool includes a description, input schema, and handler function.
import { z } from 'zod'; const tools = { calculateSum: { description: 'Add two numbers together', input: z.object({ a: z.number().describe('First number'), b: z.number().describe('Second number') }), handler: async ({ a, b }) => { return { result: a + b }; } } };javascript
Tool components:
description: Clear explanation of what the tool does (used by AI to decide when to use it)input: Zod schema defining expected parametershandler: Async function that executes the tool logic
Advanced Tool Examples
import { exec } from 'child_process'; import { promisify } from 'util'; import { readFile, writeFile } from 'fs/promises'; const execAsync = promisify(exec); const advancedTools = { readFile: { description: 'Read contents of a file from the local file system', input: z.object({ path: z.string().describe('File path to read'), encoding: z.string().default('utf-8').describe('File encoding') }), handler: async ({ path, encoding }) => { try { const content = await readFile(path, encoding); return { success: true, content }; } catch (error) { return { success: false, error: error.message }; } } }, executeCommand: { description: 'Execute a shell command and return the output', input: z.object({ command: z.string().describe('Shell command to execute'), cwd: z.string().optional().describe('Working directory') }), handler: async ({ command, cwd }) => { try { const { stdout, stderr } = await execAsync(command, { cwd }); return { success: true, stdout, stderr }; } catch (error) { return { success: false, error: error.message }; } } }, apiRequest: { description: 'Make an HTTP request to an external API', input: z.object({ url: z.string().url().describe('API endpoint URL'), method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).default('GET'), body: z.any().optional().describe('Request body') }), handler: async ({ url, method, body }) => { const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined }); const data = await response.json(); return { success: response.ok, data, status: response.status }; } } };javascript
Execution Modes
Complete Mode
Complete mode provides streaming agent responses with tool execution for conversational AI that can take actions.
import { complete } from '@chatbotkit/agent'; const stream = complete({ client, tools, botId: 'bot_123', // Optional: use existing bot configuration model: 'gpt-4o', // Or specify model directly messages: [ { type: 'user', text: 'What is 234 plus 567?' } ] }); // Process the stream for await (const event of stream) { if (event.type === 'token') { process.stdout.write(event.data.token); } else if (event.type === 'toolCallStart') { console.log(`\\\\nCalling tool: ${event.data.name}`); } else if (event.type === 'toolCallEnd') { console.log(`Tool result:`, event.data.result); } }javascript
Event types in complete mode:
token: Streaming text tokens from the AI responsetoolCallStart: Tool execution beginstoolCallEnd: Tool execution completes successfullytoolCallError: Tool execution failed
Execute Mode
Execute mode runs agents with built-in planning, progress tracking, and controlled exit for autonomous task completion.
import { execute } from '@chatbotkit/agent'; const stream = execute({ client, tools, botId: 'bot_123', messages: [ { type: 'user', text: 'Analyze my git changes, check for issues, and create a Jira ticket if needed' } ], maxIterations: 25 }); // Process execution events for await (const event of stream) { if (event.type === 'token') { process.stdout.write(event.data.token); } else if (event.type === 'iteration') { console.log(`\\\\n[Iteration ${event.data.iteration}]`); } else if (event.type === 'exit') { console.log(`\\\\nTask completed with code ${event.data.code}`); if (event.data.message) { console.log(`Message: ${event.data.message}`); } } }javascript
Execute mode provides additional event types:
iteration: New iteration begins (agent continues working)exit: Task completion with status code (0 = success)
System Tools
Execute mode includes three built-in system tools:
// These tools are automatically available in execute mode: plan: { // Create or update task execution plan steps: ['Step 1', 'Step 2', 'Step 3'], rationale: 'Brief explanation of approach' } progress: { // Track completion status completed: ['Finished task 1', 'Finished task 2'], current: 'Working on task 3', blockers: ['Waiting for API access'], nextSteps: ['Complete authentication', 'Test endpoints'] } exit: { // Signal task completion code: 0, // 0 = success, non-zero = failure message: 'All tasks completed successfully' }javascript
Platform Integration
Using Platform Capabilities
Agents automatically have access to all ChatBotKit platform features when you specify a botId:
const stream = execute({ client, tools: localTools, // Your custom tools botId: 'bot_123', // Bot with configured integrations messages: [{ type: 'user', text: 'Check local logs, create Jira ticket if errors found, notify team on Slack' }] });javascript
Platform capabilities available to agents:
- Integrations: Slack, Discord, Jira, Linear, Google Workspace, Notion, etc.
- Datasets: Query knowledge bases for relevant information
- Skillsets: Use pre-built abilities and custom functions
- Authenticated sessions: No need to manage OAuth flows
Combining Local and Remote Tools
The power of the Agent SDK comes from combining local operations with remote integrations:
import { execute } from '@chatbotkit/agent'; import { readFile } from 'fs/promises'; // Define local-only tools const localTools = { scanLocalFiles: { description: 'Scan local directory for specific files', input: z.object({ directory: z.string(), pattern: z.string() }), handler: async ({ directory, pattern }) => { // Local file system operations const { stdout } = await execAsync(`find ${directory} -name "${pattern}"`); return { files: stdout.trim().split('\\\\n') }; } }, analyzeCode: { description: 'Analyze code quality metrics locally', input: z.object({ filePath: z.string() }), handler: async ({ filePath }) => { const content = await readFile(filePath, 'utf-8'); // Perform local analysis return { lines: content.split('\\\\n').length, hasTests: content.includes('describe('), complexity: calculateComplexity(content) }; } } }; // Agent can use both local tools AND remote integrations const stream = execute({ client, tools: localTools, botId: 'bot_with_jira_and_slack', // Bot has Jira and Slack configured messages: [{ type: 'user', text: `Scan src/ for .js files, analyze code quality, create Jira tickets for files with high complexity, and notify #dev-team on Slack with summary` }], maxIterations: 30 });javascript
Real-World Examples
Development Workflow Automation
const devTools = { checkGitStatus: { description: 'Get current git repository status', input: z.object({}), handler: async () => { const { stdout } = await execAsync('git status --porcelain'); return { changes: stdout, hasChanges: stdout.length > 0 }; } }, runTests: { description: 'Execute test suite', input: z.object({ path: z.string().optional() }), handler: async ({ path = '.' }) => { try { const { stdout } = await execAsync(`npm test -- ${path}`); return { success: true, output: stdout }; } catch (error) { return { success: false, error: error.message }; } } } }; const stream = execute({ client, tools: devTools, botId: 'bot_123', messages: [{ type: 'user', text: 'Run all tests, check git status, and create deployment checklist in Jira' }], maxIterations: 20 });javascript
Data Processing Pipeline
const dataTools = { readCSV: { description: 'Read and parse CSV file', input: z.object({ filePath: z.string() }), handler: async ({ filePath }) => { const content = await readFile(filePath, 'utf-8'); const lines = content.split('\\\\n'); const headers = lines[0].split(','); const rows = lines.slice(1).map(line => { const values = line.split(','); return Object.fromEntries(headers.map((h, i) => [h, values[i]])); }); return { headers, rows, count: rows.length }; } }, validateData: { description: 'Validate data against rules', input: z.object({ data: z.array(z.any()), rules: z.object({ requiredFields: z.array(z.string()).optional(), emailFields: z.array(z.string()).optional() }) }), handler: async ({ data, rules }) => { const errors = []; // Validation logic return { valid: errors.length === 0, errors }; } } }; const stream = execute({ client, tools: dataTools, botId: 'bot_with_google_sheets', messages: [{ type: 'user', text: 'Read customers.csv, validate email addresses, update Google Sheet with results' }] });javascript
System Monitoring
const monitoringTools = { checkDiskSpace: { description: 'Check available disk space', input: z.object({}), handler: async () => { const { stdout } = await execAsync('df -h /'); return { output: stdout }; } }, analyzeLog: { description: 'Analyze log file for errors', input: z.object({ logPath: z.string(), pattern: z.string().default('ERROR') }), handler: async ({ logPath, pattern }) => { const { stdout } = await execAsync(`grep "${pattern}" ${logPath} | tail -n 100`); return { matches: stdout.split('\\\\n').filter(Boolean), count: stdout.split('\\\\n').filter(Boolean).length }; } } }; const stream = execute({ client, tools: monitoringTools, botId: 'bot_with_pagerduty_slack', messages: [{ type: 'user', text: 'Check disk space and error logs. If critical issues, create PagerDuty alert and notify #ops' }] });javascript
Error Handling
Tool-Level Error Handling
const tools = { riskyOperation: { description: 'An operation that might fail', input: z.object({ param: z.string() }), handler: async ({ param }) => { try { // Attempt operation const result = await performOperation(param); return { success: true, result }; } catch (error) { // Return structured error return { success: false, error: error.message, code: error.code }; } } } };javascript
Best practices for tool error handling:
- Always wrap operations in try-catch
- Return structured error objects
- Include helpful error messages
- Log errors for debugging
- Don't throw exceptions from handlers
Agent-Level Error Handling
try { for await (const event of execute({ client, tools, botId: 'bot_123', messages: [{ type: 'user', text: 'Complete this task' }] })) { if (event.type === 'toolCallError') { console.error(`Tool error: ${event.data.error}`); // Handle tool failure } else if (event.type === 'exit') { if (event.data.code !== 0) { console.error(`Task failed: ${event.data.message}`); } } } } catch (error) { console.error('Agent execution failed:', error); // Handle fatal errors }javascript
Best Practices
- Tool Design
- Keep tools focused on single responsibilities
- Provide clear, descriptive tool descriptions
- Use detailed Zod schemas with descriptions
- Return structured, consistent results
- Handle errors gracefully
- Security
- Validate all tool inputs
- Sanitize file paths and commands
- Limit tool access scope
- Use environment variables for secrets
- Implement rate limiting for external APIs
- Performance
- Design tools for parallel execution when possible
- Avoid blocking operations in tool handlers
- Implement timeouts for long-running operations
- Cache results when appropriate
- Monitor token usage
- Development
- Test tools independently before agent integration
- Use TypeScript for type safety
- Log tool executions for debugging
- Set appropriate maxIterations limits
- Provide clear task instructions
- Agent Prompts
- Be specific about desired outcomes
- Break complex tasks into clear steps
- Specify output formats
- Include validation criteria
- Provide necessary context
Advanced Patterns
Multi-Step Workflows
const stream = execute({ client, tools: comprehensiveTools, botId: 'bot_123', messages: [{ type: 'user', text: ` Phase 1: Analyze codebase for security issues Phase 2: Generate detailed report with findings Phase 3: Create Jira tickets for high-severity issues Phase 4: Update team documentation in Notion Phase 5: Send summary to #security Slack channel ` }], maxIterations: 50 });javascript
Conditional Tool Execution
const tools = { checkCondition: { description: 'Check if condition is met', input: z.object({ condition: z.string() }), handler: async ({ condition }) => { // Evaluate condition return { met: true, value: 'some_value' }; } }, conditionalAction: { description: 'Perform action based on previous check', input: z.object({ action: z.string(), data: z.any() }), handler: async ({ action, data }) => { // Execute conditional logic return { success: true }; } } };javascript
Progress Monitoring
for await (const event of execute({ client, tools, botId, messages })) { if (event.type === 'iteration') { console.log(`Progress: Iteration ${event.data.iteration}`); } else if (event.type === 'toolCallStart') { console.log(`Executing: ${event.data.name}`, event.data.args); } else if (event.type === 'toolCallEnd') { console.log(`Completed: ${event.data.name}`); } }javascript
Configuration Options
Complete Mode Options
complete({ client, // ChatBotKit client instance tools, // Tool definitions object botId, // Optional: existing bot ID model, // Optional: AI model override messages, // Conversation messages array extensions: { // Optional: additional configuration backstory: 'Custom context for this session', // ... other bot configuration } })javascript
Execute Mode Options
execute({ client, // ChatBotKit client instance tools, // Tool definitions object botId, // Optional: existing bot ID model, // Optional: AI model override messages, // Task messages array maxIterations: 50, // Maximum iteration limit extensions: { // Optional: additional configuration backstory: 'Custom agent context', // ... other bot configuration } })javascript
References
Related Tools and Libraries
- ChatBotKit CLI - Command-line interface with agent mode
- ChatBotKit Node SDK - Core SDK for platform access
- React Prompt Kit - Structured prompt building