Harnesses · · 2,749 words · 12 min read
Coding agent memory across sessions: Claude Code, Codex, Gemini CLI
What Claude Code, Codex, and Gemini CLI carry from one session to the next: transcripts, compaction, and agent-written memory, plus a script that checks what loads.
memory sessions compaction claude code codex
You get what three coding-agent harnesses carry from one session to the next, split into the layers their documentation actually describes: the saved transcript you can resume, the summary that replaces history when the context fills, and the memory the agent writes for itself. Every behaviour below is quoted from vendor documentation or source read on 2026-09-14. Versions come from each project's GitHub releases page that day: Claude Code v2.1.270 (2026-09-12), Codex CLI rust-v0.154.0 (2026-09-09), and Gemini CLI v0.59.0 (2026-09-08), whose pages I read from the docs/ folder at that tag. No agent ran for this article. What ran is a standard-library script that checks a Claude Code memory index against its documented read limit, on a synthetic fixture, with Python 3.13.12 on Windows; its output below is verbatim.
The fourth layer, the instruction file you write yourself, was the subject of /posts/context-files-claude-md-agents-md-cursor-rules.html. This article is about the three layers the harness manages and you mostly do not see.
Three layers hiding behind one word
"Memory" gets used for all of these, and they fail in different ways. A transcript is complete but expires and costs tokens to reload. A summary is cheap but lossy, and a model decides what it drops. Agent-written memory is small and persistent, and it is also written by a model, usually without you reading it first.
| Claude Code v2.1.270 | Codex rust-v0.154.0 | Gemini CLI v0.59.0 | |
|---|---|---|---|
| Resume a session | claude --continue, claude --resume |
codex resume, codex exec resume |
gemini --resume, /resume |
| Transcript retention | 30 days, cleanupPeriodDays |
not stated on the pages read | 30 days, general.sessionRetention.maxAge |
| Compact in a session | /compact [instructions], automatic |
/compact |
/compress, automatic at model.compressionThreshold |
| Agent-written memory | auto memory, on by default | memories, off by default | memory files on request; Auto Memory, experimental, off |
| Review before memory is used | no | no | Auto Memory only: an inbox |
The downloadable CSV in the box below has the same comparison with storage paths, controls, and the source page for every row.
Layer one: the transcript you resume
Claude Code's sessions page: "Sessions are saved continuously to local transcript files as you work, so you can return to one after exiting or running /clear." They live at ~/.claude/projects/<project>/<session-id>.jsonl, and the page warns against building on them directly: "The entry format is internal to Claude Code and changes between versions, so scripts that parse these files directly can break on any release." The retention sweep deletes them after cleanupPeriodDays, default 30.
What a resume brings back is narrower than "the session". The page lists the conversation history, the model, the agent, the permission mode, an active goal, and unexpired scheduled tasks. It does not bring back launch flags: "If the session depended on --mcp-config, --settings, --plugin-dir, --fallback-model, or directories added with --add-dir, pass them again when you resume". The permission rule is a safety property worth knowing: a session that ended in bypassPermissions and is resumed from a terminal starts in "The permission mode a new session would start in". On a Pro or Max plan, resuming a session idle for "more than about an hour" and "over 100,000 tokens" opens a dialog offering to resume from a summary, because the prompt cache has expired and "the next request processes the full history once no matter which of the dialog's options you pick."
Codex's command reference describes codex resume as "Continue an interactive session by ID or resume the most recent chat", with --last scoped "to the current working directory unless you pass --all". When the directories differ, Codex asks which one to use, and tui.resume_cwd answers in advance. codex exec resume does the same for non-interactive runs. Inside the TUI, /resume "reloads the selected chat's transcript so you can pick up where you left off, keeping the original history intact", and /fork "clones the current chat into a new chat with a fresh ID". The configuration reference has history.persistence, typed save-all | none, to "Control whether Codex saves session transcripts to history.jsonl", and history.max_bytes, which "caps the history file size in bytes by dropping oldest entries". I found no retention period on the pages I read.
Gemini CLI stores sessions in ~/.gemini/tmp/<project_hash>/chats/, including "All tool executions (inputs and outputs)" and token usage. gemini --resume loads the latest; an index from gemini --list-sessions or a full UUID picks another. The default policy is to "retain sessions for 30 days", with maxCount unlimited and a minRetention of "1d" below which automatic cleanup never deletes. Deletion is thorough: "Gemini CLI also removes all associated data, including implementation plans, task trackers, tool outputs, and activity logs." For a named branch point inside a session there is /resume save decision-point, and model.maxSessionTurns caps a session's length outright.
The practical reading is the same across all three: the transcript is the complete record, and it is perishable. A decision you need next month does not belong only in a transcript.
Layer two: the summary that replaces history
Claude Code is the only one of the three whose documentation says, item by item, what comes back after compaction. From the context window page: the project-root CLAUDE.md, unscoped rules, auto memory, and the plan written in plan mode are "Re-injected from disk"; rules with paths: frontmatter and nested CLAUDE.md files reload only as Claude reads matching files; Claude Code "re-reads up to five, most recently modified first" of the files the session touched; invoked skills come back "capped at 5,000 tokens per skill and 25,000 tokens total"; and context that hooks added earlier is "Summarized with the rest of the conversation". The memory page's troubleshooting section turns that into a diagnosis: "If an instruction disappeared after compaction, it was given only in conversation, lives in a nested CLAUDE.md that hasn't reloaded yet, or is a path-scoped rule that hasn't matched a file since." You can steer it with /compact focus on the auth bug fix or move the trigger with /autocompact 500k.
Codex's /compact entry says "Codex replaces earlier turns with a concise summary, freeing context while keeping critical details." Gemini CLI's /compress will "Replace the entire chat context with a summary", and compression triggers on its own at model.compressionThreshold, "The fraction of context usage at which to trigger context compression", default 0.5, and a setting that requires a restart. Neither page lists what survives.
So in all three, an instruction you typed into the chat is at the mercy of a summarizer. Only in Claude Code can you look up which files will be re-read afterwards.
Layer three: memory the agent writes
Claude Code: auto memory, on by default
"Auto memory lets Claude accumulate knowledge across sessions without you writing anything." Notes carry a type of user, feedback, project, or reference, and "Claude skips anything it can derive from the codebase, such as architecture, file paths, or debugging fixes." Each project gets ~/.claude/projects/<project>/memory/, shared by every worktree of the same repository, holding a MEMORY.md index and one topic file per memory. It is "machine-local", and "Files are not shared across machines or cloud environments."
The load rule is the part that bites: "The first 200 lines of MEMORY.md, or the first 25KB, whichever comes first, are loaded at the start of every conversation. Content beyond that threshold is not loaded at session start." Topic files are not loaded at startup; Claude "reads them on demand using its standard file tools". The errors page records how the guard rail evolved. "Before v2.1.210, an over-limit index was silently truncated on the next load with no write-time signal"; since then an over-limit write returns an error to Claude, not to your terminal, telling it to rewrite the index. Since v2.1.211, frontmatter and block-level HTML comments are stripped before measuring. Since v2.1.214, a memory file with frontmatter gets a modified timestamp on each write.
Memory also outlives the transcripts. The .claude directory page says the retention sweep "doesn't delete the memory files in a project's auto memory directory", and that "Before v2.1.228, the sweep treated folders inside the memory directory as session data and could delete old files beneath it." You turn it off with autoMemoryEnabled: false or CLAUDE_CODE_DISABLE_AUTO_MEMORY=1, and browse it with /memory. Subagents do not load the main conversation's auto memory; a fork does.
Codex: memories, off by default, generated in two phases
The Codex memories page is explicit that this is opt-in: "Local Codex memories are off by default." You enable it with memories = true under [features] in config.toml, and /memories controls, per chat, "whether the current chat can use existing memories and whether Codex can use the chat to generate future memories." The page's summary: "Codex skips active or short-lived sessions, redacts secrets from generated memory fields, and updates memories in the background instead of immediately at the end of every chat."
The pipeline README at tag rust-v0.154.0 shows the mechanism. It runs when a root session starts, and only if "the session is not ephemeral", the feature is on, "the session is not a sub-agent session", and the state database is available. Phase 1 sends each eligible past thread to a model and expects "a detailed raw_memory" and "a compact rollout_summary". Phase 2 consolidates them into ~/.codex/memories/, which it keeps as a git baseline, writes a phase2_workspace_diff.md, and hands that diff to a consolidation sub-agent that runs "with no approvals, no network, and local write access only". The configuration reference supplies the defaults: memories.min_rollout_idle_hours 6, memories.max_rollout_age_days 30, memories.max_rollouts_per_startup 16, memories.max_unused_days 30, and memories.min_rate_limit_remaining_percent 25, so generation waits when you are near your usage limit.
The read side is a prompt template in the same tree. It tells the model that memory_summary.md is "already provided below; do NOT open again", that MEMORY.md is the "searchable registry; primary file to query", to keep lookup to "ideally <= 4-6 search steps before main work", and "Do not present unverified memory-derived facts as confirmed-current." The documentation's stance on the files themselves: "Treat these files as generated state", and "Treat memories as a helpful recall layer, not as the only source for rules that must always apply."
Gemini CLI: memory files on request, Auto Memory behind an inbox
Gemini CLI's plain memory is file editing. Ask it to remember something and "The agent will edit the appropriate memory Markdown file". The tool page routes "shared project instructions" to repository GEMINI.md files, "private project notes" to "the per-project private memory folder", and "cross-project personal preferences" to ~/.gemini/GEMINI.md. /memory show prints "the raw text the model receives at the start of the session."
Auto Memory is the generated layer, and it is marked "an experimental feature currently under active development". It is off until you set experimental.autoMemory to true and restart. It considers only sessions that "have been idle for at least three hours and contain at least 10 user messages", and its output goes to an inbox: "It cannot directly edit active memory files, settings, credentials, or project GEMINI.md files." You review with /memory inbox, and nothing applies without approval. Two limits are stated plainly: "The extraction agent runs on a preview Gemini Flash model", and "transcript excerpts may be sent to the configured model as part of those calls."
| Claude Code auto memory | Codex memories | Gemini CLI Auto Memory | |
|---|---|---|---|
| Default | on | off | off, experimental |
| Written | during the session, by the working agent | at a later session start, from threads idle 6+ hours | at a later session start, from sessions idle 3+ hours with 10+ user messages |
| Review before use | none; audit with /memory |
none; per-chat /memories switches |
inbox approval |
| Loads at start | first 200 lines or 25KB of MEMORY.md |
memory_summary.md; MEMORY.md searched on demand |
applied memory files, through GEMINI.md context |
| Stated size bound | 200 lines or 25KB | 256 raw memories for consolidation, capped at 4,096 | not stated |
Memory is a place text can persist
The security piece at /posts/coding-agent-security-prompt-injection-mcp-secrets.html was about untrusted text reaching the model. A memory store changes the time scale: a note written in a session that read a hostile README is loaded into sessions that never saw it. The documentation does not frame any of the following as an injection defence, so that reading is mine, but the controls exist. Codex has memories.disable_on_external_context: "When true, threads that use external context such as MCP tool calls, web search, or tool search are kept out of memory generation. Defaults to false." Gemini CLI's inbox puts a human between extraction and use, and its extraction agent "is instructed to redact secrets, tokens, and credentials it encounters". Claude Code's memory page says of CLAUDE.md and auto memory alike that "Claude treats them as context, not enforced configuration", and its advice for an action that must be blocked "regardless of what Claude decides" is a PreToolUse hook, not a note.
Checking what actually loads
The only one of these limits you can check without running the agent is Claude Code's index cut, so the shipped script does that. audit walks a projects directory and reports, per project, the index lines and bytes, the lines that survive the cut, topic files by frontmatter and modified stamp, topic files the index never names, topic files named only after the cut, and transcripts still on disk. preview prints where one index stops loading and what it drops. The cut itself:
def load_cut(text: str, max_bytes: int) -> tuple[int, int, int]:
"""Return (lines kept, bytes kept, total lines) under 200 lines or max_bytes, whichever first."""
lines = text.splitlines(keepends=True)
kept_lines = kept_bytes = 0
for line in lines:
size = len(line.encode("utf-8"))
if kept_lines == MAX_LINES or kept_bytes + size > max_bytes:
break
kept_lines += 1
kept_bytes += size
return kept_lines, kept_bytes, len(lines)
I built two synthetic projects. The first has a 219-line MEMORY.md: 4 lines of frontmatter, a one-line HTML comment, and 214 one-line entries, the same 214 the errors page uses in its example message. It has three topic files: one indexed on line 3 with a modified stamp, one indexed only on line 205, and one never named. The second has 150 entries of 200 bytes each, 30,000 bytes, to hit the byte limit before the line limit. The runs:
> python coding-agent-memory-across-sessions.py audit projects
read limit 200 lines or 25000 bytes
project directories 2
with MEMORY.md 2
index past a read limit 2
p01: 150 lines, 30000 bytes -> loads 125 lines, drops 25 lines / 5000 bytes
p02: 214 lines, 14437 bytes -> loads 200 lines, drops 14 lines / 938 bytes
largest index 214 lines, 30000 bytes
topic files 3 (frontmatter 2, modified stamp 1)
topic types {'project': 1, 'feedback': 1}
never named in index 1
named only past the cut 1
memory dirs, no transcript 1
projects with transcripts 1 (oldest 12.0 days by mtime)
exit=1
> python coding-agent-memory-across-sessions.py audit projects --kb-bytes 25600
read limit 200 lines or 25600 bytes
project directories 2
with MEMORY.md 2
index past a read limit 2
p01: 150 lines, 30000 bytes -> loads 128 lines, drops 22 lines / 4400 bytes
p02: 214 lines, 14437 bytes -> loads 200 lines, drops 14 lines / 938 bytes
> python coding-agent-memory-across-sessions.py preview projects/C--work-line-limit/memory/MEMORY.md
projects/C--work-line-limit/memory/MEMORY.md: 214 lines after stripping; loads lines 1-200 (13400 bytes)
dropped 201: - [Entry 201](entry_201.md) - one line per memory, as the docs ask
dropped 202: - [Entry 202](entry_202.md) - one line per memory, as the docs ask
dropped 203: - [Entry 203](entry_203.md) - one line per memory, as the docs ask
exit=1
Three things in that output are worth more than the arithmetic. The stripping works: 219 raw lines count as 214, as the errors page says they should since v2.1.211. The second run exists because the documentation says "25KB" without saying whether that is 25,000 or 25,600 bytes, and on a byte-bound index the answer moves the cut by three lines. And the "named only past the cut" row is the failure the load rule creates without any error at read time: entry_205.md is still on disk and Claude can still open it with its file tools, but the part of the index that loads at startup no longer points to it. The 12-day transcript is a file I backdated to exercise that column; it says nothing about real retention.
These are fixture numbers. They show the script computes the documented rule; they do not show how often real indexes overflow, and I did not audit anyone's real memory directory for this article.
The CSV in the box is the comparison, not the fixture:
_README coding-agent-memory-across-sessions.csv
harness Claude Code | Codex | Gemini CLI
version_read release tag the documentation was matched to on 2026-09-14
layer transcript | checkpoint | compaction | generated memory | written instructions
mechanism commands, flags, or feature name as documented
default on | off | as stated, or "not stated on the pages read"
storage path as documented; placeholders in angle brackets
loads_at_session_start what enters the context when a new session starts or resumes
limit_or_retention documented size limit, age window, or retention period
control settings keys, environment variables, and slash commands
source_url the page or tagged file each row was read from
What the pages do not say
Codex's pages I read give no transcript file path and no retention period, the configuration excerpt for history.persistence shows its type but not its default, and I did not read any description of automatic compaction in Codex, so those cells say so. Gemini CLI's documentation at the same tag disagrees with itself: the memory tutorial and the GEMINI.md page say /memory reload, while the commands reference lists list, refresh, and show; I could not settle which is current without running v0.59.0, and neither Codex nor Gemini CLI is installed on the machine this was written on. The Gemini tool page names "the per-project private memory folder" without giving its path. Whether Claude Code keeps part of a line that straddles the 25KB boundary is not documented, so the script counts whole lines only. None of the pages measures whether memory improves task outcomes, and a search for papers on coding-agent memory got HTTP 429 or a timeout from arXiv on every attempt today, so there is no research claim in this article.
What holds across all three, on the pages as read today: the transcript expires in about a month where a period is stated, the summary keeps what a model chooses, and agent-written memory persists longer than either, with only one of the three asking you before it is used. After a session that settled something, open /memory in Claude Code, /memories in Codex, or /memory inbox in Gemini CLI, and move anything that must always apply into the instruction file you write yourself.
Code and data
- coding-agent-memory-across-sessions.py — the complete listing used in this article.
- coding-agent-memory-across-sessions.csv — the data behind the numbers here.
Sources
- Anthropic, "How Claude remembers your project" (Claude Code docs, read 2026-09-14)
- Anthropic, "Manage sessions" (Claude Code docs, read 2026-09-14)
- Anthropic, Claude Code docs, context window page, section "What survives compaction" (read 2026-09-14)
- Anthropic, Claude Code docs, .claude directory page, section "Cleaned up automatically" (read 2026-09-14)
- Anthropic, Claude Code docs, errors page, section "Memory index is over its read limit" (read 2026-09-14)
- anthropics/claude-code, "Release v2.1.270" (read 2026-09-14)
- OpenAI, "Memories" (Codex docs, read 2026-09-14)
- OpenAI, "Command line options" and "Slash commands in Codex CLI" (Codex CLI docs, read 2026-09-14)
- OpenAI, "Configuration Reference" (Codex docs, read 2026-09-14)
- openai/codex, memories pipeline README at tag rust-v0.154.0 (read 2026-09-14)
- openai/codex, memory read-path prompt template at tag rust-v0.154.0 (read 2026-09-14)
- openai/codex, "Release rust-v0.154.0" (read 2026-09-14)
- google-gemini/gemini-cli, "Session management" at tag v0.59.0 (read 2026-09-14)
- google-gemini/gemini-cli, "Checkpointing" at tag v0.59.0 (read 2026-09-14)
- google-gemini/gemini-cli, "Auto Memory" at tag v0.59.0 (read 2026-09-14)
- google-gemini/gemini-cli, "Memory files" at tag v0.59.0 (read 2026-09-14)
- google-gemini/gemini-cli, "Manage context and memory" at tag v0.59.0 (read 2026-09-14)
- google-gemini/gemini-cli, commands reference at tag v0.59.0 (read 2026-09-14)
- google-gemini/gemini-cli, configuration reference at tag v0.59.0 (read 2026-09-14)
- google-gemini/gemini-cli, "Release v0.59.0" (read 2026-09-14)