Skip to content

Bring your own thread list

The library keeps no threads. The thread list is the runtime’s: give your runtime a thread list adapter, then show it in the panel or in your layout.

The example keeps each user’s threads in a ThreadHub agent on the Worker and feeds them to an ExternalStoreThreadListAdapter. From examples/app/app/assistant.tsx:

export const Assistant = ({ children }: { children?: ReactNode }) => {
const hub = useAgent<HubState>({ agent: "ThreadHub", name: user, host });
const [picked, setPicked] = useState<string>();
const threads = useMemo(
() => hub.state?.threads.map((thread) => ({ ...thread, status: "regular" as const })),
[hub.state],
);
const threadId = threads?.find((thread) => thread.id === picked)?.id ?? threads?.[0]?.id;
if (!threadId) return null;
const select = (id: string) => startTransition(() => setPicked(id));
const threadList: ExternalStoreThreadListAdapter = {
threadId,
threads,
onSwitchToThread: select,
onSwitchToNewThread: async () => select(await hub.call<string>("createThread")),
onRename: (id, title) => hub.call<void>("renameThread", [id, title]),
onDelete: (id) => hub.call<void>("deleteThread", [id]),
};
return (
<Suspense>
<Conversation threadList={threadList}>{children}</Conversation>
</Suspense>
);
};

Conversation opens the picked thread’s chat and passes the adapter to the runtime. It also gives the runtime a fresh MessageRepository per chat, so a thread switch in place never shows the previous thread’s messages under the new one:

const repository = useMemo(() => new MessageRepository(), [chat.id]);
const runtime = useAISDKRuntime(chat as Parameters<typeof useAISDKRuntime>[0], {
adapters: { threadList },
unstable_messageRepositoryInstance: repository,
});

MessageRepository comes from @assistant-ui/core/internal, and the option is unstable. Re-check both when you upgrade assistant-ui.

Threads switch in place, so AssistedProvider stays mounted. The panel stays open and the page keeps what it lends. The Worker side, a RoutedAgents hub with createThread, renameThread and deleteThread, is in examples/agent/src/hub.ts.

Other runtimes bring their own thread list adapters. For example, useChatRuntime takes a cloud option for assistant-ui’s hosted threads.

Set threads on a floating or sheet panel. The header gets a picker over the runtime’s threads and a new-thread button. From examples/app/app/assistant.tsx:

<AssistedPanel mode="floating" threads description={description} />

Leave threads off when the runtime has no thread list. Every runtime lists one in-memory thread, so the panel cannot tell on its own.

In embedded mode the panel has no header. Place assistant-ui’s ThreadList element in your layout beside it. From examples/app/app/assistant.tsx:

<aside className="w-64 shrink-0 border-e p-2">
<ThreadList />
</aside>

Add ThreadList with assistant-ui’s CLI:

Terminal window
npx assistant-ui@latest add thread-list