Threads
AgentFlow organises conversation history into threads. Each thread has a unique thread_id and stores a sequence of messages and a state snapshot (checkpoint). The client provides methods to list, inspect, update, and delete threads and their messages.
Thread endpoints require the checkpointer field to be configured in agentflow.json. Without a checkpointer the server returns empty results or 404 for single-thread operations.
Source: src/client.ts, src/endpoints/threads*.ts, src/endpoints/threadState.ts
Thread management
threads(request?)
List all threads. Supports optional search and pagination.
// List all threads
const response = await client.threads();
// With search
const response = await client.threads({ search: 'Paris' });
// With pagination
const response = await client.threads({ offset: 0, limit: 20 });
Overloads:
client.threads(): Promise<ThreadsResponse>
client.threads(request: ThreadsRequest): Promise<ThreadsResponse>
client.threads(search?: string, offset?: number, limit?: number): Promise<ThreadsResponse>
ThreadsRequest:
| Field | Type | Description |
|---|---|---|
search | string | Substring filter applied to thread names. |
offset | number | Number of threads to skip (for pagination). Must be ≥ 0. |
limit | number | Maximum number of threads to return. Must be > 0. |
ThreadsResponse:
interface ThreadsResponse {
data: {
threads: ThreadItem[];
};
metadata: ResponseMetadata;
}
interface ThreadItem {
thread_id: string;
thread_name: string | null;
user_id: string | null;
metadata: Record<string, any> | null;
updated_at: string | null;
run_id: string | null;
}
threadDetails(threadId)
Fetch metadata for a single thread.
const response = await client.threadDetails('thread-123');
console.log(response.data.thread_data.thread);
Parameters:
| Parameter | Type | Description |
|---|---|---|
threadId | string | number | The thread ID. Must be non-empty (string) or ≥ 1 (integer). |
ThreadDetailsResponse:
interface ThreadDetailsResponse {
data: {
thread_data: {
thread: Record<string, any>;
};
};
metadata: ResponseMetadata;
}
deleteThread(threadId, config?)
Delete a thread and all its associated state and messages. This is irreversible.
await client.deleteThread('thread-123');
// With optional config
await client.deleteThread('thread-123', { user_id: 'u-456' });
Parameters:
| Parameter | Type | Description |
|---|---|---|
threadId | string | number | The thread ID to delete. |
config | Record<string, any> | Optional configuration passed with the delete request body. |
Thread state
threadState(threadId)
Fetch the current state snapshot for a thread (the full graph state at the last checkpoint).
const response = await client.threadState(12345);
console.log(response.data);
// { state: { messages: [...], user_id: 'abc', ... } }
Parameters:
| Parameter | Type | Description |
|---|---|---|
threadId | number | The thread ID. See the note below: the TypeScript signature says number, but the server accepts strings and real thread ids usually are strings. |
updateThreadState(threadId, config, state)
Write a new state snapshot for a thread. Use this to seed initial state, repair a corrupted thread, or inject values that the graph needs but cannot derive from messages alone.
await client.updateThreadState(
12345,
{}, // config body (the server derives it from the path thread_id)
{ user_preferences: { lang: 'fr' } } // new state
);
Parameters:
| Parameter | Type | Description |
|---|---|---|
threadId | number | The thread ID. Typed number; see the note below. |
config | Record<string, any> | Config body. The server rebuilds the config from the path thread_id and ignores what you send here, so {} is fine. |
state | any | New state object to write. Keys must match the graph's state schema. |
clearThreadState(threadId)
Delete all checkpointed state for a thread. The thread itself (its metadata and messages) is not deleted — only the state snapshot is cleared.
await client.clearThreadState(12345);
Parameters:
| Parameter | Type | Description |
|---|---|---|
threadId | number | The thread ID. |
Messages
threadMessages(threadId, request?)
List messages for a thread with optional search and pagination.
// All messages
const response = await client.threadMessages('thread-123');
// With search term
const response = await client.threadMessages('thread-123', 'capital');
// As a request object
const response = await client.threadMessages('thread-123', {
search: 'capital',
offset: 0,
limit: 50,
});
Overloads:
client.threadMessages(threadId: string | number): Promise<ThreadMessagesResponse>
client.threadMessages(threadId: string | number, request: ThreadMessagesRequest): Promise<ThreadMessagesResponse>
client.threadMessages(threadId: string | number, search?: string, offset?: number, limit?: number): Promise<ThreadMessagesResponse>
ThreadMessagesRequest (without threadId):
| Field | Type | Description |
|---|---|---|
search | string | Substring filter applied to message content. |
offset | number | Number of messages to skip (≥ 0). |
limit | number | Maximum number to return (> 0). |
ThreadMessagesResponse:
interface ThreadMessagesResponse {
data: {
messages: Message[];
total?: number;
};
metadata: ResponseMetadata;
}
addThreadMessages(threadId, messages, config?, metadata?)
Append messages to a thread's saved history. Useful for injecting context, system prompts, or synthetic messages without running the graph.
await client.addThreadMessages(
'thread-123',
[Message.text_message('You are a travel guide.', 'system')],
{}, // config body (the server derives it from the path thread_id)
{ injected_by: 'setup' } // metadata
);
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
threadId | string | number | — | The thread ID. |
messages | Message[] | — | Messages to append. |
config | Record<string, any> | {} | Config body. The server rebuilds the config from the path thread_id, so this is effectively ignored. |
metadata | Record<string, any> | undefined | Optional metadata attached to the checkpoint. |
singleMessage(threadId, messageId)
Fetch a single message from a thread by message ID.
const response = await client.singleMessage('thread-123', 'msg-001');
console.log(response.data.message);
Parameters:
| Parameter | Type | Description |
|---|---|---|
threadId | string | number | The thread ID. |
messageId | string | The message ID. Must be non-empty. |
deleteMessage(threadId, messageId, config?)
Delete a single message from a thread by message ID.
await client.deleteMessage('thread-123', 'msg-001');
Parameters:
| Parameter | Type | Description |
|---|---|---|
threadId | string | number | The thread ID. |
messageId | string | The message ID to delete. Must be non-empty. |
config | Record<string, any> | Optional configuration passed in the request body. |
Validation rules
These rules are enforced by the server and will produce AgentFlowError status 422 if violated:
| Rule | Description |
|---|---|
thread_id non-empty | String thread IDs must not be empty or whitespace. Integer IDs must be ≥ 1. |
message_id non-empty | messageId must not be empty. |
offset ≥ 0 | Pagination offset must be a non-negative number. |
limit > 0 | Pagination limit must be a positive number. |
Common patterns
Paginate through all threads
async function* allThreads(pageSize = 50) {
let offset = 0;
while (true) {
const response = await client.threads({ offset, limit: pageSize });
const threads = response.data.threads;
if (threads.length === 0) break;
yield* threads;
offset += threads.length;
if (threads.length < pageSize) break;
}
}
for await (const thread of allThreads()) {
console.log(thread.thread_id, thread.thread_name);
}
Display a conversation history
const response = await client.threadMessages('thread-123', {
offset: 0,
limit: 100,
});
for (const msg of response.data.messages) {
const text = msg.content
.filter(b => b.type === 'text')
.map(b => (b as any).text)
.join('');
console.log(`[${msg.role}] ${text}`);
}
Reset a thread
// Keep the thread metadata but wipe the state
await client.clearThreadState(12345);
// Or delete everything including metadata and messages
await client.deleteThread('thread-123');
threadId is typed inconsistentlythreadState(), updateThreadState(), and clearThreadState() declare threadId: number. Every sibling method — threadDetails(), threadMessages(), addThreadMessages(), singleMessage(), deleteMessage(), deleteThread() — declares string | number, and observability() declares string.
The server side has no such split: the thread routes validate thread_id as str | int and reject only empty strings and integers below 1. Real thread ids handed out by the server are strings, and they go into the URL path either way.
So the number signatures are a client-side narrowing, not a server constraint. Passing a string thread id to those three methods works at runtime but fails to type-check. Until the signatures are widened, cast at the call site:
await client.threadState(threadId as unknown as number);
Common errors
| Error | Cause | Fix |
|---|---|---|
AgentFlowError status 404 | Thread not found, or no checkpointer configured. | Verify thread_id, and confirm checkpointer is set in agentflow.json. |
TypeScript error passing a string id to threadState / updateThreadState / clearThreadState | Those three are typed threadId: number. | Cast at the call site; the server accepts strings. See the caution above. |
AgentFlowError status 422 | Validation failure — invalid thread_id, empty message_id, bad pagination values. | Check the field constraints listed in Validation rules. |
What you learned
- Threads persist conversation history and state between
invoke()calls whenconfig.thread_idis set. threadMessages()supports search and pagination.clearThreadState()removes the state snapshot but not the thread or messages.- All thread operations require the checkpointer to be configured in
agentflow.json. threadState,updateThreadState, andclearThreadStateare typedthreadId: numberwhile their siblings acceptstring | number; the server accepts both.
Next step
See reference/client/memory to learn how to store and search long-term memories beyond per-thread conversation history.