-
-
One Strands agent on Amazon Bedrock AgentCore: anonymous A2A discovery, a Managed Knowledge Base, Agentcore Payment x402 Base Sepolia.
-
One human approval before anything is signed. The amount, recipient, network and asset are shown, and the spend cap is enforced server side
-
Confirmed, with an on-chain receipt. A 0.1 USDC deposit settled on Base Sepolia, and the merchant confirms the booking in its own words.
-
Verified on Base Sepolia, block 46801718: 0.100000 USDC, status SUCCESS. Gas was paid by the facilitator, not the payer, under EIP-3009.
-
What the restaurant actually learned: party size, date and time. Not your name, phone, email, calendar, contacts or dining history.
-
Discovery with zero credentials. The public agent card lists five skills, then the merchant holds the table and asks for a deposit over x402
Inspiration
A no-show does not cost a restaurant one meal. It costs a cover that cannot be resold, because the table sat empty through the only two hours it could have earned.
The standard fix is a deposit. Taking one requires a payment processor, a card on file and PCI exposure that a family-run restaurant does not have and will never acquire. So they take no deposit and absorb the loss. Separately, to be findable at all, they pay a booking platform per cover or per month. Discovery and payment are two taxes on the same small business.
Then there is where this is all heading. A website is a human interface: it renders for eyeballs and collects input through forms. When a customer arrives through an AI assistant instead of a browser, none of that is reachable. The assistant wants an endpoint, a machine-readable description of what the business can do, and a way to pay.
I think merchant-side agents become as normal as websites are today. Table Relay is that idea shrunk to something small enough to actually verify.
What it does
A restaurant runs as a headless public agent instead of a platform listing.
- Anyone can find it with no credentials. The merchant publishes an A2A agent card behind an anonymous AgentCore Gateway. One skill per tool, generated from the same definitions the model sees.
- It answers real questions about its own menu. "I am lactose intolerant, what can I eat here?" is answered from a Bedrock Managed Knowledge Base, not from model memory. Onboarding a restaurant means dropping a text file in Amazon S3.
- It takes a no-show deposit with no payment processor. The merchant answers with an x402 payment challenge, the diner's agent signs a USDC authorisation, and settlement lands on Base Sepolia through a Coinbase facilitator.
- A human approves every payment. The diner-side agent stops on a Strands human-in-the-loop intervention, shows the amount and the recipient, and waits for a yes.
- It never learns anything it does not need. The merchant's tools accept party size, date and time. Nothing else. A booking platform knows your dining history and monetises it. This cannot, because the tool schemas have no field for it and a Bedrock Guardrail masks personal data before the model sees it.
Six deposits settled on Base Sepolia during the build, 0.6 USDC total, testnet.
How I built it
One Strands agent on Amazon Bedrock AgentCore, and the architecture is a chain where each constraint forced the next choice.
The agent card is the interface, so AgentCore Runtime runs with serverProtocol=A2A and
direct-code deployment: a Python zip through S3, no Dockerfile, no ECR, no ARM64 cross-build.
A card nobody can fetch is not an interface, so the front door is an AgentCore Gateway with
authorizerType=NONE, the only anonymous door in the stack. It signs onward to the runtime with
its own IAM role, so the runtime itself never accepts unauthenticated traffic.
An anonymous door must not front sensitive data, so menus get a second gateway with AWS_IAM,
fronting a Bedrock Managed Knowledge Base as an MCP target. Two doors, two postures.
An anonymous endpoint in front of a foundation model is both an attack surface and a cost
surface, so Bedrock Guardrails attach to the model call for prompt-attack filtering and PII
anonymisation, with max_tokens and a request rate limit bounding spend.
A held table must survive the next turn, so one DynamoDB table carries two contracts: the domain ledger through conditional writes, and Strands session snapshots through the Storage contract.
Payment runs on AgentCore Payments with a Coinbase CDP connector: a managed wallet with keys in a trusted execution environment, and a spend session capped at $2 over 120 minutes, enforced server-side.
There is no orchestrator anywhere. No function sequences search, then hold, then charge. The
model drives it because the data forces it to: the deposit amount does not exist until
hold_table returns it. A test enforces this by grepping for orchestrator and port classes and
failing if any appear.
Challenges I ran into
x402 had nowhere to live. The protocol is HTTP-native: answer 402 Payment Required, attach
a signed proof, retry. AgentCore Runtime exposes no HTTP status seam, because invocations carry no
path and A2A JSON-RPC always returns 200 with the outcome in the payload. So the challenge moved
into A2A message metadata using the Apache-2.0 x402_a2a extension keys, and settlement runs in
middleware that executes before the model does. The cost is honest: a plain-HTTP client cannot pay
this merchant.
A docstring example nearly misdirected a payment. The merchant confirmed a booking against
reference RS-0001, which was not the live reservation. RS-0001 was the example in the tool's
own docstring. A docstring ships on every request, so it is prompt text, and its examples anchor
the model. Nothing was lost, because the settlement path refuses to settle unless the reservation
is held, has a deposit due and is unpaid. A server-side invariant caught what the model got wrong.
Identifiers now travel in task metadata rather than model output.
A valid payment was silently reported as rejected. I read .valid on a response whose field
is .is_valid. A missing attribute returns None, None is falsy, and every good payment looked
bad with no error anywhere. A plausible wrong answer sends you searching everywhere except the
broken line.
The ecosystem friction was worse than the code. The Coinbase connector needs an AWS Marketplace subscription before it will create. The funding onramp is mainnet-only, so testnet funding is a manual faucet transfer.
Accomplishments that I'm proud of
Real settlements, not a simulation. Six USDC deposits settled on Base Sepolia with a managed wallet, a server-enforced budget, and a human approving each one.
Four client generations, one unchanged card. A ChatGPT Custom GPT Action, a Claude Desktop MCP
bridge, the A2A Inspector and curl, and a Strands coordinator agent all talked to the same
merchant with no server-side change. Clients come and go; the card is the stable interface. That
is how the web behaved.
It runs with no AWS account at all. Every integration is environment-gated. Unset the
variables and you get an in-memory ledger, no guardrail, no knowledge base and a scripted model
implementing the Strands Model interface. 96 tests and the full local demo touch nothing in the
cloud. 15 more run only when you opt in.
Privacy enforced by test, not by policy. A Gherkin scenario asserts that the generated tool schemas contain no field for calendars, contacts, preferences or customer history.
Under $2 of infrastructure for a weekend of live traffic.
What I learned
A spend cap has to live outside the prompt. A budget in a system prompt is a suggestion. A budget in an AgentCore payment session is a constraint, and the docs note it specifically resists prompt injection. Once I saw that distinction the rest of the design followed.
An agent may propose a payment and must never determine its payee. Give identifiers a structured channel and validate them server-side against an invariant the model cannot see.
Strands already owns the seams. I started with hand-written ports and adapters around the
model, the payment rail and the approval step, and deleted all of them. A2AAgent, Plugin,
HumanInTheLoop, the Model ABC and SessionManager were already there.
Managed beats assembled at this scale. A classic knowledge base needs a vector store that bills for provisioned capacity whether anyone books a table or not. Five menus total nine kilobytes.
The paying agent never needs gas. Under EIP-3009 the payer signs and the merchant submits, so the wallet holds USDC and nothing else.
What's next for Table Relay
- Refunds and no-show forfeiture. A deposit only means something if not showing up costs it, and that is the half that turns a demo into a product.
- Pricing declared in the agent card, so a coordinator can compare terms before committing to a call rather than discovering them at the challenge.
- Bridging the two x402 layers properly instead of picking one, so plain-HTTP clients can pay.
- Mainnet and a fiat on-ramp. The technical change is a config flip. The real work is the compliance and off-ramp story for a merchant who wants euros in a bank account.
- A hosted merchant-agent builder. A restaurateur will not run a deploy script. Card, menu, deposit policy, wallet and guardrails as a form is the product gap, and whoever ships it owns the long tail.
Honest scope
This is a working demonstration of a discovery-and-settlement mechanism, not a production booking platform. All restaurants, menus and reservations are fictional. Settlement runs on Base Sepolia testnet, so no real funds are involved. Refunds and forfeiture are described, not implemented.
The narrow claim, fully demonstrated: a small restaurant can take a no-show deposit with no payment processor, and be discovered with no platform.
I wrote the build up in three posts on AWS Builder Center as the series Headless Merchants, Real Payments: the architecture, the payment path, and what the ecosystem still needs.
Built With
- a2a
- agentcore
- agentic
- amazon-web-services
- bedrock
- booking
- coinbase
- hexagonal
- kiro
- mcp
- payments
- pytest
- python
- sepolia
- stablecoin
- strandsagent
- usdc
- uv
- web3
- x402
Log in or sign up for Devpost to join the conversation.