Skip to main content
ThunderLang
← All articles
ai-engineering

Spec Rot: Why Your Intent Document Expires Before Your Code Does

6 min read · 2026-07-27 · Allen Codewell

By the time most teams notice their AI-generated codebase has drifted from its original spec, the drift is already six weeks old and tangled through four layers of abstraction. That is the actual failure mode in agentic development right now. Not hallucinated logic. Not bad test coverage. The quiet, compounding divergence between what you said you wanted and what the agent slowly built instead.

What Spec Rot Actually Looks Like

Here is a concrete scenario. You write a requirements document describing a payment processing module: idempotency on retry, no PII in logs, webhook delivery guaranteed within 30 seconds. An AI agent generates the initial implementation and it matches the spec well enough. You ship it.

Three weeks later, the agent patches a rate-limit bug. Then it refactors the retry logic to reduce latency. Then it adds a debug logging block during a production incident. Each change is locally reasonable. No test fails. The PR diffs look clean. But now the logs contain a hashed customer identifier that the security team considers PII, the retry handler no longer guarantees idempotency on network timeout, and webhook delivery under load is averaging 47 seconds.

Nothing broke. The spec rotted.

Spec rot is the gradual, undetected divergence of implemented behavior from declared intent. It is not a bug in the traditional sense because no invariant that the code itself enforces has been violated. It is a contract violation between the humans who defined requirements and the system that implemented them, and it surfaces only when someone eventually compares the two.

In human-authored codebases, spec rot is mostly a documentation problem: engineers drift from specs because no one is re-reading them. In agentic codebases, the problem is categorically different. The agent is making hundreds of micro-decisions per sprint, each plausible in isolation, and none of them are being checked against the original contract. The spec becomes an archived artifact instead of a live constraint.

Why the Standard Mitigations Do Not Work Here

The instinct is to write better specs, enforce more thorough code review, or configure stricter prompts. None of these address the root problem.

Better specs help at generation time, but they do not create a feedback loop at change time. An agent writing a new feature reads the spec. An agent patching an existing module often does not, because the task description is scoped to the patch, not the original intent. The spec is not in context.

Code review catches what reviewers notice. Reviewers are good at catching logic errors, performance regressions, and style violations. They are genuinely bad at holding an entire requirements document in their head while reading a 200-line diff. Research on code review effectiveness consistently shows that reviewers miss semantic-level issues at high rates, especially when the diff looks structurally clean. A logging statement that adds PII does not look alarming in isolation.

Stricter prompts are the most naïve fix. Prompts are instructions at generation time. They do not verify output. Telling an agent "never log PII" and then shipping without machine verification is the same as telling a junior engineer the same thing and never running a linter.

Teams are treating their specs as input artifacts rather than persistent contracts. Once code ships, the spec gets filed in Confluence or a GitHub wiki and the system moves on. In agentic development, that is a structural mistake.

Machine-Checkable Intent Is the Only Scalable Answer

The insight that changes the approach: intent drift detection requires that your spec be written in a form the machine can diff against real code. A prose requirements document cannot be programmatically verified. A machine-checkable spec can.

This is not a theoretical position. Consider what a checkable intent statement looks like versus a prose requirement:

# Prose (uncheckable)
# "The retry handler must be idempotent on network timeout."

# Machine-checkable intent (ThunderLang spec format)
rule: payment_retry_idempotency
  scope: src/payments/retry_handler.ts
  assert:
    - no_side_effects_on: ["network_timeout", "connection_reset"]
    - idempotency_key_checked_before: write_operations
  on_violation: block_merge
  severity: critical

The prose statement is a wish. The structured rule is a gate. When the agent modifies retry_handler.ts, the intent spec is diffed against the new implementation automatically. If the change violates the idempotency assertion, it surfaces as a first-class signal before merge, not in a post-mortem.

This is the model ThunderLang is built around: declare what a change must satisfy, run a verify-diff on every agent-authored commit, and produce durable proof artifacts that the spec was checked. The specs live in version control alongside the code, not in a separate documentation system that nobody opens.

The operational consequence is significant. When specs are machine-checkable and co-located with code, they get updated when behavior legitimately changes. A team decides to relax the webhook SLA from 30 seconds to 60 seconds because the downstream system changed. They update the spec, they update the code, both changes go through review together. The contract stays current because it has to: the gate will fail if the code and spec diverge in either direction.

Compare that to the current state at most teams: the spec is in Confluence, the code is in GitHub, and the only thing keeping them synchronized is human memory. That is not a process. That is a hope.

Making Spec Enforcement Operational

Adopting machine-checkable intent requires some changes to how you structure work.

First, not every requirement translates cleanly into a checkable rule on day one. Behavioral requirements like "the UI must feel responsive" are not immediately machine-verifiable. Start with the requirements that have the highest compliance cost if violated: security properties, data handling rules, SLA thresholds, and idempotency guarantees. These are the ones where drift has legal, financial, or availability consequences.

Second, intent specs need to be owned. The same way a CODEOWNERS file routes code review, your intent specs need an owner who updates them when requirements legitimately change. Without ownership, specs drift in their own way, becoming stale constraints that the team learns to bypass.

Third, wire the check into your CI pipeline as a hard gate, not a warning. A warning that never blocks is noise that gets ignored within two weeks. The verify step needs to fail the build. This feels strict until the first time it catches a security property being quietly removed in a refactor, and then it feels like the most valuable line in your pipeline.

The ThunderLang getting started docs walk through setting up the verify-diff step in a standard GitHub Actions workflow, including how to write your first intent rules for an existing codebase without needing to retrofit everything at once.

Fourth, treat the first spec violation caught in CI as a team event worth examining. Walk through what the agent changed, why the rule fired, and whether the rule was right or the change was wrong. This is how teams calibrate their intent specs from theoretical constructs into genuinely useful gates. The first few violations teach you more about your real requirements than writing specs in a vacuum ever will.

The Deeper Shift: From Documentation to Contract

Spec rot is not primarily a tooling problem. It is a mental model problem.

Most engineering teams still think of a spec as a document that describes what was intended before implementation. In agentic development, that framing is obsolete. A spec in an agentic codebase is a contract that must be continuously re-verified as agents iterate, patch, and refactor. Every change an agent makes is a renegotiation of that contract, whether you treat it that way or not. If you are not explicitly checking the contract on every change, you are implicitly accepting whatever the agent decided.

The teams that will run stable agentic systems at scale are the ones who shift from "the spec describes what we meant to build" to "the spec is an always-current constraint on what is allowed to exist in production." That shift requires the spec to be machine-readable, version-controlled, and checked on every commit, not stored in a wiki and read quarterly.

Bugs are visible. Regressions fire tests. Spec rot is invisible until the damage compounds into something you cannot quietly fix.

Gate your first AI change

ThunderLang lets you declare what a change must satisfy, then gates AI-written code against that intent with a verify-diff and durable proof artifacts. Try it here.

If your spec is not checked on every commit, you are not shipping what you specified: you are shipping whatever the agent felt like at the time.