The first prompt is load-bearing: how anchoring quietly hardcodes your app
Give a coding agent a free hand and you get a static, hardcoded app. Not because the model is weak, but because your first words collapsed its option space. A field guide to prompt anchoring, with a live experiment (one sentence of boundaries turned a 586-line rewrite into a 1-line change) and a product-scale example of how it breaks.
Ask a coding agent to "build me a sales dashboard" and watch what happens. It invents fake data, wires it in as literals, freezes a layout, and quietly makes a dozen product decisions you never saw. Now ask it to "connect it to the real database" and you pay for every one of them: either adapters get bolted onto the fake-data skeleton, or half the app gets demolished, because as far as the agent is concerned, the skeleton is the app. We ran exactly this experiment for this post, twice, changing only the first prompt. The bill for the second turn came to 586 changed lines in one session and 1 changed line in the other. The numbers are below.
This is the paradox of the free hand. Giving an agent an open-ended prompt feels like giving it freedom. It is the opposite. You are forcing it to commit to the first plausible interpretation of your words, and every turn after that hardens the commitment. By turn five you are no longer steering the project. You are negotiating with its past decisions.
We call this prompt anchoring, and it is why "even the flagship model with the best coding tool" so often produces a static, hardcoded app. The model fills every gap you leave, and it fills them with the most statistically average thing it has seen, not with what you were thinking.
Why anchoring happens
None of this is a bug, and none of it is fixed by a bigger model. It falls out of four properties of how agents work.
1. LLMs are next-token machines, not requirement gatherers. Faced with ambiguity, a model does not stop and ask. It samples the most typical completion. Your underspecified dashboard becomes the median dashboard of the training data: same layout, same fake numbers, same three charts. That is why freehand output feels generic. It literally is the average.
2. Context is a ratchet. Everything the agent generates goes back into its context and becomes evidence for what the project is. Early choices compound, because the model treats its own past output with roughly the same authority as your instructions. An assumption made on turn one is indistinguishable, three turns later, from a requirement you stated.
3. Agents are biased toward minimal edits. Once files exist, the agent's goal shifts from "design the right thing" to "make the smallest change that satisfies the request." Most of the time that is a feature; it is what stops an agent from rewriting your codebase every turn. But it also means turn-one decisions get grandfathered in forever. The agent will patch a wrong skeleton twenty times before it will question the skeleton.
4. Models fill gaps silently. The agent will not push back and say "you have not told me where the data comes from." It decides for you, without announcing that a decision was made. You never see the fork in the road. You only discover, three turns later, which branch you are on.
Put together: an underspecified prompt is not a blank canvas. It is a lottery ticket for defaults.
The anchors, catalogued
The words that fix a model on a path fall into a few recognizable families. Once you can name them, you start seeing them in every failed session.
Noun anchors
"Dashboard," "admin panel," "chatbot." Each of these nouns drags in an entire median archetype: a stack, a layout, a set of features, and yes, fake data. The noun is doing far more specification than you intended, and all of it is someone else's average.
Example anchors
Show the model one example of what you mean and it treats that example as the complete spec, including every irrelevant detail in it. We covered this in prompting techniques and mistakes: examples are instructions. In agent sessions the effect is stronger, because the example does not just shape one output, it shapes the architecture everything else is built on.
Adjective anchors
"Simple," "quick," "modern," "clean." The model optimizes for looking simple, and the simplest-looking way to make a dashboard appear on screen is to hardcode the data. Adjectives specify vibes; the model converts vibes into whatever shortcut produces them fastest.
Omission anchors
The strongest family, because it is invisible. Anything you do not mention gets the default, and defaults are singular and static: one user, one language, one currency, one country, happy path only. You did not say the app needs to support multiple visa types, so it supports the two you happened to mention.
Self-anchoring
The ratchet from the mechanism section. After the first turn, the agent's most influential prompt is its own previous output. This is why corrections feel like they slide off: your one-line instruction is competing with three thousand lines of code that all quietly assert the old design.
We ran it: one prompt, two very different apps
Rather than describe the dashboard failure from memory, we ran it as an experiment while writing this post. Two fresh agent sessions, same model, same rules: one self-contained HTML file, no packages, no external libraries. The only variable was the first prompt.
Session A got the freehand version, word for word: "Build me a sales dashboard."
Session B got one extra sentence of boundaries: data comes from GET /api/sales, the shape is {date: string, region: string, amount: number}[], regions are an open set that must render without code changes, handle loading, empty, and error states, and put the mock behind the same interface as the real endpoint.
Turn one
Session A produced a 556-line dashboard, and to be clear, it is genuinely impressive work: a seeded random generator producing two years of daily data with weekend dips and yearly seasonality, KPI cards, an interactive chart with hover tooltips, a sortable table. It also invented an entire fictional company:
const products = [
{ name: "Aurora Standing Desk", cat: "Furniture", share: 0.16 },
{ name: "Volt Wireless Keyboard", cat: "Accessories", share: 0.13 },
// ...6 more invented products
];
const channels = [
{ name: "Online store", share: 0.46, color: "#2563eb" },
{ name: "Retail", share: 0.27, color: "#7c3aed" },
// ...
];
const regions = [
{ name: "North America", share: 0.41 },
{ name: "Europe", share: 0.30 },
// ...a closed, hardcoded set
];
const customers = ["Olivia Chen", "Marcus Reid", "Priya Nair", /* ... */];Nothing in "build me a sales dashboard" mentions products, channels, or customers. Every one of those is an omission anchor filled with the median of the training data, down to a conversion-rate KPI that would require traffic data no one has. Roughly half the UI displayed information with no counterpart in any real system.
Session B produced a comparable dashboard (538 lines, KPIs, chart, region breakdown, table) with one structural difference. Everything flows through a single integration point, and regions are discovered from the data at runtime:
const USE_MOCK = true;
async function realFetchSales() {
const res = await fetch("/api/sales");
if (!res.ok) throw new Error("API responded with HTTP " + res.status);
const data = await res.json();
if (!Array.isArray(data)) throw new Error("API returned unexpected payload");
return data;
}
function fetchSales() {
return USE_MOCK ? mockFetchSales() : realFetchSales();
}Turn two
Both sessions then got the same follow-up: the real API is ready, GET /api/sales, connect the dashboard to it.
Session B changed one line: USE_MOCK = true became USE_MOCK = false. No layout, chart, or aggregation code was touched.
Session A changed 586 lines (326 added, 260 removed) against a 556-line file. Effectively a rewrite. And here is the part worth sitting with: the agent handled it well. It did not bolt adapters onto the fake skeleton; it correctly reasoned that mixing real revenue with fabricated channels and customer names would be worse than removing them, and demolished half the product. The channel donut, the products table, the orders feed, the conversion KPI: all deleted, because the data behind them never existed. Whatever the user had already approved, screenshotted, or shown to a stakeholder on turn one was half hallucination.
That is the anchoring tax measured on a toy: one sentence of boundaries in the first prompt was the difference between a 1-line change and a 586-line rewrite by turn two. A real project has more turns, more anchored decisions, and no clean moment where the fake half gets demolished instead of patched around.
Anchoring at product scale: the immigration assistant
The dashboard is a toy. Here is the same failure on a real product, lightly disguised.
We work on an agentic immigration assistant. The task: add Saudi Arabia to the list of supported countries. The developer had not yet researched how Saudi visas work, but the coding agent is strong and the model is a flagship, so the session starts anyway, with the one or two visa types the developer had in mind as examples of what "support" means.
The agent does exactly what example anchors and omission anchors predict. Eligibility checks, document requirements, the conversation flow, all of it gets built around those two visa types, hardcoded as branches. Then reality arrives: family visas, Hajj and Umrah visas, transit visas, premium residency, each with different rules, documents, and dependencies. The app does not degrade gracefully. It gets stuck in loops, because every fix is a patch on a skeleton that assumed two types, and each patch makes the skeleton more rigid.
"Just let it research first" does not fix it
The intuitive rescue is to tell the agent: research Saudi visa types, then fix the app. It fails, and the reason is the ratchet.
Research produces a wall of correct information in context. But the code shaped around two visa types is still sitting there, and the agent's bias is minimal edits to existing structure. So fifteen visa types worth of knowledge gets bolted onto a two-type skeleton as special cases and conditionals, instead of triggering the redesign the research actually implies. Knowledge in context does not automatically become architecture. A model can know every visa type and still maintain code shaped for two.
The real failure is upstream of the prompt
Here is the uncomfortable part: the developer had not done the research either. The specification did not exist anywhere. Not in the prompt, not in the agent's context, not in anyone's head.
Users assume the LLM knows what they are thinking. That is already wrong. In this case it is doubly wrong, because the developer was also assuming the LLM knew things the developer had never decided. The model cannot be anchored to the right thing when the right thing was never established.
This is the core distinction the whole post rests on. Context management and task management are different jobs. Context management is deciding what the model sees. Task management is knowing what the task even is: which decisions are load-bearing, what the domain actually looks like, and in what order choices must be made. Agents are getting very good at the first job. No model, at any size, can do the second one for you.
How to not anchor yourself
The fixes are not "prompt harder." They are about controlling where decisions get made, and by whom.
1. Do the domain research before the agent does. Not because the model cannot search, but because research findings must become decisions before code exists. For the immigration assistant, the missing artifact was a one-page enumeration: every Saudi visa type, what varies between them, what they share. Pasted as the spec, that page produces a schema-driven design where visa types are data. Without it, visa types become branches. The deliverable of research is a data model, not a summary.
2. Anchor on the boundaries, not the pixels. Specify the things that are expensive to change: data sources, interfaces, types, the cardinality of things ("countries and visa types are open sets, new ones will be added without code changes"). Leave the cheap-to-change things open: styling, copy, layout. This is the entire difference between the two sessions in the experiment above: one sentence about the API contract was worth 585 lines by turn two.
3. Make it plan before it builds. Force the decision list into the open where you can veto it before it becomes code. A plan is cheap to correct. A skeleton is not.
4. Ask what it assumed. "List the assumptions you made that I did not specify." It costs one message and it surfaces the silent gap-filling: the fake data, the single-currency assumption, the two visa types quietly promoted to a universe.
5. Seed the constraint that matters most. One sentence about the hard requirement ("must support an arbitrary number of visa types from day one") outweighs a paragraph of adjectives. If you can only specify one thing, specify the thing that is most expensive to retrofit.
6. Reset instead of renegotiate. When an early anchor turns out to be wrong, a fresh session with a better first prompt usually beats ten corrective turns. Corrections fight the context; a restart replaces it. The visa case is the cleanest illustration we have: ten turns of patching produced loops, while one fresh session seeded with the full visa taxonomy produced a design where adding a country is a data entry task.
Vibe coding is anchor management done badly
This is also, we think, the honest explanation of why vibe coding fails when it fails. The workflow is: accept the model's first interpretation, then keep vibing on top of it. That is maximum ratchet. Every "best practices for vibe coding" list you have read is really just anchor management folklore: plan first, spec the data, restart when stuck. The mechanism above is why those practices work.
Better models raise the bar for engineers
Here is the conclusion the whole post has been walking toward, and it is the opposite of the one the industry keeps reaching for.
The models are getting better at writing code, and the reflex is to conclude that we need fewer engineers, or lesser ones. The experiment above says otherwise. Both sessions had the same flagship model and the same tool. The 585-line difference between them was not produced by the model. It was produced by one sentence that only an engineer would think to write: someone who knew that the data contract is load-bearing and the pixel layout is not, that regions are an open set, that a mock belongs behind the same interface as the real thing.
Entry to a POC has never been cheaper. That part is real: the freehand session produced a genuinely impressive dashboard in one turn, and anyone can have that experience today. But the POC barrier collapsing did not move the production barrier by an inch. Everything between the two (the domain research, the data model, the boundaries, the assumptions surfaced and vetoed, the blast radius owned) is exactly the work this post describes, and the models cannot do it, because it happens before and above the prompt.
So the gap between "it works in the chat" and "it works" is now wider than it has ever been, and it is bridged by fewer keystrokes and more judgment. Vibe coding did not lower the skill bar. It moved the bar from writing the code to specifying and reviewing the decisions the code is built on, and raised it: the better the tools get, the more one well-placed sentence is worth, and the more one missing sentence costs.
The first prompt is load-bearing. Write it like one. That is what engineering looks like now.
Was this useful?
Comments
No comments yet. Be the first to leave one.