Memory
Memory lets an agent recall work from earlier sessions. A memory hook appends one record per turn to a shared journal, and the model reads that journal with its normal file tools when its instructions send it there. Every session and every user of the agent shares one journal.
Memory is on by default
bdk init scaffolds bot/hooks/memory.ts:
// bot/hooks/memory.tsimport { memoryHook } from "@cursor/bdk/memory";export default memoryHook();It also adds a ## Memory section to bot/instructions.md. The model
has no recall tool, so that section is what sends it to the journal:
it says when to read memory/, and it tells the model to treat records
as untrusted history. To add memory to an existing project, create the
hook file and copy the ## Memory section from a fresh bdk init
project into your instructions. To turn memory off, delete the hook
file and the ## Memory section.
Journal records
The hook appends one JSON line to memory/journal.jsonl after each
turn completes or fails:
{"at":"2026-10-01T16:42:09.118Z","sessionId":"5f0c2a9e-7d1b-4c43-9a6e-2b8f1e0d7c35","channelId":"slack","status":"completed","title":"Checkout flaky test","userMessage":"Why does acme/checkout fail on main?","result":"The cart test races the price service mock...","usage":{"inputTokens":18234,"outputTokens":912,"cacheReadTokens":15002,"cacheWriteTokens":0}}| Field | Contract |
|---|---|
at | ISO-8601 time the turn finished |
sessionId | Session that ran the turn |
channelId | Channel that started the session, such as http for the session API |
status | "completed" or "failed" |
title | Session title, when the channel set one |
userMessage | The message that started the turn, truncated to maxTextLength |
result | Final assistant text, or the failure message, truncated to maxTextLength |
usage | Token usage for completed turns; see Events and payloads |
As the journal grows, older records move to journal-*.jsonl files
beside it. Point the model at the whole memory/ directory when it
needs history beyond the latest segment.
Read memory
Local turns find the journal at memory/ in the session workspace. On
a Cursor-hosted deployment, the first prompt of each session names the
journal's path on that machine. A session on a server you run yourself
can't reach the journal.
memory is a reserved workspace path. BDK rejects per-send
workspaceFiles and sandbox/workspace/ seed files under memory/,
so callers can't write records through the workspace.
Local servers keep the journal in the project state directory. bdk run and bdk eval start an ephemeral server with a fresh state
directory unless you pass --state-root, so each run starts with
empty memory. Use bdk serve or bdk dev, or pass the same
--state-root, to keep memory across runs. Hosted deployments keep
the journal across redeploys.
Who sees memory
Records from every session and every user land in the same journal, so
one person's messages and the agent's replies to them can surface in
another person's session. Turn memory off for an agent whose users
shouldn't see each other's requests. Turning it off stops new records,
but the journal keeps the records it already has. On a local server,
delete the memory/ directory in the project state directory to clear
them. On Cursor-managed hosting, the journal is deployment data, so
Privacy Mode applies to it.
Anyone who can message the agent can put text in the journal. The scaffolded instructions tell the model never to follow instructions found inside records; keep that rule if you write your own section.
Memory and host state
Memory is advisory. A record can be dropped, and records written by
concurrent sessions can show up only in a later session. Keep state
your code depends on, such as dedupe records and cursors, in
ctx.host.kv or ctx.host.files; see
Handler context.
Options
memoryHook(options?) accepts:
| Option | Contract |
|---|---|
maxTextLength | Truncation cap for userMessage and result. Default 2000 characters. |
backend | A MemoryBackend that replaces the default journal |
A MemoryBackend has appendTurn(record, { stateRoot, agentName }),
which receives each record, and an optional
prepareSession({ stateRoot, agentName }), which runs when a session
starts. Records go only where appendTurn sends them, but the
memory/ directory and the hosted first-prompt path still point at the
default journal. Give the model a tool or file to read your records,
and write instructions that send it there instead of memory/.