Skip to content

Threads

Thread lifecycle. A thread is one conversation; memory belongs to the dataset, not the thread.

const thread = await memory.createThread({ dataset: 'user_42' });

create(opts: WMCreateThreadRequest): Promise<WMCreateThreadResponse>
Option Type Default Notes
dataset string generated Stable user identifier. Always pass this.
tags string[] [] Free labels.
metadata Record<string, unknown> null Arbitrary JSON.
autoCompactThreshold number null Compact after this many un-compacted messages. Minimum 2. null disables.
settings.episodic Partial<ProjectEpisodicSettings> project defaults Per-thread episodic overrides.
const { threadId, dataset, createdAt, settings } = await memory.createThread({
dataset: 'user_42',
tags: ['support', 'billing'],
metadata: { channel: 'web', ticketId: 'T-1094' },
autoCompactThreshold: 30,
settings: {
episodic: { autoEpisodeIntervalMs: 60_000 },
},
});

Omitting dataset generates a random one. Fine for demos; a bug in production, because you can never recall that memory again unless you store the generated value. See Choosing a dataset key.

Turn off long-term memory for one conversation:

await memory.createThread({
dataset: 'user_42',
settings: { episodic: { enabled: false } },
});

Messages are still stored and prepare() still works, nothing becomes a fact.


get(threadId: string): Promise<WMThread>
const thread = await memory.getThread(threadId);
{
"threadId": "f2cb…",
"dataset": "user_42",
"tags": ["support"],
"metadata": { "channel": "web" },
"createdAt": "2026-08-16T09:02:11.000Z",
"lastActivityAt": "2026-08-16T09:14:02.000Z",
"settings": {
"autoCompactThreshold": 30,
"episodic": { "enabled": true, "autoEpisodeIntervalMs": 60000, "": "" }
},
"lastCompactedAt": null,
"lastCompactedSequence": 0
}

Throws ApiError with status: 404 when the thread does not exist or belongs to another project, the two are deliberately indistinguishable.

WMThread has no messageCount. Counts come from thread stats, an HTTP-only endpoint.


Merge-updates metadata. Existing keys are preserved; only the keys you send are overwritten.

update(threadId: string, opts: WMPatchThreadRequest): Promise<WMThread>
// before: { channel: 'web', ticketId: 'T-1094' }
await memory.updateThread(threadId, { metadata: { resolved: true } });
// after: { channel: 'web', ticketId: 'T-1094', resolved: true }

Merging is one level deep, a nested object is replaced wholesale, not merged.

// removing a key requires reading, deleting and writing the whole object
const { metadata } = await memory.getThread(threadId);
delete metadata!.ticketId;
await memory.updateThread(threadId, { metadata: metadata! });

Only metadata is patchable. tags, autoCompactThreshold and settings are fixed at creation.


end(threadId: string): Promise<WMEndThreadResponse>
const { threadId, episodeQueued } = await memory.endThread(threadId);
// { threadId: 'f2cb…', episodeQueued: true }

This does not end the thread. The thread stays writable and you can keep appending to it. end() queues episode extraction immediately instead of waiting out the inactivity timer.

Read it as checkpoint, not close.

episodeQueued is false when episodic memory is disabled for the thread or project.

  • The user closed the chat window or the session expired
  • A support ticket was resolved
  • A test needs facts to exist now rather than in 30 seconds

Calling it repeatedly creates multiple episodes; each one archives the previous and covers only the messages since the last. Harmless, but each costs three LLM calls.


async function getThread(userId: string, conversationId: string) {
const stored = await db.conversations.findOne({ id: conversationId });
if (stored?.threadId) return stored.threadId;
const { threadId } = await memory.createThread({
dataset: userId,
metadata: { conversationId },
});
await db.conversations.update(conversationId, { threadId });
return threadId;
}

One thread per session, memory across all of them

Section titled “One thread per session, memory across all of them”
// Every session gets a fresh thread; all of them feed user_42's memory.
const { threadId } = await memory.createThread({
dataset: 'user_42',
metadata: { sessionId, startedAt: new Date().toISOString() },
});
socket.on('disconnect', () => {
memory.endThread(threadId).catch((err) =>
logger.warn({ err, threadId }, 'failed to queue extraction'),
);
});

Status
Delete a thread No endpoint. Deleting the row cascades to messages; do it in SQL.
List threads for a dataset Dashboard only, GET /dashboard/browse/threads.
Change tags or autoCompactThreshold after creation Not patchable.