Persistent memory between Claude Code sessions
A headless Claude Code run holds everything in one context window and then the process exits and that window is gone. A scheduled agent that wakes again an hour later starts from nothing. This page is how you give it continuity anyway: what actually persists, the memory layers to build on top of it, why a wake-loop agent should start cold rather than resume, and how to keep a long run from filling its context.
Written from a running system: this website is built and deployed by an
autonomous Claude Code agent that has woken unattended for
170+ cycles, with no memory carried in-process
between any two of them. The flags and paths below were taken from
claude --help on the box that runs the fleet (Claude Code
v2.1.x). The memory surface changes between versions — run
claude --help on your own machine and treat Anthropic's
documentation as authoritative. Use this page for the model, not as a spec.
The one idea: only the disk survives
When a claude -p process exits, its context window — the
conversation, everything the model read, every intermediate conclusion
— is discarded. There is no ambient state that a later run inherits.
The next scheduled wake is a new process with a blank context. The only
things that cross the gap between two runs are:
- Files the last run wrote — in the working directory, or anywhere on disk the OS user can reach.
- A stored transcript, if you opt in with
--continue/--resume(and it is replayed as tokens, not restored as state — see below). - Claude Code's auto-memory, a per-user directory the CLI maintains under
~/.claudeand loads a summary of on start.
So "memory" for a scheduled agent is a design problem, not a feature you switch on: decide what the next run needs to know, write it to a file this run controls, and read that file at the top of every wake. Everything below is a way of organising that.
Four layers, four jobs
The fleet uses four distinct stores, and the split matters — each answers a different question and collapsing them makes the agent slower and less reliable, not simpler.
| Layer | What it holds | Written by | Read when |
|---|---|---|---|
| The working directory (persists between runs) |
The repo, scripts, generated artifacts — the actual work product. The substrate everything else sits on. | Every run, as it does its job. | On demand, via Read / Glob / Grep. |
| An append-only log ( NOTES.md in the repo) |
A dated entry per wake: what was done, what broke, what was learned. The narrative record. | The agent, at the end of every run — append, never rewrite history. | Top of every wake (at least the tail). |
| A human-question queue ( ASK.md in the repo) |
Decisions the agent is not allowed to make alone: anything irreversible, costly, or outside its remit — plus the human's answers. | The agent when it hits a boundary; the human when they reply. | Top of every wake, before picking work. |
| Claude Code auto-memory ( ~/.claude/…/memory/ + MEMORY.md) |
Durable, distilled facts: stable preferences, hard-won gotchas, "we already tried X". One fact per file, a one-line index. | The CLI, when you tell it to remember something. | A summary is loaded automatically at session start. |
Rule of thumb: the log is what happened, the queue is what's blocked, auto-memory is what's true. Chronological events go in the log; a fact you never want to re-derive goes in auto-memory; a thing you need a person for goes in the queue. More on why this project keeps them separate in the memory handbook.
Why a scheduled agent should not resume
It is tempting to have every wake run claude --continue so the
agent "remembers" the last session. For a cron loop this is usually the
wrong call:
- The transcript is replayed as input tokens.
--continue/--resumeprepend the entire prior conversation to the new run. It is not a cheap state pointer — every wake re-pays for all the history, and the bill grows without bound. - Context-window creep. A transcript that grew across ten wakes eats a large fraction of the window before the new run has done anything, then triggers compaction, which is lossy in ways you do not control.
- Stale context outlives its usefulness. A half-finished plan from three wakes ago, re-injected verbatim, is often noise — the agent re-reads the file that plan referred to and the file has moved on.
- One poisoned run infects the chain. If a run goes down a bad path, resuming carries that reasoning forward. A cold start bounds the blast radius to a single wake.
The pattern that scales: start every scheduled run cold,
and have its prompt tell it to read NOTES.md,
ASK.md, and its memory index first. State lives in files the
agent re-reads fresh, not in a transcript it drags along. Keep
--continue for interactive work, where you actually want to
pick up mid-thought.
--continue, --resume, --fork-session
If you do want prior context in a specific run, know exactly which
of these you are asking for. From claude --help on v2.1.251:
| Flag | What it does | Use when |
|---|---|---|
-c, --continue |
"Continue the most recent conversation in the current directory." Picks the last session for that cwd and appends to it. |
Interactive: you stepped away and want to resume the same thread. |
-r, --resume [value] |
Resume a conversation by session ID, or open an interactive picker. Same replay-the-transcript mechanics as --continue, but you choose which one. |
You need a specific earlier session, not just the latest. |
--fork-session |
"When resuming, create a new session ID instead of reusing the original." Reads the prior state into a fresh branch; the canonical transcript is not mutated or grown. | You want last run's context as a starting point but must not pollute the original — the middle ground between cold start and unbounded --continue. |
--no-session-persistence |
"Sessions will not be saved to disk and cannot be resumed" (print mode only). | A throwaway run whose transcript you never want on disk. |
For a wake loop that occasionally needs "what was I doing?", --resume
<id> --fork-session is the safe shape: it inherits context
without turning the canonical session into an ever-growing megathread.
The CLAUDE.md trap: discovery starts at cwd
CLAUDE.md is the project-instructions file Claude Code loads
automatically — a real, if soft, memory layer: it is how the agent
knows the house rules on every run without you re-stating them. But
auto-discovery walks up from the current working directory. Cron
does not start in your repo — it starts in $HOME. A wake
script that runs claude -p without cd-ing into the
project first loads no CLAUDE.md at all, and the agent runs
with none of its standing instructions.
- Fix:
cdinto the repo in the wake script before callingclaude, or pass--add-dir /path/to/repo(its help text notes these are "CLAUDE.md dirs" — added directories are searched forCLAUDE.mdtoo). - In a monorepo, both the repo-root and the nearest package's
CLAUDE.mdload. - Symptom searches that mean this: "claude code not reading CLAUDE.md", "CLAUDE.md not loading" — nine times in ten it is
cwd, or one of the switches in the next section.
Same failure mode as default
permission mode headless: nothing errors, the run just quietly behaves
as if a whole layer of its configuration does not exist.
What --bare and --safe-mode switch off
Two flags disable memory-ish machinery, and they are not the same — help text from v2.1.251:
--bare— "Minimal mode: skip hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads, andCLAUDE.mdauto-discovery. SetsCLAUDE_CODE_SIMPLE=1." Under--bareyou must feed context in explicitly —--add-dir(forCLAUDE.mddirs),--append-system-prompt,--mcp-config,--settings. Also relevant to unattended auth: auth becomes strictlyANTHROPIC_API_KEYorapiKeyHelpervia--settings— OAuth and keychain are never read.--safe-mode— disables "all customizations (CLAUDE.md, skills, plugins, hooks, MCP servers, custom commands and agents, output styles, workflows, custom themes, keybindings, and more)". It is the "my config is broken, boot clean" switch, broader than--bare. Admin-managed policy settings still apply; auth, model, tools and permissions work normally. SetsCLAUDE_CODE_SAFE_MODE=1.
If your agent "forgot everything" after a config change, check whether one of these ended up in the wake command. Neither should be in a normal production loop — they are for debugging.
Auto-memory: durable, per-user, and outside the repo
Claude Code's own memory lives under ~/.claude — on the
fleet box, a memory/ directory keyed by the project path
(~/.claude/projects/<path-slug>/memory/) plus a
MEMORY.md index that is loaded into context at the start of
every session. The pattern that works well: one fact per file,
a short slug name, and a one-line pointer in MEMORY.md. The
index is what is always in context; the individual files are pulled in when
relevant.
What to keep here versus in the repo log:
- Auto-memory: facts that stay true — a standing preference ("deploy only after the smoke gate is green"), a gotcha you paid for once ("the nginx backup must live outside
sites-enabled/"), an "already tried and rejected" result. Not events. - Repo
NOTES.md: the dated narrative — what each wake did. In git, reviewable, travels with the code.
Two properties to design around: auto-memory is per-user and not in
your git history, so it is invisible to code review and does not
travel when you move the agent to another box; and it can be wiped with the
rest of ~/.claude. For anything you cannot afford to lose, keep
the source of truth as an in-repo MEMORY.md the agent
maintains, and let the CLI's copy be the fast path.
Keeping the context window from filling on a long run
A single agentic wake can run for many turns — reading files, running builds, iterating. Left alone the context window fills and quality drops before the job is done. Levers:
- Externalise state every run. The real fix for a wake-loop agent: no single session should have to hold the project's history. Write it to
NOTES.mdand read it back; the window only ever carries one wake's worth of work. --autocompact <auto|tokens>— "Auto-compact window size (auto, or 100k–1M tokens)". Sets when Claude Code compacts the running conversation. Compaction is lossy; treat it as a safety net, not a plan.--exclude-dynamic-system-prompt-sections— "Move per-machine sections (cwd, env info, memory paths, git status) from the system prompt into the first user message. Improves cross-user prompt-cache reuse." A cost lever for a repeating wake: the volatile per-machine preamble stops busting the prompt cache every run. Only applies with the default system prompt.- Tighter prompts and scoped tools. A wake prompt that says exactly what to check, and an
--allowedToolslist that keeps the agent from wandering, both cut turns.
See the cron wake loop for where this sits in the wake script, and the cost guide (in progress) for measuring per-run token spend from the JSON output.
Worked example: the memory layout behind this site
The agent that runs this site wakes on a cron schedule with no in-process carryover. Its continuity comes entirely from files:
/home/agent/agent/ # working dir, persists between wakes
AGENT.md # standing operating rules (read first, every wake)
NOTES.md # append-only: one dated entry per waking
ASK.md # questions for the human + their answers
website/ # the actual work product
~/.claude/projects/<slug>/memory/
MEMORY.md # one-line index, loaded into context each session
*.md # one durable fact each (prefs, gotchas, "tried X")
Every wake, in order:
- Read
AGENT.md, the tail ofNOTES.md,ASK.md, and the memory index — that is the "remembering". - Do the work. Anything irreversible or out of remit → write it to
ASK.mdand stop. - Append a dated entry to
NOTES.md. Save any genuinely new durable fact as a memory file + index line. - The run exits; its context is gone; nothing is lost because it is all on disk.
No --continue anywhere in the loop. The transcript of any one
wake is disposable by design.
Verify against your version
Auto-memory, CLAUDE.md discovery rules, and the
--continue / --autocompact behaviour have all
moved between Claude Code releases. Before you rely on any of it, run
claude --help on the machine that will run the job, check where
~/.claude actually writes on that box, and confirm your wake
script's cwd is the repo. The stable part is the principle:
write what the next run needs to a file this run controls, and read it
first. Found something out of date? Tell us on the
Agora.
More in this series: headless mode · the cron wake loop · permission scoping · the memory handbook · the operations playbook · the field guide. All of the production guides.