Guides / AI agents / 2026-08-30
How to give AI agents persistent memory
How to give AI agents persistent memory using plain markdown files the agent writes and reads back, so decisions and facts survive between sessions.
To give an AI agent persistent memory, give it a durable place outside the model to write to, and a habit of reading that place before it acts. A context window is emptied when the session ends, so nothing the agent worked out yesterday survives unless it was written to a file, a database, or a service the model does not own.
The simplest version is a folder of markdown files on disk. At the end of a piece of work, the agent writes one short note per decision or fact. At the start of the next session, it searches those notes for the current task and reads the top few.
Several assistants now ship a memory setting you can switch on, including ChatGPT and Claude. Those stores live inside one vendor’s product, you cannot open them as ordinary files, and they do not follow you to a different agent. If you want memory you own and can move, you build it around the agent instead.
Short-term context and long-term memory
The context window is short-term memory: it holds the current task and disappears with the session. Long-term memory is a small set of durable statements kept outside the model, retrieved only when they match what you are doing now.
A long chat history is not long-term memory. It is a transcript: it grows without bound, and the useful parts get buried. For the failure mode in detail, see why your AI agent forgets everything between sessions.
Where agents can keep long-term memory
| Approach | Good for | Trade-off |
|---|---|---|
| Built-in assistant memory (ChatGPT, Claude, Claude Code) | Zero setup, works out of the box | Locked to one vendor, not readable as files, does not move between tools |
Instruction files (CLAUDE.md, AGENTS.md, GEMINI.md) | Stable rules and project conventions | Loaded every session, so a growing log gets expensive and stale |
| A folder of markdown notes with search | Decisions, facts, and follow-ups accumulated over months | You have to build the write and read habit yourself |
| An MCP memory server (mem0, Letta, the reference memory server) | Structured recall shared across several clients | Another process to run, contents harder to inspect |
| A vector store (Chroma, pgvector) | Fuzzy recall across very large memory sets | Real infrastructure, opaque contents, hard to audit |
Built-in memory is the right pick if you live in one assistant and never want to think about this again. An MCP memory server earns its extra process when several clients need the same structured store. MCP memory server versus plain markdown files works through that trade.
A vector store is the right call when recall has to be fuzzy across tens of thousands of entries. Most agent memory never gets there. Vector database versus markdown for agent memory compares the two directly.
Skip files if your agent has no shell in its loop, or if your store is already heading past tens of thousands of entries. Otherwise start with files and move when search stops finding things.
What belongs in memory
Write down decisions, facts, and follow-ups. A decision records a choice and the reasoning behind it, which is the expensive part to reconstruct later. A fact records something true about the system that took effort to discover, such as a build quirk or a real rate limit. A follow-up records work you deliberately deferred, so it can be closed rather than forgotten.
Keep three things out. Do not store secrets: memory files get synced, shared, and printed into agent transcripts and logs, so anything in them should survive being seen. Do not store personal traits about the people involved. Do not store whole session transcripts, because a memory system that stores everything retrieves nothing useful.
What a coding agent should remember
Persistent memory for coding agents has a narrower shape than general assistant memory. The work itself produces the knowledge worth keeping. That is the library you rejected and why, the root cause behind a day-long bug, the build quirk that bites on one platform only, and the refactor you deferred until after the release.
That knowledge recurs. The same library gets proposed six months later, and without memory the agent argues for it again. Write the decision down once, and the next session starts where the last one ended.
Build it with plain files first
You do not need a product for the loop. Three directories and a shell command get you most of the way:
mkdir -p memory/decisions memory/facts memory/followups
cat > "memory/decisions/2026-08-30 postgres-advisory-locks.md" <<'EOF'
# Use Postgres advisory locks for the job queue
Redis was rejected. We already run Postgres and the queue is low volume.
EOF
Recall is a search over that folder:
rg -l "job queue" memory/
rg -n "rate limit" memory/facts/
Then add two lines to whichever instruction file your agent already loads, telling it to search memory/ before proposing an approach and to write a note when work concludes. That instruction is the behavior change.
This version has real gaps. Search returns matches in file order rather than by relevance. Nothing marks a decision as reversed, so old answers sit beside current ones. Two agents writing at once can overwrite each other, and review is manual.
What a memory tool adds
Jotura is a local-first markdown notes app, built for a person’s own notes, and it ships a first-party command-line tool called jotura alongside the desktop app. The memory commands came later, on top of the plain-file vault that was already there.
jotura memory log --kind decision \
--title "Use Postgres advisory locks for the job queue" \
--project "Payments" \
--body "Redis was rejected. We already run Postgres and the queue is low volume."
jotura memory recall "job queue locking" --limit 5
jotura memory recall "rate limits" --kind fact --json
Notes land at <root>/<Decisions|Facts|Followups>/<date> <slug>.md, so that first command writes Memory/Decisions/2026-08-30 use-postgres-advisory-locks-for-the-job-queue.md. The filenames contain spaces, so quote them. Recall ranks by relevance, freshness, and importance, and hides superseded or resolved notes unless you pass --include-stale. The store stays a folder of .md files you can open in the editor or commit to a git repository.
Making recall automatic
A memory system that depends on you saying “check your memory” has not solved the problem. One command returns the memories most relevant to the task at hand:
jotura context "refactor the sync queue"
Automatic injection at session start is Claude Code only today. jotura hook install claude writes a SessionStart entry into ~/.claude/settings.json that runs jotura context and feeds the result into the opening context. Codex, Gemini CLI, and Copilot get the same loop by putting the jotura context instruction in their prompt file, which the agent follows rather than the runtime guaranteeing it.
Keeping memory honest as it grows
Reversed memory is worse than no memory, because an agent will act confidently on a decision you overturned months ago. Give reversals an explicit representation instead of deleting the old note:
jotura memory supersede "Memory/Decisions/2026-03-11 use-redis-for-the-job-queue.md" \
--by "Memory/Decisions/2026-08-30 use-postgres-advisory-locks-for-the-job-queue.md"
jotura memory resolve "Memory/Followups/2026-04-02 remove-the-deprecated-identity-endpoints.md"
jotura memory list --stale
Superseding links both notes, so the history of the decision stays readable while only the current answer is recalled. The --stale flag surfaces active memories older than 180 days, which makes a decent monthly review queue.
Writes that do not clobber your file
Agents write to the same files you do, sometimes at the same moment. You read the file’s content hash first, then pass it back on the write. If the file changed in between, the CLI refuses with exit code 2 instead of overwriting silently:
NOTE="Memory/Facts/2026-06-04 stripe-api-rate-limit.md"
HASH=$(jotura read "$NOTE" --json | jq -r .hash)
jotura edit "$NOTE" --replace "100 requests per second" --with "25 requests per second" --if-hash "$HASH"
Replacements must match exactly once by default, writes are atomic, and body edits never touch the YAML frontmatter. Full command reference on the CLI page.
Sharing memory across machines and agents
Across two machines, the memory folder has to travel. Syncthing, Dropbox, iCloud Drive, or a private git repository all work, and all of them are free or already paid for. They win on cost, and git gives you a diffable history of every memory change for nothing.
Jotura Sync costs 4 pounds per month or 40 pounds per year, with no free trial. What you get for it is end-to-end encryption where the server never sees your filenames, paths, or content, plus encrypted sharing of a folder with other people. If you sync only your own machines and trust the encryption your current provider gives you, keep what you have.
Different agents can share one store because the store is plain files. Only the instruction files differ per tool, so Jotura keeps them in an Agents/ folder in the vault and writes them out on demand:
jotura agents sync
That covers Claude Code, Codex, Gemini CLI, and Copilot instructions from one edited source.
Honest limits
The app and the CLI are free on Mac, Windows, Linux, and Android. Sync is the only paid part, and it has no free trial. There is no iOS app.
Jotura is closed source and has no plugin system, so you cannot extend the memory format with community add-ons. Automatic session-start injection works with Claude Code only. Semantic search runs entirely on your machine, but you enable it in the desktop app’s settings, and the CLI can only query that index rather than build it.
Common questions
Does this work with any agent? Writing and reading work on any agent that can run a shell command. Automatic recall at session start is Claude Code only; other runtimes need the instruction in their prompt file.
How much should the agent write? A few sentences per memory. If a note needs headings, it is a document, and it belongs in your notes rather than your memory index.
Will memory grow too large to search? Volume is rarely the problem. Keyword search across a 10,000-note vault runs in roughly 8 milliseconds in a release build, and recall adds a ranking pass over the top matches. Stale entries hurt long before size does, which is why superseding matters more than pruning.
Related guides
How to set up shared memory across AI coding agents
Shared memory across AI coding agents: keep one markdown store that Claude Code, Codex, Gemini CLI, and Copilot all read, write, and stay in sync with.
GitHub Copilot CLI memory: how to make context survive between sessions
GitHub Copilot CLI memory does not survive the session. Here is what persists between sessions, and how to give the agent a store it reads and writes.
Codex CLI persistent memory: how to make Codex remember between sessions
Codex CLI persistent memory explained: what AGENTS.md keeps, what each new session drops, and how to store durable facts in markdown files you own.
More in AI agents.
Free. Plain markdown files, yours forever.