Company OAsAll ProblemsOA CalendarInterview ExperiencesPremium
OAHelper

Built by students, for students - practice company-specific OAs, DSA sheets, and real interview experiences to land your dream role.

© 2026 OAHelper.in·Terms·Privacy·Refunds·Trust & Safety·Contact·
Ready to crack your next OA?

Practice company-specific questions trusted by thousands of students across India.

Start PracticingGo Premium
OA Practice·DSA·Placements

Disclaimer: OAHelper is an independent educational platform. We (oahelper.in) do not own the images or questions shown. Content is uploaded by users.

Module 06

Repo Memory and State

  • Why Models Fail
  • A Minimal Workbench
  • Executable Constraints
  • Repo Memory and State
  • Initialization Scripts
  • Scope Contracts
On this page

This week

  • Why Models Fail
  • A Minimal Workbench
  • Executable Constraints
  • Repo Memory and State
  • Initialization Scripts
  • Scope Contracts

In plain words

In a group project, WhatsApp messages scroll away but a shared sheet stays. Agents are the same. Chat history dies when the session ends, so the agent should write what matters into JSON files in the repo. Check every write against a schema, and save by writing a temp file and renaming it, so a crash can never leave you with half a file.

How it flows

  1. 1Agent changes state→
  2. 2Check against schema→
  3. 3Write temp file→
  4. 4Rename over target→
  5. 5Next session loads it

A tiny example

Python
state = load("agent_state.json")
state["status"] = "blocked"
problems = validate(state)
if problems:
    raise ValueError(problems)
tmp = write_temp(state)
os.replace(tmp, "agent_state.json")

Notice the real file is never opened for writing — it is only ever replaced by an already-complete temp file.


What you will learn

  • Why an agent should keep its memory in files inside the repo, not in the chat window.
  • What deserves to be saved and what is just noise.
  • How to write a state file safely so a crash never leaves you with a half-written file.
  • How a schema and a version number stop one bad write from breaking everything.

The problem, simply

Think about a college group project. Rahul tells the group on WhatsApp: "I finished the login page." Two weeks later Sneha joins, scrolls up 400 messages, gives up, and rebuilds the login page from scratch.

Now imagine the team had a shared sheet instead. One row per task, one column for status. Anybody who joins knows in ten seconds where things stand.

Agents have the same problem. An agent works for two hours, the session ends, and everything it learned dies with it. Next morning a fresh session says "let me check the files", reads stale notes, and redoes finished work. Sometimes worse: it rewrites a file that was already done, because nobody told it so.

See, the fix is not a bigger chat window. The fix is to stop trusting chat at all. Chat is a feed that scrolls away. The repo is permanent. So the agent should write its state into JSON files that sit in the repo, get committed to git, and show up in code review like any other file. That is what we call repo memory.

The idea

Chat is transient, the repo is the record

Basically there are two kinds of information an agent produces.

Some of it is durable. "Task T-014 is in progress." "I already touched payments.py." "I assumed the column is called user_id." "I am blocked, the API key is missing."

Some of it is throwaway. The chat transcript. Every intermediate thought. Which model version answered. Whether the user sounded annoyed.

Here is the simple test to decide. Ask: would this be useful three months from now, when a build runs on a server at 2 AM and nobody is watching? If yes, it goes in the repo. If no, it is just logs, and logs can be thrown away.

Tip

Tip: When you cannot decide, imagine a new teammate opening only the state file, with no chat access. If they would be stuck without that piece of information, save it.

Schema first, then the writer

A schema is just a written-down rule about what the file is allowed to contain. Which keys must exist. What types they are. Which values are legal for status. What a task id must look like.

Why bother? Because without a rule, every agent invents its own field names. One writes blockers, the next writes risks, a third writes issues. Six months later nothing can read anything.

With a schema, a bad write becomes a refused write. The agent tries to save garbage, the manager says no, and the file on disk stays clean.

  1. 1Agent wants to save→
  2. 2Check against schema→
  3. 3Valid?→
  4. 4Write to temp file→
  5. 5Rename over real file

Write atomically, or lose everything

Now here is the trick that people miss. Suppose Priya's agent is halfway through writing agent_state.json when the laptop battery dies. Half the JSON is on disk. Next morning the file will not even parse. She has not just lost the new state, she has lost the old state too.

A half-written state file is worse than no state file at all.

So you never write directly to the real file. You write to a temporary file in the same folder, force it to disk, then rename the temp file over the real one. Rename is atomic — the operating system guarantees it either fully happens or does not happen. There is no in-between moment. Readers always see either the full old file or the full new file.

This is not theory. A real open-source agent project shipped exactly this bug: state written straight to the file, errors swallowed silently. Sessions kept resuming against corrupt state with no warning.

Versions and migrations

Put an integer schema_version in every state file. It is the contract number.

When you change the shape of your state — say you rename blockers to risks — you bump the version and ship a small migration script that converts old files to the new shape.

IMPRemember: if the manager sees a version it does not understand, it must refuse to load. Never silently guess and upgrade. Silent upgrades corrupt data quietly, and quiet corruption is the kind you find three weeks too late.

A worked example

Suppose Ananya's agent is doing a placement-portal refactor.

Turn one: it starts task T-101, touches resume.py, saves state with status in_progress.

Turn two: the upload API needs a key it does not have. It adds a blocker and saves again.

Now the session crashes. Ananya reopens tomorrow. The new agent loads the state file, sees T-101 in progress, sees resume.py already touched, sees the blocker. It does not redo the file — it asks Ananya for the key and continues.

Three more patterns worth knowing

Keep big things outside the state file. If your agent generates a 40 MB CSV, do not paste it into the state JSON. Save the CSV as its own file and store only the path in state. Then your state file stays tiny and cheap to read and write, hundreds of times per run.

Use idempotency keys for actions you cannot repeat. Reading a file twice is harmless. Sending an email twice is not. So before the agent runs a risky tool, it writes that call's id into a pending-calls file. If it crashes and restarts, it checks that file first — if the id is already there, it skips the call instead of sending the mail again.

Event log for audit, snapshot for resume. Append one line to an events file on every change, and every so often write a full snapshot. To resume, read the snapshot then replay the events that came after it. Costs more disk, but lets you replay exactly what the agent decided and when. Databases like Postgres use the same shape internally for their write-ahead log.

  1. 1Load snapshot→
  2. 2Replay newer events→
  3. 3Current state→
  4. 4Agent acts→
  5. 5Append event

Build it

Python
"""Repo memory: a tiny schema-checked, atomically-written state manager."""
import json, os, re, tempfile

SCHEMA = {
    "required": ["schema_version", "task_id", "status", "touched", "blockers"],
    "types": {"schema_version": int, "task_id": str, "status": str,
              "touched": list, "blockers": list},
    "enum": {"status": ["todo", "in_progress", "blocked", "done"]},
    "pattern": {"task_id": r"^T-\d{3,}$"},
}
VERSION = 1

def validate(state):
    """Return a list of problems. Empty list means the state is safe to save."""
    bad = []
    for key in SCHEMA["required"]:
        if key not in state:
            bad.append(f"missing key: {key}")
    for key, want in SCHEMA["types"].items():
        if key in state and not isinstance(state[key], want):
            bad.append(f"{key} should be {want.__name__}")
    for key, allowed in SCHEMA["enum"].items():
        if key in state and state[key] not in allowed:
            bad.append(f"{key}={state[key]!r} is not one of {allowed}")
    for key, rx in SCHEMA["pattern"].items():
        if key in state and not re.match(rx, str(state[key])):
            bad.append(f"{key}={state[key]!r} does not match {rx}")
    return bad

def atomic_write(path, state):
    """Write to a temp file in the same folder, flush to disk, then rename."""
    folder = os.path.dirname(os.path.abspath(path)) or "."
    fd, tmp = tempfile.mkstemp(dir=folder)
    with os.fdopen(fd, "w") as f:
        json.dump(state, f, indent=2)
        f.flush()
        os.fsync(f.fileno())      # really on the disk, not just in a buffer
    os.replace(tmp, path)          # atomic: old file or new file, never half

def save(path, state):
    problems = validate(state)
    if problems:
        raise ValueError("refused to write: " + "; ".join(problems))
    atomic_write(path, state)

def load(path):
    with open(path) as f:
        state = json.load(f)
    if state.get("schema_version") != VERSION:
        raise ValueError("unknown schema_version, run a migration first")
    return state

if __name__ == "__main__":
    path = "agent_state.json"
    s = {"schema_version": 1, "task_id": "T-101", "status": "in_progress",
         "touched": ["resume.py"], "blockers": []}
    save(path, s)
    print("turn 1 saved:", load(path))

    s = load(path)                       # next turn reads from disk, not memory
    s["blockers"].append("upload API key missing")
    s["status"] = "blocked"
    save(path, s)
    print("turn 2 saved:", load(path))

    try:
        save(path, {**s, "status": "almost_done"})   # not in the enum
    except ValueError as e:
        print("rejected:", e)
    print("file on disk is still clean:", load(path)["status"])
    os.remove(path)

Look at three things in the output. First, turn two reads state back from the file, not from a variable — that is the whole point. Second, the bad status is rejected before it touches disk. Third, after the rejection the file still holds the last good state, unharmed.

Where you will see this

  • Claude Code and Cursor keep project notes and task state in files in your repo so a new session picks up where the last one stopped.
  • Agent frameworks ship "checkpointers" that save graph state to SQLite or Postgres — same idea, bigger storage.
  • Long-running assistant products keep structured memory blocks per user instead of replaying the whole chat every time.
  • Customer-support bots store ticket state (stage, refund amount, agent notes) in a real table, and use the chat only for the current message.
  • Any CI pipeline that resumes a failed job from the last successful step is doing checkpointing with a different name.

Common mistakes

  • Writing straight to the real file. One crash mid-write and the JSON will not parse, so you lose the new state and the old state together. Always temp-write then rename.
  • Swallowing write errors. A try/except: pass around the save means the agent happily continues while nothing is being saved. You find out days later.
  • Dumping the whole chat transcript into state. The file balloons, every save gets slower, and the useful three lines are buried. Save decisions, not conversation.
  • Changing the shape without a version bump. Old files and new code silently disagree. Bump schema_version and ship a migration.
  • Storing big artifacts inline. A generated CSV inside the state JSON makes every checkpoint expensive. Store the path, keep the file outside.

If they ask in an interview

Q: Why not just keep agent memory in the chat context?

A: Chat context is volatile and bounded — it disappears when the session ends and it gets truncated when it grows. Durable decisions belong in versioned files in the repo, so the next session, a teammate, and a CI run all read the same source of truth.

Q: What is an atomic write and why does an agent need one?

A: You write to a temporary file in the same directory, flush it to disk, then rename it over the target. Rename is atomic at the OS level, so a crash leaves you with either the complete old file or the complete new one, never a corrupt half-written state file.

Q: How do you handle a schema change in agent state?

A: Keep an integer version field in every state file. When the shape changes, bump the version and ship a migration script that converts old files. If the loader sees a version it does not know, it refuses to load rather than guessing.

Try these

  1. Add a last_human_touch timestamp to the state and make save refuse any agent write within five seconds of a human edit.
  2. Write a migration that turns version 1 state into version 2 by renaming blockers to risks, and make load run it automatically for old files.
  3. Extend the validator so a task can be either a build task or a review task, each with its own required keys.
  4. Replace the JSON file with a SQLite table while keeping save and load exactly the same from the outside.

Words, simply

WordMeaning in simple words
Repo memoryAgent state kept in real files in your repo, not in the chat
SchemaThe written rule for what a file is allowed to contain
Atomic writeWrite to a temp file, then rename, so a crash cannot corrupt the real file
MigrationA small script that converts old-shaped state into the new shape
SnapshotA full copy of the state saved at one moment
Event logA file where every change is appended as one line
Idempotency keyAn id you record before a risky action so a retry does not repeat it
System of recordThe one file everyone agrees is the truth

Quick recap

  • Chat disappears, the repo stays — so write durable agent state into versioned files in the repo.
  • Validate against a schema before writing, and write via temp file plus rename so a crash never corrupts state.
  • Version your state, migrate on change, and keep big artifacts outside the state file.

Check what you learned

1 / 7. How do you decide whether some piece of information should be saved in repo memory?
1/7
PreviousExecutable ConstraintsNextInitialization Scripts

On this page