Cedar’s thread management system allows you to organize conversations into separate threads, each with their own message history and state. This enables users to maintain multiple conversation contexts simultaneously.
Overview
The thread system is built into the messagesSlice
and provides:
Thread isolation : Each thread maintains its own message history
Backward compatibility : Existing code continues to work with the main thread
Persistent storage : Threads can be persisted using storage adapters
UI components : Ready-to-use thread management components
Core Concepts
Thread Structure
Each thread contains:
interface MessageThread {
id : string ;
name ?: string ;
lastLoaded : string ;
messages : Message [];
}
Default Thread
Cedar automatically creates a default thread with ID DEFAULT_THREAD_ID
that serves as the main conversation thread.
Using Thread Management
Basic Thread Operations
React Hook
Direct Store Access
import { useThreadController } from 'cedar-os' ;
function MyComponent () {
const {
currentThreadId ,
threadIds ,
createThread ,
deleteThread ,
switchThread ,
updateThreadName
} = useThreadController ();
const handleCreateThread = () => {
const newThreadId = createThread ( undefined , 'New Conversation' );
switchThread ( newThreadId );
};
return (
< div >
< button onClick = { handleCreateThread } >
Create New Thread
</ button >
< p > Current: { currentThreadId } </ p >
< p > Total threads: { threadIds . length } </ p >
</ div >
);
}
Thread-Aware Message Operations
All message operations support an optional threadId
parameter. If not provided, they operate on the current thread:
import { useCedarStore } from 'cedar-os' ;
function MessageOperations () {
const addMessage = useCedarStore (( state ) => state . addMessage );
const getThreadMessages = useCedarStore (( state ) => state . getThreadMessages );
const clearMessages = useCedarStore (( state ) => state . clearMessages );
// Add message to current thread
const addToCurrentThread = () => {
addMessage ({
role: 'user' ,
content: 'Hello from current thread' ,
});
};
// Add message to specific thread
const addToSpecificThread = ( threadId : string ) => {
addMessage (
{
role: 'user' ,
content: 'Hello from specific thread' ,
},
true ,
threadId
);
};
// Get messages from specific thread
const getMessages = ( threadId : string ) => {
return getThreadMessages ( threadId );
};
// Clear specific thread
const clearThread = ( threadId : string ) => {
clearMessages ( threadId );
};
return (
< div >
< button onClick = { addToCurrentThread } > Add to Current </ button >
< button onClick = { () => addToSpecificThread ( 'thread-123' ) } >
Add to Specific Thread
</ button >
</ div >
);
}
useThreadController Hook
The useThreadController
hook provides a convenient interface for thread management:
Return Values
The ID of the currently active thread
Array of all thread IDs (memoized to prevent re-renders)
Methods
createThread
(threadId?: string, name?: string) => string
Creates a new thread and returns its ID. If no threadId provided, generates a
unique one.
deleteThread
(threadId: string) => void
Deletes a thread. Cannot delete the default thread or current thread.
switchThread
(threadId: string, name?: string) => void
Switches to a thread, creating it if it doesn’t exist.
updateThreadName
(threadId: string, name: string) => void
Updates the name of an existing thread.
setMainThreadId
(threadId: string) => void
Sets the main thread ID (ensures thread exists first).
ChatThreadController Component
Cedar provides a ready-to-use UI component for thread management:
import { ChatThreadController } from 'cedar-os-components' ;
function MyChat () {
const handleThreadChange = ( threadId : string ) => {
console . log ( 'Switched to thread:' , threadId );
};
return (
< div >
< ChatThreadController
onThreadChange = { handleThreadChange }
showCreateButton = { true }
showThreadList = { true }
className = 'my-thread-controls'
/>
</ div >
);
}
Props
Optional CSS class name for styling
onThreadChange
(threadId: string) => void
Callback fired when thread changes
Whether to show the create new thread button
Whether to show the thread history dropdown
MessagesSlice Thread API
The messagesSlice
provides the core thread management functionality:
State Structure
interface MessagesSlice {
// Core thread state
threadMap : MessageThreadMap ;
mainThreadId : string ;
// Backward compatibility
messages : Message []; // Current thread messages
// Thread management methods
createThread : ( threadId ?: string , name ?: string ) => string ;
deleteThread : ( threadId : string ) => void ;
switchThread : ( threadId : string , name ?: string ) => void ;
updateThreadName : ( threadId : string , name : string ) => void ;
setMainThreadId : ( threadId : string ) => void ;
// Thread getters
getThread : ( threadId ?: string ) => MessageThread | undefined ;
getThreadMessages : ( threadId ?: string ) => Message [];
getAllThreadIds : () => string [];
getCurrentThreadId : () => string ;
// Thread-aware message operations
setMessages : ( messages : Message [], threadId ?: string ) => void ;
addMessage : (
message : MessageInput ,
isComplete ?: boolean ,
threadId ?: string
) => Message ;
updateMessage : (
id : string ,
updates : Partial < Message >,
threadId ?: string
) => void ;
deleteMessage : ( id : string , threadId ?: string ) => void ;
clearMessages : ( threadId ?: string ) => void ;
}
Thread Safety
Thread operations are designed to be safe: - Cannot delete the default thread
(DEFAULT_THREAD_ID
) - Cannot delete the currently active thread - Switching
to non-existent threads creates them automatically - All operations ensure
thread existence before proceeding
Storage Integration
Threads integrate with Cedar’s storage system for persistence:
// Storage adapter interface supports threads
interface MessageStorageAdapter {
createThread ?: (
userId : string ,
threadId : string ,
meta : MessageThreadMeta
) => Promise < void >;
listThreads ?: ( userId : string ) => Promise < MessageThreadMeta []>;
// ... other methods
}
Auto-Thread Creation
When using storage adapters, Cedar can automatically create threads:
// In messageStorage.ts - automatically creates a thread if none exist
const loadAndSelectThreads = async (
userId : string ,
autoCreateThread : boolean = true
) => {
if ( threads . length === 0 && autoCreateThread ) {
const newThreadId = `thread- ${ Date . now () } - ${ Math . random ()
. toString ( 36 )
. substring ( 2 , 9 ) } ` ;
await adapter . createThread ( userId , newThreadId , {
id: newThreadId ,
title: 'New Chat' ,
updatedAt: new Date (). toISOString (),
});
}
};
Best Practices
Thread Management
Thread Naming : Always provide meaningful names when creating threads to improve user experience:const threadId = createThread ( undefined , 'Product Discussion' );
Memory Management
Efficient Updates : The thread system uses memoization to prevent unnecessary re-renders:// threadIds is memoized in useThreadController
const threadIds = useMemo (() => Object . keys ( threadMap ), [ threadMap ]);
Error Handling
Safe Deletion : Always check if a thread can be deleted before attempting:const handleDelete = ( threadId : string ) => {
if ( threadId === DEFAULT_THREAD_ID || threadId === currentThreadId ) {
console . warn ( 'Cannot delete this thread' );
return ;
}
deleteThread ( threadId );
};
Migration from Single Thread
Existing Cedar applications automatically work with the thread system:
Backward Compatibility
The messages
property continues to work and reflects the current thread’s messages.
Gradual Migration
You can gradually adopt thread-specific operations: tsx // Old way (still works) const messages = useCedarStore(state => state.messages); // New way (thread-aware) const messages = useCedarStore(state => state.getThreadMessages());
Enhanced Features
Add thread management UI when ready: import { ChatThreadController } from 'cedar-os-components' ;
// Add to your chat interface
< ChatThreadController onThreadChange = { handleThreadChange } />
Examples
Complete Thread Management
import React from 'react' ;
import { useThreadController , useCedarStore } from 'cedar-os' ;
import { ChatThreadController } from 'cedar-os-components' ;
function AdvancedChatInterface () {
const { currentThreadId , threadIds } = useThreadController ();
const messages = useCedarStore (( state ) => state . messages );
const addMessage = useCedarStore (( state ) => state . addMessage );
const handleSendMessage = ( content : string ) => {
addMessage ({
role: 'user' ,
content ,
});
};
const handleThreadChange = ( threadId : string ) => {
console . log ( `Switched to thread: ${ threadId } ` );
};
return (
< div className = 'chat-interface' >
< div className = 'chat-header' >
< ChatThreadController
onThreadChange = { handleThreadChange }
className = 'thread-controls'
/>
< span > Thread: { currentThreadId } </ span >
</ div >
< div className = 'messages' >
{ messages . map (( msg ) => (
< div key = { msg . id } className = { `message ${ msg . role } ` } >
{ msg . content }
</ div >
)) }
</ div >
< div className = 'thread-info' > Total threads: { threadIds . length } </ div >
</ div >
);
}
Custom Thread UI
import React , { useState } from 'react' ;
import { useThreadController } from 'cedar-os' ;
function CustomThreadManager () {
const [ newThreadName , setNewThreadName ] = useState ( '' );
const {
currentThreadId ,
threadIds ,
createThread ,
switchThread ,
deleteThread ,
updateThreadName ,
} = useThreadController ();
const handleCreateThread = () => {
if ( newThreadName . trim ()) {
const threadId = createThread ( undefined , newThreadName . trim ());
switchThread ( threadId );
setNewThreadName ( '' );
}
};
return (
< div className = 'custom-thread-manager' >
< div className = 'create-thread' >
< input
type = 'text'
value = { newThreadName }
onChange = { ( e ) => setNewThreadName ( e . target . value ) }
placeholder = 'New thread name'
/>
< button onClick = { handleCreateThread } > Create </ button >
</ div >
< div className = 'thread-list' >
{ threadIds . map (( threadId ) => (
< div
key = { threadId }
className = { `thread-item ${
threadId === currentThreadId ? 'active' : ''
} ` } >
< span onClick = { () => switchThread ( threadId ) } >
Thread { threadId . slice ( 0 , 8 ) }
</ span >
{ threadId !== 'DEFAULT_THREAD_ID' && (
< button onClick = { () => deleteThread ( threadId ) } > × </ button >
) }
</ div >
)) }
</ div >
</ div >
);
}
The thread management system provides a robust foundation for organizing conversations while maintaining backward compatibility with existing Cedar applications.