showcase/shell-docs/src/content/reference/channels/classes/Channel.mdx
createChannel(options) returns the Channel you declare on
CopilotRuntime({ channels }).
import { createChannel } from "@copilotkit/channels";
import { makeAgent } from "./agent.js";
const channel = createChannel({
name: "support-slack",
provider: "slack",
agent: makeAgent,
});
Use provider: "teams" and a project-unique Teams Channel Code when declaring
the Microsoft Teams provider.
| Option | Type | Description |
|---|---|---|
name | string | Project-unique Intelligence Code. Required for managed Channels; lowercase kebab-case, 3–64 characters, and not channels. |
provider | "slack" | "teams" | Managed provider declared to Intelligence. Slack is the default when omitted; set it explicitly in production code. |
showToolStatus | boolean | Controls managed Slack tool-call progress. It is hidden by default; set true to show it. |
agent | AbstractAgent | (threadId: string) => AbstractAgent | Agent instance or factory. Factory preferred; a singleton is isolated per run via clone(). |
tools | ChannelTool[] | Typed tools forwarded to the agent. |
context | ContextEntry[] | Stable { description, value } context sent on each run. |
components | ChannelComponent[] | Named JSX components whose callbacks can be reconstructed from stored snapshots. |
commands | ChannelCommand[] | Declared commands for providers that support them. |
store | StoreConfig | State schema, persistence, turn concurrency (parallel default), dedup, and optional transcript bridge. |
adapters | PlatformAdapter[] | Low-level direct transports. Managed Slack/Teams quickstarts do not use this option. |
One Channel declares one provider. Names must be unique inside one
CopilotRuntime.
| Option | Type | Description |
|---|---|---|
state | StandardSchema | Types thread.state() and validates every setState(value). |
adapter | StateStore | Persistence implementation for SDK state. Without it, the current managed realtime path falls back to process memory. |
identity | Identity | Resolves a stable user key for the optional transcript bridge. Must be paired with transcripts. |
transcripts | TranscriptsConfig | SDK-owned cross-platform transcript configuration. Distinct from managed conversation history. Must be paired with identity. |
concurrency | "parallel" | "serial" | "drop" | How overlapping turns on the same conversation are handled. Default: "parallel". See StoreConfig. |
onLockConflict | "drop" | "force" | callback | Deprecated. Prefer concurrency. Maps to drop/parallel when static. |
lockTtl | number | Conversation lock TTL in milliseconds (drop / legacy paths). Default: 60_000. |
dedupTtl | number | Inbound event deduplication window in milliseconds. Default: 300_000. |
StateStore contains kv, list, lock, dedup, and queue facets. Remote
implementations must preserve JSON-serializable values.
A production adapter implements this asynchronous contract. Locks, deduplication, and queue operations must remain atomic when multiple runner instances share the same backend.
interface StateStore {
kv: {
get<T>(key: string): Promise<T | undefined>;
set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
delete(key: string): Promise<void>;
};
list: {
append<T>(
key: string,
value: T,
options?: { maxLen?: number; ttlMs?: number },
): Promise<number>;
range<T>(key: string, start?: number, stop?: number): Promise<T[]>;
trim(key: string, maxLen: number): Promise<void>;
delete(key: string): Promise<void>;
};
lock: {
acquire(
key: string,
options?: { ttlMs?: number },
): Promise<{ token: string } | null>;
release(key: string, token: string): Promise<void>;
};
dedup: {
// true means the key was already recorded inside the TTL.
seen(key: string, ttlMs: number): Promise<boolean>;
};
queue: {
enqueue<T>(
key: string,
value: T,
options?: {
maxSize?: number;
onFull?: "drop-oldest" | "drop-newest";
},
): Promise<number>;
dequeue<T>(key: string): Promise<T | undefined>;
depth(key: string): Promise<number>;
};
}
All values must round-trip through JSON. A distributed implementation must release a lock only when the supplied token still owns it.
| Property | Type | Description |
|---|---|---|
name | string | undefined | Declared Intelligence Code. |
provider | "slack" | "teams" | undefined | Managed provider. undefined resolves to Slack. |
showToolStatus | boolean | undefined | Managed Slack tool-call visibility preference from createChannel(). |
adapters | readonly PlatformAdapter[] | Attached transport snapshot. Managed Channels start empty and receive the Intelligence adapter at activation. |
commandNames | string[] | Normalized names declared through commands or onCommand. |
transcripts | Transcripts | Optional SDK transcript API. It is not the managed provider-history store. |
The transcripts getter is only available after Channel activation and throws
if it is read before the runtime listener starts the Channel.
channel.onMessage(async ({ thread, message }) => {
await thread.runAgent({
prompt: message.contentParts?.length
? [
...(message.text
? [{ type: "text" as const, text: message.text }]
: []),
...message.contentParts,
]
: message.text,
});
});
| Method | Handler input | Notes |
|---|---|---|
onMessage(handler) | { thread, message } | Use this for managed Slack and Teams turns. |
onMention(handler) | { thread, message } | V1 shares turn routing with onMessage. If any mention handler is registered, it receives all turns and message handlers are skipped. Do not register both for managed Channels. |
onThreadStarted(handler) | { thread, user? } | Fires for provider surfaces that report a conversation-open event. |
message.platform is the native provider ("slack" or "teams").
thread.platform is "intelligence" on a managed Channel.
| Method | Purpose | Managed Slack/Teams |
|---|---|---|
onInteraction(id, handler) | Handle a specific opaque action id. | Supported for delivered actions. JSX callbacks are usually simpler. |
onInterrupt(eventName, handler) | Post UI for an agent interrupt; later call thread.resume(value). | Use eventName: "on_interrupt" for the current managed renderer. |
onReaction(emoji?, handler) | Handle delivered reaction events. | Inbound support is provider-dependent. |
onCommand(command) | Register a typed or free-text command. | Provider-dependent. |
onModalSubmit(callbackId, handler) | Handle a modal form submission. | Not delivered by the managed realtime path. |
onModalClose(callbackId, handler) | Handle a modal dismissal. | Not delivered by the managed realtime path. |
See the Slack interactive messages guide, the Teams interactive messages guide, and the JSX callback reference.
Pass tools in createChannel({ tools }), or add one before activation:
channel.tool(getIncident);
See Tools and context for Slack or Tools and context for Teams.
A Channel has no public start() method. Creating the long-running runtime
listener exposes lifecycle control but opens no connection. The first
ready() call is required and lazily opens the Realtime Gateway connection:
const listener = createCopilotNodeListener({ runtime });
const channels = listener.channels;
if (!channels) throw new Error("Channels were not configured.");
await channels.ready({ timeoutMs: 30_000 });
const status = channels.status();
if (status.overall !== "online") {
throw new Error(`Channel not online: ${JSON.stringify(status)}`);
}
// During graceful shutdown:
await channels.stop();
ready() reports the initial online or setup_required outcome once; inspect
status() before accepting traffic. Observe later drops and reconnects through
status().
| Status | Meaning |
|---|---|
connecting | ready() has not activated the Channel yet, or activation is in progress. |
online | The managed session is healthy and can receive delivery invitations. |
setup_required | The runtime declaration exists but provider setup is incomplete. |
reconnecting | The gateway connection dropped and is retrying. |
error | Activation or bounded reconnect failed. |
stopped | The listener was torn down. |