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 TenxGraphClient instance. 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.

TypeScript
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:

TypeScript
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:

TypeScript
const response = await client.threads({ offset: 0, limit: 20 });
const firstPage = response.data.threads;

For iterating through all threads, use an async generator:

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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 with addThreadMessages(), and deleted individually with deleteMessage().
  • Thread state (checkpoint) is read with threadState(), updated with updateThreadState(), and cleared with clearThreadState().
  • Deletion is permanent: deleteThread() removes everything, while clearThreadState() 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.
Last updated for v0.10.0Edit this page on GitHubReport an issue