Skip to main content
Glama
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