Skip to content
All posts
5 min read

progress.md: How to Know What Your AI Coding Agent Actually Built

Amit Raz

Amit Raz

Founder, RZ AI Labs

After the post about decisions.md, here is the second half of the story: a second file, progress.md, and it works in exactly the opposite way.

Where decisions.md is append-only history, progress.md is a file that gets updated all the time. It is a mirror of the spec, but carrying status only, and it answers exactly one question: what actually exists in the system today. Not what was planned. Not what the agent said it finished.

Illustration of a progress.md board with four columns (DONE, PARTIAL, NOT STARTED, DIVERGED), a robot pinning evidence to the DONE column while a developer studies the board
The spec says what should be. progress.md says what is.

The one rule: no "done" without proof

This is the most important part of the whole file. Nothing gets marked done without evidence: a file path plus a passing test, a real recording, or a commit. A docstring is not proof. A TODO is not proof. And a checkmark the agent itself wrote yesterday is not proof either.

Coding agents are excellent at declaring themselves done. Ask for a receipt.

In practice this means every row in the file has an evidence column, and that column is what keeps everyone honest. If the evidence is a test, the test has a path. If it is a recording, the recording is on disk. If it is a commit, the hash is there.

What are the four statuses?

This is the status legend, verbatim from the project I run this on:

| Status | Meaning |
|---|---|
| DONE | Implemented AND proven (passing test / real recorded fixture). |
| PARTIAL | Implemented but unproven or incomplete. |
| NOT STARTED | No implementation. Expected for most rows. Stated honestly. |
| DIVERGED | Implemented differently from the spec (gap described). |

Two notes on the legend. NOT STARTED will be most of the file early on, and that is fine; an honest empty column beats an optimistic full one. And every DIVERGED row also gets an entry in decisions.md, because a divergence from the spec is a decision, whether anyone made it deliberately or not.

A real slice of the table looks like this:

| Stage | Status | One-line evidence |
|---|---|---|
| 02 company_registry | DONE | stage.py + seven recorded cassettes + 53 replay tests |
| 03 address_databases | NOT STARTED | No module, no registry entry, no cassette. |
| 06 news_web_content | DONE | stage.py; 13.7 MB real cassette; smoke test asserts output keys + cost ledger |

It is not just for the agent

Sometimes you are forced to stop mid-session and only come back two days later. Or you return to a project after a week, sit down at the screen and wonder: wait, where exactly did I stop? What already works? What is the sensible thing to build next? Instead of digging through git trying to reconstruct the state, you open one file and see it, or you send the agent to read it and report back.

Log the failures too

I ask the agent to record failures in the same file, not just wins. Mine says, in black and white, that one fix attempt broke 51 tests. That entry stays there. Condensed, it reads like this:

2026-07-12 - Live-call guard for the test suite, second attempt. DONE.
The first attempt (2026-07-06) hooked the HTTP transport layer and broke
51 tests. This attempt sits one layer lower, at the connection-pool seam,
below every test double in the tree. Full suite: 1552 passed, 0 failed.

Why keep the embarrassing part? Because the failed attempt is exactly what the next session needs to know before it tries the same thing. This is the same logic as recording rejected alternatives in decisions.md: the dead ends are the expensive knowledge.

Why not put status inside the spec?

Bottom line:

The spec says what the system should be. progress.md says what it actually is. Mix the two in one file and the spec quietly becomes a lie.

The spec has to stay a clean target you can diff reality against. The moment "done" marks and status notes leak into it, you lose the ability to ask the only question that matters at any given moment: how far is the system from what it is supposed to be? I keep this separation on every project I run agents on, including custom AI agents built for clients.

FAQ

How is this different from a TODO list?

A TODO list records intentions. progress.md records reality. It mirrors the spec's structure and stamps each part with what actually exists in code, backed by evidence you can click through to. Nothing about it is aspirational.

Who updates it, me or the agent?

The agent updates it as part of finishing work; I audit it. The protection against self-serving updates is the evidence rule: a status change must cite proof, and a checkmark the agent wrote yesterday does not count as proof today.

Doesn't it drift out of date like every status document?

Less than you would think, for one reason: it is the first thing consulted when resuming work. A stale row gets caught the next time anyone (human or agent) acts on it and hits reality. And because DONE requires evidence, stale optimism is auditable: rerun the test, check the path.

This is part two of a series on running an AI-agent project from plain markdown files. Part one covered decisions.md; next up are markdown tickets and a CI dead man's switch for tech debt. The original version was a tweet in Hebrew.

Building something with AI?

I help teams ship custom agents, AI strategy, and software that works.