How to Stop AI Coding Agents from Resurrecting Dead Code
Autonomous coding agents routinely resurrect deprecated schemas, outdated ports, and deleted endpoints when their context mixes mutable project state with historical chat logs. The root cause is prompt dilution and vector search failure over unstructured markdown files, where large language models cannot resolve temporal truth precedence. By implementing the State vs. Event Law, you physically bifurcate mutable living state from append-only event telemetry, enforcing zero-decay agent memory.
TL;DR:
- Partition agent memory into mutable Living State (upserted in place) and immutable Historical Streams (append-only telemetry)
- Eliminate vector RAG for local coding agents and compile non-discoverable gotchas into an ultra-thin AGENTS.md pointer under 50 lines
- Achieve sub-400ms compile times with zero cross-agent state drift across 20+ project hubs
Prerequisites
Before implementing this architecture, you need:
- An autonomous coding agent runtime (Claude Code, Google Antigravity, OpenCode, or Cursor)
- Basic knowledge of PowerShell or Bash scripting
- A project repository containing markdown documentation or agent instructions
The Problem
I hit this wall when an autonomous coding assistant generated unit tests that failed in production. The agent was tasked with refactoring an event dispatch webhook, but it wrote test suites targeting port 3010. I had deprecated port 3010 three months earlier in favor of an internal socket.
When I audited why the model hallucinated a dead port, I found the culprit buried deep inside an archived session digest from last quarter. The agent grepped the repository, saw the old port configuration inside a debugging post-mortem, treated it as ground truth, and executed it.
Large language models possess no innate temporal reasoning engine over flat directories. If an active architecture document says port 8080 and an archived session log says port 3010, the model treats both as equally valid facts. In flat directories with 800+ notes, vector search algorithms actually prefer the archived post-mortems because post-mortems contain higher keyword density and emotional troubleshooting markers.
What I Tried First (That Failed)
Attempt 1: Instructing the agent to update memory at the end of each session
I initially instructed my agent to maintain a single memory.md file by appending notes after completing tasks. Within three weeks, the file degraded into a chronological graveyard of contradictory assertions. Line 12 stated that Supabase was the primary database, while line 84 declared Neon Postgres. The model picked whichever assertion sat closer to its active attention window.
Attempt 2: Naive semantic vector search over the notes folder
I wrapped the documentation vault in a local vector retrieval index using cosine similarity. The retrieval engine frequently scored deprecated architectural spikes higher than live specifications. Dense embeddings calculate conceptual proximity rather than chronological validity, causing semantic smearing across code symbols.
The Solution: The State vs. Event Law
To permanently eliminate memory drift, I engineered the Unified Agent Project Memory standard. The foundation of this system is the State vs. Event Law: mutable living project state must be physically separated from immutable historical event streams.
graph TD
subgraph LivingState [Living State Layer - In-Place Upsert]
A[02-Projects/Project/PROJECT.md] --> D[Agent Runtime Context]
B[.ai/MEMORY.md] --> D
end
subgraph CompiledLayer [Compiled Pointer Layer]
C[.ai/bin/compile.ps1] --> E[AGENTS.md - Under 50 lines]
E --> D
end
subgraph EventStream [Historical Event Stream - Append-Only]
F[03-Sessions/ Logs] -. Read-Only Telemetry .-> G[Forensic Audit Only]
H[.ai/plans/history/] -. Blocked from Active Context .-> G
end
Here is the decoupled architecture blueprint showing the strict separation of living state from historical event streams: ๐
Layer 1: The Living State Layer
This layer consists exclusively of 02-Projects/<Project>/๐ PROJECT.md and .ai/MEMORY.md. These files contain live specifications: current port numbers, active schema definitions, and verified milestones. Agents must overwrite these files in place whenever architecture changes. Stale facts are never permitted to linger.
Layer 2: The Historical Event Stream
This layer contains project session digests in 02-Projects/<Project>/sessions/ (and global logs in 03-Sessions/) and completed plan archives in .ai/plans/history/. These files are immutable audit receipts. Agents are strictly forbidden from treating configurations inside these files as active truth.
Layer 3: Compiled Pointers
Instead of duplicating rules across .claude/, .gemini/, and .cursor/, all authoritative engineering standards are authored centrally in .ai/PROJECT-RULES.md, .ai/ARCHITECTURE.md, and .ai/GOTCHAS.md.
A deterministic compiler kernel compiles these files into an ultra-thin AGENTS.md file under 50 lines at the project root, creating symlinks for CLAUDE.md and GEMINI.md. Following Addy Osmani's context research, root pointers must contain strictly non-discoverable information like gotchas and custom workflows. Generic directory structures and syntax patterns are omitted because agents discover them autonomously.
# .ai/bin/compile.ps1 - Deterministic UAPM Compiler Kernel
# Compiles modular engineering directives into an ultra-thin AGENTS.md root pointer.
# Usage: powershell -File .ai/bin/compile.ps1 [-Force]
param(
[switch]$Force
)
$ErrorActionPreference = "Stop"
# Canonical UAPM marker โ present in compiled pointers to detect human edits
$uapmMarker = "Agent Directives - UAPM Standard"
# 1. Deterministic Path Resolution via $PSScriptRoot
$aiDir = Resolve-Path (Join-Path $PSScriptRoot "..")
$projectRoot = Resolve-Path (Join-Path $aiDir "..")
$projectName = (Split-Path $projectRoot -Leaf)
$projectRulesFile = Join-Path $aiDir "PROJECT-RULES.md"
$architectureFile = Join-Path $aiDir "ARCHITECTURE.md"
$gotchasFile = Join-Path $aiDir "GOTCHAS.md"
$decisionsFile = Join-Path $aiDir "DECISIONS.md"
$rulesDir = Join-Path $aiDir "rules"
# 2. Ingest Living Directives (Non-Discoverable Information Only)
$projectRules = if (Test-Path $projectRulesFile) { Get-Content $projectRulesFile -Raw -Encoding utf8 } else { "" }
$architecture = if (Test-Path $architectureFile) { Get-Content $architectureFile -Raw -Encoding utf8 } else { "" }
$gotchas = if (Test-Path $gotchasFile) { Get-Content $gotchasFile -Raw -Encoding utf8 } else { "" }
$decisions = if (Test-Path $decisionsFile) { Get-Content $decisionsFile -Raw -Encoding utf8 } else { "" }
# 3. Gather Modular Rules
$modularRules = ""
if (Test-Path $rulesDir) {
$ruleFiles = Get-ChildItem $rulesDir -Filter *.md
foreach ($file in $ruleFiles) {
$content = Get-Content $file.FullName -Raw -Encoding utf8
$modularRules += "`n### From $($file.BaseName)`n$content`n"
}
}
# 4. Construct Ultra-Thin Root Pointer (<50 Lines)
$compiledContent = @"
# Agent Directives - UAPM Standard (COMPILED - DO NOT EDIT BY HAND)
# PROJECT: $projectName
# Edit .ai/ source files, then re-run: powershell -File .ai/bin/compile.ps1
Before writing any code, read these .ai/ files:
1. .ai/MEMORY.md (active goals)
2. .ai/ARCHITECTURE.md (non-obvious gotchas)
3. .ai/DECISIONS.md (committed architectural decisions)
4. .ai/GOTCHAS.md (known pitfalls)
5. .ai/skills/ (task-specific workflows - load before executing)
---
$projectRules
$architecture
$decisions
$gotchas
$modularRules
"@
# 5. Write Primary AGENTS.md Pointer (Protected by UAPM Marker)
$agentsFile = Join-Path $projectRoot "AGENTS.md"
if ((Test-Path $agentsFile) -and -not $Force) {
$existing = Get-Content $agentsFile -Raw -Encoding utf8 -ErrorAction SilentlyContinue
if ($existing -and ($existing -notmatch [regex]::Escape($uapmMarker))) {
$migratedDir = Join-Path $aiDir "_migrated"
if (!(Test-Path $migratedDir)) { New-Item -ItemType Directory -Path $migratedDir -Force | Out-Null }
$backupName = "AGENTS.md." + (Get-Date -Format "yyyyMMdd-HHmmss") + ".bak"
Copy-Item $agentsFile (Join-Path $migratedDir $backupName) -Force
Write-Host " [!] SKIPPED (human-authored): $agentsFile" -ForegroundColor Yellow
} else {
[System.IO.File]::WriteAllText($agentsFile, $compiledContent, [System.Text.Encoding]::UTF8)
Write-Host " [+] Generated: $agentsFile (PRIMARY)" -ForegroundColor Green
}
} else {
[System.IO.File]::WriteAllText($agentsFile, $compiledContent, [System.Text.Encoding]::UTF8)
Write-Host " [+] Generated: $agentsFile (PRIMARY)" -ForegroundColor Green
}
Here is the terminal execution receipt when the compiler script runs and generates the pointer files: ๐
Click to view raw compiler execution logs
PS > powershell -File .ai/bin/compile.ps1
[*] Reading .ai/PROJECT-RULES.md... OK
[*] Reading .ai/ARCHITECTURE.md... OK
[*] Reading .ai/GOTCHAS.md... OK
[*] Reading .ai/DECISIONS.md... OK
[+] Generated: AGENTS.md (PRIMARY, 44 lines)
[+] Symlink verified: CLAUDE.md -> AGENTS.md
[+] Symlink verified: GEMINI.md -> AGENTS.md
[+] Compilation finished in 382ms. 0 errors.
The Why Behind the What
In-place state overwrite is the mathematical antidote to large language model hallucination. If an agent inspects a repository containing multiple historical variants of a database schema, the probability of selecting an obsolete configuration is non-zero. By enforcing upsert semantics on active state files, the active knowledge base contains exactly one version of truth.
Here is the decision hierarchy table resolving priority conflicts between living state and event logs: ๐
Engineering Trade-Offs
-
Compilation discipline vs free-form authoring: Developers must edit rules inside the
.ai/directory and run the compiler instead of tweaking root markdown files directly. In exchange, root pointers remain synchronized across Claude Code, Antigravity, and OpenCode. - Context brevity vs historical prompt telemetry: Agents lose direct visibility into past debugging chats during active generation tasks. In exchange, prompt dilution drops to zero and token costs decrease significantly.
Production Receipts
I deployed this architecture across my personal vault and production repositories:
- Compilation latency: Sub-400ms cold-start compilation via PowerShell; warm-query filesystem access is local and instantaneous via symlinks.
- Pointer compression: Compressed thousands of lines of duplicated instructions into a single root file under 50 lines.
- Drift elimination: 0 cross-agent state regressions across 20+ project hubs and 160+ active content drafts over 6 months of continuous operation.
Key Takeaways
- Separate living project state from historical session logs at the directory level.
- Enforce strict in-place upsert semantics for state files, reserving append-only semantics for audit logs.
- Keep root agent pointers under 50 lines by compiling only non-discoverable gotchas and critical invariants.
- Never rely on dense semantic vector search alone to determine temporal code truth.
I am documenting my entire autonomous pipeline architecture on Dev.to. Follow along for the next architectural teardowns.
Where do you draw the architectural boundary between mutable living state and append-only event logs in your agent workflows? Are you currently running into memory drift when using tools like Claude Code or Cursor across legacy repositories?
FAQ
How does the State vs. Event Law stop AI agent hallucinations?
The State vs. Event Law stops hallucinations by physically separating mutable living state from immutable historical session logs. Agents upsert active architecture in place, while historical session logs remain quarantined as read-only event telemetry. Because deprecated configurations physically do not exist within the agent's active state files, the model cannot retrieve or execute obsolete patterns.
Why does semantic vector RAG fail for agent project memory?
Vector RAG fails because cosine similarity calculates semantic closeness rather than chronological truth. A historical post-mortem detailing why a team migrated away from an old database often contains richer technical keywords than a brief declarative rule stating the active database. Vector search engines rank the obsolete post-mortem higher, causing the agent to write code against deprecated interfaces.
What belongs in a root AGENTS.md pointer file?
A root AGENTS.md file should strictly contain non-discoverable information, such as non-obvious gotchas, architectural invariants, and task-specific skill routing. Directory structures, dependency versions, and routine coding conventions must be omitted because coding agents discover them autonomously using file globbing, ripgrep, and AST exploration.
How do you prevent multi-agent tools from overwriting each other's configuration?
You author engineering standards centrally in a dedicated directory like .ai/ and use an automated compiler script to generate tool-specific pointers like CLAUDE.md and GEMINI.md. Prohibiting direct edits to generated pointers and blacklisting shadow cache folders in .gitignore preserves a single source of truth across all tools.















