Start

Introduction

AsOrdered is a pre-checkout guard for AI shopping agents. It reads the words of the order, reads the cart, and answers proceed or ask before anything is paid.

The problem#

Agent payment mandates sign the limits of a purchase: which shop, which items, how much, how often. They are good at it. They have no field for “in black”, “EU 42”, “returnable”, “by Friday” or “anything but Specialized”, so a cart can pass every signature check and still be the wrong order.

What the guard does#

The guard sits between “the cart is ready” and “pay”. It turns the person's words into facts, compares the facts with every cart line, and returns a decision with plain-language reasons. When it says ask, it also writes the question for the person and, when the store has it, the right cart.

What is in the box#

  • lib/asordered.js, one ES module with no dependencies: parser, comparator, Guard class.
  • A sandbox bike store with four shops and the traps real stores have.
  • A naive agent that buys the first search result, and a benchmark that runs in the browser.
NOTENo model is involved anywhere. That is a design choice, not a shortcut: every decision is reproducible and can be checked by hand. See How it works.
Start

Quick start

Import the module from this site, build a guard from the person's words, and check the cart right before your payment call.

import { Guard } from "https://asordered.vercel.app/lib/asordered.js";

const guard = new Guard("Black Giro Cadet in EU 42, I need to be able to return them.");
const verdict = guard.check(cart);

if (verdict.decision === "proceed") {
  await pay(cart);
} else {
  // show the person the reasons and the ready question
  const answer = await askPerson(verdict.question, verdict.reasons);
  if (answer === "switch" && verdict.suggestion) await pay(verdict.suggestion);
}
<script type="module">
  import { Guard } from "https://asordered.vercel.app/lib/asordered.js";
  const v = new Guard("Two GP5000 tyres, 28 mm, black").check(window.cart);
  console.log(v.decision, v.reasons);
</script>
# Node 18+: take the file, it has no dependencies
curl -O https://asordered.vercel.app/lib/asordered.js
mv asordered.js asordered.mjs
node -e 'import("./asordered.mjs").then(A => console.log(new A.Guard("Any helmet, not Giro").facts))'

Where to put it#

Right before the payment tool call, after the agent has built its final cart. Keep the person's original words: the guard checks against them, not against the agent's summary of them.

WARNINGDo not let the agent rewrite the order before the guard reads it. A summary that drops “returnable” makes the guard blind to returns.
Start

Try it

This box imports the same module from ../lib/asordered.js. Type an order, choose whose cart to check, and read the raw verdict.

// press guard.check(cart)
The guard

How it works

Two steps and an answer. Both steps are plain code.

flow
 words ──► parse() ──► facts ──┐
                               ├──► check() ──┬──► proceed
 cart ─────────────────────────┘              └──► ask + reasons + question (+ suggestion)

1. Parse#

parse(words) lowercases the order and runs a fixed list of rules over it: model names, category words, brand names after “not” or “anything but”, colour words, sizes in the shape each category uses, numbers before product words, money after “under” or “no more than”, weekdays after “by”, and the words that mean returns.

2. Check#

check(facts, cart) walks the cart line by line and fact by fact. Each mismatch becomes one sentence that names what was asked and what the cart has. There is no scoring and no threshold: one mismatch is enough to ask.

Why no model#

A model would understand more phrasings. It would also give different answers on different days, and “the AI decided so” is worth nothing when someone disputes a charge. The parser understands less, but what it understands it understands the same way every time, and the loose set shows exactly where it stops.

The guard

Facts

Every field parse() can fill. Anything it cannot read stays empty, and an empty fact is never checked.

facttyperead from
categorystringhelmet, tyre, shoes, light, bottle; from a model name or a category word
modelstringa known model name, plural allowed: “GP5000s”
brandsstring[]brand names not inside an exclusion
excludeBrandsstring[]every brand after not / no / anything but / except / nothing from / avoid / never, up to the next comma
coloursstring[]colour words; cleared by “any colour”
colourSoftbooleanideally / preferably / if possible / prefer next to a colour
excludeColoursstring[]“except the white one”, “not red”, “nothing in black”
sizes{size, qty}[]EU 42, “in 43”, “forty-two”; S / M / L, small / medium / large; 28 mm, 700x28
qtynumbera number before a product word or model; “a pair of tyres” is two
budgetnumberunder / below / less than / max / no more than / up to / at most / budget of
deadlineDaysnumber“by Friday” counted from today, “by the weekend”, “tomorrow”, “within 3 days”
returnsbooleanreturn, returnable, send it back, refund, money back
cheapestbooleancheapest, lowest price, as cheap as
vaguebooleantrue when no category could be read at all
NOTEparse(words, { today }) takes the weekday as 0–6 (Sunday is 0). The benchmark always orders on a Monday, so “by Friday” is four days.
The guard

