Memory Schema v1
June 9, 2026 · View on GitHub
Machine-readable contract for what goes into
.cursor/memory/*.md. Without this schema, memory files are just freeform markdown that the agent may or may not update honestly. With it, every entry is structured, correlation-linked, and validated.
File roles
| File | Required kind | Purpose |
|---|---|---|
session-handoff.md | state | Rolling snapshot of project state. New entry appended at the end of every task. Most recent = authoritative. |
decisions.md | decision | Append-only architectural / engineering decisions. Never edit past entries. |
patterns.md | pattern | Lessons learned — what worked, what didn't. Append-only. |
Files may contain free markdown (headers, intro text) outside of structured blocks — the validator ignores it. Only the blocks are contract.
Entry block
Every structured entry is delimited by HTML comments so prose in the file stays human-readable:
<!-- memory-entry:start -->
---
id: 1745333456-0-state
correlation_id: 1745333456-0
at: 2026-04-22T19:30:56Z
kind: state
status: done
author: orchestrator
summary: Integrated Stripe webhook handler for checkout flow
tags: [payments, backend]
---
Any markdown body the author wants. Sections, lists, code, links.
Bodies are part of the record — they can be arbitrary, but they must
exist (≥ 20 non-whitespace characters).
<!-- memory-entry:end -->
The YAML between the two --- delimiters is the frontmatter. The text
after it (up to <!-- memory-entry:end -->) is the body.
Required frontmatter fields
| Field | Type | Constraint |
|---|---|---|
id | string | Unique across all memory files. Lowercase [a-z0-9-]+. Convention: <correlation_id>-<kind>[-<n>]. |
correlation_id | string | Shape <session_id>-<task_seq> where both are integers. Shared across entries that belong to the same task. Generated by the hooks, NOT by the LLM. |
at | string | ISO 8601 UTC timestamp (YYYY-MM-DDTHH:MM:SSZ). Normally monotonically increasing within each file; the validator reports an out-of-order at as an advisory notice, never an error — a system clock correction (NTP step, VM resume) is unfixable in an append-only file and must not block every future task. |
kind | enum | One of state, decision, pattern. MUST match the file's required kind (see table above). |
status | enum | One of in_progress, done, blocked, rejected. |
author | enum | One of orchestrator, subagent, human. |
summary | string | Single line, ≤ 160 chars, no linebreaks. The human-readable title of the entry. |
Optional frontmatter fields
| Field | Type | When to use |
|---|---|---|
tags | list[string] | Routing/search labels. Lowercase kebab. |
links | list[string] | Entry ids this record references (causation notes, "supersedes", …). |
author_detail | string | Specific agent slug or human name when author alone is insufficient. |
scope | string | Bounded context / module the entry affects. |
Body requirements
- ≥ 20 non-whitespace characters (guard against empty entries).
- No secrets. Use placeholders like
<STRIPE_KEY>if referencing them. - Freeform markdown is allowed; the validator does not police its shape.
Correlation IDs
Correlation IDs are generated by the enforcement hooks, not by the LLM.
The current active correlation ID lives in
.cursor/hooks/.state/session.json under active_correlation_id. The CLI
(memory.py) reads it automatically.
- Format:
<session_id>-<task_seq>session_idis the epoch seconds when the Cursor session started.task_seqstarts at0and increments each time thestopgate allows completion after a task that contained writes.
- Purpose: link a
decisionand apatternback to thestatesnapshot from the same task. - Rule: an entry whose
correlation_iddoes not matchactive_correlation_idat write time is rejected by the validator unless the entry is manually authored (status:human) and the validator is run with--allow-manual.
Validation
.cursor/memory/validate.py enforces every rule above. It runs:
- Automatically via the
stophook (any memory file edited → validate). - On demand:
python3 .cursor/memory/validate.py [--path <memory-dir>]. - In CI (
.gitlab-ci.yml,.github/workflows/).
Exit 0 = clean, 1 = errors printed to stderr.
Writing an entry — always through the CLI
The CLI is the only supported path for adding entries. It generates timestamps, reads the active correlation ID, and validates the block before writing. Direct hand-edits are allowed but must pass the validator.
# From the project root
python3 .cursor/memory/memory.py append \
--file session-handoff \
--kind state \
--status done \
--summary "Integrated Stripe webhook handler for checkout flow" \
--tags payments,backend \
--body-file /tmp/handoff-body.md
Output: the generated id on stdout. Exit 0 on success.
Other verbs:
show <id>— print an entry by idlist --file <file> [--kind <k>] [--correlation <cid>]— list entry headerslatest --file <file> --kind <k> [--n <N>]— print the N latest entriesvalidate [--path <memory-dir>]— delegates tovalidate.py
Privacy
Memory files WILL accumulate project-sensitive detail (service names, deploy hosts, internal module layout, named customers, etc.). Default guidance:
.cursor/memory/*.mdSHOULD be listed in the project's.gitignorewhen the project is public or shared with untrusted parties.- Files ending in
.shared.mdare explicitly team-shareable and should be reviewed before commit; the validator treats them identically to the base files. - NEVER write raw secrets. Use placeholders.
Schema versioning
Memory entries carry an optional schema_version field in their
frontmatter. When absent, the entry is assumed to be the current version
(backwards-compatible default). When present, it must be a member of
KNOWN_SCHEMA_VERSIONS in validate.py.
| Constant | Defined in | Purpose |
|---|---|---|
SCHEMA_VERSION | memory/validate.py | The version memory.py append writes into every new entry. Current: "1". |
KNOWN_SCHEMA_VERSIONS | memory/validate.py | Every version the validator accepts. Unknown → hard error. |
Bumping the version
- Implement the new field / constraint in
validate.pyandmemory.py. - Register a one-shot migrator in
memory/migrations.py(the registry skeleton already ships with the pack; see its docstring for the step-by-step workflow). - Bump
SCHEMA_VERSIONand add the old version toKNOWN_SCHEMA_VERSIONSso old entries still validate while the migration lands.
Adding an optional field (e.g. author_detail) does NOT require a
version bump. Bump only when an existing entry would fail validation
under the new rules.