You explained the whole project yesterday. You told it how you like things done, which files matter, what you already tried and why it did not work. This morning you opened a new session and it knows none of it. So you paste the context in again, and you will paste it in again tomorrow.
That is not a bug and no setting fixes it. A model has no memory between sessions. It has a context window, the window is filled at the start of a conversation and thrown away at the end, and everything you want it to know next time has to be written down somewhere outside the model and read back in. The question is not “how do I make it remember.” It is “where does the state live, and what puts it back.”
This page is the whole answer: what to store, the exact shape to store it in, what reads it back, and the four rules that decide whether it still works in three months or has quietly rotted into a file nobody trusts. You can build all of it today and never pay anybody.
Most people’s first attempt is to save the transcript. Do not. A transcript is a recording of how you got somewhere, and almost none of it is the destination. Re-reading it costs a fortune in tokens and buries the two useful sentences under two thousand useless ones.
Keep these three instead:
1. The standing brief — who this is and what good looks like. Stable. Changes maybe monthly. This is the thing you retype every morning: who you are, what you are building, what you will not accept, how you like the output. It is not instructions for a task, it is the frame every task sits inside.
2. The working state — what is true right now. Changes constantly. The current list of open items, the decisions already made so they do not get re-litigated, what was tried and failed. This is the file that stops the assistant proposing on Thursday the thing you rejected on Tuesday.
3. The last result — what the previous run produced. One artefact, or a pointer to one. Not the reasoning that produced it.
And keep them in three separate places, because they change at three different speeds. Merged into one file, the fast-changing part forces you to rewrite the slow-changing part, and a file rewritten under pressure is a file that loses things.
Plain files, plain directories, no database needed to start:
memory/
brief.md # who this is, what good looks like. Stable.
state.md # open items, decisions made, what failed. Volatile.
runs/
2026-09-01.md # what that day produced
2026-09-02.md
decisions/
use-postgres-not-mongo.md
Two rules make this work rather than sprawl.
brief.md has a hard ceiling — about 2,000 words. When it will not fit, something in it was never a standing truth, it was a task. Move it to state.md or delete it. A brief that grows without limit gets skimmed, and a skimmed brief is not read.
decisions/ is one file per decision, and it is append-only. Each file says what was decided, when, and what the alternative was. Never edit one. When a decision is reversed, write a new file that says so and references the old one. You want the history, because the single most expensive failure mode is re-arguing a settled question, and the second most expensive is a file that quietly changed its mind and left no trace.
Every session starts by reading brief.md and state.md, in that order. That is the entire mechanism. If your assistant supports project-level instruction files, put brief.md there and it loads automatically. If not, the first message of every session is “read memory/brief.md and memory/state.md before anything else,” and you accept the eight seconds.
Ending a session matters more than starting one, and it is where nearly everyone fails. Before you close it, one instruction:
Update memory/state.md: what changed today, what is now open, what got decided. Write the decisions as new files in memory/decisions/. Do not rewrite brief.md.
If you skip this, tomorrow reads yesterday’s state and is confidently wrong — which is worse than starting blank, because you will trust it.
Files work until one of three things happens.
More than one machine. Your laptop’s memory/ and your desktop’s memory/ diverge within a week and neither is right. Git fixes this and is genuinely the correct answer for a long time.
More than one agent. Two assistants writing state.md at once produce a torn file. Now you need per-key writes rather than whole-file writes, and that is a database.
Something running when you are not there. A scheduled job at 6am has no session to inherit context from. It has to read state cold, and it has to write back atomically, because a job that dies halfway through a whole-file rewrite leaves you with nothing.
Here is the schema, since this is the part people usually have to work out themselves. One DynamoDB table:
PK (partition key) OWNER#<user-id>
SK (sort key) ROLE#<role-name>
ROLE#<role-name>#STATE
ROLE#<role-name>#DECISION#<iso-timestamp>
ROLE#<role-name>#RUN#<iso-date>
Everything for one owner sits under one partition, so loading a role at the start of a run is a single query, not a scan. Decisions and runs sort naturally by time because the timestamp is in the sort key, so “the last five runs” is a query with a limit and a reverse, not a filter over everything.
Large artefacts — anything over a few kilobytes, so any actual output — go in S3 at s3://<bucket>/<owner>/<role>/<date>/<filename>, and the DynamoDB row holds the key, not the bytes. Rows stay small, queries stay fast, and you can read yesterday’s output without loading it into a context window.
Two details that are not obvious until they bite. Put a TTL attribute on RUN rows and set it to ninety days; without it you accumulate rows forever and your table gets slower every month. And write state with a conditional update on a version attribute rather than a plain put, so two agents writing at once produce a rejected write you can retry rather than a silently lost one.
That is the whole architecture. It is genuinely not much — a table, a bucket, and the discipline to write at the end of every session rather than only at the start.
Write at the end, not the beginning. State written before the work reflects intentions. State written after reflects what happened. Only the second one is worth reading.
Never store what you can re-derive. If you can look at the repository and see which tests pass, do not store which tests pass — you will store it, it will go stale, and you will trust the stale copy over the live one. Store only what is genuinely unrecoverable: decisions, preferences, and things that happened.
Separate “we could not see it” from “it was zero.” If a run could not read a number, write NOT READ with the date, never 0. Zero says we measured and failed. NOT READ says we could not look. Confusing them is how a system starts congratulating itself, and it is the single most common way these setups become worthless while still appearing to work.
Give it a bar, not just a brief. Write down what would make the output unacceptable, not only what to do. “Refuse an answer that recommends a library without saying what it costs” is worth ten lines of description, because it can actually be checked.