Co-evolving prompts and application code
When the app changes, the prompt drifts — design for that.
A prompt lives inside an application. When the application changes, prompts drift. Here's how to spot it, prevent it, and refactor prompts and code together instead of separately.
The silent drift problem
Scenario: you ship a prompt that extracts {name, email} from a document. Works well. Six months later, a new feature needs {name, email, phone}. A junior engineer updates the schema in code — new DB column, new UI field. They don't update the prompt.
Now the prompt still returns two fields. Phone column is always null. Nobody notices for three weeks because the UI happens to hide null values. Production data is half-complete.
This is prompt-and-code drift. It's pervasive and usually invisible until something depends on the missing piece.
The contract pattern
Treat every prompt as having a contract with the code that calls it:
- Input contract. What variables get interpolated into the prompt? What are their types?
- Output contract. What fields does the output contain? What are their types and allowed values?
Encode this contract somewhere the compiler sees it. TypeScript types, Zod schemas, Pydantic models — whichever fits. When the code changes, the compiler errors at the boundary.
// The contract is explicit:
const TriageOutput = z.object({
category: z.enum(["billing", "technical", "account", "other"]),
priority: z.enum(["low", "medium", "high", "urgent"]),
});
type Triage = z.infer<typeof TriageOutput>;
// Prompt + schema live together:
export const triagePrompt = {
version: "2.0.0",
schema: TriageOutput,
text: `...`,
};
Making prompt refactors visible
Add a lint rule or pre-commit hook: any PR that touches a file matching prompts/*.md also requires a corresponding eval file change or a changelog entry.
The rule prevents silent prompt changes. It's aggressive but the signal-to-noise is worth it.
Refactoring the prompt + code together
When you're making a substantive change that affects both:
- Update the schema / types first. Let the compiler find all the places code is affected.
- Update the prompt to match.
- Update the eval set.
- Run evals.
- Merge as one PR.
Never merge schema-only or prompt-only changes when the underlying behavior is changing. It's the "interface + implementation" rule, applied to prompts.
Deprecation without breaking
When you need to remove a field from a prompt's output:
- Make the field optional in the schema (stop depending on it).
- Deploy.
- Remove from prompt.
- Remove from schema.
The gap between 1 and 3 gives downstream code time to stop depending on the field. Same pattern as a database schema migration.
Signals of drift
Watch for:
- Silent nulls. Fields that are increasingly often empty. Either the prompt isn't producing them or downstream code stopped reading them.
- Prompt complexity growing faster than feature complexity. Usually a sign of bolted-on special cases that should be refactored into the surrounding code.
- "We fixed that bug two months ago, why is it back?" The prompt and code got out of sync.
The team practice
A healthy team:
- Treats prompt changes as code changes.
- Runs prompt evals in CI.
- Doesn't let prompts and schemas drift apart.
- Refactors prompts alongside the code they serve.
An unhealthy team:
- Has "the prompt person" who owns everything AI-shaped.
- Doesn't notice drift until it's an incident.
- Treats prompts as a soft, reversible thing.
Check your understanding
2-question self-check
Optional. Your answers feed your knowledge score on the track certificate.
Q1.A prompt and the code that calls it drift apart when…
Q2.How do you catch schema-prompt drift at review time?