AI Agent Memory Between Sessions: Progress File, Decision Log, Git and Init Script
An AI agent has no memory between sessions: anything not written into the repository files is unknown to the new session. AI agent memory is built from artifacts: PROGRESS.md (where we are now), DECISIONS.md (why it is this way and what was rejected), git commits that explain the intent, and a shift protocol in AGENTS.md that tells the agent to read them at the start and update them at the end. An init script checks the environment and prints the current state.
Why the agent remembers nothing
A model does not keep state between runs. Everything the agent "knows" about your task lives in the context window of the current session: the files it has read, the output of commands, the dialogue with you. When the session ends or the context overflows, a new one starts from zero and sees only what is written on disk.
A useful model: a brilliant engineer who comes to work every morning with a completely wiped memory. They will quickly understand the code, but will not recall which architecture option was chosen yesterday, why the others were rejected, and what state the tests are in. Without notes, they will spend fifteen to twenty minutes on reconnaissance and, with good chances, redo something you had deliberately rejected.
So AI agent memory is not a property of the model but a part of the harness: a set of files and rules that you design yourself. The good news is that no vector databases or special services are needed. A few Markdown files, git and discipline are enough.
Why a large context window does not solve the problem
It seems enough to wait for a model with a bigger window. But a complex task fills any window: the agent reads the codebase, accumulates the history of decisions, receives verbose test output and keeps the whole dialogue. Needs grow together with the window. This is an architectural problem, not a question of volume.
There is a second effect. Closer to the window's limit, many models start to rush toward finishing: they skip checks and pick the simple solution over the right one. Anthropic describes this observation for its models, and the strength of the effect differs from model to model. A long session at the edge of the context is not only a risk of losing information but also a risk of degrading the quality of the work.
Compaction: what is kept and what is lost
Compaction is the squeezing of the early part of a dialogue into a short summary to free space in the same session. In Claude Code this is done by the /compact command, and compression also happens automatically when the limit approaches. The /clear command, by contrast, wipes the history and starts the conversation over.
The problem with compaction is that the summary usually keeps the "what" and loses the "why". Finished code stays in the files, but the reasoning, why option B was chosen, why a library was not taken, why an optimization was deliberately skipped, is compressed into a couple of words or disappears. The agent's next step "improves" a deliberate decision, because it no longer sees the reasons.
| Compaction | Reset (new session) | |
|---|---|---|
| What happens | The early part of the dialogue is replaced with a summary | The session closes, a new one reads the state files |
| What is kept | What the model judged important during compression | Exactly what you wrote into the files |
| Rush near the window limit | May be kept | Reset to zero |
| When it is appropriate | The task will surely fit into the current session | The task horizon is long or unclear |
A practical rule without complex metrics: when the task has taken about 60% of the window, stop writing code and prepare the handoff. If the context is 40% used and the end of the task is within reach, compaction is enough.
Four artifacts that make up agent memory
Instead of hoping for the model's memory, set up a logbook. It has four parts, and each has its own role.
- PROGRESS.md — where we are now.
- DECISIONS.md — why it is done this way and not otherwise.
- Git commits — free checkpoints of the state.
- The shift protocol in AGENTS.md — what to do when arriving and when leaving.
PROGRESS.md: the progress file
It answers one question: what state is the project in right now. The agent reads it first at the start of a session and updates it last before leaving. The file is short; it is rewritten, not endlessly appended to: the history lives in git.
# PROGRESS.md
## Current goal
Product catalog search (feature search-01)
## State
- HEAD: 4f2a9c1
- make check: green, 142/142 tests
- Done: PostgreSQL index (tsvector), GET /search endpoint
- Verified: unit tests + manual curl with 3 queries
## Blockers
- No morphology data for the Kazakh language — waiting for a decision
## Next step
Add pagination to GET /search, criterion: a test for page 2
Note the four fields that are enough for a handoff: the commit hash, the share of passing tests, the blockers and the next step. If a fresh session cannot unambiguously continue the work from this file, the template is missing a field.
DECISIONS.md: the decision log
It stores the very "why" that is lost during compaction. The entry format: the decision, the reason, what was rejected and why, the constraints.
## 2026-03-14 — Cache of user settings in Redis
- Decision: Redis, TTL 5 minutes, active invalidation on write.
- Reason: read on every request, the data volume is tiny.
- Rejected: a materialized view in PostgreSQL — changes too often,
maintaining it does not pay off.
- Constraint: do not cache payment settings.
The most valuable line here is "Rejected". It is what stops the next session from "optimizing" the decision back toward an option that was already considered and dropped. Unlike the progress file, the decision log only grows: old entries are not deleted but marked as replaced by new ones.
Git history as checkpoints
Commits are versioned snapshots of the state that come built into the work. The agent can read git log and git diff and reconstruct what has changed. For the history to work as memory, the commit message must explain the intent, not retell the diff.
# bad
git commit -m "update search.py"
# good
git commit -m "search: tsvector index instead of LIKE
LIKE '%q%' does not use the index; on 200k products the query takes 1.8 s.
Pagination is not done yet, see PROGRESS.md."
Small, frequent commits after each green test run also give a rollback point. If a session went the wrong way, it is easier to return to the last verified state than to untangle half-finished edits.
The shift protocol in AGENTS.md
The state files are useless if the agent does not know about them. So the entry instructions file (AGENTS.md or CLAUDE.md) gets a short protocol: what to do on arrival and on departure.
## Arriving on shift
1. Read PROGRESS.md and DECISIONS.md.
2. Find the active task in .agent/feature_list.json.
3. Run make check — make sure the repository is consistent.
4. Continue from the "Next step", without reopening what is already decided.
## Leaving the shift
1. Run make check, commit the green state.
2. Update PROGRESS.md: HEAD, tests, blockers, next step.
3. Add new decisions to DECISIONS.md.
4. Do not leave unfinished work in the working tree.
Keep the protocol short: a few lines in the entry file, not a separate wall of text.
The init script: how to speed up the start of a session
So the agent does not assemble the picture by hand, give it one script that checks the environment and prints the state. The script should be idempotent — it can be run any number of times — and end with an explicit "ready" message.
#!/usr/bin/env bash
set -euo pipefail
echo "== 1. Environment =="
command -v uv >/dev/null || { echo "FAIL: uv is not installed"; exit 1; }
uv sync --frozen
echo "== 2. Repository =="
if [ -n "$(git status --porcelain)" ]; then
echo "WARN: working tree is dirty"
else
echo "OK: clean"
fi
echo "HEAD: $(git log -1 --oneline)"
echo "== 3. Check =="
make check
echo "== 4. State =="
cat PROGRESS.md
echo "Ready to work"
In the arrival protocol, one line is then enough: "run scripts/init.sh". In Claude Code such a run can be automated with a hook on session start (the SessionStart event), but even without a hook the agent will execute the script following the instruction in the file.
What to put into agent memory and what not to
The main mistake is turning memory into a junk heap. Every line the agent reads at startup takes up space in the context, and clutter drowns out what matters.
| Put in | Do not put in |
|---|---|
| The current goal and the next step | A recap of code the agent can read itself |
| Decisions with reasons and rejected options | The full dialogue log and command output |
| The commit hash and the test status | A list of dependencies — it is in the package manifest |
| Blockers and open questions | "Did a great job, everything works" without saying how it was verified |
| Dead ends: what was tried and why it did not work | Secrets, tokens, passwords |
| Tasks with acceptance criteria | Outdated notes about closed tasks |
Separate by lifetime too. Project rules change rarely and apply to all tasks — they belong in the entry instructions file. The state changes every session — it belongs in the progress file. If you mix them, the entry file quickly bloats with stale notes.
The first session: initialization as a separate phase
Memory between sessions does not help if the first session descended into chaos. A typical picture: you write "add search" on a clean repository, and half an hour later it turns out the test framework is misconfigured and the migration format is wrong. Search appeared, but the foundation is shaky.
The reason is that implementation and initialization optimize for different things: the first for the number of finished features in this session, the second for the speed and reliability of all the sessions that follow. The agent gravitates toward the visible result, that is, toward code. So initialization should be set apart as its own session with a clear done criterion made of four conditions.
- It starts:
make setupandmake devwork from a clean clone. - It verifies: there is
make testand at least one real passing test. - It shows progress: the state file exists and can be read.
- It can be picked up: there is a list of tasks with acceptance criteria.
If you often start new projects, create a repository template: a Makefile, a test framework, a skeleton AGENTS.md, the .agent/ folder, scripts/init.sh, a linter and CI. Then initialization comes down to the specifics of the particular project.
How to check that memory works
The most honest metric is the time from the start of a new session to the first meaningful code change. Without artifacts it is usually fifteen to twenty minutes of reconnaissance; with a good handoff, two or three. Measure it once before and once after, and you will see whether the discipline pays off.
The second way is a cold-start test. Open a fresh session, write "continue" and ask five questions: what is the current goal, what state are the tests in, which decision was made on the key topic and why, what is blocking, and what is the next step. If the agent answers all five from the files, without guessing, the memory is set up correctly. Each uncertain answer is a missing field in the template.
Common mistakes
- The progress file only grows. After a month it has hundreds of lines of history. Rewrite it for the current state; the history belongs in git.
- No "rejected" line. A decision log without rejected options does not protect against repeated debates.
- The agent updates the state "by eye". Write how it was verified: the command and the result, not "everything works".
- A handoff at the edge of the window. Near the context limit the agent is already rushing; start the handoff early, around 60%.
- A dirty working tree on exit. Half-finished edits without a commit confuse the next session.
Summary
AI agent memory between sessions is built not on the model but on files: PROGRESS.md holds the current state, DECISIONS.md holds the reasons and rejected options, git commits are checkpoints with an explanation of the intent, and the shift protocol in AGENTS.md makes the agent read and update all of this. The init script reduces startup to one command, and a cold-start test shows whether the records are enough. The agent's logbook and the bootstrap contract are covered in practice in the harness engineering course.
FAQ
Can I just ask the agent to remember something for later?
The agent will remember it only if it writes it into a file that the next session will read. Claude Code has memory files and CLAUDE.md for this, but for the state of a specific task an explicit progress file in the repository is more reliable.
How is PROGRESS.md different from a CHANGELOG?
A CHANGELOG describes changes for users by version. PROGRESS.md describes the current working state for the next agent session and is rewritten every time the state changes.
Do I need a vector database for a programming agent's memory?
Usually not for work within one repository: the state of a task fits into a few short files, and the agent reads the code directly. Vector search makes sense when you need to search a large volume of external documents.
Should I commit the state files to git?
Yes, so the state is available to any session and any team member, and its history of changes is preserved. The exception is personal notes and anything containing secrets.
What if the agent forgets to update PROGRESS.md at the end?
Make the update part of the done criterion in the leaving protocol, and check it mechanically, for example with a hook or a script that compares the HEAD recorded in the file with the current commit.
This topic is covered in depth with practice in the course “Harness Engineering: Engineering Reliability for AI Agents”.
Go to course