Skip to content
On this page

Write your own memories ​

A memory is one markdown file holding one rule: YAML frontmatter with a name and a one-line description, then a body with the why and the how. maxims reads the format Claude Code's auto-memory already writes, unchanged, so a file that works there is a memory here without edits.

This page is the format, the folder layout, and the two verbs that help you write one: init scaffolds a file and lint checks a folder before you publish it.

A complete example ​

markdown
---
name: commit-small-and-often
description: "Keep every commit to one change; a reviewer reads a small diff in one pass, and a revert takes back one thing"
metadata:
  node_type: memory
  type: feedback
  scope: common
---

One change per commit, however small the second one looks.

**Why:** 2026-08-23. A two-change commit was reverted for one of them and took the other down with it.
**How to apply:** Stage by hunk when the tree holds two changes. Sibling of [[prefer-timeouts-to-hangs]].

The description is the one-liner that reaches the rule file. Everything below the frontmatter stays in this file on disk, behind the pointer at the end of the rule line.

The contract ​

fieldrequiredrule
nameyeskebab-case, [a-z0-9]+(-[a-z0-9]+)*, at most 200 characters, equal to the filename stem
descriptionyesnon-empty, one line after YAML unquoting; this is the one-liner that reaches the rule file
metadata.node_typenomemory when present; absent is accepted
metadata.typenouser, feedback, project, or reference; an unknown value passes with a warning
metadata.internalnotrue hides the memory unless MAXIMS_INSTALL_INTERNAL=1 is set; absent or false is normal
metadata.scope, any other keynocarried in the store, never interpreted
bodynomay be empty; [[links]] are preserved and resolved as dependencies (below)

The name is the identity everywhere. metadata.internal hides a memory from every add and refresh, for a source repo's own maintainers. **Why:** and **How to apply:** are conventions maxims preserves verbatim and never parses.

A file that fails the contract is skipped with one warning line, never fatally. A source repo that gains a stray file must not break every session's hook.

Two files are reserved and are never memories, skipped by name with no warning: MEMORY.md, the index the auto-memory format keeps beside its files, and README.md, the folder's own readme, matched in any letter case (readme.md, Readme.MD). Only .md files are read at all, so a readme.txt or an extensionless README is never looked at, and readme is not a memory name.

Hidden characters are refused ​

A memory carrying a character the reader cannot see fails the whole install, because the plan shown at add is the review gate and a hidden instruction passes it unseen. The check runs over the whole file, frontmatter and body.

refusedwhy
zero-width characters (U+200B to U+200D, U+2060, U+FEFF)invisible in a terminal, so a one-liner can differ from what the plan showed
bidi control characters (U+061C, U+200E, U+200F, U+202A to U+202E, U+2066 to U+2069)reorder what a reader sees, so displayed text and stored text disagree
control characters (U+0000 to U+001F except tab, U+007F to U+009F)a terminal prints nothing or moves the cursor, so file and display disagree
ANSI escape sequencesa terminal executes them while printing the plan
HTML commentsinvisible in a rendered rule file, and the block markers are HTML comments

The refusal is exit 3, nothing written, with the file and the character named. --allow-hidden on add installs the source anyway; the flag table owns it. Unlike a contract failure, this is not a per-file skip, because an unseen instruction is the failure the gate exists to catch.

A [[name]] in the body names another memory this rule depends on. add requires every link target to resolve, either inside the same install or among memories already installed. A dangling link aborts the add and names the unmet dependency, with exit 7, the way a package manager refuses a missing dependency.

Resolution runs through the rename map, so a memory renamed locally after a name collision still satisfies links written against its upstream name. The file content is never rewritten to match.

Layout in a source ​

text
<source root>/
`-- memories/
    |-- commit-small-and-often.md
    |-- prefer-timeouts-to-hangs.md
    |-- README.md                          # reserved, skipped
    `-- MEMORY.md                          # reserved, skipped

A source that also ships skills uses this layout, a memories/ directory at the repo root beside them, so one repo and one review gate carry both.

flag on addwhat it reads
nonememories/ at the source root
--from <path>the folder you name instead; recorded in state for every later sync
--full-depththe whole source tree, not stopping at the memories folder

--full-depth is the companion to --from for an unusual layout. Autodetecting memories by frontmatter is not attempted, because any README with a name: field would become a rule. You name the folder; the tool never guesses.

Scaffold a file with init ​

bash
npx -y @vivswan/maxims init commit-small-and-often

init <name> writes memories/<name>.md under the current directory, creating memories/ if needed, and refuses to overwrite an existing file. Without a name it prompts; non-interactively without one it exits 1.

The file passes the contract as written:

fieldvalue
namethe name you gave
descriptiona placeholder for you to replace
metadata.node_typememory
metadata.typefeedback
body**Why:** and **How to apply:** stubs

Lint a folder before publishing ​

bash
npx -y @vivswan/maxims lint            # checks memories/ under the current directory
npx -y @vivswan/maxims lint path/to/folder --full-depth --cap 30

lint is the source repo's check. It reads every .md in the folder but the two reserved files, and prints one path:line: reason per problem, so an editor can jump to it. It never writes into a harness.

checkproblem it reports
the contracta file add would skip, with the contract's reason; a metadata.type warning counts
hidden charactersa description carrying one, with its code point and column
risky shapesa description carrying one, as kind: detail at column N; add warns about these
wikilinksa [[link]] that names no memory in this folder
the rule capmore memories than the cap allows; --cap <n> sets this run's threshold
outcomeexit
no problems0
any problem3, the same code an incomplete install gets
a folder or file that cannot be read1; "no problems" is a claim about files that were inspected

On lint, --cap persists nothing. --full-depth scans subfolders too. --json prints { "ok": true, "problems": [] } with one object per problem instead of the lines.

Built from main at 5f6e985Source: docs/write-memories.md