AsAsked

Docs

Check a cart before it is paid for

The guard is a Python library. Give it the person's words and the cart; it answers proceed or ask. The sandbox store and the benchmark run your agent against the same traps we use.

1. Install

Python 3.10 or newer. The guard is one package, asasked on PyPI; its only required dependency is cryptography. The same files are attached to the v0.1.10 release on GitHub, for installing without PyPI.

pip install asasked

Extras add what you use:

extraadds
asasked[anthropic]Claude as an extractor; Gemini and OpenAI-compatible endpoints need nothing extra
asasked[mcp]the MCP server, asasked-mcp
asasked[claude-agent]the hook for Claude Agent SDK
asasked[openai-agents]the guardrail for OpenAI Agents SDK
asasked[langchain]the middleware for LangChain 1.x

As an MCP server. asasked-mcp is a local (stdio) server with one tool, check_order, that the agent calls before paying. It is a second opinion the agent chooses to ask for, not a lock: if payment goes through your own code, call the guard there instead.

# A. nothing to install first, with uv
uvx --from "asasked[mcp,anthropic]" asasked-mcp

# B. installed with pip
pip install "asasked[mcp,anthropic]"
which asasked-mcp                  # the full path to put in the app's config

Ready configs for Claude Code, Claude Desktop, Cursor, VS Code, Codex CLI and Gemini CLI are on the Install page.

From source, to run the sandbox store and reproduce the benchmark (sections 5–7). Checkout in the store builds a real Verifiable Intent chain, so the reference SDK is expected as a sibling checkout:

git clone https://github.com/agent-intent/verifiable-intent.git
git clone https://github.com/ginjikurata/asasked.git
cd asasked
python3 -m venv .venv && source .venv/bin/activate
pip install -e ../verifiable-intent -e ".[dev]"
pip install -e ".[anthropic]"     # if Claude extracts

2. Choose the extractors

Turning words into facts is the only step that uses a model. One model works; two are safer, because a fact they disagree on only warns. Use Gemini, Claude or an OpenAI-compatible endpoint, alone or in any pair. The decision itself is plain code.

.env
# environment variables; in a checkout, copy .env.example
# one extractor works; two are compared
LLM_AGREE_PROVIDERS=gemini,anthropic

LLM_API_KEY=...             # Gemini, Google's own API
CLAUDE_API_KEY=...          # Claude (CLAUDE_BASE_URL: a proxy)
OPENAI_COMPAT_BASE_URL=...  # any /chat/completions
OPENAI_COMPAT_API_KEY=...
OPENAI_COMPAT_MODEL=...

3. Check a cart

Facts are extracted once per request; each check compares them with a cart. today turns “by Saturday” into a number of days; it defaults to the current date.

python
from asasked.guard import Guard
from asasked.store.store import Mandate, Store

store = Store()   # the sandbox catalog; in production, your own find()
sku = "ASI-GELRESOLUTION9-42-BLA-OUT"
mandate = Mandate(prompt_summary="Black ASICS, size 42, returnable",
                  allowlist=(sku,), quantity_cap=1,
                  amount_min_cents=0, amount_max_cents=20000)

guard = Guard(request="Black ASICS in size 42, I need to be able to return them.",
              today="Monday, 2 November 2026")
guard.check([(sku, 1)], mandate, store.find)
result
{'decision': 'ask',
 'reasons': ['Asked to be able to return it, the item is final sale:
              ASICS Gel-Resolution 9, size 42, black.'],
 'question': 'Before I buy this, please check:\n'
             '  - Asked to be able to return it, the item is final sale:
                  ASICS Gel-Resolution 9, size 42, black.\n'
             'Should I still go ahead?',
 'notes': []}

reasons block the purchase; notes are mismatches on facts the person marked as optional, that the two models disagreed on, or that the item does not say (no returnable field, say). A requirement no field holds, such as “gluten-free”, is kept in the person's words: if it is a must, the guard asks; if not, it adds a note. If extraction fails, the guard answers ask: it fails closed. For tests or replays, pass extract= with ready facts instead of calling models.

4. Your own catalog

find(sku) returns the item as a dict. These are the attributes the checks read; a check whose attribute is missing cannot run.

fieldmeaning
name, brandwhat the item is; brand is compared case-insensitively
color, size_label, levelvariant attributes; level is beginner, intermediate, pro or any
refundablewhether the item can be returned
ships_in_daysdays until it arrives
price_centsprice in minor units
units (optional)how many the person would count: a 4-can pack is 4

The mandate supplies the amount band and the allowlist, so the guard can tell “expensive” from “there was nothing cheaper”.

5. The sandbox store

A deterministic store with 456 products from 4 merchants, driven through tools: search_products, get_product, add_to_cart, remove_from_cart, view_cart, ask_user, checkout. Every order goes through a real Verifiable Intent chain. The guard runs in one of three modes: off, advisory (the agent gets a check_before_checkout tool) or enforced (checkout holds the order until the agent has asked the person).

python
session = store.new_session(mandate, guard=guard, guard_mode="enforced")
session.call("search_products", {"query": "black asics 42", "limit": 2})
session.call("add_to_cart", {"sku": sku, "quantity": 1})
session.call("checkout", {"confirm": True})
checkout, enforced
{'status': 'needs_confirmation',
 'reasons': ['Asked to be able to return it, the item is final sale: ...'],
 'question': 'Before I buy this, please check: ...',
 'next_step': 'Ask the person with ask_user. If they agree, call checkout
               again; otherwise change the cart or stop.'}

Agents outside Python reach the same store over HTTP on localhost:

python scripts/serve_store.py --port 8765

GET  /tools                     the tools, as JSON schemas
POST /sessions                  {"mandate": {...}, "user_reply": "..."}
POST /sessions/<id>/call        {"tool": "search_products", "args": {...}}
GET  /sessions/<id>/transcript  every call and the final order

6. Run the benchmark

The same agent runs each task without the guard and with it enforced. Every run is one JSON line in runs/ with the full tool-call transcript. Agents are scored against the human label, not against the guard: a right purchase, a wrong purchase that payment let through, or no purchase.

python scripts/run_agents.py --tasks v1-open --guard both \
    --agents naive
python scripts/run_agents.py --tasks v1-open --guard both \
    --agents claude-sonnet-5,gpt-6-sol --parallel 8
python scripts/run_agents.py --tasks v1-open --guard both \
    --agents openai_compat:your/model
python scripts/run_agents.py --tasks v2-open --guard both \
    --agents claude-sonnet-5

The open half of the dataset is in the repository; see Dataset. So is the open half of v2 (--tasks v2-open). The rest of v1 can be rebuilt from the seed in the repository; the rest of v2 cannot.

7. Tests

pytest covers the deterministic check, the Verifiable Intent chain, the store and the harness, with no model and no network. The behaviour every implementation must match is in the spec.

pytest -q

Code: the repository · every snippet on this page was run against it