--- title: "Thread" description: "The per-conversation handle — post and stream messages, run the agent, block on a human choice, and reach platform power through capability-gated methods." --- ## 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](/reference/bot/components/Message) 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; post(ui: Renderable): Promise; update(ref: MessageRef, ui: Renderable): Promise; delete(ref: MessageRef): Promise; stream(src: string | AsyncIterable): Promise; runAgent(input?: { context?: ContextEntry[]; tools?: BotTool[]; prompt?: string; }): Promise; resume(value: unknown): Promise; awaitChoice(ui: Renderable): Promise; getMessages(): Promise; lookupUser(query: string): Promise; postFile(args: { bytes: Uint8Array; filename: string; title?: string; altText?: string; }): Promise<{ ok: boolean; fileId?: string; error?: string }>; } ``` ## Properties The surface this conversation lives on, e.g. `"slack"`. ## Methods 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`. Re-render an existing message in place — e.g. flipping a confirmation card to its approved state from a button's `onClick`. Delete a posted message. 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). 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. Extra context entries merged on top of the bot-level `context` for this run only. Extra tools merged on top of the bot-level `tools` for this run only. 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. 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. 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. 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`. Resolve a platform user from a free-form query (name, handle, email). **Capability-gated**: returns `undefined` when unsupported. 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`. ## 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 }>( , ); return choice ?? { confirmed: false }; // serialized for the agent automatically } ``` ## Behavior - **Capability gating keeps tools portable** — `getMessages` / `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 merging** — `runAgent`'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. ## Related - [createBot](/reference/bot/functions/createBot) — where handlers receive a Thread - [defineBotTool](/reference/bot/functions/defineBotTool) — tools receive the Thread in `ctx.thread` - [Button](/reference/bot/components/Button) — interactive messages and `awaitChoice` - [slack()](/reference/bot/slack) — how the Slack adapter backs these methods