showcase/shell-docs/src/content/reference/bot/classes/Thread.mdx
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.
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 }>;
}
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.
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>
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 },
],
});
});
// 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
}
// 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();
}
});
// 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 });
});
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.runAgent's tools and context apply to that run only, layered on top of the bot-level defaults.agent.messages are rebuilt from Slack history each turn; the platform is the source of truth, so bot restarts don't lose conversations.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.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.ctx.threadawaitChoice