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 onMention handler is registered, all turns route to the mention handlers; otherwise onMessage handlers 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 ActionExpiredError internally; createBot swallows 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 — omitting agent is fine for bots that only post UI, but thread.runAgent() will throw.

Related