Manage threads
In shortList, inspect, update, and delete conversation threads and messages from the TypeScript client.
- 6 min read
- 14 sections
- Updated
- v0.10.0
- Markdown
10xGraph stores conversation history and state in threads. This guide shows you how to manage threads from the TypeScript client: listing and searching threads, inspecting their messages and state, updating or clearing state, and deleting threads.
By the end of this guide, you will understand how to read and modify thread data in all its forms, and when to use each operation.
Prerequisites
- A configured
TenxGraphClientinstance. See Create a client. - The 10xGraph API server running with a durable checkpointer (SQLite, Postgres+Redis, or custom).
- Node 18+ or a browser with fetch support.
List all threads
Retrieve all threads from the server. This is useful for discovering past conversations, searching for a specific thread, or bulk operations on threads.
const response = await client.threads();
const threads = response.data.threads;
console.log(`Found ${threads.length} thread(s)`);
for (const t of threads) {
console.log(` [${t.thread_id}] ${t.thread_name ?? '(no name)'}, updated: ${t.updated_at}`);
}Filter threads by name
Search for threads containing a keyword in their name:
const response = await client.threads({ search: 'Paris' });
const matchingThreads = response.data.threads;
for (const thread of matchingThreads) {
console.log(`Matched: ${thread.thread_name}`);
}Paginate through threads
Retrieve threads in pages to handle large thread lists:
const response = await client.threads({ offset: 0, limit: 20 });
const firstPage = response.data.threads;For iterating through all threads, use an async generator:
async function* allThreads(pageSize = 50) {
let offset = 0;
while (true) {
const res = await client.threads({ offset, limit: pageSize });
const page = res.data.threads;
if (page.length === 0) break;
yield* page;
offset += page.length;
if (page.length < pageSize) break;
}
}
// Iterate through all threads
for await (const thread of allThreads()) {
console.log(`Processing: ${thread.thread_id}`);
}Fetch thread details
Get metadata for a single thread, including its ID, name, user, and timestamps:
const details = await client.threadDetails('thread-abc123');
const thread = details.data.thread_data.thread;
console.log(`Thread ID: ${thread.thread_id}`);
console.log(`Name: ${thread.thread_name ?? '(unnamed)'}`);
console.log(`Updated: ${thread.updated_at}`);This is useful for displaying thread information in a UI or validating that a thread exists before performing operations on it.
List and search messages in a thread
Retrieve all messages from a thread, optionally searching by content:
const messages = await client.threadMessages('thread-abc123', {});
for (const msg of messages.data.messages) {
const text = msg.content
.filter(b => b.type === 'text')
.map(b => (b as any).text as string)
.join('');
console.log(`[${msg.role.toUpperCase()}] ${text.slice(0, 80)}`);
}To search messages by keyword:
const results = await client.threadMessages('thread-abc123', {
search: 'capital of France',
limit: 10,
offset: 0,
});
console.log(`Found ${results.data.messages.length} matching message(s)`);Messages can be paginated using offset and limit parameters.
Fetch a single message
Retrieve a specific message by its ID:
const msg = await client.singleMessage('thread-abc123', 'msg-001');
console.log(`Role: ${msg.data.role}`);
console.log(`Content: ${JSON.stringify(msg.data.content)}`);Use this when you need to read or inspect a specific message without fetching the entire thread.
Delete a message
Remove a message from the thread’s history. This is useful for cleaning up incomplete tool calls or messages that shouldn’t appear in the conversation:
await client.deleteMessage('thread-abc123', 'msg-001');
console.log('Message deleted');The thread continues to exist with the remaining messages. deleteMessage() also accepts an optional third config argument.
Inspect and update thread state
The thread state is the graph’s state snapshot at the last checkpoint. You can read it, modify it, or reset it.
Read the current state
const stateResponse = await client.threadState('thread-abc123');
const state = stateResponse.data.state;
console.log('Current state:', JSON.stringify(state, null, 2));The state object shape depends on your graph’s StateGraph definition. It contains the message context and any custom fields your graph stores.
Update the state
Merge a partial state into a thread’s stored state. Use this to inject values, repair state, or seed initial data. The server appends context messages to the existing context, deep-merges dictionaries, and ignores null values:
await client.updateThreadState(
'thread-abc123',
{}, // config (the thread_id comes from the path)
{
user_preferences: { language: 'fr', timezone: 'Europe/Paris' },
}
);
console.log('State updated. Next invoke() will use this state.');Clear the state
Remove the state snapshot without deleting messages. The thread exists but will start fresh on the next invoke() call:
await client.clearThreadState('thread-abc123');
console.log('State cleared. Thread messages remain.');This is useful if you want to reset a conversation while keeping the message history.
Add messages to a thread
Inject messages directly into a thread’s history. This is useful for synthetic context (system prompts), importing data, or continuing a conversation from an external source:
import { Message } from '10xgraph-client';
await client.addThreadMessages(
'thread-abc123',
[
Message.text_message('You are a Paris travel expert.', 'system'),
],
{} // config (the thread_id comes from the path)
);
console.log('Messages added to thread');Added messages become part of the thread history and will be included when you list messages or start a new invoke.
Delete a thread
Delete a thread and all its associated state and messages permanently. This operation is irreversible:
await client.deleteThread('thread-abc123');
console.log('Thread deleted');After deletion, the thread ID cannot be reused. If you want to keep the message history but reset the state, use clearThreadState() instead.
Complete example: thread history viewer
Putting all operations together in a utility function that displays a thread’s full history:
import { TenxGraphClient, Message } from '10xgraph-client';
const client = new TenxGraphClient({ baseUrl: 'http://localhost:8000' });
async function displayThreadHistory(threadId: string) {
try {
// Fetch thread details
const details = await client.threadDetails(threadId);
const thread = details.data.thread_data.thread;
console.log(`\n=== Thread: ${thread.thread_name ?? threadId} ===`);
console.log(`Updated: ${thread.updated_at}`);
// Fetch all messages with pagination
const messagesResponse = await client.threadMessages(threadId, {
offset: 0,
limit: 100,
});
const messages = messagesResponse.data.messages;
console.log(`\n--- Messages (${messages.length} total) ---`);
for (const msg of messages) {
const text = msg.content
.filter(b => b.type === 'text')
.map(b => (b as any).text as string)
.join('') || '[non-text content]';
console.log(`[${msg.role.toUpperCase()}] ${text.slice(0, 120)}`);
}
// Fetch and display current state
const stateResponse = await client.threadState(threadId);
console.log(`\n--- Current State ---`);
console.log(JSON.stringify(stateResponse.data.state, null, 2));
} catch (error) {
console.error(`Failed to display thread: ${error}`);
}
}
// Usage
await displayThreadHistory('thread-abc123');Troubleshooting: common errors
| Error | Cause | Solution |
|---|---|---|
404 Not Found |
Thread does not exist. | Verify the thread ID exists. |
422 Unprocessable Entity |
Invalid parameter, such as an empty or whitespace thread ID. | Ensure the thread ID is a non-empty string or a positive integer. |
Empty threads list |
No threads have been created yet, or the server restarted with the default in-memory checkpointer. | Run a test invocation to create a thread, and compile with a durable checkpointer for persistence. |
State is null |
The thread exists but has no state snapshot (e.g., after clearThreadState()). |
Call updateThreadState() to set a state, or addThreadMessages() to add messages to the thread. |
Key takeaways
- List threads with
threads()to discover or search for conversations. - Thread details via
threadDetails()give you metadata (ID, name, timestamps). - Messages are read with
threadMessages(), modified withaddThreadMessages(), and deleted individually withdeleteMessage(). - Thread state (checkpoint) is read with
threadState(), updated withupdateThreadState(), and cleared withclearThreadState(). - Deletion is permanent:
deleteThread()removes everything, whileclearThreadState()keeps messages but resets state.
Next step
Learn how to store and retrieve long-term memories separate from conversation history. See Memory API.
Frequently asked questions
- What happens if no checkpointer is configured?
- The server falls back to InMemoryCheckpointer, so threads live only in that server process and are lost on restart. For persistence, compile the graph with a SQLite, Postgres+Redis, or custom checkpointer.
- Can I update thread state while a graph is running?
- Yes, you can call updateThreadState() at any time, but the next invoke() call will continue from the new state you set. Use this for injection, repair, or seeding initial data.
- What's the difference between clearThreadState() and deleteThread()?
- clearThreadState() removes only the state snapshot, keeping all messages. deleteThread() removes everything: state, messages, and thread metadata. Both are irreversible.