@botpress/adk-cli 1.11.6 → 1.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1260 @@
1
+ # Botpress ADK Project Context
2
+
3
+ This project is built with the **Botpress Agent Development Kit (ADK)** - a TypeScript-first framework for building AI agents.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Quick Reference: Use the Botpress MCP Server](#quick-reference-use-the-botpress-mcp-server)
8
+ - [What is the ADK?](#what-is-the-adk)
9
+ - [ADK CLI](#adk-cli)
10
+ - [Core Concepts](#core-concepts)
11
+ - [1. Agent Configuration](#1-agent-configuration-agentconfigts)
12
+ - [2. Conversations](#2-conversations-srcconversations)
13
+ - [3. Workflows](#3-workflows-srcworkflows)
14
+ - [4. Tools](#4-tools-srctools)
15
+ - [5. Knowledge Bases](#5-knowledge-bases-srcknowledge)
16
+ - [6. Actions](#6-actions-srcactions)
17
+ - [7. Zai Library](#7-zai-library)
18
+ - [Project Structure](#project-structure)
19
+ - [Development Workflow](#development-workflow)
20
+ - [Examples](#examples)
21
+ - [Best Practices](#best-practices)
22
+ - [Common APIs](#common-apis)
23
+ - [Advanced Autonomous Execution](#advanced-autonomous-execution)
24
+ - [State and Metadata Management](#state-and-metadata-management)
25
+ - [Advanced Table Operations](#advanced-table-operations)
26
+ - [Knowledge Base Operations](#knowledge-base-operations)
27
+ - [Advanced Conversation Patterns](#advanced-conversation-patterns)
28
+ - [Citations System](#citations-system)
29
+ - [When Making Changes](#when-making-changes)
30
+ - [Resources](#resources)
31
+
32
+ ## Quick Reference: Use the Botpress MCP Server
33
+
34
+ **IMPORTANT**: When working on this project, always search the Botpress documentation using the `mcp__botpress-docs__SearchBotpress` tool before making changes. The ADK has specific patterns and APIs that are well-documented.
35
+
36
+ ## What is the ADK?
37
+
38
+ The ADK allows developers to build Botpress agents using **code instead of the Studio interface**. It provides:
39
+
40
+ - Project scaffolding with TypeScript
41
+ - Hot reloading development server (`adk dev`)
42
+ - Type-safe APIs and auto-generated types
43
+ - Build and deploy to Botpress Cloud
44
+
45
+ ## ADK CLI
46
+
47
+ The ADK CLI is installed globally. You can run it using `adk <command>`.
48
+ Always use bash to run ADK. (`Bash(adk)`)
49
+ To install an integration: `adk install <integration>`
50
+ To generate types without running in dev mode: `adk build`
51
+
52
+ ## Core Concepts
53
+
54
+ ### 1. Agent Configuration (`agent.config.ts`)
55
+
56
+ The main configuration file defines:
57
+
58
+ - **Agent name and description**
59
+ - **Default models** for autonomous and zai operations
60
+ - **State schemas** (bot-level and user-level state using Zod)
61
+ - **Configuration variables** (encrypted, secure storage for API keys)
62
+ - **Integration dependencies** (webchat, chat, etc.)
63
+
64
+ ```typescript
65
+ export default defineConfig({
66
+ name: "my-agent",
67
+ defaultModels: {
68
+ autonomous: "cerebras:gpt-oss-120b",
69
+ zai: "cerebras:gpt-oss-120b",
70
+ },
71
+ bot: { state: z.object({}) },
72
+ user: { state: z.object({}) },
73
+ dependencies: {
74
+ integrations: {
75
+ webchat: { version: "webchat@0.3.0", enabled: true },
76
+ },
77
+ },
78
+ });
79
+ ```
80
+
81
+ ### 2. Conversations (`src/conversations/`)
82
+
83
+ **Primary way agents handle user messages**. Each conversation handler:
84
+
85
+ - Responds to messages from specific channels
86
+ - Uses `execute()` to run autonomous AI logic
87
+ - Can access conversation state, send messages, and call tools
88
+
89
+ **Key Pattern**: The `execute()` function runs the agent's AI loop:
90
+
91
+ ```typescript
92
+ export default new Conversation({
93
+ channel: "webchat.channel",
94
+ handler: async ({ execute, conversation, state }) => {
95
+ await execute({
96
+ instructions: "Your agent's instructions here",
97
+ tools: [myTool1, myTool2],
98
+ knowledge: [myKnowledgeBase],
99
+ });
100
+ },
101
+ });
102
+ ```
103
+
104
+ ### 3. Workflows (`src/workflows/`)
105
+
106
+ **Long-running processes** for complex, multi-step operations:
107
+
108
+ - Can run on schedules (cron syntax)
109
+ - Run independently or triggered by events
110
+ - NOT the same as Studio Workflows
111
+ - Use `step()` for durable execution (survives restarts)
112
+
113
+ ```typescript
114
+ export default new Workflow({
115
+ name: "periodic-indexing",
116
+ schedule: "0 */6 * * *",
117
+ handler: async ({ step }) => {
118
+ await step("task-name", async () => {
119
+ // Your logic here
120
+ });
121
+ },
122
+ });
123
+ ```
124
+
125
+ #### Advanced Workflow Step Methods
126
+
127
+ Beyond basic `step()`, workflows have powerful methods for complex orchestration:
128
+
129
+ **Parallel Processing:**
130
+
131
+ - `step.map()` - Process array items in parallel with concurrency control
132
+ - `step.forEach()` - Like map but for side effects (returns void)
133
+ - `step.batch()` - Process in sequential batches
134
+
135
+ ```typescript
136
+ // Process items in parallel
137
+ const results = await step.map(
138
+ 'process-items',
139
+ items,
140
+ async (item, { i }) => processItem(item),
141
+ { concurrency: 5, maxAttempts: 3 }
142
+ )
143
+
144
+ // Batch processing
145
+ await step.batch(
146
+ 'bulk-insert',
147
+ records,
148
+ async (batch) => database.bulkInsert(batch),
149
+ { batchSize: 100 }
150
+ )
151
+ ```
152
+
153
+ **Workflow Coordination:**
154
+
155
+ - `step.waitForWorkflow()` - Wait for another workflow to complete
156
+ - `step.executeWorkflow()` - Start and wait in one call
157
+
158
+ ```typescript
159
+ const result = await step.executeWorkflow('run-child', ChildWorkflow, { input })
160
+ ```
161
+
162
+ **Timing Control:**
163
+
164
+ - `step.sleep()` - Pause execution (< 10s in-memory, >= 10s uses listening mode)
165
+ - `step.sleepUntil()` - Sleep until specific time
166
+ - `step.listen()` - Pause and wait for external event
167
+
168
+ ```typescript
169
+ await step.sleep('wait-5s', 5000)
170
+ await step.sleepUntil('wait-until-noon', new Date('2025-01-15T12:00:00Z'))
171
+ ```
172
+
173
+ **Request Data from Conversation:**
174
+
175
+ ```typescript
176
+ // In workflow
177
+ const { topic } = await step.request('topic', 'What topic should I research?')
178
+
179
+ // In conversation
180
+ if (isWorkflowDataRequest(event)) {
181
+ await workflow.provide(event, { topic: userInput })
182
+ }
183
+ ```
184
+
185
+ **Execution Control:**
186
+
187
+ - `step.fail()` - Mark workflow as failed
188
+ - `step.abort()` - Abort without failing
189
+ - `step.progress()` - Record progress checkpoint
190
+
191
+ ### 4. Tools (`src/tools/`)
192
+
193
+ **AI-callable functions** that enable agents to perform actions:
194
+
195
+ - Must have clear name and description
196
+ - Use Zod schemas for input/output
197
+ - Can be passed to `execute()`
198
+
199
+ ```typescript
200
+ export default new Autonomous.Tool({
201
+ name: "searchDatabase",
202
+ description: "Search the database",
203
+ input: z.object({ query: z.string() }),
204
+ output: z.object({ results: z.array(z.any()) }),
205
+ handler: async ({ query }) => {
206
+ // Tool logic
207
+ return { results: [] };
208
+ },
209
+ });
210
+ ```
211
+
212
+ ### 5. Knowledge Bases (`src/knowledge/`)
213
+
214
+ **RAG (Retrieval-Augmented Generation)** for providing context:
215
+
216
+ - Website scraping
217
+ - Document ingestion
218
+ - Can be passed to `execute()` via `knowledge` parameter
219
+
220
+ ### 6. Actions (`src/actions/`)
221
+
222
+ **Reusable business logic** that can:
223
+
224
+ - Be called from anywhere (import `actions` from `@botpress/runtime`)
225
+ - Be converted to tools with `.asTool()`
226
+ - Encapsulate logic not tied to conversational flow
227
+
228
+ ### 7. Zai Library
229
+
230
+ **Zai** is an LLM utility library that provides a clean, type-safe API for common AI operations. It's designed to work seamlessly with the ADK and SDK to process LLM inputs and outputs programmatically.
231
+
232
+ #### Importing Zai in ADK
233
+
234
+ In the ADK, Zai is available from `@botpress/runtime`:
235
+
236
+ ```typescript
237
+ import { adk } from '@botpress/runtime'
238
+ // then adk.zai.<method_name>
239
+ ```
240
+
241
+ The default model for Zai operations is configured in `agent.config.ts`:
242
+
243
+ ```typescript
244
+ export default defineConfig({
245
+ defaultModels: {
246
+ autonomous: "cerebras:gpt-oss-120b",
247
+ zai: "cerebras:gpt-oss-120b", // Model used for Zai operations
248
+ },
249
+ })
250
+ ```
251
+
252
+ #### When to Use Zai
253
+
254
+ Use Zai when you need to:
255
+ - Extract structured data from unstructured text
256
+ - Answer questions from documents with source citations
257
+ - Verify Boolean conditions in content
258
+ - Summarize long text into concise summaries
259
+ - Generate text programmatically based on prompts
260
+
261
+ **Use Zai instead of `execute()` when**: You need deterministic, structured outputs for specific AI tasks (extraction, validation, summarization) rather than conversational interactions.
262
+
263
+ #### Zai Methods
264
+
265
+ **1. `answer()` - Answer Questions with Citations**
266
+
267
+ Answers questions from documents with intelligent source citations.
268
+
269
+ ```typescript
270
+ const documents = [
271
+ 'Botpress was founded in 2016.',
272
+ 'The company is based in Quebec, Canada.',
273
+ ]
274
+
275
+ const result = await zai.answer(documents, 'When was Botpress founded?')
276
+
277
+ if (result.type === 'answer') {
278
+ console.log(result.answer) // "Botpress was founded in 2016."
279
+ console.log(result.citations) // Array of citations with source references
280
+ }
281
+ ```
282
+
283
+ **When to use**: When you need to answer questions from a set of documents with traceable sources (e.g., custom RAG implementations, document Q&A).
284
+
285
+ **2. `extract()` - Extract Structured Data**
286
+
287
+ Extracts structured data from unstructured input using Zod schemas.
288
+
289
+ ```typescript
290
+ import { z, adk } from '@botpress/runtime'
291
+
292
+ const userSchema = z.object({
293
+ name: z.string(),
294
+ email: z.string().email(),
295
+ age: z.number()
296
+ })
297
+
298
+ const input = "My name is John Doe, I'm 30 years old and my email is john@example.com"
299
+ // zai.extract returns the extracted data DIRECTLY (not wrapped in { output: ... })
300
+ const result = await adk.zai.extract(input, userSchema)
301
+
302
+ console.log(result)
303
+ // { name: "John Doe", email: "john@example.com", age: 30 }
304
+ ```
305
+
306
+ **When to use**: When you need to parse unstructured user input into structured data (e.g., form extraction from natural language, parsing contact information).
307
+
308
+ **3. `check()` - Verify Boolean Conditions**
309
+
310
+ Verifies a condition against some input and returns a boolean with explanation.
311
+
312
+ ```typescript
313
+ const email = "Get rich quick! Click here now!!!"
314
+ const { output } = await zai.check(email, 'is spam').result()
315
+
316
+ console.log(output.value) // true
317
+ console.log(output.explanation) // "This email contains typical spam indicators..."
318
+ ```
319
+
320
+ **When to use**: When you need to validate content or make binary decisions (e.g., content moderation, intent verification, condition checking).
321
+
322
+ **4. `summarize()` - Summarize Text**
323
+
324
+ Creates concise summaries of lengthy text to a desired length.
325
+
326
+ ```typescript
327
+ const longArticle = "..." // Long article content
328
+
329
+ const summary = await zai.summarize(longArticle, {
330
+ length: 100, // tokens
331
+ prompt: 'key findings and main conclusions'
332
+ })
333
+ ```
334
+
335
+ **When to use**: When you need to condense long content (e.g., article summaries, transcript summaries, document overviews).
336
+
337
+ **5. `text()` - Generate Text**
338
+
339
+ Generates text of the desired length according to a prompt.
340
+
341
+ ```typescript
342
+ const generated = await zai.text('Write a welcome message for new users', {
343
+ length: 50 // tokens
344
+ })
345
+ ```
346
+
347
+ **When to use**: When you need to generate specific text content programmatically (e.g., dynamic content generation, templated responses).
348
+
349
+ #### Response Methods
350
+
351
+ All Zai operations return a Response object with promise-like behavior and additional functionality:
352
+
353
+ ```typescript
354
+ // Await the result directly
355
+ const result = await zai.extract(input, schema)
356
+
357
+ // Or use .result() for explicit promise handling
358
+ const { output } = await zai.check(content, 'is valid').result()
359
+ ```
360
+
361
+ ## Project Structure
362
+
363
+ ```
364
+ agent.config.ts # Main configuration
365
+ src/
366
+ conversations/ # Message handlers (primary user interaction)
367
+ workflows/ # Long-running processes
368
+ tools/ # AI-callable functions
369
+ actions/ # Reusable business logic
370
+ knowledge/ # Knowledge bases for RAG
371
+ triggers/ # Event-based triggers
372
+ tables/ # Database tables
373
+ .botpress/ # Auto-generated types (DO NOT EDIT)
374
+ ```
375
+
376
+ ## Development Workflow
377
+
378
+ 1. **Start dev server**: `adk dev` (http://localhost:3001 for console)
379
+ 2. **Add integrations**: `adk add webchat@latest`
380
+ 3. **Build**: `adk build`
381
+ 4. **Deploy**: `adk deploy`
382
+ 5. **Chat in CLI**: `adk chat`
383
+
384
+ ## Examples
385
+
386
+ Official examples: https://github.com/botpress/adk/tree/main/examples
387
+
388
+ ### subagents
389
+
390
+ **What you'll learn:** How to build a multi-agent system where an orchestrator delegates to specialists.
391
+
392
+ Shows the `SubAgent` pattern where each specialist (HR, IT, Sales, etc.) runs in its own context with `mode: "worker"`, returns structured results via custom exits, and reports progress through `onTrace` hooks.
393
+
394
+ ### webchat-rag
395
+
396
+ **What you'll learn:** How to build a RAG assistant with scheduled indexing, guardrails, and admin features.
397
+
398
+ Shows `Autonomous.Object` for dynamic tool grouping, `onBeforeTool` hooks to enforce knowledge search before answering, scheduled workflows for KB refresh, and `ThinkSignal` for interrupting execution.
399
+
400
+ ### deep-research
401
+
402
+ **What you'll learn:** How to build complex, long-running workflows with progress tracking.
403
+
404
+ Shows `step()` and `step.map()` for workflow phases, `Reference.Workflow` for conversation-workflow linking, Tables for activity tracking, and extensive Zai usage (`extract`, `answer`, `filter`, `text`).
405
+
406
+ ## Best Practices
407
+
408
+ 1. **Search Botpress docs first** - Use the MCP tool before implementing
409
+ 2. **Keep tools focused** - Single responsibility per tool
410
+ 3. **Use Zod schemas** with `.describe()` for clarity
411
+ 4. **State management** - Minimize large variables in main workflow
412
+ 5. **Type safety** - Run `adk dev` or `adk build` to regenerate types after config changes
413
+ 6. **Conversations vs Workflows**:
414
+ - Conversations: User interactions, real-time responses
415
+ - Workflows: Background tasks, scheduled jobs, long-running processes
416
+
417
+ ## Common APIs
418
+
419
+ ### Conversation Handler
420
+
421
+ ```typescript
422
+ handler: async ({
423
+ execute, // Run autonomous AI loop
424
+ conversation, // Send messages, manage conversation
425
+ state, // Conversation state (persisted)
426
+ message, // Incoming message
427
+ client, // Botpress API client
428
+ }) => {};
429
+ ```
430
+
431
+ ### Execute Function
432
+
433
+ ```typescript
434
+ await execute({
435
+ instructions: "String or function returning instructions",
436
+ tools: [tool1, tool2], // Optional tools
437
+ knowledge: [kb1, kb2], // Optional knowledge bases
438
+ exits: [customExit], // Optional custom exits
439
+ hooks: { onTrace, onBeforeTool }, // Optional hooks
440
+ mode: "worker", // Optional: autonomous until exit
441
+ iterations: 10, // Max loops (default 10)
442
+ });
443
+ ```
444
+
445
+ ## Advanced Autonomous Execution
446
+
447
+ ### Autonomous Namespace
448
+
449
+ The `Autonomous` namespace provides powerful primitives for controlling LLM behavior:
450
+
451
+ #### Autonomous.Exit - Custom Exit Conditions
452
+
453
+ Define custom exits for autonomous execution loops:
454
+
455
+ ```typescript
456
+ import { Autonomous, z } from '@botpress/runtime'
457
+
458
+ const AnswerExit = new Autonomous.Exit({
459
+ name: 'answer',
460
+ description: 'Return when you have the final answer',
461
+ schema: z.object({
462
+ answer: z.string(),
463
+ confidence: z.number()
464
+ })
465
+ })
466
+
467
+ const NoAnswerExit = new Autonomous.Exit({
468
+ name: 'no_answer',
469
+ description: 'No answer could be found'
470
+ })
471
+
472
+ const result = await execute({
473
+ instructions: 'Research and answer the question',
474
+ exits: [AnswerExit, NoAnswerExit],
475
+ mode: 'worker' // Run until exit triggered
476
+ })
477
+
478
+ // ✅ CORRECT - Use result.is() and result.output
479
+ if (result.is(AnswerExit)) {
480
+ console.log(result.output.answer) // Type-safe access
481
+ console.log(result.output.confidence)
482
+ } else if (result.is(NoAnswerExit)) {
483
+ console.log('No answer found')
484
+ }
485
+
486
+ // ❌ WRONG - Don't use result.exit.name or result.exit.value
487
+ // if (result.exit?.name === 'answer') { ... }
488
+ ```
489
+
490
+ #### Autonomous.ThinkSignal - Inject Context
491
+
492
+ Provide context to the LLM without continuing execution:
493
+
494
+ ```typescript
495
+ const results = await fetchData()
496
+
497
+ if (!results.length) {
498
+ throw new ThinkSignal('error', 'No results found')
499
+ }
500
+
501
+ // Inject formatted results into LLM context
502
+ throw new ThinkSignal('results ready', formatResults(results))
503
+ ```
504
+
505
+ #### Autonomous.Object - Dynamic Tool Grouping
506
+
507
+ Group tools dynamically based on state:
508
+
509
+ ```typescript
510
+ const adminTools = new Autonomous.Object({
511
+ name: 'admin',
512
+ description: user.isAdmin ? 'Admin tools available' : 'Login required',
513
+ tools: user.isAdmin ? [refreshKB, manageBots] : [generateLoginCode]
514
+ })
515
+
516
+ await execute({
517
+ objects: [adminTools]
518
+ })
519
+ ```
520
+
521
+ ### Execution Hooks
522
+
523
+ Full control over the autonomous execution loop:
524
+
525
+ ```typescript
526
+ await execute({
527
+ instructions: '...',
528
+ hooks: {
529
+ // Before tool execution - can modify input
530
+ onBeforeTool: async ({ iteration, tool, input, controller }) => {
531
+ console.log(`About to call ${tool.name}`)
532
+ return { input: modifiedInput } // Optional: transform input
533
+ },
534
+
535
+ // After tool execution - can modify output
536
+ onAfterTool: async ({ iteration, tool, input, output, controller }) => {
537
+ console.log(`${tool.name} returned:`, output)
538
+ return { output: modifiedOutput } // Optional: transform output
539
+ },
540
+
541
+ // Before code execution in iteration
542
+ onBeforeExecution: async (iteration, controller) => {
543
+ return { code: modifiedCode } // Optional: transform generated code
544
+ },
545
+
546
+ // When exit is triggered
547
+ onExit: async (result) => {
548
+ console.log('Exited with:', result)
549
+ },
550
+
551
+ // After each iteration completes
552
+ onIterationEnd: async (iteration, controller) => {
553
+ if (iteration > 5) {
554
+ controller.abort() // Stop execution
555
+ }
556
+ },
557
+
558
+ // On trace events (synchronous, non-blocking)
559
+ onTrace: ({ trace, iteration }) => {
560
+ if (trace.type === 'comment') {
561
+ console.log('LLM thinking:', trace.comment)
562
+ }
563
+ if (trace.type === 'tool_call') {
564
+ console.log('Calling:', trace.tool_name)
565
+ }
566
+ }
567
+ }
568
+ })
569
+ ```
570
+
571
+ **Hook use cases:**
572
+ - Logging and debugging
573
+ - Input/output validation and transformation
574
+ - Rate limiting tool calls
575
+ - Custom abort conditions
576
+ - Injecting dynamic context
577
+
578
+ ## State and Metadata Management
579
+
580
+ ### Tags - Key-Value Metadata
581
+
582
+ Track metadata for any entity (bot, user, conversation, workflow):
583
+
584
+ ```typescript
585
+ import { TrackedTags } from '@botpress/runtime'
586
+
587
+ // Create tags instance
588
+ const tags = TrackedTags.create({
589
+ type: 'bot', // or 'user' | 'conversation' | 'workflow'
590
+ id: entityId,
591
+ client: botClient,
592
+ initialTags: { status: 'active' }
593
+ })
594
+
595
+ // Load from server
596
+ await tags.load()
597
+
598
+ // Modify tags
599
+ tags.tags = {
600
+ ...tags.tags,
601
+ lastSync: new Date().toISOString()
602
+ }
603
+
604
+ // Check if modified
605
+ if (tags.isDirty()) {
606
+ await tags.save()
607
+ }
608
+
609
+ // Batch operations
610
+ await TrackedTags.saveAllDirty()
611
+ await TrackedTags.loadAll()
612
+ ```
613
+
614
+ **Access via workflow instance:**
615
+
616
+ ```typescript
617
+ workflow.tags = { status: 'processing' }
618
+ await workflow.save()
619
+ ```
620
+
621
+ ### Reference.Workflow - Typed Workflow References
622
+
623
+ Serialize workflow references in state that auto-hydrate on access:
624
+
625
+ ```typescript
626
+ import { Reference, z } from '@botpress/runtime'
627
+
628
+ // In conversation state schema
629
+ state: z.object({
630
+ research: Reference.Workflow('deep_research').optional()
631
+ // or untyped: Reference.Workflow().optional()
632
+ })
633
+
634
+ // In handler - always a WorkflowInstance
635
+ handler: async ({ state }) => {
636
+ if (state.research) {
637
+ // state.research is typed WorkflowInstance
638
+ console.log(state.research.status) // 'running' | 'completed' | etc
639
+ console.log(state.research.output) // Typed output
640
+
641
+ if (state.research.status === 'completed') {
642
+ // Access completed workflow data
643
+ }
644
+ }
645
+ }
646
+ ```
647
+
648
+ ### Context Object - Runtime Access
649
+
650
+ Global context for accessing runtime information:
651
+
652
+ ```typescript
653
+ import { context } from '@botpress/runtime'
654
+
655
+ // Get specific context
656
+ const client = context.get('client')
657
+ const citations = context.get('citations')
658
+ const logger = context.get('logger')
659
+
660
+ // Get all context
661
+ const { client, cognitive, logger, operation } = context.getAll()
662
+ ```
663
+
664
+ **Available context properties:**
665
+ - `client` - Botpress API client
666
+ - `cognitive` - LLM access
667
+ - `logger` - Logging
668
+ - `operation` - Current operation info
669
+ - `citations` - Citation tracking
670
+ - `chat` - Chat interface
671
+ - `bot` - Bot tags and metadata
672
+ - `user` - User information
673
+ - `conversation` - Current conversation
674
+ - `message` - Incoming message
675
+ - `event` - Current event
676
+ - `workflow` - Current workflow
677
+ - `workflowControlContext` - Workflow control (abort, fail, restart)
678
+
679
+ ### State Management
680
+
681
+ Access and modify tracked state:
682
+
683
+ ```typescript
684
+ import { bot, user } from '@botpress/runtime'
685
+
686
+ // Bot state
687
+ bot.state.lastIndexed = new Date().toISOString()
688
+ bot.state.config = { theme: 'dark' }
689
+
690
+ // User state
691
+ user.state.preferences = { notifications: true }
692
+ user.state.lastActive = Date.now()
693
+ ```
694
+
695
+ State persists automatically across executions.
696
+
697
+ ## Advanced Table Operations
698
+
699
+ ### Table Naming Rules
700
+
701
+ **IMPORTANT**: Tables have strict naming requirements:
702
+
703
+ ```typescript
704
+ // ✅ CORRECT - Name must end with "Table"
705
+ export const MyDataTable = new Table({
706
+ name: "mydataTable", // Must end with "Table"
707
+ columns: { ... }
708
+ });
709
+
710
+ // ❌ WRONG - Missing "Table" suffix
711
+ name: "mydata"
712
+ name: "my_data"
713
+ ```
714
+
715
+ **Reserved column names** - Cannot use these as column names:
716
+ - `id` (auto-generated)
717
+ - `createdAt` (auto-generated)
718
+ - `updatedAt` (auto-generated)
719
+ - `computed`
720
+ - `stale`
721
+
722
+ ```typescript
723
+ // ❌ WRONG - Using reserved column name
724
+ columns: {
725
+ createdAt: z.string() // Reserved!
726
+ }
727
+
728
+ // ✅ CORRECT - Use alternative name
729
+ columns: {
730
+ savedAt: z.string()
731
+ }
732
+ ```
733
+
734
+ ### Auto-Registration
735
+
736
+ Files in `src/tables/` are **auto-registered** by the ADK. Do NOT re-export from index.ts:
737
+
738
+ ```typescript
739
+ // src/tables/index.ts
740
+ // ❌ WRONG - Causes duplicate registration errors
741
+ export { MyTable } from "./myTable";
742
+
743
+ // ✅ CORRECT - Leave empty or add comment
744
+ // Tables are auto-registered from src/tables/*.ts files
745
+ ```
746
+
747
+ Same applies to `src/conversations/`, `src/workflows/`, `src/triggers/`, etc.
748
+
749
+ Beyond basic CRUD, Tables support powerful query and manipulation features:
750
+
751
+ ### Complex Filtering
752
+
753
+ Use logical operators and conditions:
754
+
755
+ ```typescript
756
+ await MyTable.findRows({
757
+ filter: {
758
+ $and: [
759
+ { status: 'open' },
760
+ { priority: { $in: ['high', 'urgent'] } }
761
+ ],
762
+ $or: [
763
+ { assignee: userId },
764
+ { reporter: userId }
765
+ ],
766
+ title: { $regex: 'bug|error', $options: 'i' }
767
+ }
768
+ })
769
+ ```
770
+
771
+ **Filter operators:**
772
+ - `$eq`, `$ne` - Equal, not equal
773
+ - `$gt`, `$gte`, `$lt`, `$lte` - Comparisons
774
+ - `$in`, `$nin` - In array, not in array
775
+ - `$exists` - Field exists
776
+ - `$regex` - Regular expression match
777
+ - `$options` - Regex options (e.g., 'i' for case-insensitive)
778
+ - `$and`, `$or` - Logical operators
779
+
780
+ ### Full-Text Search
781
+
782
+ Search across searchable columns:
783
+
784
+ ```typescript
785
+ await MyTable.findRows({
786
+ search: 'query string',
787
+ filter: { status: 'active' }
788
+ })
789
+ ```
790
+
791
+ Mark columns as searchable in schema:
792
+
793
+ ```typescript
794
+ columns: {
795
+ title: z.string().searchable(),
796
+ description: z.string().searchable()
797
+ }
798
+ ```
799
+
800
+ ### Aggregation and Grouping
801
+
802
+ Group and aggregate data:
803
+
804
+ ```typescript
805
+ await MyTable.findRows({
806
+ group: {
807
+ status: 'count',
808
+ priority: ['sum', 'avg'],
809
+ complexity: ['max', 'min']
810
+ }
811
+ })
812
+ ```
813
+
814
+ **Aggregation operations:** `key`, `count`, `sum`, `avg`, `max`, `min`, `unique`
815
+
816
+ ### Computed Columns
817
+
818
+ Columns with values computed from row data:
819
+
820
+ ```typescript
821
+ columns: {
822
+ fullName: {
823
+ computed: true,
824
+ schema: z.string(),
825
+ dependencies: ['firstName', 'lastName'],
826
+ value: async (row) => `${row.firstName} ${row.lastName}`
827
+ },
828
+ age: {
829
+ computed: true,
830
+ schema: z.number(),
831
+ dependencies: ['birthDate'],
832
+ value: async (row) => {
833
+ const today = new Date()
834
+ const birth = new Date(row.birthDate)
835
+ return today.getFullYear() - birth.getFullYear()
836
+ }
837
+ }
838
+ }
839
+ ```
840
+
841
+ ### Upsert Operations
842
+
843
+ Insert or update based on key column:
844
+
845
+ ```typescript
846
+ await MyTable.upsertRows({
847
+ rows: [
848
+ { externalId: '123', name: 'Item 1' },
849
+ { externalId: '456', name: 'Item 2' }
850
+ ],
851
+ keyColumn: 'externalId', // Update if exists, insert if not
852
+ waitComputed: true // Wait for computed columns to update
853
+ })
854
+ ```
855
+
856
+ ### Bulk Operations
857
+
858
+ Efficient batch operations:
859
+
860
+ ```typescript
861
+ // Delete by filter
862
+ await MyTable.deleteRows({
863
+ filter: { status: 'archived', createdAt: { $lt: '2024-01-01' } }
864
+ })
865
+
866
+ // Delete by IDs
867
+ await MyTable.deleteRowIds([1, 2, 3])
868
+
869
+ // Delete all
870
+ await MyTable.deleteAllRows()
871
+
872
+ // Update multiple
873
+ await MyTable.updateRows({
874
+ rows: [
875
+ { id: 1, status: 'active' },
876
+ { id: 2, status: 'inactive' }
877
+ ],
878
+ waitComputed: true
879
+ })
880
+ ```
881
+
882
+ ### Error Handling
883
+
884
+ Collect errors and warnings from bulk operations:
885
+
886
+ ```typescript
887
+ const { errors, warnings } = await MyTable.createRows({
888
+ rows: data,
889
+ waitComputed: true
890
+ })
891
+
892
+ if (errors?.length) {
893
+ console.error('Failed rows:', errors)
894
+ }
895
+ if (warnings?.length) {
896
+ console.warn('Warnings:', warnings)
897
+ }
898
+ ```
899
+
900
+ ## Knowledge Base Operations
901
+
902
+ ### Data Sources
903
+
904
+ Multiple source types for knowledge bases:
905
+
906
+ #### Directory Source
907
+
908
+ ```typescript
909
+ import { DataSource } from '@botpress/runtime'
910
+
911
+ const docs = DataSource.Directory.fromPath('src/knowledge', {
912
+ id: 'docs',
913
+ filter: (path) => path.endsWith('.md') || path.endsWith('.txt')
914
+ })
915
+ ```
916
+
917
+ #### Website Source
918
+
919
+ ```typescript
920
+ const siteDocs = DataSource.Website.fromSitemap('https://example.com/sitemap.xml', {
921
+ id: 'website',
922
+ maxPages: 500,
923
+ fetch: 'node:fetch' // or custom fetch implementation
924
+ })
925
+ ```
926
+
927
+ ### Knowledge Base Definition
928
+
929
+ ```typescript
930
+ import { Knowledge } from '@botpress/runtime'
931
+
932
+ export default new Knowledge({
933
+ name: 'docs',
934
+ description: 'Product documentation',
935
+ sources: [docsDirectory, websiteSource]
936
+ })
937
+ ```
938
+
939
+ ### Refresh Operations
940
+
941
+ Manually refresh knowledge base content:
942
+
943
+ ```typescript
944
+ // Refresh entire knowledge base
945
+ await DocsKB.refresh({ force: true })
946
+
947
+ // Refresh specific source
948
+ await DocsKB.refreshSource('website', { force: true })
949
+ ```
950
+
951
+ **Options:**
952
+ - `force: true` - Force refresh even if recently updated
953
+ - Automatic refresh via scheduled workflows recommended
954
+
955
+ ### Using Knowledge in Execute
956
+
957
+ ```typescript
958
+ await execute({
959
+ instructions: 'Answer using the documentation',
960
+ knowledge: [DocsKB, APIKB],
961
+ tools: [searchTool]
962
+ })
963
+ ```
964
+
965
+ Knowledge bases are automatically searchable via the `search_knowledge` tool.
966
+
967
+ ## Advanced Conversation Patterns
968
+
969
+ ### Multiple Channel Support
970
+
971
+ Handle messages from multiple channels in one handler:
972
+
973
+ ```typescript
974
+ export default new Conversation({
975
+ channel: ['chat.channel', 'webchat.channel', 'slack.dm'],
976
+ handler: async ({ channel, execute }) => {
977
+ console.log(`Message from: ${channel}`)
978
+ await execute({ instructions: '...' })
979
+ }
980
+ })
981
+ ```
982
+
983
+ ### Event Handling
984
+
985
+ Subscribe to integration events:
986
+
987
+ ```typescript
988
+ export default new Conversation({
989
+ channel: 'webchat.channel',
990
+ events: ['webchat:conversationStarted', 'webchat:conversationEnded'],
991
+ handler: async ({ type, event, message }) => {
992
+ if (type === 'event' && event.type === 'webchat:conversationStarted') {
993
+ // Send welcome message
994
+ await conversation.send({
995
+ type: 'text',
996
+ payload: { text: 'Welcome!' }
997
+ })
998
+ }
999
+
1000
+ if (type === 'message' && message?.type === 'text') {
1001
+ // Handle regular messages
1002
+ await execute({ instructions: '...' })
1003
+ }
1004
+ }
1005
+ })
1006
+ ```
1007
+
1008
+ ### Workflow Request Handling
1009
+
1010
+ Handle data requests from workflows:
1011
+
1012
+ ```typescript
1013
+ import { isWorkflowDataRequest } from '@botpress/runtime'
1014
+
1015
+ handler: async ({ type, event, execute }) => {
1016
+ // Check if this is a workflow requesting data
1017
+ if (type === 'workflow_request' && isWorkflowDataRequest(event)) {
1018
+ const userInput = await promptUser(event.payload.message)
1019
+
1020
+ // Provide data back to workflow
1021
+ await workflow.provide(event, { topic: userInput })
1022
+ return
1023
+ }
1024
+
1025
+ // Regular message handling
1026
+ await execute({ instructions: '...' })
1027
+ }
1028
+ ```
1029
+
1030
+ ### Typed Workflow Interactions
1031
+
1032
+ Work with typed workflow instances:
1033
+
1034
+ ```typescript
1035
+ import { isWorkflow, ResearchWorkflow } from '@botpress/runtime'
1036
+
1037
+ handler: async ({ state }) => {
1038
+ if (state.research && isWorkflow(state.research, 'research')) {
1039
+ // state.research is now typed as ResearchWorkflow
1040
+ console.log(state.research.status)
1041
+ console.log(state.research.output) // Typed output
1042
+
1043
+ if (state.research.status === 'completed') {
1044
+ await conversation.send({
1045
+ type: 'text',
1046
+ payload: { text: state.research.output.result }
1047
+ })
1048
+ }
1049
+ }
1050
+ }
1051
+ ```
1052
+
1053
+ ### Dynamic Tools Based on State
1054
+
1055
+ Provide different tools based on conversation state:
1056
+
1057
+ ```typescript
1058
+ handler: async ({ state, execute }) => {
1059
+ const tools = () => {
1060
+ if (state.workflowRunning) {
1061
+ return [cancelWorkflowTool, checkStatusTool]
1062
+ } else {
1063
+ return [startWorkflowTool, browseTool, searchTool]
1064
+ }
1065
+ }
1066
+
1067
+ await execute({
1068
+ instructions: '...',
1069
+ tools: tools()
1070
+ })
1071
+ }
1072
+ ```
1073
+
1074
+ ### Message Sending
1075
+
1076
+ Send different message types:
1077
+
1078
+ ```typescript
1079
+ // Text message
1080
+ await conversation.send({
1081
+ type: 'text',
1082
+ payload: { text: 'Hello!' }
1083
+ })
1084
+
1085
+ // Custom message type (integration-specific)
1086
+ await conversation.send({
1087
+ type: 'custom:messageType',
1088
+ payload: { data: 'custom payload' }
1089
+ })
1090
+ ```
1091
+
1092
+ ## Citations System
1093
+
1094
+ Track and manage source citations for LLM responses:
1095
+
1096
+ ### CitationsManager
1097
+
1098
+ Access via context:
1099
+
1100
+ ```typescript
1101
+ import { context } from '@botpress/runtime'
1102
+
1103
+ const citations = context.get('citations')
1104
+ ```
1105
+
1106
+ ### Registering Sources
1107
+
1108
+ Register sources that can be cited:
1109
+
1110
+ ```typescript
1111
+ // Register with URL
1112
+ const { tag } = citations.registerSource({
1113
+ url: 'https://example.com/doc',
1114
+ title: 'Documentation Page'
1115
+ })
1116
+
1117
+ // Register with file reference
1118
+ const { tag } = citations.registerSource({
1119
+ file: fileKey,
1120
+ title: 'Internal Document'
1121
+ })
1122
+ ```
1123
+
1124
+ ### Using Citation Tags
1125
+
1126
+ Inject citation tags into LLM content:
1127
+
1128
+ ```typescript
1129
+ const results = await searchKnowledgeBase(query)
1130
+
1131
+ for (const result of results) {
1132
+ const { tag } = citations.registerSource({
1133
+ file: result.file.key,
1134
+ title: result.file.name
1135
+ })
1136
+
1137
+ content += `${result.content} ${tag}\n`
1138
+ }
1139
+
1140
+ // Return cited content
1141
+ throw new ThinkSignal('results', content)
1142
+ ```
1143
+
1144
+ ### Citation Format
1145
+
1146
+ Citations are automatically formatted with tags like `[1]`, `[2]`, etc., and tracked by the system for reference.
1147
+
1148
+ ### Example: Tool with Citations
1149
+
1150
+ ```typescript
1151
+ export default new Autonomous.Tool({
1152
+ name: 'search_docs',
1153
+ description: 'Search documentation',
1154
+ handler: async ({ query }) => {
1155
+ const citations = context.get('citations')
1156
+ const results = await searchDocs(query)
1157
+
1158
+ let response = ''
1159
+ for (const doc of results) {
1160
+ const { tag } = citations.registerSource({
1161
+ url: doc.url,
1162
+ title: doc.title
1163
+ })
1164
+ response += `${doc.content} ${tag}\n\n`
1165
+ }
1166
+
1167
+ return response
1168
+ }
1169
+ })
1170
+ ```
1171
+
1172
+ ## Common Mistakes to Avoid
1173
+
1174
+ ### 1. Wrong Zai Import
1175
+ ```typescript
1176
+ // ❌ WRONG
1177
+ import { zai } from '@botpress/runtime'
1178
+ const result = await zai.extract(...)
1179
+
1180
+ // ✅ CORRECT
1181
+ import { adk } from '@botpress/runtime'
1182
+ const result = await adk.zai.extract(...)
1183
+ ```
1184
+
1185
+ ### 2. Expecting `.output` from zai.extract
1186
+ ```typescript
1187
+ // ❌ WRONG - zai.extract returns data directly
1188
+ const result = await adk.zai.extract(input, schema)
1189
+ console.log(result.output) // undefined!
1190
+
1191
+ // ✅ CORRECT
1192
+ const result = await adk.zai.extract(input, schema)
1193
+ console.log(result) // { name: "John", age: 30 }
1194
+ ```
1195
+
1196
+ ### 3. Wrong Exit Result Handling
1197
+ ```typescript
1198
+ // ❌ WRONG
1199
+ if (result.exit?.name === 'my_exit') {
1200
+ const data = result.exit.value
1201
+ }
1202
+
1203
+ // ✅ CORRECT
1204
+ if (result.is(MyExit)) {
1205
+ const data = result.output // Type-safe!
1206
+ }
1207
+ ```
1208
+
1209
+ ### 4. Reserved Table Column Names
1210
+ ```typescript
1211
+ // ❌ WRONG - These are reserved
1212
+ columns: {
1213
+ id: z.string(),
1214
+ createdAt: z.string(),
1215
+ updatedAt: z.string()
1216
+ }
1217
+
1218
+ // ✅ CORRECT - Use alternatives
1219
+ columns: {
1220
+ visibleId: z.string(),
1221
+ savedAt: z.string(),
1222
+ modifiedAt: z.string()
1223
+ }
1224
+ ```
1225
+
1226
+ ### 5. Re-exporting Auto-Registered Files
1227
+ ```typescript
1228
+ // ❌ WRONG - src/tables/index.ts
1229
+ export { MyTable } from "./myTable" // Causes duplicates!
1230
+
1231
+ // ✅ CORRECT - Leave index.ts empty
1232
+ // Files in src/tables/, src/conversations/, etc. are auto-registered
1233
+ ```
1234
+
1235
+ ### 6. Table Name Missing "Table" Suffix
1236
+ ```typescript
1237
+ // ❌ WRONG
1238
+ name: "users"
1239
+ name: "user_data"
1240
+
1241
+ // ✅ CORRECT
1242
+ name: "usersTable"
1243
+ name: "userdataTable"
1244
+ ```
1245
+
1246
+ ## When Making Changes
1247
+
1248
+ 1. **Always search Botpress docs** using `mcp__botpress-docs__SearchBotpress`
1249
+ 2. **Check examples** for patterns
1250
+ 3. **Regenerate types** after changing `agent.config.ts` (run `adk dev`)
1251
+ 4. **Test in dev mode** with hot reloading (`adk dev`)
1252
+ 5. **Follow TypeScript types** - They're auto-generated from integrations
1253
+
1254
+ ## Resources
1255
+
1256
+ - [ADK Overview](https://botpress.com/docs/for-developers/adk/overview)
1257
+ - [ADK Getting Started](https://botpress.com/docs/for-developers/adk/getting-started)
1258
+ - [Project Structure](https://botpress.com/docs/for-developers/adk/project-structure)
1259
+ - [Conversations](https://botpress.com/docs/for-developers/adk/concepts/conversations)
1260
+ - [Workflows](https://botpress.com/docs/for-developers/adk/concepts/workflows)