RE-call Setup

Your memory

One file taught you the mechanism. Now fill it without regretting the shape.


You have a working install and one example memory. Everything below is about the decisions that are cheap now and expensive in six months: what goes in a file, how many facts it holds, and how you find it again.

01

What a memory actually is

A memory is a markdown file whose text is worth trusting later. That is the whole definition, and it is stricter than it sounds. The test is not "did I write this down", it is "would I be comfortable if this paragraph came back to me in six months as the answer to a question, with no chance to explain it".

A folder of scratch notes fails that test. Notes are written to get something out of your head today; a memory is written to be read by someone who has forgotten the context, which includes you.

RE-call reads each markdown file, splits it into chunks of a few sentences, turns each chunk into a vector with your embedder, and stores the text and the vector together. A search compares your question against those vectors and then decides whether anything it found is good enough to answer from. Text that is vague, undated or half about something else survives the first two stages and fails the third.

Only markdown is picked up

The default file pattern is **/*.md, so a PDF, a text file or a spreadsheet sitting in the same folder is ignored. --glob overrides the pattern when you want something else, and a glob ending in .py switches RE-call to code chunking automatically, which splits on function boundaries rather than on prose.

02

One durable fact per file

This is the single most useful rule on the page, and it is the one people break first. If you follow nothing else here, follow this.

The reason is mechanical. A file is split into chunks, and each chunk is stored as one point of meaning. When a file bundles ten unrelated topics, the split lands wherever the character count says it should, not where the topics change, so several chunks end up half about one thing and half about another. Each of those is a middling match for all ten questions and a strong match for none of them. The gate that decides whether to answer sees a middling score, abstains, and you get nothing back from a file that genuinely contained the answer.

A file about one thing does not have that problem. Every chunk in it is about the same subject, so a question on that subject matches strongly and clears the threshold, and a question on any other subject matches weakly enough that RE-call correctly declines to use it.

Good

memory/rate-limits.md
---
name: api-rate-limit
description: The request ceiling we agreed for the public API.
metadata:
  type: project
---

Clients may issue 100 requests per minute against the public API.
We agreed this on 12 August 2026, after the load test showed the
gateway degrading above 140. Bursts up to 150 are tolerated for
10 seconds before throttling starts.

One subject. A number, a date, and the reason the number is what it is. Every chunk of it answers the same question.

Bad

memory/notes.md
---
name: notes
description: Notes.
---

API limit is 100/min I think, ask Sam to confirm.
Postgres 16 is the floor now. 15 was dropped.
TODO: check whether the retry backoff is still 2s.
Sam is in GMT+1 and prefers asynchronous review.
The staging box ran out of disk on Tuesday.
Retry policy: three attempts, then dead letter.

Six subjects, one description that describes none of them, and a hedge ("I think") that makes the first line unusable. Ask about rate limits and you get a chunk that is also about Postgres versions. Split it into six files, drop the TODO, and every one of them becomes retrievable.

A test you can apply while writing

Say out loud the question this file should answer. If you need the word "and" to say it, you have two files.

03

The frontmatter convention

The block at the top of the file, between the two lines of dashes, is frontmatter. memory/MEMORY.md documents the convention the setup wizard scaffolds, and it is three keys.

memory/api-rate-limit.md
---
name: api-rate-limit
description: The request ceiling we agreed for the public API.
metadata:
  type: project
---

name is a short kebab case slug. description is a summary of one line, and it is the line used to judge whether this file is relevant, so write it as the answer to "what is this file for" rather than as a title. metadata.type is one of four values, and the four are worth using consistently.

typeWhat it holdsUse it when
project Decisions, constraints and agreements that belong to one piece of work. The fact would stop being true if the project ended. This is the most common type by a wide margin.
user Facts about the person the work is for: preferences, working hours, constraints they have stated. The fact would still be true on a completely different project.
feedback Something you were told about your own output: a correction, a rejected approach, a standard imposed after the fact. You want the next attempt to avoid a mistake the last one made.
reference Durable background nobody decided: environment facts, external specifications, a distilled version of documentation you keep looking up. You would go and look it up otherwise, and looking it up is slow.

Nothing enforces the vocabulary

RE-call will index a file whose type says anything at all, and a file with no frontmatter is indexed too. The value earns its keep by being consistent: it tells a later reader what kind of claim the file makes, and it is the difference between "the project decided this" and "this is just how the world is", which is exactly the distinction you lose first.

04

The MEMORY.md index

The wizard scaffolds memory/ and memory/MEMORY.md. That second file is an always loaded index: one line per memory, pointing at the file that holds it. The facts themselves stay in their own files.

memory/MEMORY.md
# Memory index

- [API rate limit](project/api-rate-limit.md): 100 per minute, agreed 12 August 2026.
- [Postgres version floor](project/postgres-floor.md): 16 is the oldest supported.
- [Retry policy](project/retry-policy.md): three attempts, then dead letter.
- [Review preferences](user/review-preferences.md): asynchronous, small pull requests.
- [Deploy environments](reference/deploy-environments.md): what staging and prod actually run.

A line is a pointer and a hook, never the fact itself. The hook exists so that a reader scanning the index can tell whether the file behind it is the one they want.

Keeping the index separate from the facts matters because the two are read in completely different ways. The index is loaded in full, every time, so its cost is paid on every single turn and it has to stay short. The facts are retrieved on demand, only when a question makes them relevant, and only if they clear the confidence threshold. Move a fact up into the index and you pay for it constantly and you bypass the gate that decides whether it should have been used at all. Move the index down into the files and nothing knows what exists.

MEMORY.md gets indexed too

It is itself a markdown file inside memory/, so index picks it up along with everything else. That is harmless. It is why the install guide's first index reported two files when you had written one.

05

A layout worth copying

A flat memory/ folder is fine for a handful of files and unreadable at forty. This is the shape I use once a corpus starts growing, and it costs nothing to adopt on day one.

Folder layout
memory/
  MEMORY.md
  project/
    api-rate-limit.md
    postgres-floor.md
    retry-policy.md
  user/
    review-preferences.md
    working-hours.md
  feedback/
    rejected-batch-import.md
  reference/
    deploy-environments.md
    gateway-error-codes.md

The subfolders mirror the four type values, so the folder a file sits in and the frontmatter inside it say the same thing, and a wrong filing is visible without opening anything. Indexing does not change: the default pattern recurses, so one command still covers the whole tree.

python -m recall.cli index memory/
Two naming habits that pay off later

Name the file after the fact, not after the occasion. api-rate-limit.md is findable by anyone; meeting-2026-08-12.md is findable only by someone who already remembers the meeting. The date belongs in the body, where it is part of the claim, and the body is what gets searched.

Match the filename to the name in the frontmatter. They serve different systems, you reading a directory listing and RE-call describing a hit, and keeping them identical means you never have to hold two names for one fact in your head.

06

What makes a weak memory

These four are the ones I see most. All of them index perfectly happily. That is the problem: a weak memory does not fail loudly, it just quietly makes your corpus less trustworthy.

WeakWhy it failsWrite instead
"The rate limit is fairly high, I think." You cannot tell whether the claim still holds, or whether it ever did. Retrieved in six months it is worse than nothing, because it looks like an answer. The number, and the evidence behind the number.
"We decided to use Postgres." An undated decision cannot be superseded, because nothing tells you which of two contradicting files is the newer one. The decision, the date it was taken, and what it replaced.
"TODO: check the backoff, ping Sam." Temporary scratch text. It is a task, not a fact, it is stale within a week, and it stays retrievable long after it stops being true. Nothing. Tasks belong in your tracker. Write the memory once the answer exists.
Anything you are not permitted to send through your embedder path. Indexing is the point at which the text leaves your control, and there is no way to unsend it. Nothing, until you have checked. Decide before you index, not after.

Where your text goes depends on the embedder you chose

With the default local embedder, indexing sends nothing off the machine. The model runs on your own hardware and the vectors land in your own database. That changes the moment you switch to a cloud embedder: the text of every chunk is sent to a third party to be turned into a vector. If you are indexing anything you would not paste into a web form, that switch is the decision to think hard about, and it is worth deciding before the corpus is large enough that re-indexing it is a chore.

07

Keeping it current

A corpus you never revisit decays into a corpus you cannot trust. Three things keep it honest: re-indexing after edits, understanding the guard that stops a bad re-index, and knowing how to delete.

Re-running index on the same folder is the normal way to pick up edits. An edited file is updated in place rather than duplicated, and files that have not changed are skipped.

python -m recall.cli index memory/

You should see

Output
indexed 4 chunks from 2 files, 9 unchanged

The counts are of what was re-indexed, not of what is in the index, so a run where you changed nothing reports zero files and a large unchanged count. If a source has disappeared from disk, the summary also reports how many were pruned, because a deletion should never happen silently.

The prune guard

Re-indexing removes rows for files that are gone from disk. That is correct when you really deleted a memo, and catastrophic when the corpus merely was not there: an unmounted drive, a half finished sync, a path that still resolves to an empty folder. At the filesystem level those look identical to "the author deleted everything", so a run that would drop most of the corpus is refused outright and nothing is deleted.

Output
refusing to prune 7 of 9 indexed source(s) (78%) under 'memory'
  nothing was deleted. Confirm the files really are gone, then re-run
  with --allow-prune.

--allow-prune is the deliberate override, and the right response to seeing this message is to look at the folder first. If the files are genuinely gone, pass the flag once and the prune goes through. If they are not, you have just been saved from wiping your memory.

Deleting on purpose

forget removes indexed memory for the sources you name. Run it without --yes and it only previews: it prints what it would delete, changes nothing, and tells you so.

python -m recall.cli forget memory/notes.md

The preview, which deletes nothing

Output
DRY RUN: would forget 1 source(s): memory/notes.md
nothing deleted. Re-run with --yes to actually delete.

Read that list before you add --yes. A source you named that is not in the index is reported as not found, which is almost always a typo rather than a fact about your corpus. With --yes the deletion happens and it is irreversible.

Name the source exactly as it is stored

forget matches the source value that appears in search output, not a path you retype from memory, and not a glob. It is also scoped to the current tenant: it deletes nothing outside the one you are operating on, so a right to erasure request against another tenant needs --tenant before the subcommand.

Which table and tenant your memory is actually in

The wizard's automatic index at the end of setup always writes to table chunks, tenant default. It ignores any table or tenant flags you pass it, so the memory it created is in that one place regardless.

Global flags go before the subcommand: --table takes a throwaway name when you want an experiment kept out of your real memory, and --tenant selects the namespace. If you index into one tenant and search another, you get no results and no error, because both are behaving correctly.

Indexing from the local filesystem is refused entirely when RECALL_ENV is set to production. A production deployment builds from an immutable manifest instead, which is a different workflow and not one you need on a laptop.

08

Checking your corpus

Two commands exist to tell you when a corpus is drifting out of shape. Both are about supersession: whether a file that closes an older decision actually says which one.

python -m recall.cli lint memory/python -m recall.cli check memory/project/retry-policy.md

lint looks over a whole corpus for broken or missing supersession edges. --fix proposes the frontmatter edge for each closure it can prove, and it is a dry run by default, printing the plan and changing nothing until you add --apply.

check is the write time version, and it needs no database at all. You point it at the files you are about to commit and it asks for the supersession edge while you still know the answer. --strict makes it exit with a failure when a memo needs an edge, which is what turns it into a pre-commit hook.

Neither command is required to have a working corpus. Reach for them when you have enough files that you can no longer remember what supersedes what.

09

Where to go next