We will be undergoing planned maintenance on Oct 7th 6:00AM UTC / Oct 7th 2:00AM ET

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

Share this project:

Updates

Submission history