Reasons

Every sentence check() can return. Each one starts with what the person said, then what the cart has, so it can be read out to them as it is.

triggerexample
categoryAsked for a helmet, the cart has Elite Fly 550, Clear
modelAsked for the Giro Cadet, the cart has Specialized Torch 2, Black, EU 42
excludeBrandsAsked for anything but Specialized, the cart has Specialized Align II, Black, size L
brandsAsked for POC, the cart has Bell Z20, Black, size M
coloursAsked for black, the item is white: Giro Cadet, White, EU 42
colourSoftSaid ideally white, and it is in stock; the cart has black: …
excludeColoursAsked for anything but white, the item is white: …
returnsAsked to be able to return it, the item is final sale at Cadence Outlet: …
deadlineDaysNeeded by Friday (4 days), Velo Depot delivers in 11: …
sizesAsked for EU 43, the cart has EU 40
qtyAsked for 3, the cart has 1
budgetBudget was $150.00, the cart is $172.98
cheapestAsked for the cheapest, the same order costs $69.99 at Velo Depot
vagueThe request does not say what to buy, so there is nothing to check the cart against
empty cartThe cart is empty

A soft colour that the store does not stock is not a reason. It goes to notes instead, and the cart proceeds.

The guard

Decisions and questions

check() returns one object.

verdict
{
  decision: "ask",                 // "proceed" | "ask"
  reasons: ["Asked to be able to return it, the item is final sale at Cadence Outlet: …"],
  notes: [],                       // things worth saying that do not stop payment
  question: "Should I switch to Giro Cadet, Black, EU 42 from Velo Depot for $158.99?",
  suggestion: [ /* cart lines */ ] // or null when nothing in the store fits
}

Three kinds of question#

  • Switch. The store has a cart that fits every fact: “Should I switch to … for $…?”
  • Nothing fits. “Nothing in the store matches every part of your order. Should I still buy this, or wait?”
  • Vague. “What exactly should I buy? A product, a size and a colour are enough.”
The guard

Fail closed

If the order says nothing the parser can check, the guard does not wave the cart through. It asks. “Hi”, “get me something nice” and an empty string all return ask with the vague question.

WARNINGFail closed only covers orders with no readable product at all. An order with a product and an unreadable detail, such as “only if it gets here in two days”, is checked on the details the parser did read. That is the guard's biggest blind spot; see Loose wording.
API

Guard

JS
import { Guard } from "https://asordered.vercel.app/lib/asordered.js";

const g = new Guard(words, { today: 1, catalog });  // both options are optional
g.facts          // what the parser read
g.check(cart)    // → verdict
optiondefaultmeaning
todayreal weekday0–6, used to turn “by Friday” into days
catalogthe sandbox storeproducts used for suggestions, soft colours and the cheapest rule
API

parse and check

The two halves of Guard, exported on their own so you can log the facts, edit them, or check one set of facts against several carts.

JS
import { parse, check } from "https://asordered.vercel.app/lib/asordered.js";

const facts = parse("Three Elite Fly 550 bottles in black, no more than $28 total", { today: 1 });
// facts.qty === 3, facts.colours = ["Black"], facts.budget === 28

const v = check(facts, cart);
API

rightPurchase

rightPurchase(facts, catalog?) returns the cheapest cart that satisfies every fact, or null. It is what the guard offers in its question. A soft colour falls back to any colour when the requested one is not stocked; a hard one never does.

JS
import { parse, rightPurchase, total } from "https://asordered.vercel.app/lib/asordered.js";

const cart = rightPurchase(parse("Two pairs of Shimano RC3 in black, one in 42 and one in 43"));
// two lines, EU 42 and EU 43
total(cart); // 227.98
API

Cart line format

The guard reads these fields from each line. Map your agent's cart to them; fields you do not have can be left out, and the facts that depend on them are then reported as mismatches, never assumed.

