Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tambo-ai/tambo/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Threads are conversations between the user and the AI. Each thread maintains:
- Message history - All user and assistant messages
- Component state - Rendered components and their current props/state
- Metadata - Thread name, creation time, last updated
- Run state - Current streaming status and run IDs
Threads are persisted on the server and scoped to a user via userKey or userToken.
Thread Lifecycle
1. Creating Threads
Threads are created automatically when you send the first message:
import { useTamboThreadInput } from '@tambo-ai/react';
function ChatInterface() {
const { value, setValue, submit } = useTamboThreadInput();
const handleSubmit = async () => {
// Creates a new thread if one doesn't exist
await submit();
};
return (
<form onSubmit={handleSubmit}>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button type="submit">Send</button>
</form>
);
}
Or start a new thread explicitly:
import { useTambo } from '@tambo-ai/react';
function NewThreadButton() {
const { startNewThread } = useTambo();
return (
<button onClick={startNewThread}>
New Conversation
</button>
);
}
2. Accessing Thread Data
Get the current thread with useTambo():
import { useTambo } from '@tambo-ai/react';
function MessageList() {
const { messages, threadId, isStreaming } = useTambo();
return (
<div>
<h2>Thread: {threadId}</h2>
{messages.map((message) => (
<Message key={message.id} message={message} />
))}
{isStreaming && <LoadingIndicator />}
</div>
);
}
3. Switching Threads
Switch between existing threads:
import { useTambo } from '@tambo-ai/react';
function ThreadSwitcher({ threadId }: { threadId: string }) {
const { switchThread } = useTambo();
return (
<button onClick={() => switchThread(threadId)}>
Load Thread
</button>
);
}
4. Listing Threads
Get all threads for the current user:
import { useTamboThreadList } from '@tambo-ai/react';
function ThreadList() {
const { data: threads, isLoading } = useTamboThreadList();
if (isLoading) return <Spinner />;
return (
<ul>
{threads?.map((thread) => (
<li key={thread.id}>
<ThreadItem thread={thread} />
</li>
))}
</ul>
);
}
Thread Structure
A thread contains:
interface TamboThread {
/** Unique thread ID */
id: string;
/** Optional thread name */
name?: string;
/** All messages in the thread */
messages: TamboThreadMessage[];
/** Current thread status */
status: "idle" | "streaming" | "error";
/** Thread metadata */
metadata?: Record<string, unknown>;
/** Creation timestamp */
createdAt: string;
/** Last update timestamp */
updatedAt: string;
/** Whether the last run was cancelled */
lastRunCancelled: boolean;
}
Messages
Each message in a thread has:
interface TamboThreadMessage {
/** Unique message ID */
id: string;
/** Message role */
role: "user" | "assistant";
/** Message content (text, components, etc.) */
content: ContentPart[];
/** Creation timestamp */
createdAt: string;
/** Thread ID this message belongs to */
threadId: string;
}
Content Parts
Messages contain an array of content parts:
type ContentPart =
| { type: "text"; text: string }
| { type: "component"; name: string; props: Record<string, unknown>; ... }
| { type: "image_url"; image_url: { url: string } }
| { type: "tool_call"; tool_call: ToolCall }
| { type: "tool_result"; tool_result: ToolResult };
Example:
// User message
{
id: "msg_123",
role: "user",
content: [
{ type: "text", text: "Show me sales data" }
]
}
// Assistant message with component
{
id: "msg_124",
role: "assistant",
content: [
{ type: "text", text: "Here's your sales data:" },
{
type: "component",
name: "SalesChart",
props: { data: [...] }
}
]
}
Thread Naming
Threads can be named automatically or manually.
Auto-Generated Names
By default, Tambo generates names after a threshold of messages:
<TamboProvider
apiKey={apiKey}
userKey={userKey}
autoGenerateThreadName={true} // Default: true
autoGenerateNameThreshold={3} // Default: 3 messages
>
<App />
</TamboProvider>
Manual Naming
Set thread names programmatically:
import { useTambo } from '@tambo-ai/react';
function RenameThread() {
const { threadId, client } = useTambo();
const handleRename = async (newName: string) => {
if (threadId) {
await client.updateThreadName(threadId, newName);
}
};
return (
<input
onBlur={(e) => handleRename(e.target.value)}
placeholder="Thread name"
/>
);
}
Store custom data with threads:
// Metadata is set server-side via the API
// It's available in the thread object
function ThreadMetadata() {
const { thread } = useTambo();
return (
<div>
<p>Project: {thread?.metadata?.projectId}</p>
<p>Tags: {thread?.metadata?.tags?.join(", ")}</p>
</div>
);
}
Metadata is read-only from the client. Update it via your backend.
Thread Status
Monitor thread state:
function ThreadStatus() {
const { status, isStreaming } = useTambo();
return (
<div>
<p>Status: {status}</p>
{isStreaming && <Spinner />}
</div>
);
}
Status values:
"idle" - No active run
"streaming" - AI is generating a response
"error" - Last run encountered an error
Cancelling Runs
Cancel an in-progress AI response:
import { useTambo } from '@tambo-ai/react';
function CancelButton() {
const { isStreaming, client, threadId } = useTambo();
const handleCancel = async () => {
if (threadId) {
await client.cancelRun(threadId);
}
};
if (!isStreaming) return null;
return (
<button onClick={handleCancel}>
Stop Generating
</button>
);
}
User Scoping
Threads are scoped to users via userKey or userToken:
// Option 1: userKey (server-side or trusted environments)
<TamboProvider
apiKey={apiKey}
userKey={currentUserId} // All threads belong to this user
>
<App />
</TamboProvider>
// Option 2: userToken (client-side with OAuth)
<TamboProvider
apiKey={apiKey}
userToken={oauthToken} // Token contains user identity
>
<App />
</TamboProvider>
All thread operations (create, list, fetch) only return threads owned by the authenticated user.
See Authentication for details.
Initial Messages
Seed new threads with initial messages:
<TamboProvider
apiKey={apiKey}
userKey={userKey}
initialMessages={[
{
role: "assistant",
content: [
{ type: "text", text: "Hello! How can I help you today?" }
],
},
]}
>
<App />
</TamboProvider>
Initial messages:
- Display immediately in new threads
- Are sent to the API when the first user message is sent
- Only appear in new threads, not when switching to existing threads
Thread Hooks Reference
useTambo()
Access current thread data:
const {
threadId, // Current thread ID
messages, // All messages
status, // Thread status
isStreaming, // Whether AI is generating
client, // TamboClient instance
startNewThread, // Start a new thread
switchThread, // Switch to existing thread
} = useTambo();
useTamboThreadList()
List all user threads:
const {
data, // Array of threads
isLoading, // Loading state
error, // Error if any
refetch, // Refetch threads
} = useTamboThreadList();
useTamboThread(threadId)
Fetch a specific thread:
const {
data, // Thread data
isLoading, // Loading state
error, // Error if any
} = useTamboThread(threadId);
Best Practices
Thread List UI
Show threads with names and timestamps:
function ThreadListItem({ thread }: { thread: TamboThread }) {
const { switchThread } = useTambo();
return (
<button onClick={() => switchThread(thread.id)}>
<h3>{thread.name ?? "Untitled"}</h3>
<p>{new Date(thread.updatedAt).toLocaleDateString()}</p>
</button>
);
}
Loading States
Handle thread loading:
function ThreadView() {
const { messages, threadId } = useTambo();
if (!threadId) {
return <EmptyState />;
}
if (messages.length === 0) {
return <Skeleton />;
}
return <MessageList messages={messages} />;
}
Error Handling
Handle thread errors:
function ThreadContainer() {
const { status } = useTambo();
if (status === "error") {
return <ErrorMessage />;
}
return <ChatInterface />;
}
Next Steps