Chapter 20: File-Based State Machine
Learning Objectives
Understand why state must persist in files instead of residing solely in-memory (main Claude's "memory")—this is the watershed for a system evolving from a "toy" to "production-grade."
In-Memory State vs. File-Based State
flowchart LR
subgraph Bad["In-Memory State (Bad)"]
M1["main Claude remembers
'Group 3 tests passed'"]
end
subgraph Good["File State (Good)"]
F1["test-reports/3.md
**Status:** PASS"]
end
M1 -->|/clear / Crash| Lost["State Lost"]
F1 -->|/clear / Crash| Recover["Recover by re-reading files"]
style Bad fill:#ffcdd2
style Good fill:#c8e6c9→ File-Based State = crash-safe + auditable + recoverable.
Complete State Machine
stateDiagram-v2
[*] --> PENDING
PENDING --> DEVELOPING : dispatch developer
DEVELOPING --> TESTING : DEV_DONE
TESTING --> DEVELOPING : FAIL
TESTING --> REVIEWING : PASS
REVIEWING --> DEVELOPING : REJECTED
REVIEWING --> GROUP_DONE : APPROVED
GROUP_DONE --> [*]
DEVELOPING --> ESCALATION : round >= 3
TESTING --> ESCALATION : fail >= 5
REVIEWING --> ESCALATION : reject >= 3
ESCALATION --> STUCK : architect dispatched
ESCALATION --> DEVELOPING : developer-deep PATH B
STUCK --> [*] : Human DecisionState Lookup Table (Core of CLAUDE.md)
Each state corresponds to which file to read and which field to check:
| State | Determination Method |
|---|---|
PENDING |
All tasks for this group are - [ ] and review/N.md does not exist |
DEVELOPING |
Some - [x] but not all |
DEV_DONE |
All tasks in the group are - [x] |
TEST_PASSED |
test-reports/N.md exists + **Status:** PASS |
APPROVED |
review/N.md exists + **Status:** APPROVED |
STUCK |
STUCK.md mentions group N |
GROUP_DONE |
DEV_DONE && TEST_PASSED && APPROVED |
→ main Claude re-reads files every turn to determine the state, avoiding caching.
Schema of State Files
Each state file must follow a fixed, parsable format:
# Review for Group 3
**Status:** APPROVED ← This line is read by the state machine
**Reviewer Round:** 1 ← This counts the round number
**Reviewed Commit:** abc123
## Findings
(Specific content)
## Required Changes
(Only when REJECTED)
Format must be strict—**Status:** APPROVED needs exact spacing, colons, and asterisks; missing any of these will prevent main Claude from parsing it correctly.
Relationships Between the Three Core State Files
flowchart TB
Tasks["tasks.md
(checkbox checked status)"] -.-> ST1["DEV State"]
TR["test-reports/N.md
**Status:** PASS|FAIL"] -.-> ST2["TEST State"]
Rev["review/N.md
**Status:** APPROVED|REJECTED"] -.-> ST3["REVIEW State"]
Stuck["STUCK.md
(Architect's Diagnosis)"] -.-> ST4["STUCK State"]
ST1 --> Combined["Group N Combined State"]
ST2 --> Combined
ST3 --> Combined
ST4 --> Combined
style Combined fill:#c8e6c9Pseudocode for State Determination
The logic running in main Claude's mind:
def get_group_state(N):
if exists(f"STUCK.md") and N in STUCK.md:
return "STUCK"
tasks = parse_tasks_md()
group_tasks = tasks[N]
if all(t.checked for t in group_tasks):
dev_done = True
elif any(t.checked for t in group_tasks):
return "DEVELOPING"
else:
return "PENDING"
if exists(f"test-reports/{N}.md"):
report = read(f"test-reports/{N}.md")
if "**Status:** PASS" in report:
test_passed = True
else:
return "DEVELOPING (rework)"
else:
return "TESTING (pending run)"
if exists(f"review/{N}.md"):
review = read(f"review/{N}.md")
if "**Status:** APPROVED" in review:
return "GROUP_DONE"
else:
return "DEVELOPING (rework)"
else:
return "REVIEWING (pending review)"
→ This is precisely what Step 3 in dev.md accomplishes.
Crash Recovery Demo
You run /dev 1
↓
Group 1 reaches the REVIEWING stage
↓
Your machine suddenly crashes / Claude Code crashes
↓
Restart Claude Code
You: /dev 1
↓
main Claude reads files:
- tasks.md shows 1.1~1.4 all checked
- test-reports/1.md has **Status:** PASS
- review/1.md does not exist
↓
Determination: State = REVIEWING (pending review)
↓
Directly dispatches the reviewer, without redoing previous work
→ Perfect recovery, not a single step lost.
Hidden Benefits of This Design
✅ git diff immediately shows "what state changed this run"
✅ git log provides historical records, showing when each round occurred
✅ State can be read by external scripts (monitoring, reporting)
✅ Zero cost for switching devices (laptop → desktop)
✅ Multi-person collaboration (A runs halfway, B takes over) just by looking at files
Anti-Patterns
✗ main Claude uses chat history to record state
→ Lost after /clear
✗ State files lack a fixed schema
→ Inconsistent formats like "**Status: APPROVED**" and "**Status:** APPROVED"
→ Parsing fails
✗ Using emojis as status markers ("✅ Done")
→ Looks good, but parsing is fragile
✗ State scattered across multiple unrelated files
→ Restoring state requires reading 10 files = exhausting
✗ Writing state and then changing state
→ e.g., reviewer.md modifies tasks.md after writing a review → role transgression
What You Can Do Now
- Map out the state machine for your own project
- Design the schema for each state file
- Explain how crash-safety is implemented
- Understand why it's necessary to "re-read files every turn instead of caching"
Milestone Summary: What You Can Achieve After Chapter 20
flowchart LR
Ch1to10["Ch 1~10
First change
(Knowledge Layer Foundation)"] --> Ch11to13["Ch 11~13
spec/design/tasks
Runnable"]
Ch11to13 --> Ch14to18["Ch 14~18
Multi-agent Design
Including Escalation Chain"]
Ch14to18 --> Ch19to20["Ch 19~20
CLAUDE.md
+ State Machine"]
Ch19to20 --> You["✓ Possess complete
'Governance Layer + Knowledge Layer'
Awaiting integration with Tool Layer"]
style You fill:#c8e6c9Core Checkpoint: By now, you should be able to:
- Write a 4-artifact set (proposal/design/spec/tasks) for your own project
- Design agent files for 4-6 roles
- Write a CLAUDE.md to make main Claude act according to the rules
- Explain how the entire state machine and escalation chain work
If not yet—go back to the corresponding chapters and redo the exercises.