Cover illustration for "Case Study: The Price Changed in June. The Copilot Still Quotes March."

Case Study: The Price Changed in June. The Copilot Still Quotes March.

A sales copilot quotes a price that stopped being true two quarters ago. Here is why searching harder doesn't fix it, and what we do instead, with the output from a live run.

What goes wrong#

A rep asks the sales copilot what to quote for the Starter plan. It answers $49 per seat per month. That was the price in March. In June it went up to $59.

Nobody catches it. The rep sends the quote. Two weeks later the customer repeats $49 back during contract review, and somebody has to decide whether to honour a price that hasn't existed since spring.

Here is why the copilot said $49.

Both pricing decisions were saved. The March one and the June one. When the copilot needs to know what to charge, it searches everything that was saved and pulls back whatever best matches "what do we charge for Starter". Both documents match that extremely well, because both are about exactly that. The copilot got handed two answers and picked one.

Why searching harder doesn't help#

This is the part that catches people out, so it's worth being slow about.

Search ranks documents by how well they match the question. That's all it does. Whether a document is still true is not written in the document, so search can't see it. The March decision isn't wrong. It's a correct, accurate description of what we charged in March. It's just not what you should quote today.

Which means the usual upgrades all fail:

  • A better search model finds both documents, faster.
  • A bigger context window sends the model both documents.
  • Saving more history adds more old prices to trip over.

Every one of those makes the copilot better at finding things, and none of them makes it better at knowing which thing is current.

What we actually need is for the old price to stop being an answer, while staying readable if someone asks what we charged in April. Those are two different jobs, and most setups can only do one of them: delete the old doc and lose the history, or keep it and keep getting burned.

What gets stored#

So we store two things, separately.

What you wrote. Every document you save, kept exactly as written, forever. The March decision stays there untouched. This is your history and nothing ever removes it.

What it means. After you save a document, the engine reads it and pulls out the specific claims it makes. Each claim gets an address: the thing it's about, and the property being described. For our March decision, the address is starter-plan plus price_per_seat_per_month, and the value is $49. We call these facts.

The address is the whole point. Because a fact has an address, a later document can land on the same address. When that happens, the engine closes the old fact: it stamps the moment it stopped being true and records which fact replaced it. Closed facts stop being given as answers. They are not deleted, and you can still see them.

One address, one current value, and a trail showing what it used to be.

Everything below ran against our production API on 21 August 2026, and the output is pasted in unedited.

Setup#

Install the SDK. In the dashboard (app.xysq.ai, under Agents) make a project, copy its id, and create an agent key with an admin grant on it. Making a vault needs admin; a write-only key gets a 403 on the first call.

A vault is just a place to keep things. Yours will have several. Here we need one.

from xysq import Xysq
 
PROJECT_ID = "1f2a..."   # from the dashboard
client = Xysq(api_key="xysq_agent_...")
 
pricing = client.vaults.create("pricing", project_id=PROJECT_ID)

That's the entire setup. This post is smaller than the support case study on purpose, because the thing I want to show doesn't need much around it to be visible.

Save the March decision#

client.vaults.push(
    pricing.vault_id,
    "Pricing decision, March 2026. The Starter plan is priced at "
    "$49 per seat per month.",
    title="Pricing decision, March 2026",
)

Give it a minute, then look at what the engine took from that document:

--- after March: 1 total / 1 live / 0 closed
    [LIVE  ] starter-plan price_per_seat_per_month = $49

One fact. Notice it didn't just file away "the March pricing doc". It read the document, found the claim inside it, and gave that claim an address. That's what makes the next step possible.

Save the June decision#

client.vaults.push(
    pricing.vault_id,
    "Pricing decision, June 2026. The Starter plan price changes from "
    "$49 to $59 per seat per month, effective immediately. "
    "Quote $59 on all new deals.",
    title="Pricing decision, June 2026",
)

Same thing, same property, new number. Here is the result, unedited:

--- after June: 2 total / 1 live / 1 closed
    [CLOSED] starter-plan price_per_seat_per_month = $49
             superseded_by=1a8a9058 invalid_at=2026-08-21T13:36:29+00:00
    [LIVE  ] starter-plan price_per_seat_per_month = $59

The $49 fact is closed. It carries the exact moment it stopped being true and a pointer to the fact that replaced it.

Nobody ran a migration. Nobody hunted down the March document to edit or delete it. The March document is exactly where it was.

Ask the question#

Now ask what the rep asked:

served = client.vaults.pull(
    pricing.vault_id,
    query="what is the Starter plan price per seat",
)
 
facts = [i for i in served if i.source == "fact"]
print([i.content for i in facts])
# ['starter-plan price_per_seat_per_month = $59']

One answer, and it's the right one.

Now the caveat, because I don't want you to be surprised by it later. The full result had four items in it, not one:

--- recall returned 4 items
    src=log  :: Pricing decision, March 2026. The Starter plan is priced at $49 ...
    src=log  :: Pricing decision, June 2026. The Starter plan price changes from ...
    src=wiki :: Starter Plan Pricing March 2026
    src=fact :: starter-plan price_per_seat_per_month = $59

The March document is still in there. It should be. You asked a question its text genuinely answers, and it's your history.

The guarantee is on the facts. An agent that takes its number from the items marked fact cannot quote $49, because closed facts never appear there. An agent that reads the raw documents can still find $49. So when you build this: take the number from the fact, and use the documents for the story around it.

The bit that didn't work first time#

My first version of this test failed, and I'm leaving it in because you would hit it in your first week.

I saved "The Starter plan is discontinued. The Team plan at $99 per seat per month is now the entry tier." I expected that to close the $49 fact. It closed nothing.

Here is everything the engine was holding afterwards. (The running list of facts is called the ledger, which is the word you'll see in the output.)

--- ledger: 5 total / 5 live / 0 closed
    [LIVE] starter-plan.price per_seat_per_month = $49
    [LIVE] starter-plan status = discontinued
    [LIVE] team-plan price.per_seat_per_month = $99
    ...

I thought this was a bug for about ten minutes, then realised the engine was right.

Nothing in that document contradicts Starter's price. Starter's price is $49. What changed is that we stopped selling it, and the engine recorded that as its own separate fact at its own address: starter-plan status = discontinued. Both statements are true at the same time, so there was nothing to close.

The rule underneath: a fact gets closed when a later document gives a new value for the same property. Saying something has been replaced by a different product is not that.

In practice this is fine, and arguably better. A copilot asking "should I quote Starter?" gets status = discontinued back, which answers the question directly. But if you expect a replacement announcement to retire the old numbers on its own, it won't. Say what changed about the thing itself.

The checks#

The whole thing as assertions, from the live run:

PASS  a fact was closed
PASS  closed fact has superseded_by + invalid_at
PASS  live facts carry the NEW price ($59)
PASS  the OLD price ($49) is NOT live
PASS  recall serves $59
PASS  the FACTS layer serves only the current price
PASS  history intact: both decisions still stored
 
7 passed, 0 failed

The last two are the pair worth caring about. Only the current price gets served, and the old one is still on the record. Most setups make you choose one.

Where else this shows up#

Pricing is the clearest example because a price is a single number and someone notices when it's wrong. But the shape repeats anywhere your team restates something over time. Which plan a customer is on. Who owns the account. Whether that integration shipped yet. Each of those is one property with a history and exactly one current value.

I looked fairly hard for a published case of a sales copilot quoting a retired price and couldn't find one. My guess is it happens often and nobody writes it up, because it's a quiet failure and an awkward one to admit. If it's happened to you, I'd like to hear about it.

Hope this was useful.