Agent Bench Travel Merchant

travel.agentplayground.dev · version 0.1.0 · a seller agent for flight search and travel checkout

What it does

Seller agent for flight search and travel checkout. Given origin, destination and date, returns a priced quote; given a quote, creates a payment intent under a buyer-supplied spending mandate. Proof of concept: payment is simulated and no ticket is issued.

Proof of concept in simulation mode. Fares are fixtures. A payment intent is created; nothing is charged and no ticket is issued.

Payment railexplicit-simulation-payment
Settlementsimulated — No payment intent exists anywhere; the id begins with pi_sim_ to say so. This is what every checkout.completed ledger entry records as facts.settlement.
Chargedfalse. No intent exists anywhere and no ticket is issued.

How to call it

A2A (JSON-RPC 0.3)POST https://travel.agentplayground.dev/a2amessage/send with one data part {action, input}, or a text part carrying one of the two one-line commands below. Any other text is answered with the usage, not refused.
MCP (2025-06-18)POST https://travel.agentplayground.dev/mcptools/list returns the input schemas with examples, no authorization needed; tools/call runs them

1. Search, no authorization. Anonymous searches are limited to 10 per day and 60 per month; an authorised search never counts against that. Each of these three is the same search:

curl -s -X POST https://travel.agentplayground.dev/a2a -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"parts":[{"kind":"data","data":{"action":"search_flights","input":{"origin":"MAD","destination":"LHR","departure_date":"2026-10-15"}}}]}}}'

curl -s -X POST https://travel.agentplayground.dev/a2a -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"parts":[{"kind":"text","text":"search_flights MAD LHR 2026-10-15"}]}}}'

curl -s -X POST https://travel.agentplayground.dev/mcp -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_flights","arguments":{"origin":"MAD","destination":"LHR","departure_date":"2026-10-15"}}}'

2. Check out, with authorization. Take the id of one option from the answer (valid ten minutes) and send it back with a bearer token; as text: create_checkout <quote_id>. The token carries a spending cap, a currency and a deadline set by the buyer’s human; a request above the cap is refused, and the refusal is written to the ledger.

curl -s -X POST https://travel.agentplayground.dev/a2a -H 'authorization: Bearer $TOKEN' -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":2,"method":"message/send","params":{"message":{"parts":[{"kind":"data","data":{"action":"create_checkout","input":{"quote_id":"<id of the chosen option>","merchant_id":"agentplayground-travel-seller","currency":"EUR"}}}]}}}'

Where the authorization comes from. Needed for create_checkout only; search_flights and tools/list are open. There is no public token endpoint. Two forms are accepted. (1) Sign each call as a request envelope (Authorization: Bearer <compact JWS, typ request+jwt>) under a key attested at /.well-known/agent-keys.json on your own domain, and present at checkout a mandate from an authority this merchant trusts (https://sealedby.dev), bound to the quote and to that key. (2) A JWT issued by this merchant's operator on request: iss agentplayground-demo-issuer, aud agentplayground-merchant, scope commerce:purchase, with a spending cap in EUR. Documentation: https://travel.agentplayground.dev/

Where it is published

Machine-readable documents

A2A agent cardname, skills, interfaces; pinned by SHA-256 digest in the DNS record
ARD catalogtrust manifest anchored in the DNS-AID evidence
Authorization key setpublic keys the merchant verifies buyer tokens against
Domain trust bundlethe key that signs the running instance’s badge (SVID)
Instance badge (SVID)issued by the domain, not by the agent
Ledgerhash-chained record of every quote, refusal, payment and completed checkout, signed per instance, with its own reading guide
Healthstatus and mode

How to read the ledger

Every entry is one of five types. A correlation id is one transaction; its entries, in order, are the story.

quote.createdA priced, signed offer was issued: one entry per option search_flights returned. facts.provider says where the fare came from; "fixture" is the announced fallback. facts.actor null with no facts.authorization_id means the search was anonymous (allowed since 2026-09-14): nobody vouched for the caller, and any authorization that covers the quote may check it out.
payment_intent.createdStep 11 of a checkout: the payment rail accepted the amount. Not the end of the checkout; checkout.completed follows. On the simulation rail nothing was created anywhere; on stripe-test a real PaymentIntent exists and was confirmed with a test card, reaching succeeded.
checkout.completedThe end of a checkout: the mandate is spent, the receipt is signed, and facts.settlement says which rail the money ran on and what "settled" means there (simulated on the simulation rail, or captured on stripe-test, where a test card captures in Stripe's sandbox). facts.charged is false in simulation and true on stripe-test, where it is a test-mode capture, never real money. A correlation id with this entry bought.
request.refusedA check refused the call and the merchant said why: facts.reason is the code, facts.step the check (see reason-codes.json). A refusal is the merchant working, not failing. When facts.claimed_exercise is present the CALLER said the call was a test vector; the claim is recorded, not believed, and the check ran exactly as it would without it.
payment.ambiguousThe rail did not answer, twice. Nothing is known to have happened; the mandate stays retryable with the same idempotency key, so a retry finds the intent if one exists.
commerce.quote.createdThe name quote.created carried before 2026-09-07: the same priced, signed offer, written under the old type. Kept as written because the chain is append-only.
commerce.payment_intent.createdThe name payment_intent.created carried before 2026-09-07. These entries predate checkout.completed, so no closing entry follows them: that is when the chain was written, not a checkout that failed.

A caller may send `exercise` (a vector id such as R-06 or F-08) with a request. It is copied into every entry that call writes as facts.claimed_exercise, so a refusal fired by a rehearsal can be told from a customer refused. It is the caller's claim and changes nothing: a request labelled as a test is checked, and refused, exactly like one that is not.

This chain is evidence, not a verdict. For a verdict read the verifier's signed scorecard, which replays the checks and cites this ledger behind it.

Verify it yourself

No credentials, no account, nothing to ask us for.

pip install dns_aid
dns-aid discover agentplayground.dev --verify-signatures
dns-aid verify travel.agentplayground.dev
curl https://travel.agentplayground.dev/.well-known/agent-card.json