Spec Shadowing: When AI Agents Quietly Rewrite the Contracts Everyone Else Depends On
An AI agent finishes a four-hour autonomous coding session, commits cleanly, passes CI, and gets merged before lunch. Three weeks later, a payments team in a different timezone opens an incident because their error envelope parser is silently dropping a field that used to always be present.
That gap between "merged cleanly" and "broke something real" is spec shadowing. It is the most structurally underappreciated failure mode in AI-assisted development today.
What Spec Shadowing Actually Is
Spec shadowing happens when an AI coding agent, working within a scoped task boundary, makes locally correct edits to a shared interface, schema, or contract that downstream consumers were never consulted about and have no visibility into. The agent is not malfunctioning. It is solving its assigned problem. The shadow is a side effect of that solution landing on a surface that belongs to more than one team.
This is categorically different from spec rot. Spec rot is the slow degradation of a contract through neglect, outdated documentation, and missed updates. Nobody decided to let the UserProfile response schema drift from the OpenAPI doc; it just happened over eighteen months of small commits. Spec shadowing is active. The agent made a purposeful edit to errorCode being optional rather than required because that was the path of least resistance for the retry logic it was implementing. Intentional. Locally defensible. Cross-boundary invisible.
The distinction matters because spec rot has known remedies: contract testing, schema registries, Pact, consumer-driven contract suites. Those tools assume a human drift problem. Spec shadowing is a velocity problem layered on top of an intent problem.
Why Agents Are Structurally Prone to This
Large language models used as coding agents, whether Claude Sonnet, GPT-4o, or a fine-tuned variant running inside Cursor or Copilot Workspace, operate on a context window. That window is bounded. When an agent is handed a task like "refactor the retry handler in payment-service to use exponential backoff," its context is loaded with the files relevant to that task. The shared ErrorEnvelope type lives in a shared types package. The agent sees it, uses it, and because the retry logic genuinely requires that retryAfterMs sometimes be absent, it makes retryAfterMs optional.
The agent has no representation of the three downstream consumers that pattern-match on retryAfterMs always being present. Those consumers are not in the context window. Their contracts are not in the task spec. From the agent's perspective, the change is correct, minimal, and scoped.
This is not a hallucination problem. Hallucination is about fabricating facts. Spec shadowing is about making a correct local decision with incomplete global context. The agent's reasoning is sound. The system boundary is wrong.
The Code Review Gap
The conventional response to this concern is: "That's what code review is for." I want to push back on that directly, because it is almost always wrong in the contexts where spec shadowing causes real damage.
Code review is effective when the reviewer has context about downstream consumers. On small, stable teams, that context often exists. On organizations that have scaled to the point of introducing AI agents to manage throughput, that context is routinely absent. The reviewer approving the payment-service PR does not carry a mental model of what the analytics-ingestion service expects from the error envelope. That is precisely why teams at that scale are using agents: to move faster than any individual's mental model can span.
There is also a timing problem. By the time a diff reaches review, the agent's reasoning is gone. The PR description says "make retryAfterMs optional to support the backoff case" and the reviewer nods. What is not visible is that this field was load-bearing for three other services. Nobody in the review thread is the agent. Nobody can reconstruct the original intent and compare it against the cross-boundary consequences.
Code review is a human attention problem. Spec shadowing is a cross-boundary intent verification problem. They require different tools.
Cross-Boundary Intent Verification at Diff Time
The right intervention point is the diff, before merge, with automated cross-boundary awareness. Not review after the diff is posted. A verification gate that runs at the moment the diff is produced.
Here is what that gate needs to do:
- Parse the diff for mutations to any surface tagged as a shared contract: exported types, OpenAPI schemas, Protobuf definitions, Avro schemas in a Kafka schema registry, JSON Schema files, config DSLs.
- Resolve which downstream consumers have declared a dependency on those surfaces, either through an explicit registry or through static analysis of import graphs.
- Reconstruct the agent's stated intent for the task and verify that the contract mutation is either explicitly in scope or explicitly acknowledged as a side effect.
- Surface a blocking artifact if the mutation is out of scope and unacknowledged. Not a warning. A block.
Step three is the hard one. Most CI tooling can do steps one and two with enough investment. Step three requires that the agent's task intent be captured in a structured, machine-readable form at the start of the session, not inferred after the fact from a PR description that the agent also wrote.
This is the architecture ThunderLang is built around: you declare intent before the agent writes a line, the diff is verified against that intent, and the result is a durable proof artifact that either clears the change or flags the shadow. The verification runs at diff time. Not at deploy time and not at incident time.
What a Spec Shadow Looks Like in Practice
Imagine a gRPC service that owns a shared PaymentResult message used by six consumers across three teams. An agent is tasked with adding support for partial authorization, a real and well-scoped product requirement. The agent correctly adds a partialAmount field and, because partial results should not trigger the same downstream retry logic as hard failures, marks the status enum as having a new PARTIAL variant and makes errorDetails conditionally absent when status is PARTIAL.
Every part of that is locally correct. The agent solved the assigned problem.
But two of the six consumers were doing exhaustive enum matching and have no case for PARTIAL. A third was assuming errorDetails always populated on non-success responses for their reconciliation logic. None of this is in the agent's context. None of it shows up as a compile error. It does not break CI. It surfaces as a reconciliation discrepancy twelve days later when the accounting team runs their monthly close.
That is a spec shadow. Active, purposeful, locally correct, and cross-boundary blind.
The Intent Capture Problem
The weak version of intent verification is reading the PR description. The strong version is capturing structured intent at task dispatch.
When you dispatch a task to an agent, you need a machine-readable spec that says: which files are in scope, which shared surfaces are explicitly authorized for modification, and what the expected observable behavior change is. That spec becomes the contract the diff is verified against. If the diff touches a shared surface not on the authorized list, it fails the intent gate.
This feels bureaucratic until you run it once and catch a shadow that would have cost you two weeks of incident triage. After that it feels like a seatbelt.
The implementation burden is lower than it sounds. Most teams already maintain some form of task spec in Jira, Linear, or a design doc. The lift is structuring that spec in a format a verification tool can consume: a YAML intent declaration, a ThunderLang verify-diff config, or a Protobuf annotation marking a message as shared-contract-gated. The agent's output does not change. The gate around it does.
You can read more about how structured intent declarations wire into verification gates in the ThunderLang getting-started docs, which walk through the full diff-time verification lifecycle.
The Strongest Counter-Argument
The most serious objection I hear is: "If you make agents ask permission for every contract touch, you kill the autonomy that makes them useful."
Partially right. Mostly wrong.
It is right that a gate requiring human approval for every shared-surface edit would nullify the throughput gains. If an agent has to pause and wait for a contract committee to bless each change, you have not built an AI-powered development workflow. You have built a slower Jira ticket.
It is wrong because the gate does not have to require human approval for every touch. It requires explicit scope declaration at task dispatch. If the agent's task spec says "authorized to modify PaymentResult including the status enum," then a PARTIAL variant addition clears automatically. The gate only blocks the unacknowledged shadow: the errorDetails optionality change that was never in scope.
The friction is at task definition, not at every commit. That is where the friction belongs. If you do not know at task dispatch whether you intend to modify a shared contract, that is a planning failure, not a tooling failure.
Autonomy without boundaries is not autonomy. It is uncontrolled mutation. The goal is bounded autonomy with explicit scope, which is what every well-run engineering organization expects from human engineers too.
Governance Is Not the Right Frame
One framing mistake will send you in the wrong direction: treating spec shadowing as an AI governance problem.
AI code governance, as the industry currently discusses it, is focused on compliance, license scanning, bias detection, and output auditing. Those are real concerns. They are not the concern here.
Spec shadowing is an architectural integrity problem. The right owner is the platform engineering team or the staff engineer responsible for cross-service contract health, not a compliance officer or an AI ethics committee. The right tools are diff-time verification gates, schema registries with consumer dependency graphs, and structured intent capture at task dispatch. The right metric is cross-boundary contract drift rate per agent-hour, not AI usage policy adherence.
If your organization is routing the spec shadowing problem to a governance committee, it will be solved slowly, badly, and too late.
Gate your first AI change
ThunderLang lets you declare what a change must satisfy before the agent starts, then verifies the resulting diff against that intent with a verify-diff step that produces durable proof artifacts your incident retrospectives can actually reference. If you are shipping with any degree of AI agent autonomy and you do not have a cross-boundary intent gate in place, start there: ThunderLang getting-started.
The agent is not the risk. The invisible contract it touched on the way to solving your problem is.