createBot
Create a bot: wire platform adapters to an AG-UI agent, register tools, context, and slash commands, and get the handler surface.
Overview
createBot is the entry point of @copilotkit/bot. It wires one or more platform adapters to an AG-UI agent and returns a Bot — the surface for registering turn handlers, interaction handlers, interrupt handlers, slash commands, and tools, plus start() / stop() lifecycle control.
For a complete walkthrough, see the Slack quickstart.
Signature
import { createBot } from "@copilotkit/bot";
function createBot<TStateSchema extends StandardSchemaV1 | undefined = undefined>(
opts: CreateBotOptions<TStateSchema>
): Bot<ThreadStateOf<TStateSchema>>;createBot is generic over the per-thread state schema. Pass a Standard Schema as store.state and the returned Bot's handler callbacks receive a thread narrowed to StatefulThread<YourStateType> — thread.state() and thread.setState() are fully typed at the call site.
Parameters
Prop
Type
Return Value
Prop
Type
Usage
Minimal
import { createBot } from "@copilotkit/bot";
import { slack, defaultSlackTools, defaultSlackContext } from "@copilotkit/bot-slack";
const bot = createBot({
adapters: [
slack({
botToken: process.env.SLACK_BOT_TOKEN!,
appToken: process.env.SLACK_APP_TOKEN!,
}),
],
agent: (threadId) => makeAgent(threadId),
tools: [...defaultSlackTools, ...appTools],
context: [...defaultSlackContext, ...appContext],
});
bot.onMention(async ({ thread }) => {
await thread.runAgent();
});
await bot.start();Durable persistence + typed per-thread state
import { createBot } from "@copilotkit/bot";
import { z } from "zod";
const WorkflowState = z.object({
step: z.enum(["idle", "awaiting-approval", "done"]),
lastUpdated: z.number(),
});
const bot = createBot({
adapters: [slack({ botToken, appToken })],
agent: (threadId) => makeAgent(threadId),
store: {
// add adapter: <your StateStore> for durability
state: WorkflowState,
},
});
// `thread` is narrowed to StatefulThread<{ step: ...; lastUpdated: number }>
bot.onMention(async ({ thread }) => {
const current = await thread.state();
await thread.setState({ step: "awaiting-approval", lastUpdated: Date.now() });
await thread.runAgent();
});Cross-platform transcripts
const bot = createBot({
adapters: [slackAdapter, discordAdapter],
agent: (threadId) => makeAgent(threadId),
store: {
// Uses MemoryStore by default (lost on restart).
// Pass adapter: <your StateStore> for durable transcript history.
identity: async ({ author }) => lookupEmailForUser(author.id),
transcripts: { retention: "30d", maxPerUser: 500 },
},
});
bot.onMention(async ({ thread }) => {
// Injects prior cross-platform history, appends the user turn,
// runs the agent, and captures the assistant reply — all in one call.
await thread.runAgent({ transcript: true });
});For a detailed guide on persistence and transcripts, see Persistence and Transcripts.
Behavior
- Mention-preferred routing — there is no per-turn "kind": when any
onMentionhandler is registered, all turns route to the mention handlers; otherwiseonMessagehandlers fire. Registering identical handlers on both never double-fires. - Expired actions are swallowed — a click whose snapshot is gone (e.g. after a restart with the in-memory store) raises
ActionExpiredErrorinternally;createBotswallows it, so the click is acked but ignored and no message is posted. - Commands are matched case-insensitively and without the leading slash.
- No agent, no
runAgent— omittingagentis fine for bots that only post UI, butthread.runAgent()will throw.
Related
- Thread — the per-conversation handle your handlers receive
- StateStore — pluggable persistence backends (in-memory default; bring your own durable backend)
- defineBotTool — tools the agent can call
- defineBotCommand — slash commands
- ActionStore — action binding and durability
- Persistence guide — choosing and configuring a backend
- Transcripts guide — cross-platform conversation history
- Slack quickstart — zero to a working bot