fieldtypeused for
namestringmodel check, “Giro Cadet”
brandstringbrands, excludeBrands
categorystringcategory, size labels
colourstringcolours, excludeColours
sizestringsizes: "42", "M", "28"
pricenumberbudget, cheapest
qtynumberqty, sizes; default 1
merchantstringreasons and questions
returnDaysnumberreturns; 0 means final sale
deliveryDaysnumberdeadlineDays
Sandbox

The store

The sandbox has 532 products: 19 models across helmets, tyres, shoes, lights and bottles, every colour and size, at four shops. Prices are set per shop from one base price, so the traps come from the structure, not from hand-placed bait.

shoppricereturnsdelivery

Colours of the same model cost the same to the cent. The first search result is therefore often simply the first colour in the list.

Sandbox

The naive agent

naiveAgent(words) finds products by model name, or by category word if no model is named, sorts them by price, and buys one of the first. It never reads colour, size, quantity, budget, deadline, returns or exclusions. It is not a strawman of a real agent; it is a fixed baseline, so the guard's effect can be measured against something that never changes.

Sandbox

Benchmark

runBenchmark(seed, n, set) makes tasks from templates, runs each one without the guard and with it, and scores both against the template's own label. With the guard, the simulated person always accepts the suggested cart.

templatetaskswrong, offwrong, onstopped right

JS
import { runBenchmark } from "https://asordered.vercel.app/lib/asordered.js";
const r = runBenchmark(1, 100);          // the open 100
const L = runBenchmark(1, 50, "loose");  // loose wording
console.log(r.wrongWithout, r.wrongWith, L.missed);
Sandbox

Loose wording

The loose set exists to find what the parser misses. Version 0.1 missed sizes in words, colour exclusions and plural model names; 0.2 reads them. One template still gets through:

templatetaskswrong, offwrong, onmissed

Deadline in words. “A Knog Blinder, but only if it gets here in two days.” The parser knows “by Friday” and “within 2 days”, not “gets here in two days”, so it sees no deadline and the slow cart proceeds. The fix is one more rule; it is left open on purpose, so the benchmark keeps a known miss to measure against.

Reference

Spec

What any implementation of a pre-checkout guard must do to be called AsOrdered-compatible. The module on this site is the reference.

  1. The guard MUST read the person's own words, not an agent's summary.
  2. The decision MUST be made by deterministic code. Extraction may use anything; the comparison may not.
  3. Every ask MUST carry at least one reason naming what was asked and what the cart has.
  4. An order with nothing checkable MUST return ask.
  5. A fact the guard could not read MUST NOT be invented, and MUST NOT block on its own.
  6. A suggested cart MUST pass the same check it was offered for.
  7. The guard SHOULD report how often it stops a right cart, not only how often it catches a wrong one.
Reference

Limits and threat model

  • Words it cannot read are invisible. See Loose wording. English only.
  • Cart data is trusted. If a shop labels a final-sale item as returnable, the guard believes the label. It checks orders, not shops.
  • The agent can lie. An agent that passes the guard a different cart from the one it pays for defeats it. Run the guard in the payment step, not in the agent.
  • The person can say yes. The guard asks; it does not overrule. “Buy it anyway” is a valid answer.
  • One store. The benchmark uses one sandbox we built. Numbers from a real store will be different.
Reference

Token

$ASORDERED is the coin of the project. The guard does not use it, does not need it and never will. It exists for everyone who has opened a box and found exactly what the agent thought they meant.

CA soon copy

Buy $ASORDEREDChartX

Reference

FAQ

Why not just ask a model whether the cart matches?#

You can, and it will catch more phrasings. You will not be able to show anyone why it said yes last Tuesday. The guard gives up coverage for receipts.

Does it work outside the bike store?#

The comparison does. The parser knows the bike store's models and size shapes; pass your own catalog and extend the word lists for your products.

Does it send anything anywhere?#

No. The module makes no network calls. The Try it boxes on this site run entirely in your tab.

The guard stopped a right cart. Is that a bug?#

Sometimes. A stop costs one extra question to the person, which is why the benchmark counts it. If the reason it gave is wrong, that is a parser rule to fix.

AsOrdered · guard v0.2.0 · as asked, as ordered, as paidasordered