A practical guide to maintaining system state for AI-assisted development
We’d spent three hours debugging a feature that worked perfectly. The code was correct. The logic was sound. The tests passed locally.
The bug was in the assumptions the AI Agent was making – based on our documentation.
Documentation lag is a predictable side effect of productive development. At one point in the project, the database schema consisted of 36 tables, correctly implemented and documented. As the system evolved, additional tables were added and deployed without issue, but the original schema documentation was never updated to reflect the new total.
Human developers working on the system naturally adjusted to this change. An AI assistant, introduced later and relying on that documentation for context, did not. The consequence was not faulty AI reasoning, but misplaced confidence: it produced code based on assumptions that were once valid, but no longer reflected the live system, leading to avoidable rework and confusion.
This is the hidden cost of AI-assisted development that rarely gets discussed: AI coding assistants have no memory between sessions.
Every conversation starts fresh. Every context window eventually gets summarised or truncated. If you don’t maintain accurate system state, every AI suggestion is built on sand—and you won’t know it until you’ve wasted hours debugging code that was technically correct but founded on wrong assumptions.
The solution isn’t more documentation. Documentation begins drifting from reality the moment it’s written unless something actively resists that drift. The solution is verified system state—a practice of maintaining a single source of truth that captures what actually exists, updated at session boundaries, with explicit timestamps showing when each element was last confirmed against reality.
We call ours SYSTEM-STATE.md. The practice takes five minutes at the start of each session. It has saved us countless hours of phantom debugging. And it’s the single most important practice we’ve adopted for making AI-assisted development actually work.
Here’s what we learned, why it matters, and how to implement it yourself.
Why AI Assistants Need External Memory
Large language models are stateless by design. Each API call is independent. While chat interfaces create the illusion of continuous conversation, that continuity exists only because previous messages are resent with each request—and that context window has hard limits.
For short tasks, this doesn’t matter. For ongoing projects spanning weeks or months, it creates a fundamental problem:
- Context summaries lose granular details. When conversations get long, they’re summarised. “We discussed database schema” replaces the specific column names you actually need.
- Session boundaries reset everything. Close the browser, start a new chat, and you’re beginning from zero—even if you pick up the “same” project.
- The AI doesn’t know what happened between sessions. Deployments, migrations, hotfixes, refactoring—all invisible unless you explicitly tell it.
- Confident incorrectness becomes the default. AI assistants don’t say “I don’t know what columns exist.” They assume the documentation you provided is accurate and write code accordingly.
This creates a specific failure mode: the AI writes code that would be correct if your documentation were accurate—but your documentation isn’t—so you debug for hours before realising the code was never the problem.
The Documentation Drift Problem
Every software project experiences documentation drift—the gradual divergence between what documents say and what actually exists. AI-assisted development amplifies this problem dramatically.
In traditional development, a developer’s mental model updates continuously. They know the migration ran because they ran it. They remember adding that column last week. Their understanding evolves alongside the code.
AI assistants have no continuous knowledge. They know only what appears in the current context window. If that context includes stale documentation, they will treat it as truth.
Specific examples from our project:
| What Documentation Said | What Actually Existed | Impact |
|---|---|---|
| 34 database tables | 46 tables | AI unaware of 12 tables worth of functionality |
| User model had 8 fields | User model had 14 fields | AI wrote migrations for columns that already existed |
| Test verification “planned” | Full backend API deployed | AI suggested building what was already built |
| Collaboration features “in progress” | 2 complete, 2 backend-only, 1 partial | Wasted effort on completed work |
The pattern was consistent: we’d ask the AI to help with a feature, it would reference our specification documents, and it would confidently write code for a version of the system that hadn’t existed for months.
The Defensive Fallback Anti-Pattern
One particularly insidious symptom of this problem is defensive fallbacks that persist long after they’re needed.
During uncertain deployments, developers write defensive code:
// Defensive: check if column exists before using
const status = user.availabilityStatus || 'available';
const capacity = user.workloadCapacity || 10;
This is reasonable when you’re genuinely unsure whether a migration has run. The problem is that these fallbacks often persist indefinitely. Six months later, the migration has long since deployed, but the defensive code remains.
When an AI assistant encounters this pattern, it learns the wrong lesson. It assumes the defensiveness is intentional and replicates it in new code. Worse, it may infer that the columns don’t reliably exist—and avoid using them entirely.
In effect, defensive fallbacks become accidental training data for the AI, teaching it uncertainty where none exists.
We found defensive fallbacks in our codebase for columns that had been deployed for months. The AI was seeing these and writing similarly defensive code for new features, increasing complexity and obscuring whether the underlying schema was actually correct.
The SYSTEM-STATE.md Practice
SYSTEM-STATE.md is not documentation in the traditional sense; it is a timestamped record of verified reality.
It captures what exists now—not what was planned, not what specifications describe, but what can be confirmed against the running system.
Core principles:
- Verified, not assumed. Every element is confirmed against the actual system.
- Timestamped. Each verification has a date. Staleness is visible.
- Machine-readable. Tables and lists, not prose. AI assistants parse structured data more reliably.
- Discrepancy-aware. Known gaps between documentation and reality are explicitly tracked.
- Session-boundary updates. Updated at the end of sessions, not during work.
What we track:
# SYSTEM-STATE.md
## Quick Reference
| Element | Value | Last Verified |
|---------|-------|---------------|
| Application Version | 4.11.0 | 2026-01-16 |
| Database Tables | 46 | 2026-01-16 |
| API Route Files | 14 | 2026-01-15 |
| Frontend Views | 32 | 2026-01-15 |
## Schema Verification
| Table/Column | Expected | Actual | Status |
|--------------|----------|--------|--------|
| Total tables | 46 | 46 | ✓ Verified |
| Users.availabilityStatus | EXISTS | EXISTS | ✓ Verified |
| Users.workloadCapacity | EXISTS | EXISTS | ✓ Verified |
| BugComments.parentId | EXISTS | MISSING | ⚠️ Migration needed |
| Bugs.verificationTestIds | EXISTS | EXISTS | ✓ Verified |
## Feature Completeness
| Feature | Backend | Frontend | End-to-End | Notes |
|---------|---------|----------|------------|-------|
| Team Workload View | ✓ | ✓ | ✓ Working | |
| QA Dashboard | ✓ | ✓ | ✓ Working | Fixed filter bug 01-15 |
| @Mention System | ✓ | ✗ | ✗ Blocked | API ready, no UI |
| Test Verification | ✓ | ✗ | ✗ Blocked | API ready, no UI |
| Developer Details | ✓ | ? | ? Untested | Component exists, routing unclear |
## Known Discrepancies
| Issue | Source | Reality | Discovered | Status |
|-------|--------|---------|------------|--------|
| FEATURES.md table count | 34 | 46 | 2026-01-14 | Open |
| workloadService.js fallbacks | Defensive code | Columns exist | 2026-01-15 | Open |
| Collaboration spec completeness | "5 features done" | 2 complete | 2026-01-16 | Open |
## Verification Commands
# Export current schema
railway run pg_dump --schema-only > backups/schema-$(date +%Y%m%d).sql
# Count tables
grep -c "CREATE TABLE" backups/schema-*.sql
# Check specific column exists
grep "availabilityStatus" backups/schema-*.sql
# List route files
find server/routes -name "*.js" | wc -l
The Session Boundary Model
When you update system state matters as much as what you track. We’ve settled on a session boundary model—verification at the start, updates at the end, with a clear separation from productive work.
The Session Boundary Model
When you update system state matters as much as what you track. We’ve settled on a session boundary model—verification at the start, updates at the end, with a clear separation from productive work.
Session Start (2-5 minutes):
┌─────────────────────────────────────────────────────┐
│ 1. Read SYSTEM-STATE.md │
│ - Note the "Last Verified" dates │
│ - Check for stale elements (>7 days) │
│ - Review known discrepancies │
│ │
│ 2. Quick health check (if needed) │
│ - git log --oneline -10 (what changed?) │
│ - Schema spot-check if suspicious │
│ - Verify critical columns exist │
│ │
│ 3. Note any new discrepancies found │
│ - Don't fix yet—just note │
│ - Fixing during verification creates drift │
└─────────────────────────────────────────────────────┘
Session End (3-5 minutes):
┌─────────────────────────────────────────────────────┐
│ 4. Update SYSTEM-STATE.md if any of: │
│ - Schema changed (migrations run) │
│ - New discrepancies discovered │
│ - Features deployed or completed │
│ - Version bumped │
│ │
│ 5. Update "Last Verified" timestamps │
│ - Only for elements you actually checked │
│ - Don't update timestamps for assumed state │
└─────────────────────────────────────────────────────┘
Why this timing matters:
- Not during work: Updating mid-session interrupts flow and may capture incomplete state. You might be mid-migration, mid-refactor, mid-deploy. The system state is temporarily invalid.
- Start verification is quick: You’re not doing exhaustive verification—you’re confirming the foundation is stable enough to build on.
- End updates capture completed state: By the end of a session, changes are committed, migrations are run, deployments are done. The system is in a stable state worth recording.
Data Gathering Methods
For each element we track, we maintain both manual and automated verification paths. We deliberately prefer manual verification supported by automation—fully automated reports can create false confidence when they fail silently.
Database Schema:
# Export full schema (manual, ~30 seconds)
railway run pg_dump --schema-only > backups/schema-$(date +%Y%m%d).sql
# Count tables
grep -c "CREATE TABLE" backups/schema-*.sql
# Check specific column
grep -E "availabilityStatus|workloadCapacity" backups/schema-*.sql
# Compare to previous export
diff backups/schema-20260115.sql backups/schema-20260116.sql
API Routes:
# Count route files
find server/routes -name "*.js" | wc -l
# List all endpoints (rough count)
grep -r "router\.\(get\|post\|put\|delete\)" server/routes/ | wc -l
# Check specific endpoint exists
grep "suggested-tests" server/routes/*.js
Frontend Views:
# Check router configuration
grep -c "path:" client/src/router/index.js
# List view files
ls client/src/views/*.vue | wc -l
# Check specific view exists
ls client/src/views/DeveloperDetails.vue
Version:
# From package.json
grep '"version"' package.json
# From git tags
git describe --tags --abbrev=0
Anti-Patterns We Learned to Avoid
1. Updating mid-session
We tried updating SYSTEM-STATE.md whenever we noticed something. This was counterproductive:
- Interrupted flow during productive work
- Captured incomplete state (mid-migration, half-deployed)
- Created anxiety about “keeping it perfect”
Now we note discrepancies in a scratch file and consolidate at session end.
2. Trusting after long gaps
After a week away from the project, we’d skim SYSTEM-STATE.md and assume it was current. Invariably, something had changed—a hotfix, an automated deployment, a colleague’s commit.
Now we treat gaps >7 days as requiring fresh verification of critical elements.
3. Over-automating
We experimented with CI jobs that auto-updated SYSTEM-STATE.md on every deployment. The problem: automation breaks silently. We’d see a recent timestamp and assume accuracy, but the CI job had been failing for a week.
Now we use automation for data gathering (schema exports) but require manual verification and updates.
4. Tracking too much
Early versions of our SYSTEM-STATE.md tried to track every table, every column, every endpoint. This made updates onerous and verifications exhausting.
Now we track: aggregate counts (table count, route count), critical specific elements (columns features depend on), and known discrepancies. The 80/20 rule applies.
5. Confusing specification with state
Specification documents describe what we intend to build. SYSTEM-STATE.md describes what actually exists. Early on, we’d copy from specs to state, assuming intent meant completion. This defeated the entire purpose.
Now the rule is absolute: nothing goes in SYSTEM-STATE.md unless verified against the running system.
Integration with Project Documentation
SYSTEM-STATE.md does not replace specifications or changelogs. It anchors them.
┌─────────────────────────────────────────────────────┐
│ SPECIFICATIONS (What we intend to build) │
│ - Feature specs, architecture docs │
│ - May be ahead of reality (planned features) │
│ - May be behind reality (undocumented changes) │
├─────────────────────────────────────────────────────┤
│ SYSTEM-STATE.md (What actually exists) │ ← You are here
│ - Verified against real system │
│ - Timestamped for staleness detection │
│ - Discrepancies explicitly tracked │
├─────────────────────────────────────────────────────┤
│ CHANGELOGS (What changed when) │
│ - Historical record of changes │
│ - May miss informal changes │
│ - Doesn't confirm current state │
└─────────────────────────────────────────────────────┘
When starting an AI-assisted coding session, we now provide context in this order:
- SYSTEM-STATE.md — what actually exists
- Relevant specifications — what we intend to build
- Recent changelog entries — what changed recently
This grounds the AI in reality before discussing intent.
Measuring Documentation Health
If you adopt this practice, you can derive a simple documentation health indicator:
Documentation Accuracy Score =
(Verified Elements / Total Tracked Elements) × 100
- (Open Discrepancies × 5)
Where:
Verified = Last checked within 7 days, matches reality
Open Discrepancies = Known gaps not yet resolved
This score is a signal, not a target. It highlights drift and trends over time rather than serving as a performance metric.
The Bottom Line
AI coding assistants are powerful tools constrained by amnesia. They reason brilliantly over accurate data and confidently produce garbage from stale documentation. The quality of their output depends entirely on the quality of context you provide.
Most projects supply specifications—documents describing intent. But intent diverges from reality the moment code ships. The AI doesn’t know what you’ve actually built; it only knows what you’ve told it.
SYSTEM-STATE.md closes that gap by recording verified reality. Not plans. Not assumptions. What exists, confirmed against the system, with timestamps that make uncertainty visible.
The practice is simple:
- Session start: Read the state file, perform a quick health check if needed, note discrepancies
- Session end: Update the state file if anything changed
Five minutes of ritual. Hours of debugging avoided.
What You Should Do Next
Today: Create your first SYSTEM-STATE.md. Start small:
- Application version
- Database table count (verified)
- One known discrepancy
This week: Establish the session-boundary ritual. Read at the start. Update at the end.
This month: Track documentation accuracy trends. Identify where drift accumulates and why.
The Principle Behind the Practice
As quality pioneer W. Edwards Deming famously said: “In God we trust; all others bring data.”
Documentation that hasn’t been verified against reality isn’t data—it’s assumption dressed as fact. And assumptions are the foundation of every phantom bug you’ll spend hours not finding.
The AI assistant can’t verify your system. That responsibility remains human. Give the AI accurate data, and it becomes a powerful collaborator. Give it stale context, and you’re pair programming with someone confidently navigating a map of a different city.
The five-minute investment at session boundaries is the highest-leverage practice we’ve found for making AI-assisted development actually work.
Your codebase has drifted from your documentation. You know it has. The only question is whether you’ll discover that drift through systematic verification—or through hours of debugging code that was never the problem.
Choose verification.
Appendix: Complete SYSTEM-STATE.md Template
# SYSTEM-STATE.md
> Verified system state for AI-assisted development sessions.
> This document describes what ACTUALLY EXISTS, not what is planned.
> All elements verified against the running system.
## Quick Reference
| Element | Value | Last Verified |
|---------|-------|---------------|
| Application Version | x.y.z | YYYY-MM-DD |
| Database Tables | NN | YYYY-MM-DD |
| API Route Files | NN | YYYY-MM-DD |
| Frontend Views | NN | YYYY-MM-DD |
| Documentation Accuracy | NN% | YYYY-MM-DD |
## Schema Verification
| Table/Column | Expected | Actual | Verified |
|--------------|----------|--------|----------|
| Total tables | NN | NN | YYYY-MM-DD |
| [Critical column 1] | EXISTS | EXISTS/MISSING | YYYY-MM-DD |
| [Critical column 2] | EXISTS | EXISTS/MISSING | YYYY-MM-DD |
## Feature Completeness
| Feature | Backend | Frontend | End-to-End | Notes |
|---------|---------|----------|------------|-------|
| [Feature 1] | ✓/✗ | ✓/✗ | ✓/✗/? | |
| [Feature 2] | ✓/✗ | ✓/✗ | ✓/✗/? | |
## Known Discrepancies
| Issue | Source Says | Reality Is | Discovered | Status |
|-------|-------------|------------|------------|--------|
| [Discrepancy 1] | X | Y | YYYY-MM-DD | Open/Resolved |
## Verification Commands
```bash
# Schema export
[your-platform] pg_dump --schema-only > backups/schema-$(date +%Y%m%d).sql
# Table count
grep -c "CREATE TABLE" backups/schema-*.sql
# Column check
grep "[column_name]" backups/schema-*.sql
Session Log
| Date | Verified | Updated | Notes |
|---|---|---|---|
| YYYY-MM-DD | Quick check | Schema count | [What changed] |
Last full verification: YYYY-MM-DD Next recommended: YYYY-MM-DD (7 days)

