Back to Copilotkit

Thread

showcase/shell-docs/src/content/reference/bot/classes/Thread.mdx

1.61.110.7 KB
Original Source

Overview

A Thread is the per-conversation handle passed to every handler, tool context, and interaction context. It posts UI (JSX from the component vocabulary or plain strings), drives the agent run loop, resolves human-in-the-loop choices, and exposes platform power through capability-gated methods that degrade gracefully on surfaces that don't support them.

ts
interface Thread {
  readonly platform: string;
  readonly platform: string;
  readonly conversationKey: string;
  post(ui: Renderable): Promise<MessageRef>;
  update(ref: MessageRef, ui: Renderable): Promise<MessageRef>;
  delete(ref: MessageRef): Promise<void>;
  stream(src: string | AsyncIterable<string>): Promise<MessageRef>;
  runAgent(input?: {
    context?: ContextEntry[];
    tools?: BotTool[];
    prompt?: string | AgentContentPart[];
    transcript?: boolean | { limit?: number };
  }): Promise<MessageRef | undefined>;
  resume(value: unknown): Promise<MessageRef | undefined>;
  awaitChoice<T = unknown>(ui: Renderable): Promise<T>;
  subscribe(): Promise<void>;
  unsubscribe(): Promise<void>;
  isSubscribed(): Promise<boolean>;
  setState<T>(value: T): Promise<void>;
  state<T>(): Promise<T | undefined>;
  getMessages(): Promise<ThreadMessage[]>;
  lookupUser(query: string): Promise<PlatformUser | undefined>;
  postFile(args: {
    bytes: Uint8Array;
    filename: string;
    title?: string;
    altText?: string;
  }): Promise<{ ok: boolean; fileId?: string; error?: string }>;
}

Properties

<PropertyReference name="platform" type="string" required> The surface this conversation lives on, e.g. `"slack"`. </PropertyReference> <PropertyReference name="conversationKey" type="string" required> Stable, platform-scoped identifier for this conversation — used internally by transcript bridging, per-thread state storage, and subscription records. Treat it as opaque. </PropertyReference>

Methods

<PropertyReference name="post" type="(ui: Renderable) => Promise<MessageRef>"> Render and post a message. JSX is rendered to the [BotNode IR](/reference/bot/types/BotNode), every event-prop handler in the tree is **bound** (minted a content-stable id, snapshotted, rewritten to the bare id), and the IR is handed to the adapter. Returns a `MessageRef` for later `update` / `delete`. </PropertyReference> <PropertyReference name="update" type="(ref: MessageRef, ui: Renderable) => Promise<MessageRef>"> Re-render an existing message in place — e.g. flipping a confirmation card to its approved state from a button's `onClick`. </PropertyReference> <PropertyReference name="delete" type="(ref: MessageRef) => Promise<void>"> Delete a posted message. </PropertyReference> <PropertyReference name="stream" type="(src: string | AsyncIterable<string>) => Promise<MessageRef>"> Post a placeholder and edit it in place as the source yields text. On Slack this is throttled `chat.update` with multi-message chunking — see [slack()](/reference/bot/slack). </PropertyReference> <PropertyReference name="runAgent" type="(input?) => Promise<MessageRef | undefined>"> Resolve the conversation's agent session, create the adapter's run renderer, and drive the run/tool/interrupt loop: streamed text is rendered into the thread, registered [tools](/reference/bot/functions/defineBotTool) are executed when the agent calls them, and captured interrupts are dispatched to `onInterrupt` handlers. <PropertyReference name="context" type="ContextEntry[]"> Extra context entries merged on top of the bot-level `context` for this run only. </PropertyReference> <PropertyReference name="tools" type="BotTool[]"> Extra tools merged on top of the bot-level `tools` for this run only. </PropertyReference> <PropertyReference name="prompt" type="string | AgentContentPart[]"> A user message injected before running. Use when the input isn't already in the history the adapter reconstructs — e.g. a slash command's text, which is never posted to the channel. Pass `AgentContentPart[]` to include multimodal content such as inbound image or file attachments. </PropertyReference> <PropertyReference name="transcript" type="boolean | { limit?: number }"> Auto-bridge cross-platform transcript history for this run. When truthy **and** the bot has `store.identity` + `store.transcripts` configured:
1. Fetches prior transcript entries for the resolved user (default 20, override with `{ limit: N }`).
2. Injects them as a context entry so the agent sees cross-platform history.
3. Appends the current user turn to the transcript before the run.
4. Captures the assistant reply after the run and appends it.

