Editorial illustration showing Intent, State, Context, and Contract drifting away from Ground Truth.

TL;DR

I used to think the main risk in AI-driven development was bad code. After a couple of concentrated weeks building Wakeelu, a corpus-grounded Q&A system for government-contracting documents, I have a different view: the expensive failure mode is drift.

The pattern showed up everywhere. Specs drifted from implementations. Plans drifted from reality. Branches drifted from main. Persistent memory drifted from the codebase. Tool docstrings drifted from the ORM. Hot-patched config drifted from canonical config.

Every session started fast, because AI is very good at turning a premise into code, docs, tests, and migration plans. When the premise is stale, speed just gets you to the wrong place faster.

The AI dev tax is confident work against stale premises.

The session that made it obvious

The day started with a clear task: ship the smallest agent-loop fix from the Plan of Record. The plan said the Q&A agent loop was not started. Before designing the fix, I read the code.

The code disagreed with the plan. The agent loop was not a stub. It was an implemented async streaming loop: mixed-model dispatch, tool calls, citation checking, thread persistence, server-sent events, and a working chat UI. The plan had not kept up with implementation.

That was drift number one.

So I stopped writing a design spike and reviewed the actual loop. A two-model review surfaced a demo-breaking failure: the agent could spiral for ten turns trying to satisfy citation rules after a malformed SQL/tool path. I ran a live smoke against the deployed API. Two of four prompts failed.

Then the smoke surfaced something the code review could not see. The SQL tool's prompt told the model that the projects table had columns like agency, status, value_usd, role, and period_start. The live table had five columns. Those fields had moved into a claim-based schema earlier in the project.

The model was doing exactly what we asked. It was writing SQL against a phantom schema.

Metric Value
Columns described in the tool prompt 17
Columns in the live table 5
Smoke prompts that failed 2 of 4
Reconciliation PRs that followed 4

I then drafted a new architecture decision to reconcile the tool and the schema. Review caught a second-layer mistake: my draft had anchored on the implementation as shipped, not the older accepted spec. The spec had a field_key column the implementation had not yet shipped. Another rewrite. Then another cleanup. Then another drift list.

By the end of the session, the output was several reconciliation PRs and a documentation rewrite. Almost none of it added new product surface. It was necessary work we only had to do because our own artifacts had aged in different directions.

Why AI makes this sharper

None of this is unique to AI. Humans have always let docs go stale. We have always forgotten hot patches. We have always had architecture decisions that shipped partially and then became folklore.

AI makes the problem sharper in four ways.

First, AI increases the volume of artifacts. A single session can produce code, tests, migrations, ADRs, runbooks, prompt changes, observability panels, and memory notes. That is useful. It also means there are more surfaces to reconcile.

Second, AI is unusually good at continuing from a stale premise. If a planning document says a module does not exist, the model can produce a beautiful implementation plan for the missing module. If a tool docstring lists deleted columns, the model will write plausible SQL using deleted columns. This is the key distinction: fluency is not grounding.

Third, session boundaries hide partial truth. Each new session starts from whatever context is loaded: summaries, memories, plans, recently touched files. If those sources are not checked against the code, the session inherits yesterday's misunderstanding and gives it fresh confidence.

Fourth, drift can introduce bugs through correct implementation. This is the part that makes it difficult to debug. The generated code can be clean, idiomatic, tested, and faithful to the requested behavior. The problem is that the requested behavior was derived from a stale understanding of the system.

That means ordinary review signals get weaker. The diff looks reasonable. The tests may pass if they share the same stale fixture or mocked contract. The model can explain the implementation coherently because it really did implement the premise. The bug only appears when the new code touches live reality: the actual schema, the actual config, the actual prompt path, the actual data distribution.

In the SQL-tool case, the failure sat upstream of the generated code. The model produced plausible SQL for the schema its tool contract described. Fixing that SQL without fixing the premise would only create a second correct-looking failure, because the model had followed a contract we allowed to rot.

A drift taxonomy

The compact model is four axes:

Grounding fails in one of four places: intent, state, context, or contract.

Axis What drifts Failure shape
Intent drift Specs and plans What we said we were building stops matching what actually exists.
State drift Branches, PRs, deployments, and config Work exists somewhere, but not in the canonical product state.
Context drift Session memory and decision continuity The system loses the decisions needed to continue correctly.
Contract drift Tool prompts, schemas, and grounding The executable interface given to the AI no longer matches reality.

The examples below unpack those axes. I still keep a longer checklist internally, but the public model only needs one question: which axis lost contact with ground truth?

1. Intent drift

Intent drift is when the map of the system stops matching the territory. The plan, spec, or architecture decision may have been true when written. Then implementation moves, priorities change, partial work lands, and the document keeps speaking in the present tense.

Spec vs implementation

An architecture decision is accepted, but only part of it ships. Later, someone reads the accepted decision and writes code against the missing half.

