Memory files
How to add, correct, find and verify the repository's frictions, todos and decisions. For what they are, see the concept overview.
Where the files live
| Scope | Path |
|---|---|
Root (cross-cutting, ci, docs, skills) | <repo>/.github/memory/<kind>.md |
| Package | <repo>/<projectFolder>/.github/memory/<kind>.md |
<kind> is friction, todo or decision. A package file starts with the entries that belong to
that package; anything cross-cutting belongs in the root file, and memory check warns when a root
file holds a package entry.
The shape of an entry
### F-014 — A nested `tap` run ignores `--reporter=json`
> _2026-09-15 · ci · resolved_
The nested tap inherited `TAP_CHILD_ID`, concluded it was a child job and reported zero tests. Unset
the `TAP_*` variables before spawning the inner run.
F-/T-/D-plus a number, allocated workspace-wide and never reused.- The title is one line, at most 80 characters, and reads on its own.
- The line under it — a blockquote, so markdownlint does not read it as a heading — carries the
date, the area and the status, and is what
list --areaandlist --statusfilter on. - The body is wrapped at 100 columns by the CLI; paragraphs are separated by a blank line. A fenced
code block is passed through untouched and needs a language (
```bash).
The generated index sits between <!-- memory:index --> and <!-- /memory:index -->. It is
derived: the CLI writes it, and check fails when it drifts from the entries.
Adding and correcting entries
blong-dev memory add friction --area core/blong-gogo --title "…" --body "…"
blong-dev memory add todo --area cross-cutting --title "…" --body-file body.md
blong-dev memory edit F-014 --body-file body.md # correct one (never hand-edit)
blong-dev memory close F-014 --note "fixed in b1f3c9a" # resolved, entry kept
blong-dev memory close D-018 --by D-042 --reason "the union is no longer debounced"
blong-dev memory retire F-014 --reason "never a real defect" # removed, id left in the ledger
blong-dev memory reopen F-014 # it came back
blong-dev memory move F-014 --area core/semantic-log # it belongs elsewhere
blong-dev memory prune F-101,F-102 --reason "duplicated by F-090"
add refuses a title longer than 80 characters and refuses an area that is neither a package folder
nor a reserved label. For a long body use --body-file; --date and --status override the
defaults (today, and the first status of the kind).
close keeps the entry, whatever the kind: the reasoning outlives the status, and the id is never
reused. A friction becomes resolved, a todo done, a decision superseded by the successor its
--by/--reason name, and all three stay readable where they are. An entry that should not be in
the files at all — a duplicate, a mis-filing, work that was cancelled — is retired instead: the
entry goes and only its id stays, as a ## Retired line. retire takes --reason for the fate the
line records, and --purge to drop the line too, which is the only thing that frees a number.
Reading them
blong-dev memory list --kind friction --area core/blong-browser --status open
blong-dev memory list --search "connection pool" --json # agents use --json
blong-dev memory show F-014 --json
blong-dev memory audit # dangling references, duplicate titles, stale entries
Start with the index block at the top of a file: it lists every entry once, grouped by status. Only read further when an entry looks relevant — that is the whole point of the index.
list --search matches the text of an entry. It is always available, it is exact, and it needs
nothing running — when you know a word the entry uses, it is the shortest path. When you know only
the meaning, use the semantic search below.
Searching by meaning
search asks a local Hindsight server which entries are close to a question, so a paraphrase finds
the entry that uses different words.
blong-dev memory search "why do agents keep editing memory files by hand"
blong-dev memory search "coverage thresholds" --kind friction --area core/blong-browser --limit 5
blong-dev memory search "the union is no longer debounced" --json
blong-dev memory search "how do the notes work" --source docs
blong-dev memory search "handler naming" --source entry,docs # both streams in one query
--source selects the stream: entry (the default), docs or skill, comma-joined for several. A
search defaults to the entries, so adding the documentation to the bank never changed what an
existing query returns. --kind, --area and --status describe entries only, so asking for them
beside a page source is refused rather than answered with nothing.
Each hit prints a rule, the entry's id, kind, status and similarity, the file it lives in, and the
entry text as it stands in that file. The number is the cosine similarity of the embedding. The
server also reports a ranking score, but it is not comparable between queries — a relevant match can
rank first at 0.0006 — so it is carried in --json rather than shown.
Treat an empty result as "nothing close", not as "not written down", and fall back to
list --search — the two read the same tree, one by text and one by meaning.
What the index holds, and when
Every entry is one document, keyed by its id, so re-ingesting an edited entry replaces it rather than duplicating it. The dimensions a search can filter on — kind, area, status, id, file path — are written as tags, and mirrored into the document's metadata for the server's own UI.
The bank holds three streams, told apart by a tag rather than by a second bank, so one query can span them:
| Stream | What it is | Id | Tags |
|---|---|---|---|
entry | one document per memory entry | the entry id (F-316) | memory, kind:, area:, status: |
docs | one document per published page in docs/blong/docs/<tier>/ | doc-<tier>-<name> | kind:documentation, tier:, path: |
skill | one document per .github/skills/*/SKILL.md | skill-<name> | kind:agent-skill, scope:, path: |
A page or a skill is stable state rather than an event, so its id comes from its path: an edit
replaces the document instead of adding a second fragment, which is what makes re-ingesting a whole
tier cheap and safe. --sources docs,skill ingests them; the default is entry, so nothing about
an existing query changes.
An agent that wants Hindsight's own documentation as a skill can pull it in beside the local ones
(Blong discovers skills from .github/skills/*/SKILL.md and has no installer of its own):
npx skills add https://github.com/vectorize-io/hindsight --skill hindsight-docs
The index is derived: the markdown is the source of truth, and the bank can be dropped and rebuilt at any time.
| Command | What it does to the index |
|---|---|
add, edit, close, reopen, move | Queues the entries the command touched (one call, not one per entry). |
prune | Deletes the pruned entries' documents, so a search cannot find what the tree no longer holds. |
index --semantic | Ingests every entry of the selected files — the first fill, and the repair. |
index --semantic --dry-run | Reports how many entries would be ingested, without touching the server. |
index --semantic --stats | Also compares the bank with the tree and names what drifted. |
index --semantic --prune | Deletes the bank documents the tree no longer holds. |
A write never fails because of the index: if the server is unreachable, the command prints one warning on stderr and the entry is written anyway. The backfill is the repair for whatever was skipped, and re-running it is safe.
blong-dev memory index --semantic # every file
blong-dev memory index --semantic --files .github/memory/friction.md
blong-dev memory index --semantic --dry-run # count only
blong-dev memory index --semantic --sources docs,skill # the docs site and the skills
blong-dev memory index --semantic --stats # what the bank holds, and what drifted
blong-dev memory index --semantic --prune # drop what the tree no longer holds
Pointing the CLI at a server
| Setting | Default | Meaning |
|---|---|---|
HINDSIGHT_API_URL | http://localhost:8888 | The server; .blong_devrc may hold it as hindsight.url. |
HINDSIGHT_BANK | blong | Which bank to read and write; .blong_devrc hindsight.bank. |
HINDSIGHT_DISABLED | unset | 1, true, on or yes turns the feature off, write hook included. |
The server is deployed by plans/memory-index/hindsight.sh, which carries the two settings that
cannot be changed later without wiping the bank: the embedding model, and therefore the vector
width, and the bank's retain_extraction_mode: chunks — entries are stored verbatim and ingestion
calls no LLM at all.
The embedding model is the built-in BAAI/bge-small-en-v1.5 on purpose. Measured over the whole
tree, it and two Ollama models three times its width rank every benchmark query identically while
the cross-encoder reranker runs, and differ by noise without it — so a bigger model buys nothing
observable against a LAN dependency. EMBED_MODEL on the script still selects an Ollama model for
anyone re-measuring; the numbers are in plans/memory-index/embedding-model-comparison.md.
One bank means one embedding model for everything in it: the entries, the documentation pages and the skills are chunked and embedded by the same settings, so a query compares them on equal terms. The width is fixed by the first write, which is why the model is chosen once, at the script.
Writing a batch (migration, or an agent's work)
A batch is JSON, authored outside the CLI and written by it:
{
"kind": "friction",
"entries": [
{
"title": "A stale line range spliced items into the index block",
"body": "Sections are line ranges; a refresh moves them. Re-read the structure after any refresh.",
"area": "tools/blong-dev",
"status": "resolved",
"date": "2026-09-15"
}
]
}
blong-dev memory import batch.json # dry run: what would be written, and what is refused
blong-dev memory import batch.json --apply # write it
Import validates the whole batch before writing anything: a bad title, an unknown area, a status the
kind does not have, an entry that is already in the tree (same title and body) or one that repeats
earlier in the batch are all reported and nothing is written. --force overrides the duplicate
check, and is almost never what you want — importing a batch twice duplicates every entry it holds.
The user's own list
The ## Manual section of the root todo is the user's: it is never indexed, never rewritten, and
never touched by an agent.
blong-dev memory manual list
blong-dev memory manual add "the thing I keep meaning to do"
blong-dev memory manual done 3
Before you finish
blong-dev memory check # format, ids, areas, index, statuses, markdown rules
blong-dev memory check --files a.md,b.md
blong-dev memory format # re-wrap and re-render everything the CLI owns
blong-dev memory index # regenerate the index blocks only
check is the gate: common/git-hooks/pre-commit runs blong-dev lint-staged, which detects the
staged files under .github/memory/ and checks them, so a malformed entry fails your commit rather
than a review comment. (The CI workflow delegates to a reusable Rush workflow and does not run this
itself, so the hook is the gate a contributor actually meets.) It also reports the markdown a
hand-written body would break — a section heading in the middle of a body, a line over 100 columns,
<placeholder> HTML, emphasis on a line of its own, a list or fence without a blank line around it.
blong-dev lint --files … covers the same ground for any markdown file plus cspell, and is the
command to run when you have touched a .md file outside the memory tree.
Pointing at a note from the code
A comment carries a summary, not the reasoning, and cites the entry that holds it with @@:
// gc keeps one directory per version, so a rebuilt tree is never rewritten under a reader (@@D-468 @@D-469).
Parentheses are optional. Never cite by line number (decision.md L1153 rots). Cite the active
entry: when a decision is superseded, retarget the comment to the successor and close the old one.
blong-dev memory refs --dangling # the linter: every reference in the tree resolves
blong-dev memory refs D-476 # what cites this entry
blong-dev memory refs --summary # references per package, the conversion ledger
blong-dev memory refs --sites # the audit view: each citation beside the title it resolved to
blong-dev memory refs --changed # sites citing an entry the working tree changed (--since <ref>)
blong-dev memory refs --fix-anchors # rewrite an anchor whose heading only moved
blong-dev memory refs --fix-superseded # follow the successor a superseded decision names
blong-dev memory comply # sites citing an entry this change edited, retired or superseded
blong-dev memory check --refs # the memory files, plus the references into and out of them
blong-dev memory export --out .github/memory/references.json # the index the editor reads
refs --dangling exits non-zero on an error; a retired id is a warning. comply compares the
memory files with a git ref (--since, default HEAD) and names the code to re-read.
A citation of a superseded decision is reported in two ways: an error when the entry names the
successor its reasoning moved to (the paragraph close --by writes), because
refs --fix-superseded follows it in one command, and a warning when it names none, because only a
reader can settle that. A resolved friction or a done todo stays the citation, because it is the
record of the workaround the code embodies — which is why the linter counts the two apart:
N retired, M superseded.
Resolution is not correctness: the linter proves an id exists, not that it holds the reasoning. A
sweep of realm/blong-kustomize found a citation that resolved cleanly and pointed at the wrong
entry, so an audit reads the title --sites prints beside each citation — a word-overlap heuristic
flagged 243 of those 388 citations, too many to be a check. --fix-anchors rewrites a token whose
heading moved and whose replacement is unambiguous, and reports rather than guesses otherwise.
Retiring an id
A retired entry, a pruned one or a renumbered one leaves a line in ## Retired, and its number is
never reused, so a reference that outlived the entry stays diagnosable. retire removes one entry
(--purge deletes it outright), prune refuses an id any file cites (--force retires it anyway),
and renumber <id> --to <new-id> fixes a collision:
blong-dev memory retire T-123 --reason "superseded by the new flow" # the id stays in the ledger
blong-dev memory prune F-101 --reason "duplicated by F-090"
blong-dev memory renumber D-001 --to D-050 # the old id stays in the ledger, pointing at the new