Headless Threads
Manage persistent, resumable conversations with useThreads and the Enterprise Intelligence Platform — list, create, rename, archive, and delete threads with realtime sync across devices.
What is this?#
CopilotKit threads enable persistent, resumable multi-turn conversations. The useThreads hook lists, creates, renames, archives, and deletes Enterprise Intelligence Platform threads with realtime synchronization via WebSocket. Threads work with any agent framework — the Enterprise Intelligence Platform stores conversation history server-side, so users can close their browser and pick up where they left off. It does not list or mutate native LangGraph, ADK, or other framework stores unless your backend explicitly bridges those systems. Thread metadata updates (renames, archives, new threads) appear on connected clients without polling.
When should I use this?#
- Your app needs multiple saved conversations per user (like a chat history sidebar)
- Users should be able to resume a prior conversation across sessions or devices
- You want realtime sync so threads created on one tab appear on another
- You need to let users organize conversations by renaming or archiving them
Prerequisites#
- The Enterprise Intelligence Platform
- An OpenAI API key
- An existing CopilotKit project on
@copilotkit/react-corev1.56+
Implementation#
Migrating existing history?
Threads capture new CopilotKit conversations once your app is connected to Enterprise Intelligence. To move historical Google ADK or LangGraph conversations into the same thread store, start with Import thread history.
Configure your runtime with the Enterprise Intelligence Platform#
Connect your CopilotKit runtime to the Enterprise Intelligence Platform. This provides the thread storage and WebSocket infrastructure. Thread names are automatically generated by the LLM after the first message — you can disable this with generateThreadNames: false.
import { CopilotRuntime } from "@copilotkit/runtime";
const runtime = new CopilotRuntime({
agents: {
default: agent,
},
// Thread names are auto-generated by default.
// Set to false to disable:
// generateThreadNames: false,
// Optional: tune thread lock behavior
// lockTtlSeconds: 20, // Lock TTL (default 20s, max 3600s)
// lockHeartbeatIntervalSeconds: 15, // Heartbeat interval (default 15s, max 3000s)
// lockKeyPrefix: "my-app", // Custom Redis key prefix for the lock
});If you're using Cloud-Hosted Enterprise Intelligence, the CLI writes the hosted platform URLs and project-scoped INTELLIGENCE_API_KEY to .env. For the persistence model behind thread storage, event replay, and realtime sync, see Threads & Persistence Architecture. For self-hosted deployments, see Self-Hosting Enterprise Intelligence for the Helm chart install and database configuration.
Thread lock options: When an agent run starts, the runtime acquires a lock on the thread to prevent concurrent runs. You can tune this behavior:
| Option | Default | Max | Description |
|---|---|---|---|
lockTtlSeconds | 20 | 3600 (1 hour) | How long the lock is held before it expires automatically. |
lockHeartbeatIntervalSeconds | 15 | 3000 (50 min) | How often the runtime renews the lock during a run. |
lockKeyPrefix | — | — | Custom Redis key prefix for the thread lock. Useful when multiple apps share a Redis instance. |
List and manage threads with useThreads#
Use the useThreads hook to fetch and manage threads for a specific agent. The hook returns the thread list, loading state, and mutation methods.
import { useThreads } from "@copilotkit/react-core/v2";
function ThreadSidebar() {
const {
threads,
isLoading,
renameThread,
archiveThread,
deleteThread,
} = useThreads({ agentId: "my-agent" });
if (isLoading) return <div>Loading...</div>;
return (
<div>
{threads.map((thread) => (
<div key={thread.id}>
<span>{thread.name ?? "New conversation"}</span>
<button onClick={() => renameThread(thread.id, "Renamed")}>
Rename
</button>
<button onClick={() => archiveThread(thread.id)}>
Archive
</button>
</div>
))}
</div>
);
}The threads array is sorted by most recently updated first and stays synchronized in realtime — new threads from other tabs or devices appear automatically.
Archive vs. delete: archiveThread is a soft delete — the thread stays in the database but is hidden from the list by default. Pass includeArchived: true to show archived threads. deleteThread is permanent and irreversible. Neither has a built-in confirmation dialog — add your own if needed.
Switch between threads#
When a user selects a thread, pass its threadId to your chat component. The chat clears the current messages, fetches the selected thread's history, and replays it. If the agent is still running on that thread, the chat picks up the live stream.
import { CopilotChat } from "@copilotkit/react-core/v2";
import { useState } from "react";
function App() {
const [activeThreadId, setActiveThreadId] = useState<string | undefined>();
return (
<div className="flex">
<ThreadSidebar onSelectThread={setActiveThreadId} />
<CopilotChat threadId={activeThreadId} /> {}
</div>
);
}When threadId changes, the chat component automatically loads the selected thread's history and reconnects to the agent's stream. If the agent is still running on that thread, the chat picks up the live stream.
Add pagination for large thread lists#
For users with many conversations, use the limit parameter to enable cursor-based pagination.
const {
threads,
hasMoreThreads,
isFetchingMoreThreads,
fetchMoreThreads,
} = useThreads({
agentId: "my-agent",
limit: 20,
});
// In your JSX:
{hasMoreThreads && (
<button
onClick={fetchMoreThreads}
disabled={isFetchingMoreThreads}
>
{isFetchingMoreThreads ? "Loading..." : "Load more"}
</button>
)}Next steps#
- Thread architecture: Threads & Persistence Architecture — event replay model and WebSocket sync
- API reference: useThreads — parameters, return values, types