In our case, the claim schema spec included a polymorphic field taxonomy: values, agencies, contract numbers, countries, dates. The implementation had shipped the money-value path first. That was reasonable as an incremental delivery choice. The mistake was not making the partial state explicit enough for the next session.

Rule: "Accepted" and "shipped" need to be separate states. A decision can be architecturally accepted and operationally incomplete.

Plan vs reality

A Plan of Record is good for deciding what to do next. It is weak at proving what is already true.

Our plan said the agent loop was not started. The code said otherwise. The plan had been right when written, then stale once implementation moved faster than the plan update loop.

Rule: a forward-looking plan needs a backward-looking companion. "What is next?" and "what is true?" are different documents.

2. State drift

State drift is when the work exists, but in the wrong place. The feature is on a branch. The fix is on a server. The config is in production but not in git. The model remembers the work as real because it is real, but it is not canonical.

Branch vs main

A pull request can contain real work without becoming part of the product. That sounds obvious until summaries, memories, and follow-on plans compress "we built X" into "X shipped."

We saw this in several forms. The sharpest case was a corpus-panel PR that had been remembered as shipped while still open. The panel was visible on a deployed branch, but it had not landed on main. A later status snapshot still listed the same corpus-panel polish as an open PR. A multimodal ADR had lived for days on a feature branch and had to be rescued because it held unique unmerged intellectual capital. Another experiment branch remained unmerged after producing useful results. None of those cases were fake work. They were real artifacts in the wrong place.

That makes this failure mode especially slippery. Future sessions can plan on top of work that exists, but only on a dangling branch. The model is not inventing the work. It is losing the merge state. The resulting bug is social and technical at the same time: the team thinks the system has a capability because a PR, plan, or journal entry says it was built, while main and production say otherwise.

This is another place where AI compression hurts. "Built," "merged," "deployed," and "documented in canonical state" collapse into a single story unless you force them apart.

Rule: an open PR is inventory, not reality. Shipped means merged to the canonical branch, deployed when relevant, and reflected in the current-state artifact.

Hot patch vs canonical config

When a service is down, you fix it where it is. That is normal. A local config edit can save the day. But every hot patch creates a reconciliation task: promote it, revert it, or record it.

For a few hours, our canonical repository and the deployed machine described different model routes. The deployed route was correct. The repository was stale. That is a perfectly understandable incident response move, and also a future bug if left unresolved.

Rule: every hot patch is a receipt. Pay it before the session ends.

3. Context drift

Context drift is when the system loses the decisions that make the next decision safe. The model can remember the nouns while forgetting which constraints were load-bearing.

Context vs decision continuity

The first alarm bell was not a failed test. It was the feeling that I was re-anchoring the model more often.

After context compaction or a cleared session, decisions that had been made would come back as open questions. Sometimes the model had saved state, but the saved state was too compressed, too stale, or pointed at the wrong layer of truth. The result was subtle: the model was not forgetting everything. It was forgetting which facts were load-bearing.

That made the human the continuity layer. I had to keep saying: we already moved to claim-based extraction; that ADR is partial, not complete; that route was hot-patched then promoted; that plan was superseded. Each reminder was small, but the pattern was alarming because it meant the system could not fully trust its own saved state.

This is also where human experience and domain expertise matter. The human is not just restoring facts; the human is preserving taste, constraints, and product intent. One repeated correction in this project was: do not fix the corpus. Build the general mechanism, and externalize corpus-specific decisions into configuration, convention packs, or review queues.

That distinction matters. A model looking at the latest failure case will naturally propose a spot fix: add a rule for this agency string, this folder shape, this contract-number pattern, this PDF artifact. Sometimes that is useful. Often it is overfitting. The domain expert has to recognize when the local fix violates the system's larger contract.

This is different from ordinary memory staleness. It is decision-continuity loss. The dangerous version is not "the model remembers nothing"; it is "the model remembers enough to sound oriented, but not enough to preserve the decisions that constrain the next move."

Rule: durable decisions need durable homes. Do not rely on compressed chat memory to preserve architectural constraints, especially constraints about generalization versus corpus-specific behavior.

Memory vs ground truth

Persistent memory helps carry context across AI sessions. It also decays. A memory saying "this foreign key is empty" is useful until a migration backfills it. After that, it becomes a confidence amplifier for a stale belief.

Rule: memory is a pointer, not proof. Anything important needs re-verification against source, schema, logs, or live state.

4. Contract drift

Contract drift is where AI makes stale documentation operational. A schema description, tool prompt, API contract, or confident explanation becomes part of the model's working environment. If that contract is wrong, the implementation can be locally correct and globally broken.

Tool prompt vs ORM

This one hurts because the LLM lives inside the prompt contract. If the SQL tool says a column exists, the model will use it. If the API tool says a field is safe, the model will send it.

In our smoke test, the tool prompt was not merely documentation. It was executable context. The stale prompt produced runtime behavior.

Prompt contract:
  projects.agency
  projects.value_usd
  projects.period_start

Live schema:
  projects.id
  projects.root_path
  projects.name
  projects.created_at
  projects.updated_at

