Threads
Thread lifecycle. A thread is one conversation; memory belongs to the dataset, not the thread.
const thread = await memory.createThread({ dataset: 'user_42' });createThread()
Section titled “createThread()”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
datasetgenerates 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.
Ephemeral threads
Section titled “Ephemeral threads”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.
getThread()
Section titled “getThread()”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.
WMThreadhas nomessageCount. Counts come from thread stats, an HTTP-only endpoint.
updateThread()
Section titled “updateThread()”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 objectconst { 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.
endThread()
Section titled “endThread()”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.
When to call it
Section titled “When to call it”- 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.
Patterns
Section titled “Patterns”Resume or create
Section titled “Resume or create”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() },});Close-out on disconnect
Section titled “Close-out on disconnect”socket.on('disconnect', () => { memory.endThread(threadId).catch((err) => logger.warn({ err, threadId }, 'failed to queue extraction'), );});Not available
Section titled “Not available”| 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. |
memory.working, appending and reading messages- Working memory, the concepts