1. Purpose
An agent has assembled a cart on a person's behalf. Before payment, the guard answers one question: does this cart match what the person asked for? It returns one of two decisions:
proceed: no blocking mismatch; payment may go ahead.ask: at least one blocking mismatch; the agent must consult the person before paying, and the guard supplies the reasons and a ready question.
The guard works alongside authorization schemes such as Verifiable Intent and does not replace them. Those prove the purchase stays within the limits the person signed (allowed items, amount, merchants). The guard checks the part those limits cannot express: colour, size, model, returnability, delivery deadline, quantity as the person counts it, a stated budget, a price preference.
The guard prevents. It does not audit. Running the same check after payment is possible and useful for disputes, but that is not its purpose.
2. Terms
| Term | Meaning |
|---|---|
| Request | The person's own words to the agent, verbatim. |
| Cart | A list of (item, quantity) the agent intends to buy. |
| Mandate | The formal limits the person authorised: allowed items, amount band, merchants. |
| Facts | Structured statements extracted from the request (§3). |
| Finding | One mismatch between a fact and the cart, with a severity (§5). |
| Decision | proceed or ask (§6). |
3. Intent facts
Facts are what the person stated, not what the extractor guesses. A field the
person did not state is null (or false for needs_refundable).
| Field | Type | Meaning |
|---|---|---|
brand |
string | Brand chosen. An excluded brand ("anything but Wilson") is not a choice: null. |
brand_not |
list of strings | Brands the person excluded ("anything but Wilson"). |
model |
string | Product model or line as named ("EZONE 98"), without the brand. |
color |
string | Colour chosen. A colour that is only excluded: null. |
size_label |
string | Size as stated ("42", "4 3/8"). |
level |
beginner | intermediate | pro |
Level stated or clearly described. |
price_preference |
cheap | premium |
Stated preference. |
explicit_max_cents |
integer | A stated spending limit, in minor units. Hyperbole is not a limit. |
quantity |
integer | How many, in the units the person counts (cans of balls, not packs). |
needs_refundable |
boolean | The person wants to be able to return or exchange. |
deadline_days |
integer | Days until a stated deadline, relative to a given today. |
lines |
list | Several variants in one order ("size 40 for my wife and 43 for me"): one entry per variant with its own model, color, size_label, quantity; everything else is shared. Empty otherwise. |
other |
list of strings | Requirements no field above holds ("gluten-free", "fits an iPhone 15"), in the person's words, that the person made a must. |
other_optional |
list of strings | The same, marked by the person as nice to have. |
soft |
set of field names | Fields the person themselves marked as optional ("black ideally, white is fine"). |
disputed |
set of field names | Fields the extractors disagreed on (§4). |
4. Extraction
Facts are extracted once per request, not once per cart check.
Extraction SHOULD use at least two independent extractors (different models or providers) and merge their results:
- all equal (case-insensitive for strings) → that value;
deadline_daysorexplicit_max_centsfound by at least one extractor → the strictest (smallest) value found, not disputed. Different estimates of one deadline are not a disagreement about whether it exists, and extractors miss stated deadlines and limits far more often than they invent them; the cost of a wrong value is one extra question;brand_not→ the union: a brand any extractor heard excluded stays excluded;- one extractor found a value and another returned
null→ the found value, field markeddisputed; - different non-null values otherwise → the first extractor's value, field
marked
disputed; forneeds_refundable,true; linesthat differ between extractors → the first non-empty set, andlinesis markeddisputed;other→ the longest list any extractor returned; if some extractor returned none,otheris markeddisputed.other_optional→ the union, without repeats ofother;soft→ the union.
A single extractor is allowed: then nothing is disputed, and every fact blocks unless the person marked it optional.
The extractor MUST be given today's date (the reference implementation defaults to the current date), so that "by Saturday" becomes a number of days. When the deadline is a named weekday, the extractor returns the weekday and the number of days is counted by code, inclusive of that day (from Monday, "by Friday" is 4): two models counting the same weekday differently is not a disagreement worth stopping a purchase over.
Extraction output MUST be validated against the schema. Malformed output is an extraction failure, never silently coerced to
null.Fail closed: if facts cannot be extracted, the decision is
ask, with a reason saying the request could not be checked.
5. Checks
Every check compares one fact with the cart. The item attributes it needs
(brand, name, colour, size, level, returnability, delivery time, units, price)
come from the catalog or checkout data. If the person stated a fact and the
item has no value for the attribute it needs, the check does not run; it
returns an unchecked finding, always warn, so the person sees what could
not be checked.
| Code | Finding when |
|---|---|
brand |
item brand ≠ brand (case-insensitive) |
brand-excluded |
item brand is in brand_not |
model |
the words of model (minus the brand) are not all in the item name |
color |
the words of color are not all in the item's first colour (below) |
size |
item has a size and it ≠ size_label (below) |
level |
item level is not level and not any |
refund |
needs_refundable and the item is final sale |
deadline |
item delivery time > deadline_days |
price |
price_preference = cheap, a single-item cart, and an item that fits every stated fact is more than 15% cheaper among the allowed items |
explicit-max |
cart total > explicit_max_cents |
quantity |
total units in the cart ≠ quantity (both more and fewer) |
other |
one finding per entry of other (block) and of other_optional (warn): the guard cannot check free text against the item, so the person decides |
unchecked |
a stated fact the item has no attribute for (always warn) |
Colour is compared by word, in English and Russian ("Triple Black" and "Чёрный" are black, "gray" is "grey"). An item with several colours ("White/Black") counts as its first one: black as a second colour is a finding, and the reason says so. Size ignores filler ("size 42" = "42"), a decimal comma ("42,5" = "42.5") and S/M/L spelled out ("Medium" = "M"); a size system ("EU", "US", "UK"…) counts only when both the item and the person name one, so "EU 42" = "42" but "US 9" ≠ "EU 9".
With lines. Cart items are assigned to lines so that the number of
blocking findings, missing lines and extra items is smallest. Every check above
then runs per pair, using the line's own fields over the shared ones;
quantity is compared per line, and two more codes apply:
| Code | Finding when |
|---|---|
missing |
a line has no cart item assigned |
extra |
a cart item is not assigned to any line |
If the extractors disagreed on a field of a line, findings on that field are
warn. If they disagreed on how many items the person asked for, every field
taken from the lines is warn, and so is extra; missing still blocks, as
an order missing half of what was asked is worse than one extra question. If
there are too many lines and items to assign exhaustively, the guard returns
a lines finding (block): the order goes to the person.
Severity. A finding is warn if its field is in soft or in disputed,
otherwise block.
6. Decision
proceedif there is noblockfinding.warnfindings are returned as notes so the person can see them.askotherwise, with everyblockfinding as a reason and a question for the person:Before I buy this, please check: – Asked to be able to return it, the item is final sale: … Should I still go ahead?
The guard does not forbid. It requires the person to be consulted. A person who knowingly accepts a final-sale item has made a decision; the guard's job is to make sure they were asked.
7. Where it runs
- Agent side, advisory: the agent calls the guard before checkout and decides what to do with the answer.
- Store or checkout side, enforced: checkout runs the guard itself. On
ask, the order is held until the agent has consulted the person; a changed cart is checked again. - As an MCP tool:
asasked-mcpserves one tool,check_order(request, cart, answers, today), that returns the decision, reasons and question; the agent passes back the person's answers on the next call. - Wrapped around a payment function:
Guard.protect(pay, cart)returns a payment function that checks the cart first and, onask, does not pay and returns the question to the agent instead. Hooks built on it exist for the Claude Agent SDK, the OpenAI Agents SDK and LangChain. - With Verifiable Intent: the guard runs before the agent creates the
final L3 mandates. The request and, optionally, the extracted facts can
travel in L2 next to
prompt_summary, so the same check is reproducible by the merchant and in a dispute.
8. Determinism and audit
The decision step is deterministic: the same facts and the same cart always
produce the same findings and decision. Only extraction involves a model. An
implementation SHOULD record, per check: the request, the facts with soft and
disputed, the cart, the findings and the decision. Every ask can then be
verified by a human without trusting a model.
9. Known limitations
- Lines cover variants, not unrelated items. "A racket and a can of balls" with different conditions for each (only the racket must be returnable) is not yet expressible; shared facts apply to every item.
- The catalog must expose the attributes. Without returnability or
delivery time on the item, the
refundanddeadlinechecks cannot run. priceis a heuristic. "Cheap" is compared only within items that fit every other stated fact, with a fixed 15% threshold.- Extraction quality bounds the guard. Two extractors reduce silent errors but do not remove them; disputed facts only warn.
- Free-text requirements are not checked. An
otherrequirement is always put to the person, even when the item plainly meets it. - Measured in a sandbox. The numbers below come from our own catalog and tasks; see the README's limitations.
10. Measured behaviour (sandbox, runs of 2026-10-31 to 2026-11-03)
Agents bought from our sandbox store with the guard off and enforced, one run
per task, scored against human-reviewed labels, on the open halves of both
datasets only: v1 with AsAsked 0.1.3, v2 with 0.1.4 and a stricter simulated
person. The run logs are in data/runs/; the figures
below are computed from them by scripts/summarize_runs.py.
- Naive agent (buys the first search result), dataset v1, 100 open tasks: 99 wrong purchases without the guard, 3 with it. The guard stopped 98 wrong carts, missed 1 and stopped no right one; the simulated person then approved 2 of the stopped carts.
- Five model agents, dataset v1, 100 open tasks each: 1 wrong purchase in 500 without the guard, 0 with it. Across 460 checks the guard caught that 1 wrong cart, missed none and stopped 9 right ones.
- The same five agents, dataset v2 (templates chosen where strong models made mistakes), 50 open tasks each: wrong purchases fell from 11 of 250 to 0. Across 255 checks the guard caught 8 wrong carts, missed none and stopped 14 right ones, each costing one extra question to the person. The simulated person never accepts a missing, deferred or substituted item; that rule was written after an earlier run on these same tasks.
Per-model tables are in the README.
11. Conformance
A conforming implementation, given the same facts and cart, MUST produce the
same decision and the same set of finding codes. Test cases live in
tests/test_coherence.py, tests/test_guard.py and tests/test_hard.py. A
standalone case file (facts, cart, item attributes, expected decision and
codes) is planned.