How to Stop Your AI Assistant Quoting Prices You No Longer Charge

It is not a prompting problem. It is a copies problem.

Summary: My website chat assistant spent a morning quoting two retired prices to real visitors. The instinct is to fix the prompt. That is the wrong layer. The actual problem is that a price existed in four places and I had updated one. The fix is to generate one canonical, public facts file from a single source, and have every assistant fetch it live rather than hold a copy.

Four places a price can hide

If you run a website with an AI assistant on it, a single price probably lives in all of these:

  1. The page. Whatever a human reads.
  2. The assistant's system prompt. Often a hardcoded list, because retrieval alone answers “what do you charge” badly.
  3. The search or vector index. Built from your site at some past moment, and frozen there until something rebuilds it.
  4. Any uploaded knowledge file. On whatever platform your voice agent or support bot runs.

Change your price and you have to remember all four. You will not. I did not, repeatedly, and I am the person who built the thing.

What makes this genuinely nasty is that the failure is invisible from the inside. You update the page, load the page, see the new number, and conclude you are done. The assistant is a different surface, and nothing about looking at your own website tells you what it is saying.

Three real failures, in order

Stale index. My assistant spent a morning quoting two retired advisory prices to visitors. The site was correct. The vector index had been built before the change and nothing rebuilt it. Retrieval was faithfully returning what it had been given.

Stale prompt. I renamed an offer, updated the site, deployed, and verified the pages. The assistant kept using the old name, because the assistant is a separate deployed service and my site build does not touch it. It had been wrong for an unknown period before I noticed.

Both at once. I retired an offer and removed it from the prompt. The assistant kept quoting the retired price anyway, because the number was still sitting in the index. The prompt was right and the answer was still wrong.

That last one is the useful one. It shows why “just update the prompt” and “just re-index” are both incomplete answers. They are different caches with different lifetimes, and the prompt usually outranks retrieval by design, so a correct index cannot save you from a stale prompt either.

Why you cannot just delete the duplicate

The obvious response is to remove the price list from the prompt and let retrieval handle it. I tried. It does not work, for a structural reason worth understanding.

Retrieval returns the top few matching chunks. If you have seven offers with published prices spread across five pages, a broad question like “what do you charge?” can never pull all of them into context at once. The assistant answers with a partial list, or with nothing, and a partial price list is arguably worse than no answer because it reads as complete.

So the duplicate is doing real work. The problem is not that it exists. The problem is that it is maintained by hand.

The pattern that actually fixes it

Three parts:

1. One source of truth, in structured data. Every price on my site lives in a single JSON file. Not in page copy, not in the assistant prompt, not in a spreadsheet. Pages read from it. If a number appears as a literal anywhere else, that is a bug, and I have found and fixed that bug more times than I would like to admit, including inside structured data where it was invisible on the rendered page.

2. A generated public fact sheet. On every build, a script emits a plain-text file from that JSON and publishes it at a fixed public URL. It contains prices, guarantees, terms, and explicit instructions for any assistant reading it, including what never to say. Mine is at stephenthorn.com/agent-facts.md, and you are welcome to look at it.

3. Every assistant fetches that URL at answer time. Not an upload. Not a copy. A live request.

My voice agent has exactly one tool for this: a GET against that URL. Its file-collection count is zero, so no uploaded copy exists to go stale. Its system prompt contains no price, no offer name, and no guarantee wording at all. It is instructed to call the tool every time pricing comes up, in its words, even if you think you remember the numbers, since prices can change same-day, and never to invent a figure if the fetch fails.

The result is that changing a price is one edit, and it reaches the phone line with no action on the voice platform at all.

Make the machine catch you, not a comment

The prompt still holds a duplicate, so I wrote a check that fails the build when the prompt's numbers and the source JSON disagree. It runs automatically before every build, which means it also runs before every deploy.

This is the part I would push hardest on. Before I wrote it, the file had a comment asking the next person to remember to update both places. A comment is not a guard. It is a note to someone who is already not reading carefully.

Know what your guard does not cover. Mine only compares the assistant prompt against the source file. It cannot see page templates, marketing docs, or the search index, which is precisely why those three went stale. A guard that covers one surface can create false confidence about the other three, so write down what it does not check, next to what it does.

Failure should be graceful, not creative

Pointing an agent at a live URL means the URL is now a production dependency. Mine is a dependency of a phone line.

So decide in advance what happens when the fetch fails, and put it in the prompt. Mine says: do not guess a number, say plainly once that you do not have the exact figure, then move to booking a call. Never invent a price under any circumstance.

That is a real answer to an outage. “I do not have that in front of me, let me get you to someone who does” is a perfectly normal thing to hear from a business. A confidently wrong price is not.

Two things I got wrong, so you do not

Do not name the retired price in the public file. My first draft of an entry named the retired tier and its price, then said not to quote that figure. That file is world-readable and is read by a language model. Writing “do not say X” puts X directly in front of the model you are instructing, and hands it to anyone who opens the URL. The same trap applies to writing about the trap, which is why there is no retired figure anywhere in this article. State the current position only. Keep the history in a comment that does not get published.

Count your deploy surfaces. I assumed one deploy. There were three: the static site, the assistant service, and the search index. Two of them were separate manual commands that no build step triggered, and both went stale on the same day. Write the full sequence down somewhere you will actually read it, because the day you change a price is the day you are thinking about pricing, not about infrastructure.

The one-line version

A fact should exist in exactly one place, and everything else should ask that place at the moment it needs the answer. Every AI assistant giving out wrong information is a violation of that sentence somewhere in its stack. Find the copy, and either delete it or make a machine check it.

Related reading

Running an AI assistant that talks to customers?

Book a free 20-minute call. Bring the setup you have and we'll find the copies that are going to drift.

Raleigh-Durham, NC. Veteran-owned. No pressure, no pitch.