Skip to main content

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.

Requires checkpointer

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:

FieldTypeDescription
searchstringSubstring filter applied to thread names.
offsetnumberNumber of threads to skip (for pagination). Must be ≥ 0.
limitnumberMaximum 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:

ParameterTypeDescription
threadIdstring | numberThe 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:

ParameterTypeDescription
threadIdstring | numberThe thread ID to delete.
configRecord<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:

ParameterTypeDescription
threadIdnumberThe 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:

ParameterTypeDescription
threadIdnumberThe thread ID. Typed number; see the note below.
configRecord<string, any>Config body. The server rebuilds the config from the path thread_id and ignores what you send here, so {} is fine.
stateanyNew 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:

ParameterTypeDescription
threadIdnumberThe 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):

FieldTypeDescription
searchstringSubstring filter applied to message content.
offsetnumberNumber of messages to skip (≥ 0).
limitnumberMaximum 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:

ParameterTypeDefaultDescription
threadIdstring | numberThe thread ID.
messagesMessage[]Messages to append.
configRecord<string, any>{}Config body. The server rebuilds the config from the path thread_id, so this is effectively ignored.
metadataRecord<string, any>undefinedOptional 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:

ParameterTypeDescription
threadIdstring | numberThe thread ID.
messageIdstringThe 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:

ParameterTypeDescription
threadIdstring | numberThe thread ID.
messageIdstringThe message ID to delete. Must be non-empty.
configRecord<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:

RuleDescription
thread_id non-emptyString thread IDs must not be empty or whitespace. Integer IDs must be ≥ 1.
message_id non-emptymessageId must not be empty.
offset ≥ 0Pagination offset must be a non-negative number.
limit > 0Pagination 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 inconsistently

threadState(), 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

ErrorCauseFix
AgentFlowError status 404Thread 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 / clearThreadStateThose three are typed threadId: number.Cast at the call site; the server accepts strings. See the caution above.
AgentFlowError status 422Validation 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 when config.thread_id is 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, and clearThreadState are typed threadId: number while their siblings accept string | 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.