agent-storefront
README.md
# agent-storefront
> **Thesis:** Every restaurant already has an accidental agent API — it's called a phone line. This prototype replaces that with a proper machine-readable storefront that any AI agent can discover, query, and order from without a human operator.
A working demo of the **human → consumer AI → merchant AI → human** ordering loop, where the restaurant exposes an MCP (Model Context Protocol) server that a consumer AI agent negotiates with to place an order — complete with menu lookup, modifier validation, pricing, and a mandatory confirmation gate before payment.
---
## Architecture
```
┌──────────────┐ natural language ┌──────────────────────┐ MCP tools ┌───────────────────────┐
│ Marmik │ ───────────────────▶ │ Siri │ ─────────────▶ │ DoorDash │
│ (human) │ │ (consumer AI agent) │ │ (merchant MCP server) │
│ │ ◀─────────────────── │ consumer/agent.py │ ◀───────────── │ merchant/main.py │
└──────────────┘ spoken/text reply └──────────────────────┘ struct. JSON └───────────────────────┘
│
▼
Mock POS (in-memory)
+ orders.json
```
Two independent services, one terminal UI:
| Service | Role | Tech |
|---|---|---|
| `merchant/` | **DoorDash** — exposes menu, validates orders, acts as mock POS | FastAPI + MCP Python SDK (streamable HTTP) |
| `consumer/` | **Siri** — LLM agent loop that takes NL input and negotiates the order | Python + Ollama (qwen3:14b) |
| Terminal | **Marmik** — types orders, sees the full negotiation stream, confirms payment | `python -m consumer.agent` |
---
## Quickstart
```bash
# 1 — start the merchant (DoorDash / store AI)
make merchant
# 2 — in a second terminal, start ordering (Siri + Marmik in one terminal)
IGNORE_STORE_HOURS=1 python -m consumer.agent
# 3 — try an order
# Marmik ▶ Large veggie pizza, thin crust, mushroom and onion. My name is Marmik.
```
You'll see the full negotiation in real time:
```
Siri → DoorDash get_store_info() .............. ✓ Tony's Pizza
Siri → DoorDash get_menu() .................... ✓ 8 items
Siri reading your order with qwen3:14b...
Siri → DoorDash validate_order() .............. ✓ $17.27
Siri ▶ Marmik
Here's your order summary, Marmik:
• Veggie Pizza (Large, Thin Crust, Mushroom, Onion) × 1
Total: $17.27 | Pickup: ~20 min | Payment: saved card (mock)
Shall I place this order? Reply yes to confirm.
Marmik ▶ yes
Siri → DoorDash place_order() ................. ✓ TON-8D0X
Siri ▶ Marmik
Order TON-8D0X confirmed! Total $17.27, pickup at 11:01.
```
---
## Makefile targets
| Command | Does |
|---|---|
| `make merchant` | Start merchant MCP server on port 8000 |
| `make consumer` | Start consumer REST API on port 8001 |
| `make demo` | Start both servers in background |
| `make test` | Run unit tests (21 validation tests) |
| `make smoke` | Run MCP smoke test (7 assertions) |
| `make scenarios` | Run all 5 §11.4 integration scenarios |
---
## Demo scenarios (try these)
```bash
# Happy path
IGNORE_STORE_HOURS=1 python -m consumer.agent
Marmik ▶ Large veggie pizza, thin crust, mushroom and onion. Name: Marmik.
# Unavailable item — Siri escalates, offers alternatives
Marmik ▶ BBQ Chicken Pizza please, large with thin crust.
# Ambiguous order — Siri auto-fills required modifiers and states assumptions
Marmik ▶ I want a cheese pizza.
# Change of mind — say no at confirmation, re-order
Marmik ▶ [order something] → no → [new order] → yes
```
---
## Why modifier validation is the hard part
Real POS menus have hundreds of modifier groups with min/max selection rules, mutual exclusions ("extra cheese" conflicts with "no cheese"), and per-item applicability constraints. A "large veggie pizza" in plain English maps to: an item ID, a required size modifier, an optional crust modifier, and 0–10 topping modifiers — each with an exact `modifier_id` the POS understands. If any ID is wrong or a required group is missing, the kitchen rejects the ticket. The validation layer in `merchant/validation.py` implements all 7 rules and returns machine-readable `suggested_fix` objects so the consumer AI can self-correct before the human ever sees an error.
---
## Where the mocked seams are
**Payments (AP2 Cart Mandate)**
`merchant/tools.py → place_order()` marks every order `payment_status: "mock_authorized"` and has a comment: `# AP2 Payment Mandate verification would happen here`. In production this is where the AP2 mandate exchange happens: the merchant presents a Cart Mandate to the consumer agent's identity provider, which verifies the agent is authorized to spend on the human's behalf and returns a signed approval before `place_order` proceeds.
Per §11.2 of the spec, `place_order` is already gated behind explicit human confirmation (`AWAITING_CONFIRMATION` state) — the code enforces this, not just a prompt. This is the agentic analogue of 3-D Secure.
**POS (Clover / Toast)**
`merchant/store.py` loads `merchant/data/menu.json` (configurable via `MENU_PATH` env). Replace with a real Clover menu export (the schema is documented in `merchant/models.py`). The `save_order()` call writes to `merchant/data/orders.json` and an in-memory dict — swap in a real POS write-back here.
---
## Menu schema (for real POS integration)
```json
{
"store_id": "tonys-pizza-001",
"items": {
"pizza-veggie": {
"id": "pizza-veggie",
"name": "Veggie Pizza",
"base_price_cents": 1199,
"modifier_group_ids": ["grp-size", "grp-crust", "grp-toppings"],
"available": true,
"category": "pizzas"
}
},
"modifier_groups": {
"grp-size": {
"id": "grp-size",
"name": "Size",
"min_select": 1,
"max_select": 1,
"required": true,
"modifier_ids": ["mod-size-s", "mod-size-m", "mod-size-l"]
}
},
"modifiers": {
"mod-size-l": {
"id": "mod-size-l",
"name": "Large",
"price_delta_cents": 400,
"available": true,
"excludes": []
}
}
}
```
---
## Tech stack
| Layer | Choice |
|---|---|
| Merchant MCP | Python 3.11+, `mcp` SDK v1.27+, FastMCP, `stateless_http=True` |
| LLM | Ollama `qwen3:14b` with `think: false` + `format` (JSON schema constrained decoding) |
| Validation | Pydantic v2, 7 rules, machine-readable error objects with `suggested_fix` |
| Tests | pytest (21 unit), integration scenarios (5 E2E with merchant + LLM) |
---
## v2 directions (out of scope for v1)
- **A2A protocol**: replace the custom MCP client with an A2A agent-to-agent discovery layer so any AI assistant can find and order from Tony's without bespoke integration
- **Real AP2 mandates**: wire in actual Cart Mandate exchange for agentic payments
- **Multi-restaurant discovery**: a directory MCP server that routes the consumer agent to the right merchant
- **Delivery logistics**: extend `place_order` to include delivery address + ETA from a mock logistics API
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues