Repo Memory, Init Scripts and Handoffs
Frame the task, keep state in schema-checked files, probe before start, and end each session with a handoff packet.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Comparisons
- Defaults and numbers
- Troubleshooting
- Questions and tips
One workbench session#
Commands#
| Command | What it does |
|---|---|
python3 code/ | Runs the lesson demo. Each one writes its files next to the script |
python3 code/main.py in lesson 34 | Writes workdir/agent_state.json and workdir/task_board.json |
python3 code/ | Lesson 35: ignore prereqs.lock and run every probe |
python3 code/ | Lesson 35: pin current HEAD as last-known-good |
git diff --name-only <baseline> HEAD | The command behind the lkg_diff probe |
python3 code/main.py in lesson 40 | Writes handoff.md and handoff.json |
python3 -m unittest discover code/ | Lesson 43: tests the task frame validator |
python3 -m unittest tests.test_accounts | Acceptance command of the example frame |
Schema-first state#
"required": ["schema_version", "active_task_id", "touched_files", "next_action"]"active_task_id": {"type": ["string", "null"], "pattern": r"^(T-\d{3,}|)$"}"status": {"enum": ["todo", "in_progress", "done", "blocked"]}mgr = StateManager(state_path, STATE_SCHEMA)state = mgr.load()read, then validatemgr.commit(state)validate, then atomic_writefd, tmp_name = tempfile.mkstemp(prefix=path.name + ".", dir=path.parent)os.fsync(fh.fileno())flush to disk firstos.replace(tmp_name, path)atomic renameInit probes#
probe_runtime- fails below
REQUIRED_PYTHON. probe_dependenciesimportlib.util.find_specfor each dep.probe_test_commandshutil.whichon the test command.probe_env- fails on a missing env var.
probe_state_freshness- warn when the state is old or absent.
probe_lkg_diff- fail above the file diff budget.
- Probe
- returns
(name, status, detail). - Report
init_report.json. Exit 1 on any fail.
Seven handoff fields#
summary- one paragraph of what was done.
changed_files- the diff at a glance.
commands_run- what was actually executed.
failed_attempts- what was tried and why it failed.
open_risks- what can bite next session, with severity.
next_action- the first concrete step. The load-bearing field.
verdict_pointeroutputs/verification/<task_id>.jsonand the review.feedback_tail- last
TAIL_Krecords plus every nonzero exit.
Prompt-only versus workbench#
| Outcome | Prompt-only | Workbench-guided |
|---|---|---|
tests_actually_run | False | True |
acceptance_met | False | True |
files_outside_scope | 2 | 0 |
handoff_quality | missing | full packet |
reviewer_total | 3 | 9 |
| Steps | Read, edit, claim done | 8 steps, init to handoff |
Defaults and numbers#
| Setting | Default | Note |
|---|---|---|
REQUIRED_PYTHON | (3, 10) | Runtime probe floor |
STATE_FRESHNESS_SECONDS | 24 60 60 | Older state gives a warn |
LOCK_TTL_SECONDS | 24 60 60 | Fresh lock skips the probes |
PROBE_BUDGET_SECONDS | 3.0 | Slower pass becomes warn |
LKG_FILE_DIFF_BUDGET | 50 | More changed files is a fail |
schema_version | 1 | Only allowed enum value |
TAIL_K | 5 | Feedback records kept in the handoff |
| Review total risk | 7 | Below it the handoff adds a warn |
Task frame#
frame = TaskFrame( goal="Prevent duplicate email addresses during signup", allowed_paths=["app/accounts.py", "tests/test_accounts.py"], forbidden_paths=["migrations/**", "deploy/**"], acceptance=["python3 -m unittest tests.test_accounts"], facts=[RepositoryFact("Duplicate errors use status 409", "tests/test_accounts.py:44")],receipt unknowns=["Whether email comparison is case-insensitive"])issues = validate(frame)empty list means READYrender(frame)Markdown, Status: READY or BLOCKEDClassify each unknown#
- Discoverable
- the repository or running system can answer it. Continue.
- Decidable
- the task contract gives the agent authority. Continue.
- Human
- changes product behavior, cost, risk or public compatibility. Pause.
- Deferred
- outside this slice. Put it in non-goals.
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
| Next session redoes finished work | State lived in chat, not the repo | Keep state in schema-checked JSON in the repo |
| Session resumes on corrupt state | write_text() with silenced exceptions | Use atomic_write: mkstemp, fsync, os.replace |
does not match on T-bogus | Task id breaks the pattern | Use ids like T-001 |
init failed; refuse to launch agent | A probe has status fail | Fix the probe. Do not start the agent |
lkg_diff fail, files changed above budget | More than 50 files since the last-known-good | A human ratifies the baseline with --write-lkg |
Probe pass turns to warn with slow: | Probe above the 3 second budget | Move it out of init or cache its result |
no next_action recorded; needs human | State has no next_action | Set next_action before the handoff |
Frame Status: BLOCKED | validate found issues | Fix each line under Blocking issues |
Questions#
- What goes in repo memory?
- Data useful three months from now in a CI rerun.
- Two init runs in a row?
- The second is a no-op except a fresh timestamp.
- Handoff without
next_action? - It is a status report, not a handoff.
handoff.mdandhandoff.jsondiffer?- The JSON wins.
- When to stop reading the repo?
- When each decision has evidence, is delegated, or is an unknown.
- When to wrap up?
- At 50-75% of the context budget, not at 95%.
Tips#
- Keep large artifacts out of
agent_state.json. Store only the path. - Log each tool call ID to
pending_calls.jsonlbefore execution. - Append each mutation to
state.events.jsonl. Snapshot tostate.json. - Keep one active handoff per branch and topic.
- Give each fact a receipt: a file path and line.
- Init has no network and no LLM calls.