This flag owns the transcript bridge — do not also manually append the same turns via `bot.transcripts.append`. No-ops with a one-time console warning when `identity`/`transcripts` are not configured.
</PropertyReference> </PropertyReference> <PropertyReference name="resume" type="(value: unknown) => Promise<MessageRef | undefined>"> Re-enter a paused interrupt run, forwarding `value` to the agent (sent as `forwardedProps.command = { resume: value }` — the LangGraph `Command` shape). Typically called from a picker button's `onClick` inside an `onInterrupt` handler. </PropertyReference> <PropertyReference name="awaitChoice" type="<T>(ui: Renderable) => Promise<T>"> Post a picker and **block** until an interaction in this conversation resolves it — the human-in-the-loop primitive. Resolves to the clicked control's `value`; pass `T` to type it. See the [Button](/reference/bot/components/Button) page for a full confirm-card example. </PropertyReference> <PropertyReference name="subscribe" type="() => Promise<void>"> Record this conversation as subscribed in the persistent store. Subscriptions survive restarts when a durable `store.adapter` is configured. Proactive delivery to subscribed conversations is not yet wired in v1 — this stores the intent for future use. </PropertyReference> <PropertyReference name="unsubscribe" type="() => Promise<void>"> Remove the subscription for this conversation. </PropertyReference> <PropertyReference name="isSubscribed" type="() => Promise<boolean>"> Returns `true` if this conversation is currently marked as subscribed. </PropertyReference> <PropertyReference name="setState" type="<T>(value: T) => Promise<void>"> Persist arbitrary per-thread state — e.g. a workflow step, user preferences, or a task id. Stored under a conversation-scoped key in the [StateStore](/reference/bot/types/StateStore).

When store.state is configured on createBot, this method validates value against the schema at runtime and throws Error: thread.setState: invalid state — … on a mismatch. Pass the schema's inferred output type as T to keep call-site types aligned with the schema. </PropertyReference>

<PropertyReference name="state" type="<T>() => Promise<T | undefined>"> Read back the per-thread state previously written with `setState`. Returns `undefined` when no state has been written for this conversation yet. Pass `T` to type the return value — it must match what was written. </PropertyReference> <PropertyReference name="getMessages" type="() => Promise<ThreadMessage[]>"> Read the conversation's messages — each a `ThreadMessage` (`{ user?, text, ts?, isBot? }`). **Capability-gated**: returns `[]` when the adapter can't read history. On Slack, backed by `conversations.replies`. </PropertyReference> <PropertyReference name="lookupUser" type="(query: string) => Promise<PlatformUser | undefined>"> Resolve a platform user from a free-form query (name, handle, email). **Capability-gated**: returns `undefined` when unsupported. </PropertyReference> <PropertyReference name="postFile" type="(args: { bytes: Uint8Array; filename: string; title?: string; altText?: string }) => Promise<{ ok: boolean; fileId?: string; error?: string }>"> Upload a file into the conversation. **Capability-gated**: resolves `{ ok: false, error }` when the surface doesn't support uploads. On Slack, backed by `files.uploadV2`. </PropertyReference>

Usage

tsx
bot.onMention(async ({ thread, message }) => {
  // Run the agent with extra per-run context:
  await thread.runAgent({
    context: [
      { description: "Requesting user", value: message.user.name ?? message.user.id },
    ],
  });
});
tsx
// Inside a tool: read the thread, then block on approval.
async handler({ summary }, { thread }) {
  const choice = await thread.awaitChoice<{ confirmed: boolean }>(
    <ConfirmWrite action={summary} />,
  );
  return choice ?? { confirmed: false }; // serialized for the agent automatically
}
tsx
// Per-thread state: track a workflow step across turns.
bot.onMention(async ({ thread }) => {
  const state = await thread.state<{ step: string }>();
  if (state?.step === "awaiting-approval") {
    await thread.runAgent({ prompt: "The user has replied to your pending approval request." });
  } else {
    await thread.setState({ step: "awaiting-approval" });
    await thread.runAgent();
  }
});
tsx
// Cross-platform transcript bridging — one call injects history, appends the
// user turn, runs the agent, and captures the assistant reply.
bot.onMention(async ({ thread }) => {
  await thread.runAgent({ transcript: true });
});

Behavior

  • Capability gating keeps tools portablegetMessages / lookupUser / postFile delegate to the adapter when supported and degrade gracefully ([] / undefined / { ok: false }) when not, so the same tool runs on any surface.
  • Per-run mergingrunAgent's tools and context apply to that run only, layered on top of the bot-level defaults.
  • History reconstruction — on Slack, the conversation's agent.messages are rebuilt from Slack history each turn; the platform is the source of truth, so bot restarts don't lose conversations.
  • setState validation — when store.state is set on createBot, setState runs the schema synchronously and throws before writing to the store on a mismatch, so invalid state never persists.
  • Transcript bridge ownership — when runAgent({ transcript: true }) is used, you must not also manually call bot.transcripts.append for the same turn; the bridge is the sole owner of that user/assistant pair.