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

FileRequired kindPurpose
session-handoff.mdstateRolling snapshot of project state. New entry appended at the end of every task. Most recent = authoritative.
decisions.mddecisionAppend-only architectural / engineering decisions. Never edit past entries.
patterns.mdpatternLessons 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

FieldTypeConstraint
idstringUnique across all memory files. Lowercase [a-z0-9-]+. Convention: <correlation_id>-<kind>[-<n>].
correlation_idstringShape <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.
atstringISO 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.
kindenumOne of state, decision, pattern. MUST match the file's required kind (see table above).
statusenumOne of in_progress, done, blocked, rejected.
authorenumOne of orchestrator, subagent, human.
summarystringSingle line, ≤ 160 chars, no linebreaks. The human-readable title of the entry.

Optional frontmatter fields

FieldTypeWhen to use
tagslist[string]Routing/search labels. Lowercase kebab.
linkslist[string]Entry ids this record references (causation notes, "supersedes", …).
author_detailstringSpecific agent slug or human name when author alone is insufficient.
scopestringBounded 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_id is the epoch seconds when the Cursor session started.
    • task_seq starts at 0 and increments each time the stop gate allows completion after a task that contained writes.
  • Purpose: link a decision and a pattern back to the state snapshot from the same task.
  • Rule: an entry whose correlation_id does not match active_correlation_id at 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 stop hook (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 id
  • list --file <file> [--kind <k>] [--correlation <cid>] — list entry headers
  • latest --file <file> --kind <k> [--n <N>] — print the N latest entries
  • validate [--path <memory-dir>] — delegates to validate.py

Privacy

Memory files WILL accumulate project-sensitive detail (service names, deploy hosts, internal module layout, named customers, etc.). Default guidance:

  • .cursor/memory/*.md SHOULD be listed in the project's .gitignore when the project is public or shared with untrusted parties.
  • Files ending in .shared.md are 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.

ConstantDefined inPurpose
SCHEMA_VERSIONmemory/validate.pyThe version memory.py append writes into every new entry. Current: "1".
KNOWN_SCHEMA_VERSIONSmemory/validate.pyEvery version the validator accepts. Unknown → hard error.

Bumping the version

  1. Implement the new field / constraint in validate.py and memory.py.
  2. 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).
  3. Bump SCHEMA_VERSION and add the old version to KNOWN_SCHEMA_VERSIONS so 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.