Rule: tool prompts that describe schemas should be checked like code. If they name tables and columns, diff them against the ORM or the database.

AI confidence vs AI grounding

This is the most subtle drift. The model can sound equally confident when grounded and when ungrounded. It can write a clean design against a stale premise. It can explain the wrong schema with the same tone it uses for the right one.

The fix is not to "trust AI less" in some vague emotional sense. The fix is to force grounding steps before generation: read the source, inspect the schema, run the smoke, compare the prompt contract to the implementation.

Rule: confidence is not a quality signal. Grounding is.

Does this generalize across harnesses?

I think yes, but not because every harness fails in the same way. The exact symptoms change. The premise generalizes because every AI development harness is mediated by artifacts: prompts, files, summaries, schemas, tool descriptions, tests, branches, evals, deployment state, and memory.

If those artifacts diverge from ground truth, the model inherits the divergence.

Different harnesses expose different parts of the taxonomy:

Harness shape Drift that tends to dominate Typical symptom
Chat-only coding assistant Intent drift and context drift The model proposes work from a stale plan or forgets why a decision was made.
Agent with tools Contract drift The model uses a tool correctly according to an obsolete prompt, schema, or API description.
PR/code-review harness State drift A branch, PR, or review summary is remembered as shipped before it reaches main or production.
Eval/benchmark harness Intent drift and contract drift A benchmark keeps testing the old contract, so passing results hide product drift.
Long-running autonomous harness Context drift and state drift The system accumulates partial state and needs periodic grounding against source, logs, and deployment.

Models change the expression, not the category. A weaker model may fail noisily: malformed tool calls, brittle retries, or shallow plans. A stronger model may fail more dangerously: it can produce cleaner code, better explanations, and more persuasive plans from the same stale premise. Reasoning models can even make the stale premise feel more justified because they elaborate it coherently.

That is why I do not think this is a "which model did you use?" story. Model quality matters, but it mostly changes the blast radius and detectability. The invariant is the harness boundary: what does the model know, what can it verify, what contracts does it act through, and which artifact is treated as canonical?

The general rule is: the more agency a harness has, the more its inputs need freshness checks. Better models reduce some implementation errors. They do not remove the need to keep those four axes grounded.

What we changed

We landed four governance fixes. None is glamorous. All of them are cheap compared with rediscovering drift mid-session.

Fix What it does Drift addressed
Implementation status on every ADR Separates "accepted" from "shipped fully", "shipped partially", and "not started". Spec vs implementation
ADR Health panel One living table of accepted decisions, current implementation state, known drift, and owner. Plan vs reality
Merge-state discipline Keeps "built," "merged," and "deployed" separate; limits dangling PR inventory. Branch vs main
drift_check.py Parses ORM columns and enum values, scans tool docstrings and ADRs, and fails on schema references that no longer exist. Tool prompt vs ORM
Module pre-read map For each code area, names the source files and ADRs that must be read before changing it. AI confidence vs grounding

The important distinction: this is not process for process's sake. These checks exist because AI collaborators can move quickly enough that stale context becomes a production risk, not merely a documentation annoyance.

What I would generalize

If you are building with AI as a primary development surface, I would not start by asking "how do I make the model write better code?" I would ask "how do I keep the model's premises fresh?"

Mapped back to the taxonomy, the operating habits are simpler:

Axis Freshness habit Practical check
Intent Separate decided from shipped. Read source before planning; require every ADR to say whether it is fully shipped, partially shipped, or only accepted.
State Treat canonical state as evidence. Verify shipped claims against main and production; reconcile hot patches and dangling PRs before they become folklore.
Context Treat memory as a cache. Re-anchor important claims against source, and externalize corpus-specific knowledge into configuration, convention packs, eval fixtures, or review queues.
Contract Check executable context like code. Quote the live schema, diff tool prompts against the ORM, and run smoke tests against the real surface.

The part that feels new

The old version of software drift was mostly about documents lying to humans. The AI version is more operational: stale documents become prompts, stale prompts become behavior, stale behavior becomes failed demos or wrong answers.

That changes the risk profile. A stale SQL docstring is not just misleading prose. It is part of the runtime context for an agent that writes SQL. A stale architecture decision is not just historical clutter. It is source material for the next implementation plan. A stale memory is not just an old note. It is a seed for the next session's confidence.

This is why I now think of AI-driven development as requiring a new kind of hygiene: faster grounding, not heavier process.

The lesson I wish I had named earlier: AI makes it cheap to create coherent artifacts. That makes it more important to know which artifacts are true.

The upside is that the fixes are not exotic. Read the source. Check the schema. Distinguish decided from shipped. Make stale contracts fail fast. Treat confidence as a smell until it is grounded.

That is the discipline I want underneath the rest of this blog. The later posts will get back to GPUs, inference engines, prompt caching, and agent reliability. This one is the frame: measurements matter, but so does knowing whether the thing you are measuring is still the thing you think you built.