diff --git a/a2a/DEVELOPER_GUIDE.md b/a2a/DEVELOPER_GUIDE.md
new file mode 100644
index 0000000..c46b7f0
--- /dev/null
+++ b/a2a/DEVELOPER_GUIDE.md
@@ -0,0 +1,82 @@
+# Cymbal Retail Agent - Developer Guide
+
+## TL;DR
+
+- **ADK Agent** with 8 shopping tools (search, checkout, payment) using Gemini 3.0 Flash
+- **UCP Integration** for standardized commerce data types and capability negotiation
+- **A2A Protocol** for agent discovery and JSON-RPC messaging
+
+## About This Guide
+
+This guide is for developers who want to understand how the sample works internally.
+
+**Prerequisites:** Complete the [Quick Start in README.md](README.md#quick-start) first.
+
+**What you'll learn:**
+- System architecture and component responsibilities
+- How UCP and A2A protocols integrate with ADK
+- The checkout state machine and commerce flows
+- How to extend and customize the sample
+
+## Reading Roadmap
+
+Choose your path based on your goal:
+
+| Goal | Start Here | Then Read |
+|------|------------|-----------|
+| **New to AI agents?** | [Glossary](docs/00-glossary.md) | This guide → 01-architecture |
+| **Understand the system** | [Architecture](docs/01-architecture.md) | 02-adk-agent → 03-ucp-integration |
+| **Add a new tool** | [ADK Agent](docs/02-adk-agent.md) | 06-extending |
+| **Modify checkout flow** | [Commerce Flows](docs/04-commerce-flows.md) | 06-extending |
+| **Customize the UI** | [Frontend](docs/05-frontend.md) | - |
+| **Debug an issue** | [Testing Guide](docs/07-testing-guide.md) | - |
+| **Deploy to production** | [Production Notes](docs/08-production-notes.md) | - |
+| **Use AI assistant** | [SKILLS.md](SKILLS.md) | Context for Claude Code, Gemini CLI, Cursor, Codex |
+
+## Architecture
+
+
+

+
System architecture — Chat Client (React + A2A Client) communicates via JSON-RPC to the Cymbal Retail Agent (A2A Server → ADKAgentExecutor → ADK Agent → RetailStore). Discovery endpoints expose agent capabilities and UCP profile.
+
+
+## Quick Reference
+
+### Key Files
+
+| File | Purpose |
+|------|---------|
+| `business_agent/src/business_agent/agent.py` | ADK agent + 8 tools |
+| `business_agent/src/business_agent/store.py` | Checkout state machine |
+| `business_agent/src/business_agent/agent_executor.py` | A2A ↔ ADK bridge |
+| `chat-client/App.tsx` | React app + A2A messaging |
+
+### Endpoints
+
+| Endpoint | Purpose |
+|----------|---------|
+| `GET /.well-known/agent-card.json` | A2A agent discovery |
+| `GET /.well-known/ucp` | UCP merchant profile |
+| `POST /` | A2A JSON-RPC endpoint |
+
+### State Keys
+
+| Key | Purpose |
+|-----|---------|
+| `user:checkout_id` | Current checkout session |
+| `__ucp_metadata__` | Negotiated capabilities |
+| `__payment_data__` | Payment instrument |
+
+## Deep Dive Guides
+
+| Guide | Topics |
+|-------|--------|
+| [Glossary](docs/00-glossary.md) | Key terms, acronyms, state keys |
+| [Architecture](docs/01-architecture.md) | System components, data flow |
+| [ADK Agent](docs/02-adk-agent.md) | Tools, callbacks, session management |
+| [UCP Integration](docs/03-ucp-integration.md) | Capabilities, profiles, negotiation |
+| [Commerce Flows](docs/04-commerce-flows.md) | Checkout lifecycle, payment |
+| [Frontend](docs/05-frontend.md) | React components, A2A client |
+| [Extending](docs/06-extending.md) | Add tools, products, capabilities |
+| [Testing Guide](docs/07-testing-guide.md) | Testing, debugging, troubleshooting |
+| [Production Notes](docs/08-production-notes.md) | Security gaps, deployment checklist |
diff --git a/a2a/README.md b/a2a/README.md
index cf95542..7e22ad1 100644
--- a/a2a/README.md
+++ b/a2a/README.md
@@ -39,7 +39,7 @@ The sample uses **[Google ADK](https://google.github.io/adk-docs/)** (Agent Deve
Complete Shopping Flow
Product search → Add items to Checkout → Payment → Order confirmation
-
+
▶️ Watch the full demo video
@@ -50,7 +50,7 @@ The sample uses **[Google ADK](https://google.github.io/adk-docs/)** (Agent Deve
System Architecture
How Client, A2A Protocol, Cymbal Retail Agent, and Store interact
-
+
**Key points:**
@@ -103,13 +103,13 @@ This starts the Cymbal Retail Agent on port 10999. You can verify by accessing:
Agent Card
/.well-known/agent-card.json
-
+
Declares UCP extension with capabilities
|
UCP Profile
/.well-known/ucp
-
+
Defines supported capabilities & payment handlers
|
@@ -139,7 +139,7 @@ The Chat Client UCP Profile can be found at http://localhost:3000/profile/agent-
-
+
▶️ Watch full video
|
@@ -158,6 +158,19 @@ The Chat Client UCP Profile can be found at http://localhost:3000/profile/agent-
|
+> **Note**: This sample is for demonstration purposes only. See [Production Notes](docs/08-production-notes.md) for security considerations and deployment requirements.
+
+## Next Steps
+
+Ready to understand how it works?
+
+| Goal | Resource |
+|------|----------|
+| **Understand the architecture** | [Developer Guide](DEVELOPER_GUIDE.md) |
+| **Deep dive into code** | [Architecture](docs/01-architecture.md) |
+| **Extend the sample** | [Extending Guide](docs/06-extending.md) |
+| **AI assistant context** | [SKILLS.md](SKILLS.md) - Context for Claude Code, Gemini CLI, Cursor, Codex |
+
## What is UCP?
**Universal Commerce Protocol (UCP)** is an open standard that enables interoperability between commerce platforms, merchants, and payment providers. It provides standardized data types for commerce transactions.
@@ -177,40 +190,6 @@ This sample uses the following UCP capabilities:
| **[A2A Protocol](https://a2a-protocol.org/latest/)** | Communication | Agent discovery via Agent Card, JSON-RPC messaging, task management |
| **[UCP](https://ucp.dev)** | Commerce Standard | Standardized product, checkout, payment, and order data types |
-## Components
-
-### Cymbal Retail Agent (`business_agent/`)
-
-AI shopping assistant built with Google ADK, exposed via A2A interface with UCP extension.
-
-| File | Purpose |
-|------|---------|
-| `agent.py` | ADK Agent with Gemini 3.0 Flash + 8 shopping tools |
-| `agent_executor.py` | Bridges ADK ↔ A2A protocol |
-| `store.py` | Mock RetailStore (products, checkouts, orders) |
-| `data/ucp.json` | UCP Profile served at `/.well-known/ucp` |
-
-### Chat Client (`chat-client/`)
-
-React UI (TypeScript, Vite, Tailwind) that communicates via A2A and renders UCP data types.
-
-| File | Purpose |
-|------|---------|
-| `App.tsx` | A2A messaging + state management |
-| `components/` | ProductCard, Checkout, PaymentMethodSelector |
-| `profile/agent_profile.json` | Client's UCP capabilities |
-
-## Mock Store
-
-The Cymbal Retail Agent uses an in-memory `RetailStore` to simulate a real backend:
-
-- **Products** - Loaded from `data/products.json` (cookies, chips, fruits, etc.)
-- **Checkouts** - Session-based checkout management with line items
-- **Orders** - Created when checkout completes successfully
-- **Payments** - Mock processor simulates payment flow
-
-Prices are in USD, tax is calculated at 10%, and shipping costs vary by method.
-
## Related Resources
- [UCP Specification](https://ucp.dev/specification/overview/)
diff --git a/a2a/SKILLS.md b/a2a/SKILLS.md
new file mode 100644
index 0000000..04eba4e
--- /dev/null
+++ b/a2a/SKILLS.md
@@ -0,0 +1,258 @@
+---
+name: cymbal-retail-agent
+description: AI-powered shopping agent built with Google ADK, demonstrating UCP commerce integration via A2A protocol. Use this context when working on the Cymbal Retail Agent codebase.
+triggers:
+ - working on a2a sample
+ - modifying agent tools
+ - updating checkout flow
+ - adding UCP capabilities
+ - debugging A2A communication
+globs:
+ - "a2a/**/*"
+ - "business_agent/**/*"
+ - "chat-client/**/*"
+---
+
+# Cymbal Retail Agent - AI Assistant Context
+
+> **Purpose**: This file provides context for AI coding assistants (Claude Code, Gemini CLI, Cursor, Codex, etc.) to understand and extend the Cymbal Retail Agent codebase.
+
+AI-powered shopping agent built with Google ADK, demonstrating UCP commerce integration via A2A protocol.
+
+## Tech Stack
+
+| Layer | Technology |
+|-------|------------|
+| Agent Framework | [Google ADK](https://google.github.io/adk-docs/) (Agent Development Kit) |
+| LLM | Gemini 3.0 Flash |
+| Commerce Protocol | [UCP](https://ucp.dev/) (Universal Commerce Protocol) |
+| Agent Protocol | [A2A](https://a2a-protocol.org/) (Agent-to-Agent) JSON-RPC 2.0 |
+| Backend | Python 3.13, Uvicorn, Starlette, Pydantic |
+| Frontend | React 19, TypeScript, Vite, Tailwind |
+
+## Directory Structure
+
+```
+a2a/
+├── business_agent/src/business_agent/
+│ ├── agent.py # ADK agent with 8 shopping tools
+│ ├── agent_executor.py # A2A ↔ ADK bridge
+│ ├── store.py # Mock retail store (replace for production)
+│ ├── main.py # Uvicorn server entry point
+│ ├── ucp_profile_resolver.py # UCP capability negotiation
+│ ├── payment_processor.py # Mock payment processing
+│ ├── constants.py # State keys and extension URLs
+│ ├── helpers/type_generator.py # Dynamic Pydantic checkout types
+│ ├── a2a_extensions/ # A2A extension implementations
+│ └── data/ # JSON configs and product images
+├── chat-client/
+│ ├── App.tsx # React main component, A2A messaging
+│ ├── components/ # ProductCard, Checkout, PaymentMethodSelector
+│ ├── types.ts # TypeScript interfaces
+│ └── profile/agent_profile.json # Client UCP capabilities
+├── docs/ # Detailed documentation (see below)
+├── DEVELOPER_GUIDE.md # Developer overview and reading roadmap
+├── README.md # Quick start and demo
+└── SKILLS.md # This file (AI assistant context)
+```
+
+## Core Concepts
+
+| Term | Definition |
+|------|------------|
+| **A2A** | Agent-to-Agent Protocol - How agents discover and communicate |
+| **UCP** | Universal Commerce Protocol - Standard commerce data types |
+| **ADK** | Agent Development Kit - Google's framework for building agents |
+| **Tool** | Python function the LLM can invoke (has `ToolContext` parameter) |
+| **Capability** | Feature set the agent supports (e.g., `dev.ucp.shopping.checkout`) |
+
+## State Keys (constants.py)
+
+```python
+# Session state keys - naming conventions:
+# - user: User-scoped data (persists across turns)
+# - __xxx__ System/internal data (managed by framework)
+# - temp: Temporary data (cleared after use)
+
+ADK_USER_CHECKOUT_ID = "user:checkout_id" # Current checkout session ID
+ADK_PAYMENT_STATE = "__payment_data__" # PaymentInstrument from client
+ADK_UCP_METADATA_STATE = "__ucp_metadata__" # Negotiated UCP capabilities
+ADK_EXTENSIONS_STATE_KEY = "__session_extensions__" # Active A2A extensions
+ADK_LATEST_TOOL_RESULT = "temp:LATEST_TOOL_RESULT" # Last tool result for output
+
+# Response data keys (used in tool returns)
+UCP_CHECKOUT_KEY = "a2a.ucp.checkout" # Checkout data in response
+UCP_PAYMENT_DATA_KEY = "a2a.ucp.checkout.payment_data"
+UCP_RISK_SIGNALS_KEY = "a2a.ucp.checkout.risk_signals"
+
+# Extension constants
+A2A_UCP_EXTENSION_URL = "https://ucp.dev/specification/reference?v=2026-01-11"
+UCP_AGENT_HEADER = "UCP-Agent" # HTTP header for client profile
+```
+
+## Agent Tools (agent.py)
+
+| Tool | Purpose | Returns |
+|------|---------|---------|
+| `search_shopping_catalog(query)` | Search products by keyword | ProductResults |
+| `add_to_checkout(product_id, quantity)` | Add item to checkout | Checkout |
+| `remove_from_checkout(product_id)` | Remove item from checkout | Checkout |
+| `update_checkout(product_id, quantity)` | Update item quantity | Checkout |
+| `get_checkout()` | Get current checkout state | Checkout |
+| `update_customer_details(email, address...)` | Set buyer and delivery info | Checkout |
+| `start_payment()` | Validate checkout, set ready status | Checkout |
+| `complete_checkout()` | Process payment, create order | Checkout + OrderConfirmation |
+
+## Checkout State Machine
+
+```
+incomplete → ready_for_complete → completed
+ ↑ ↑ ↑
+ add item start_payment complete_checkout
+```
+
+| State | Meaning | Transition |
+|-------|---------|------------|
+| `incomplete` | Missing buyer email or fulfillment address | Add required info |
+| `ready_for_complete` | All info collected, awaiting payment | Call `complete_checkout()` |
+| `completed` | Order created with OrderConfirmation | Terminal state |
+
+## UCP Capabilities
+
+```
+dev.ucp.shopping.checkout # Base checkout capability
+dev.ucp.shopping.fulfillment # Shipping (extends checkout)
+dev.ucp.shopping.discount # Promotional codes (extends checkout)
+dev.ucp.shopping.buyer_consent # Consent management (extends checkout)
+```
+
+## Common Tasks
+
+### Add a New Tool
+```python
+# In agent.py
+def my_tool(tool_context: ToolContext, param: str) -> dict:
+ """Tool docstring (visible to LLM for reasoning)."""
+ # 1. Access state
+ checkout_id = tool_context.state.get(ADK_USER_CHECKOUT_ID)
+ metadata = tool_context.state.get(ADK_UCP_METADATA_STATE)
+
+ # 2. Validate
+ if not metadata:
+ return {"message": "Missing UCP metadata", "status": "error"}
+
+ # 3. Business logic
+ result = store.some_method(...)
+
+ # 4. Update state if needed
+ tool_context.state[ADK_USER_CHECKOUT_ID] = result.id
+
+ # 5. Return UCP-formatted response
+ return {UCP_CHECKOUT_KEY: result.model_dump(mode="json")}
+
+# Add to root_agent tools list
+root_agent = Agent(..., tools=[..., my_tool])
+```
+
+### Add a Product
+Edit `data/products.json`:
+```json
+{
+ "productID": "NEW-001",
+ "name": "New Product",
+ "image": ["http://localhost:10999/images/new.jpg"],
+ "brand": {"name": "Brand"},
+ "offers": {"price": "9.99", "priceCurrency": "USD", "availability": "InStock"}
+}
+```
+
+### Modify Checkout Flow
+Key methods in `store.py`:
+- `add_to_checkout()` - Creates checkout, adds items
+- `_recalculate_checkout()` - Updates totals, tax, shipping
+- `start_payment()` - Validates readiness, transitions state
+- `place_order()` - Creates OrderConfirmation
+
+## Key Files for Changes
+
+| Change | File |
+|--------|------|
+| Add/modify tools | `agent.py` |
+| Checkout logic | `store.py` |
+| A2A/ADK bridging | `agent_executor.py` |
+| UCP profiles | `data/ucp.json`, `chat-client/profile/agent_profile.json` |
+| Products | `data/products.json` |
+| Frontend components | `chat-client/components/` |
+| Frontend types | `chat-client/types.ts` |
+
+## Commands
+
+```bash
+# Start backend
+cd a2a/business_agent && uv sync && uv run business_agent
+
+# Start frontend
+cd a2a/chat-client && npm install && npm run dev
+
+# Verify endpoints
+curl http://localhost:10999/.well-known/agent-card.json
+curl http://localhost:10999/.well-known/ucp
+```
+
+## Response Format Pattern
+
+```python
+# For UCP data (checkout, products)
+return {
+ UCP_CHECKOUT_KEY: checkout.model_dump(mode="json"),
+ "status": "success",
+}
+
+# For errors
+return {"message": "Error description", "status": "error"}
+```
+
+## Production Considerations
+
+> **WARNING**: This sample is NOT production-ready. See `docs/08-production-notes.md`.
+
+| Component | Current | Production |
+|-----------|---------|------------|
+| Session Storage | In-memory | Redis |
+| Checkout Storage | Python dict | PostgreSQL |
+| Authentication | None | JWT/API key |
+| Secrets | Plaintext .env | Secret Manager |
+
+## Documentation
+
+| Guide | Topics |
+|-------|--------|
+| [Glossary](docs/00-glossary.md) | Key terms, acronyms, external resources |
+| [Architecture](docs/01-architecture.md) | System components, data flow, mock store |
+| [ADK Agent](docs/02-adk-agent.md) | Tools, callbacks, prompt engineering |
+| [UCP Integration](docs/03-ucp-integration.md) | Capabilities, profiles, negotiation |
+| [Commerce Flows](docs/04-commerce-flows.md) | Checkout lifecycle, payment flow |
+| [Frontend](docs/05-frontend.md) | React components, A2A client |
+| [Extending](docs/06-extending.md) | Add tools, products, capabilities |
+| [Testing Guide](docs/07-testing-guide.md) | Testing, debugging, troubleshooting |
+| [Production Notes](docs/08-production-notes.md) | Security gaps, deployment checklist |
+
+## External Resources
+
+| Resource | URL |
+|----------|-----|
+| **ADK Docs** | https://google.github.io/adk-docs/ |
+| **A2A Protocol** | https://a2a-protocol.org/latest/ |
+| **UCP Specification** | https://ucp.dev/specification/overview/ |
+| **Gemini API** | https://ai.google.dev/gemini-api/docs |
+
+## Dependencies
+
+**Backend** (pyproject.toml):
+- `google-adk[a2a]>=1.22.0`
+- `ucp-sdk==0.1.0`
+- `pydantic>=2.12.3`
+
+**Frontend** (package.json):
+- `react ^19.2.0`
+- `vite ^6.2.0`
diff --git a/a2a/assets/agent_card.png b/a2a/assets/agent_card.png
deleted file mode 100644
index c25bf1c..0000000
Binary files a/a2a/assets/agent_card.png and /dev/null differ
diff --git a/a2a/assets/agent_card.webp b/a2a/assets/agent_card.webp
new file mode 100644
index 0000000..07be348
Binary files /dev/null and b/a2a/assets/agent_card.webp differ
diff --git a/a2a/assets/architecture_diagram.jpeg b/a2a/assets/architecture_diagram.jpeg
deleted file mode 100644
index e42a0a9..0000000
Binary files a/a2a/assets/architecture_diagram.jpeg and /dev/null differ
diff --git a/a2a/assets/architecture_diagram.webp b/a2a/assets/architecture_diagram.webp
new file mode 100644
index 0000000..baa6997
Binary files /dev/null and b/a2a/assets/architecture_diagram.webp differ
diff --git a/a2a/assets/diagrams/00_01_architecture_overview.webp b/a2a/assets/diagrams/00_01_architecture_overview.webp
new file mode 100644
index 0000000..b068d8a
Binary files /dev/null and b/a2a/assets/diagrams/00_01_architecture_overview.webp differ
diff --git a/a2a/assets/diagrams/00_01_checkout_states.webp b/a2a/assets/diagrams/00_01_checkout_states.webp
new file mode 100644
index 0000000..545bc60
Binary files /dev/null and b/a2a/assets/diagrams/00_01_checkout_states.webp differ
diff --git a/a2a/assets/diagrams/00_02_architecture_layers.webp b/a2a/assets/diagrams/00_02_architecture_layers.webp
new file mode 100644
index 0000000..90219cd
Binary files /dev/null and b/a2a/assets/diagrams/00_02_architecture_layers.webp differ
diff --git a/a2a/assets/diagrams/01_01_system_overview.webp b/a2a/assets/diagrams/01_01_system_overview.webp
new file mode 100644
index 0000000..27cb161
Binary files /dev/null and b/a2a/assets/diagrams/01_01_system_overview.webp differ
diff --git a/a2a/assets/diagrams/01_02_request_flow.webp b/a2a/assets/diagrams/01_02_request_flow.webp
new file mode 100644
index 0000000..d69faea
Binary files /dev/null and b/a2a/assets/diagrams/01_02_request_flow.webp differ
diff --git a/a2a/assets/diagrams/01_03_mock_store_structure.webp b/a2a/assets/diagrams/01_03_mock_store_structure.webp
new file mode 100644
index 0000000..a6b62eb
Binary files /dev/null and b/a2a/assets/diagrams/01_03_mock_store_structure.webp differ
diff --git a/a2a/assets/diagrams/02_01_tool_execution_flow.webp b/a2a/assets/diagrams/02_01_tool_execution_flow.webp
new file mode 100644
index 0000000..a4d7513
Binary files /dev/null and b/a2a/assets/diagrams/02_01_tool_execution_flow.webp differ
diff --git a/a2a/assets/diagrams/02_02_multi_tool_conversation.webp b/a2a/assets/diagrams/02_02_multi_tool_conversation.webp
new file mode 100644
index 0000000..b148cf2
Binary files /dev/null and b/a2a/assets/diagrams/02_02_multi_tool_conversation.webp differ
diff --git a/a2a/assets/diagrams/03_01_capability_negotiation.webp b/a2a/assets/diagrams/03_01_capability_negotiation.webp
new file mode 100644
index 0000000..3abd70d
Binary files /dev/null and b/a2a/assets/diagrams/03_01_capability_negotiation.webp differ
diff --git a/a2a/assets/diagrams/03_02_type_hierarchy.webp b/a2a/assets/diagrams/03_02_type_hierarchy.webp
new file mode 100644
index 0000000..86f9e09
Binary files /dev/null and b/a2a/assets/diagrams/03_02_type_hierarchy.webp differ
diff --git a/a2a/assets/diagrams/04_01_checkout_state_machine.webp b/a2a/assets/diagrams/04_01_checkout_state_machine.webp
new file mode 100644
index 0000000..3a27fc3
Binary files /dev/null and b/a2a/assets/diagrams/04_01_checkout_state_machine.webp differ
diff --git a/a2a/assets/diagrams/04_02_payment_flow.webp b/a2a/assets/diagrams/04_02_payment_flow.webp
new file mode 100644
index 0000000..b9cc7a3
Binary files /dev/null and b/a2a/assets/diagrams/04_02_payment_flow.webp differ
diff --git a/a2a/assets/diagrams/05_01_request_response_cycle.webp b/a2a/assets/diagrams/05_01_request_response_cycle.webp
new file mode 100644
index 0000000..82a5162
Binary files /dev/null and b/a2a/assets/diagrams/05_01_request_response_cycle.webp differ
diff --git a/a2a/assets/diagrams/05_02_component_hierarchy.webp b/a2a/assets/diagrams/05_02_component_hierarchy.webp
new file mode 100644
index 0000000..19ffa82
Binary files /dev/null and b/a2a/assets/diagrams/05_02_component_hierarchy.webp differ
diff --git a/a2a/assets/diagrams/06_01_extension_decision_tree.webp b/a2a/assets/diagrams/06_01_extension_decision_tree.webp
new file mode 100644
index 0000000..c7380a9
Binary files /dev/null and b/a2a/assets/diagrams/06_01_extension_decision_tree.webp differ
diff --git a/a2a/assets/diagrams/06_02_tool_architecture.webp b/a2a/assets/diagrams/06_02_tool_architecture.webp
new file mode 100644
index 0000000..d1dd8c6
Binary files /dev/null and b/a2a/assets/diagrams/06_02_tool_architecture.webp differ
diff --git a/a2a/assets/diagrams/06_03_capability_hierarchy.webp b/a2a/assets/diagrams/06_03_capability_hierarchy.webp
new file mode 100644
index 0000000..3cb2978
Binary files /dev/null and b/a2a/assets/diagrams/06_03_capability_hierarchy.webp differ
diff --git a/a2a/assets/diagrams/06_04_custom_payment_flow.webp b/a2a/assets/diagrams/06_04_custom_payment_flow.webp
new file mode 100644
index 0000000..ceef7e3
Binary files /dev/null and b/a2a/assets/diagrams/06_04_custom_payment_flow.webp differ
diff --git a/a2a/assets/diagrams/07_01_setup_flow.webp b/a2a/assets/diagrams/07_01_setup_flow.webp
new file mode 100644
index 0000000..923b5c5
Binary files /dev/null and b/a2a/assets/diagrams/07_01_setup_flow.webp differ
diff --git a/a2a/assets/diagrams/07_02_debug_strategy.webp b/a2a/assets/diagrams/07_02_debug_strategy.webp
new file mode 100644
index 0000000..d9688fe
Binary files /dev/null and b/a2a/assets/diagrams/07_02_debug_strategy.webp differ
diff --git a/a2a/assets/diagrams/08_01_current_vs_production.webp b/a2a/assets/diagrams/08_01_current_vs_production.webp
new file mode 100644
index 0000000..9f8f6f8
Binary files /dev/null and b/a2a/assets/diagrams/08_01_current_vs_production.webp differ
diff --git a/a2a/assets/diagrams/08_02_security_gaps.webp b/a2a/assets/diagrams/08_02_security_gaps.webp
new file mode 100644
index 0000000..bccae63
Binary files /dev/null and b/a2a/assets/diagrams/08_02_security_gaps.webp differ
diff --git a/a2a/assets/ucp_a2a_demo.gif b/a2a/assets/ucp_a2a_demo.gif
deleted file mode 100644
index 05254d1..0000000
Binary files a/a2a/assets/ucp_a2a_demo.gif and /dev/null differ
diff --git a/a2a/assets/ucp_a2a_demo.webp b/a2a/assets/ucp_a2a_demo.webp
new file mode 100644
index 0000000..6e33b95
Binary files /dev/null and b/a2a/assets/ucp_a2a_demo.webp differ
diff --git a/a2a/assets/well_known.png b/a2a/assets/well_known.png
deleted file mode 100644
index 51f0840..0000000
Binary files a/a2a/assets/well_known.png and /dev/null differ
diff --git a/a2a/assets/well_known.webp b/a2a/assets/well_known.webp
new file mode 100644
index 0000000..d3da549
Binary files /dev/null and b/a2a/assets/well_known.webp differ
diff --git a/a2a/docs/00-glossary.md b/a2a/docs/00-glossary.md
new file mode 100644
index 0000000..4d8806d
--- /dev/null
+++ b/a2a/docs/00-glossary.md
@@ -0,0 +1,157 @@
+# Glossary
+
+Quick reference for key terms used throughout this documentation.
+
+## TL;DR
+
+- **A2A** = How agents talk to each other (discovery + messaging)
+- **UCP** = Standard data types for commerce (Checkout, LineItem, Payment)
+- **ADK** = Google's framework for building AI agents
+
+---
+
+## Core Concepts
+
+| Term | Definition | Example in This Sample |
+|------|------------|------------------------|
+| **A2A** | Agent-to-Agent Protocol - How AI agents discover and communicate with each other | `/.well-known/agent-card.json` endpoint |
+| **UCP** | Universal Commerce Protocol - Standard data types for commerce transactions | `Checkout`, `LineItem`, `PaymentInstrument` |
+| **ADK** | Agent Development Kit - Google's framework for building agents with tools | `Agent()`, `ToolContext`, `Runner` |
+| **Agent** | In this sample: the Cymbal Retail Agent service (includes LLM + tools + state) | The backend running on port 10999 |
+| **Tool** | A Python function the LLM can invoke to perform actions | `search_shopping_catalog()`, `add_to_checkout()` |
+| **Capability** | A feature set the agent supports, declared in UCP profile | `dev.ucp.shopping.checkout` |
+| **Negotiation** | Client and merchant agreeing on shared capabilities before transacting | Happens when first message is sent |
+
+---
+
+## Protocol Terms
+
+| Term | What It Does | Where to Find It |
+|------|--------------|------------------|
+| **Agent Card** | JSON file declaring agent identity and capabilities | `/.well-known/agent-card.json` |
+| **UCP Profile** | JSON file declaring commerce capabilities and payment handlers | `/.well-known/ucp` |
+| **JSON-RPC 2.0** | Message format used for A2A communication | Request/response structure in A2A calls |
+| **UCP-Agent Header** | HTTP header containing client's profile URL | Sent with every A2A request |
+
+---
+
+## State Management
+
+State is stored in ADK's session service (in-memory by default).
+
+| Key | Purpose | Lifetime |
+|-----|---------|----------|
+| `user:checkout_id` | Current checkout session ID | Until checkout completed or session expires |
+| `__ucp_metadata__` | Negotiated capabilities from client/merchant profiles | Set once per session |
+| `__payment_data__` | Payment instrument for current checkout | Set during payment flow |
+| `__session_extensions__` | Active A2A extensions for this session | Set once per session |
+| `temp:LATEST_TOOL_RESULT` | Temporary storage for last UCP tool response | Cleared after each agent response |
+
+**Naming conventions**:
+- `user:` prefix — User-scoped data (persists across turns)
+- `__` prefix — System/internal data (managed by framework)
+- `temp:` prefix — Temporary data (cleared after use)
+
+---
+
+## Checkout States
+
+The checkout follows a 3-state lifecycle:
+
+
+

+
Figure 1: Checkout state transitions from incomplete → ready_for_complete → completed
+
+
+| State | Meaning | What's Needed to Progress |
+|-------|---------|---------------------------|
+| `incomplete` | Missing required info | Add email, address, or items |
+| `ready_for_complete` | Ready for payment | User confirms payment |
+| `completed` | Order placed successfully | Terminal state - checkout finalized |
+
+---
+
+## ADK Components
+
+| Component | Role | File |
+|-----------|------|------|
+| **Agent** | Orchestrates LLM and tools | `agent.py` |
+| **Tool** | Individual function the LLM can call | Defined in `agent.py` |
+| **ToolContext** | Provides state access to tools | Passed to each tool function |
+| **Runner** | Executes agent with session management | `InMemoryRunner` |
+| **Session** | Stores conversation history and state | `InMemorySessionService` |
+| **Callback** | Hook to modify tool/agent output | `after_tool_callback`, `after_agent_callback` |
+
+---
+
+## Architecture Layers
+
+
+

+
Figure 2: Vertical stack from Chat Client through A2A Server, Agent Executor, ADK Agent, to RetailStore
+
+
+---
+
+## Common Acronyms
+
+| Acronym | Full Name | Context |
+|---------|-----------|---------|
+| A2A | Agent-to-Agent | Protocol for agent communication |
+| UCP | Universal Commerce Protocol | Commerce data standard |
+| ADK | Agent Development Kit | Google's agent framework |
+| LLM | Large Language Model | Gemini 3.0 Flash in this sample |
+| SDK | Software Development Kit | UCP Python SDK |
+
+---
+
+## External Resources
+
+Official documentation for the core technologies used in this sample.
+
+### ADK (Agent Development Kit)
+
+| Resource | URL |
+|----------|-----|
+| **Official Docs** | [google.github.io/adk-docs](https://google.github.io/adk-docs/) |
+| **Getting Started** | [ADK Get Started Guide](https://google.github.io/adk-docs/get-started/) |
+| **Agents Guide** | [Building Agents](https://google.github.io/adk-docs/agents/) |
+| **GitHub (Python SDK)** | [github.com/google/adk-python](https://github.com/google/adk-python) |
+| **Google Cloud Docs** | [Vertex AI Agent Builder](https://docs.cloud.google.com/agent-builder/agent-development-kit/overview) |
+
+### A2A (Agent-to-Agent Protocol)
+
+| Resource | URL |
+|----------|-----|
+| **Official Protocol Site** | [a2a-protocol.org](https://a2a-protocol.org/latest/) |
+| **Specification** | [A2A Specification](https://a2a-protocol.org/latest/specification/) |
+| **ADK Integration** | [ADK with A2A](https://google.github.io/adk-docs/a2a/) |
+| **GitHub Repository** | [github.com/a2aproject/A2A](https://github.com/a2aproject/A2A) |
+| **Google Cloud Docs** | [A2A Agents on Cloud Run](https://docs.cloud.google.com/run/docs/ai/a2a-agents) |
+
+### UCP (Universal Commerce Protocol)
+
+| Resource | URL |
+|----------|-----|
+| **Official Site** | [ucp.dev](https://ucp.dev/) |
+| **Specification Overview** | [UCP Specification](https://ucp.dev/specification/overview/) |
+| **Developer Guide** | [Google Merchant UCP Guide](https://developers.google.com/merchant/ucp) |
+| **GitHub Repository** | [github.com/Universal-Commerce-Protocol/ucp](https://github.com/Universal-Commerce-Protocol/ucp) |
+| **Python SDK** | [github.com/Universal-Commerce-Protocol/python-sdk](https://github.com/Universal-Commerce-Protocol/python-sdk) |
+
+### Related Technologies
+
+| Technology | Documentation |
+|------------|---------------|
+| **Gemini API** | [ai.google.dev/gemini-api/docs](https://ai.google.dev/gemini-api/docs) |
+| **MCP (Model Context Protocol)** | [modelcontextprotocol.io](https://modelcontextprotocol.io/) |
+| **JSON-RPC 2.0** | [jsonrpc.org/specification](https://www.jsonrpc.org/specification) |
+
+---
+
+## Related Documentation
+
+- [Architecture Overview](01-architecture.md) - System components
+- [ADK Agent Guide](02-adk-agent.md) - Tool and callback patterns
+- [UCP Integration](03-ucp-integration.md) - Capability negotiation
+- [Commerce Flows](04-commerce-flows.md) - Checkout state machine
diff --git a/a2a/docs/01-architecture.md b/a2a/docs/01-architecture.md
new file mode 100644
index 0000000..29c9757
--- /dev/null
+++ b/a2a/docs/01-architecture.md
@@ -0,0 +1,175 @@
+# System Architecture
+
+## TL;DR
+
+- **4 layers**: A2A Server → Agent Executor → ADK Agent → Retail Store
+- **2 protocols**: A2A (agent communication) + UCP (commerce data)
+- **Request flow**: JSON-RPC → ADK Runner → Tool execution → Response
+
+## System Overview
+
+
+

+
Figure 1: System architecture showing the 4-layer structure — Chat Client (React), A2A Server, ADK Agent Layer, and Business Layer with their components and connections.
+
+
+The architecture follows a clean separation of concerns:
+
+- **Chat Client (React :3000)** — User interface, A2A messaging client, and CredentialProviderProxy for mock payments
+- **Cymbal Retail Agent (Python :10999)** — A2A Starlette server, ADKAgentExecutor bridge, and ProfileResolver for UCP negotiation
+- **ADK Layer** — Runner for execution, Agent with 8 shopping tools, and Session service for state
+- **Business Layer** — RetailStore for products/checkouts/orders and MockPaymentProcessor for payment simulation
+
+## Components
+
+### Backend
+
+| Component | File | Responsibility |
+|-----------|------|----------------|
+| A2A Server | `main.py` | HTTP server, routing, static files |
+| Agent Executor | `agent_executor.py` | Bridge A2A ↔ ADK, session management |
+| Profile Resolver | `ucp_profile_resolver.py` | UCP capability negotiation |
+| ADK Agent | `agent.py` | LLM reasoning, tool execution |
+| Retail Store | `store.py` | Products, checkouts, orders |
+| Payment Processor | `payment_processor.py` | Mock payment handling |
+
+### Frontend
+
+| Component | File | Responsibility |
+|-----------|------|----------------|
+| App | `App.tsx` | State management, A2A messaging |
+| ChatMessage | `components/ChatMessage.tsx` | Message rendering |
+| Checkout | `components/Checkout.tsx` | Checkout display |
+| ProductCard | `components/ProductCard.tsx` | Product cards |
+| PaymentMethodSelector | `components/PaymentMethodSelector.tsx` | Payment selection |
+
+## Request Flow
+
+
+

+
Figure 2: Request flow from user query through A2A Server, Agent Executor, ADK Agent, to RetailStore and back. Shows the tool execution loop and callback processing.
+
+
+**Key steps in the request flow:**
+
+1. **React UI** sends a POST request with JSON-RPC payload and `UCP-Agent` header
+2. **A2A Server** routes to the AgentExecutor
+3. **AgentExecutor** resolves UCP profile, prepares input, and gets/creates session
+4. **ADK Agent** runs via `Runner.run_async()` and executes tools as needed
+5. **Tool execution loop** — Agent calls store methods, receives results, triggers `after_tool_callback`
+6. **Response path** — `after_agent_callback` processes final response, returns Parts[] to client
+
+## Layer Responsibilities
+
+| Layer | Input | Output | Key Class |
+|-------|-------|--------|-----------|
+| **A2A Server** | HTTP request | HTTP response | `A2AStarletteApplication` |
+| **Agent Executor** | A2A context | Event queue | `ADKAgentExecutor` |
+| **ADK Agent** | User query + state | Tool results | `Agent` (google.adk) |
+| **Retail Store** | Method calls | Domain objects | `RetailStore` |
+
+## Mock Store Architecture
+
+### Why a Mock Store?
+
+The sample uses an in-memory mock store (`store.py`) to demonstrate UCP integration without requiring a real commerce backend. This lets you:
+
+- **Run standalone** - Zero external dependencies (no database, no API keys beyond Gemini)
+- **Learn the patterns** - Understand UCP/ADK integration before connecting real systems
+- **Prototype quickly** - Test new features without backend complexity
+
+### Store Structure
+
+
+

+
Figure 3: Mock store architecture showing the integration layer (keep), mock layer (replace), and your backend implementation. Solid arrows show current data flow; dashed arrows show migration paths.
+
+
+The diagram illustrates the separation between:
+
+- **Keep These (Integration Layer)** — Agent Tools, RetailStore Methods, UCP Type Generation — these patterns remain the same regardless of backend
+- **Replace These (Mock Layer)** — products.json, In-Memory Dict, MockPaymentProcessor — swap these with real implementations
+- **Your Backend** — Commerce API (Shopify, Magento), Database, Payment Provider (Stripe, Adyen)
+
+| Storage | Type | Purpose |
+|---------|------|---------|
+| `_products` | `dict[str, Product]` | Product catalog (loaded from `products.json`) |
+| `_checkouts` | `dict[str, Checkout]` | Active shopping sessions |
+| `_orders` | `dict[str, Checkout]` | Completed orders |
+
+### Key Methods
+
+| Method | Line | Called By | Purpose |
+|--------|------|-----------|---------|
+| `search_products()` | 100 | `search_shopping_catalog` tool | Keyword search in catalog |
+| `add_to_checkout()` | 186 | `add_to_checkout` tool | Create/update checkout session |
+| `get_checkout()` | 244 | `get_checkout` tool | Retrieve current checkout state |
+| `start_payment()` | 463 | `start_payment` tool | Validate checkout for payment |
+| `place_order()` | 498 | `complete_checkout` tool | Finalize order, generate confirmation |
+
+### Replacing with Real Backend
+
+To connect a real commerce platform (Shopify, Magento, custom API):
+
+**1. Create interface** (recommended for clean separation):
+
+```python
+# interfaces.py
+from abc import ABC, abstractmethod
+
+class IRetailStore(ABC):
+ @abstractmethod
+ def search_products(self, query: str) -> ProductResults: ...
+
+ @abstractmethod
+ def add_to_checkout(self, checkout_id: str | None, product_id: str,
+ quantity: int, ucp_metadata: UcpMetadata) -> Checkout: ...
+
+ @abstractmethod
+ def get_checkout(self, checkout_id: str) -> Checkout | None: ...
+```
+
+**2. Implement adapter** for your platform:
+
+```python
+# shopify_store.py
+class ShopifyStore(IRetailStore):
+ def __init__(self, api_key: str, store_url: str):
+ self.client = ShopifyClient(api_key, store_url)
+
+ def search_products(self, query: str) -> ProductResults:
+ shopify_products = self.client.products.search(query)
+ # Convert to UCP ProductResults format
+ return ProductResults(results=[...])
+```
+
+**3. Swap in agent.py** (line 43):
+
+```python
+# Before
+store = RetailStore()
+
+# After
+store = ShopifyStore(
+ api_key=os.getenv("SHOPIFY_API_KEY"),
+ store_url=os.getenv("SHOPIFY_STORE_URL")
+)
+```
+
+### What to Keep vs Replace
+
+| Keep (UCP Patterns) | Replace (Mock Specifics) |
+|---------------------|--------------------------|
+| Tool function signatures | Data storage layer |
+| State management via ToolContext | Product catalog source |
+| Checkout type generation | Tax/shipping calculation |
+| Response formatting with UCP keys | Payment processing |
+| A2A/ADK bridging | Order persistence |
+
+## Discovery Endpoints
+
+| Endpoint | Purpose | Source |
+|----------|---------|--------|
+| `/.well-known/agent-card.json` | A2A agent capabilities | `data/agent_card.json` |
+| `/.well-known/ucp` | UCP merchant profile | `data/ucp.json` |
+| `/images/*` | Product images | `data/images/` |
diff --git a/a2a/docs/02-adk-agent.md b/a2a/docs/02-adk-agent.md
new file mode 100644
index 0000000..b8bf9f3
--- /dev/null
+++ b/a2a/docs/02-adk-agent.md
@@ -0,0 +1,358 @@
+# ADK Agent Patterns
+
+## TL;DR
+
+- **Agent**: Gemini 3.0 Flash with 8 shopping tools
+- **Tools**: Access `ToolContext` for state, return dict with UCP keys
+- **Callbacks**: `after_tool_callback` captures results, `after_agent_callback` formats output
+
+## Agent Configuration
+
+```python
+# agent.py:437
+root_agent = Agent(
+ name="shopper_agent",
+ model="gemini-3-flash-preview",
+ description="Agent to help with shopping",
+ instruction="You are a helpful agent who can help user with shopping...",
+ tools=[
+ search_shopping_catalog,
+ add_to_checkout,
+ remove_from_checkout,
+ update_checkout,
+ get_checkout,
+ start_payment,
+ update_customer_details,
+ complete_checkout,
+ ],
+ after_tool_callback=after_tool_modifier,
+ after_agent_callback=modify_output_after_agent,
+)
+```
+
+## Tool Pattern
+
+Every tool follows this pattern:
+
+```python
+def tool_function(tool_context: ToolContext, param: str) -> dict:
+ """Docstring visible to LLM for reasoning."""
+
+ # 1. Get state
+ checkout_id = tool_context.state.get(ADK_USER_CHECKOUT_ID)
+ metadata = tool_context.state.get(ADK_UCP_METADATA_STATE)
+
+ # 2. Validate
+ if not metadata:
+ return _create_error_response("Missing UCP metadata")
+
+ # 3. Execute business logic
+ try:
+ result = store.method(...)
+ except ValueError as e:
+ return _create_error_response(str(e))
+
+ # 4. Update state if needed
+ tool_context.state[ADK_USER_CHECKOUT_ID] = result.id
+
+ # 5. Return UCP-formatted response
+ return {UCP_CHECKOUT_KEY: result.model_dump(mode="json")}
+```
+
+## All 8 Tools
+
+| Tool | Line | Purpose | State Access |
+|------|------|---------|--------------|
+| `search_shopping_catalog` | 51 | Search products | Read metadata |
+| `add_to_checkout` | 73 | Add item to cart | Read/write checkout_id |
+| `remove_from_checkout` | 115 | Remove item | Read checkout_id |
+| `update_checkout` | 151 | Update quantity | Read checkout_id |
+| `get_checkout` | 187 | Get current state | Read checkout_id |
+| `update_customer_details` | 212 | Set buyer/address | Read checkout_id |
+| `start_payment` | 340 | Begin payment flow | Read checkout_id |
+| `complete_checkout` | 270 | Finalize order | Read checkout_id, payment |
+
+## Tool Execution Flow
+
+
+

+
Figure 1: Tool execution flow from user query through the ADK Agent (LLM + Tool Selection), Tool execution (ToolContext, State, Business Logic), to the data store (Products, Checkouts).
+
+
+The flow illustrates how each tool invocation works:
+
+- **Input** — User's natural language query enters the system
+- **Agent** — Gemini 3.0 Flash reasons about the query and selects the appropriate tool
+- **Tool** — ToolContext provides state access, then business logic executes
+- **Store** — Products and Checkouts data are queried or modified
+
+### Multi-Tool Conversation Flow
+
+In a typical shopping session, multiple tools are called across turns:
+
+
+

+
Figure 2: A complete shopping conversation showing 4 steps — product search, add to cart, customer details, and payment initiation. Each step involves tool execution and state updates.
+
+
+**The 4-step shopping flow:**
+
+1. **Product Search** — User asks for cookies → `search_shopping_catalog` returns ProductResults
+2. **Add to Cart** — User selects item → `add_to_checkout` creates Checkout (status: incomplete)
+3. **Customer Details** — User provides email/address → `update_customer_details` adds buyer + fulfillment
+4. **Payment** — User says "checkout" → `start_payment` sets status to ready_for_complete
+
+## Callbacks
+
+### Why Callbacks?
+
+ADK callbacks solve a key problem: **the LLM sees tool results as text, but the frontend needs structured data**.
+
+Without callbacks:
+- Tool returns `{UCP_CHECKOUT_KEY: {...checkout data...}}`
+- LLM summarizes: "Added cookies to your cart for $4.99"
+- Frontend only sees text, can't render checkout UI
+
+With callbacks:
+- `after_tool_callback` captures the structured data in state
+- `after_agent_callback` attaches it to the response as a `data` part
+- Frontend receives both text AND structured data for rich UI
+
+### after_tool_callback
+
+Captures UCP data from tool results for later use:
+
+```python
+# agent.py:379
+def after_tool_modifier(
+ tool: BaseTool,
+ args: dict[str, Any],
+ tool_context: ToolContext,
+ tool_response: dict,
+) -> dict | None:
+ """Stores UCP responses in state for output transformation."""
+ extensions = tool_context.state.get(ADK_EXTENSIONS_STATE_KEY, [])
+ ucp_response_keys = [UCP_CHECKOUT_KEY, "a2a.product_results"]
+
+ # Only capture if UCP extension is active
+ if UcpExtension.URI in extensions and any(
+ key in tool_response for key in ucp_response_keys
+ ):
+ tool_context.state[ADK_LATEST_TOOL_RESULT] = tool_response
+
+ return None # Don't modify the response
+```
+
+### after_agent_callback
+
+Transforms agent output to include structured data:
+
+```python
+# agent.py:408
+from google.genai import types
+
+def modify_output_after_agent(
+ callback_context: CallbackContext,
+) -> types.Content | None:
+ """Adds UCP data parts to agent's response."""
+ latest_result = callback_context.state.get(ADK_LATEST_TOOL_RESULT)
+ if latest_result:
+ # Create function response with UCP data
+ return types.Content(
+ parts=[
+ types.Part(
+ function_response=types.FunctionResponse(
+ response={"result": latest_result}
+ )
+ )
+ ],
+ role="model",
+ )
+ return None
+```
+
+## Session & State Management
+
+### State Keys
+
+```python
+# constants.py
+ADK_USER_CHECKOUT_ID = "user:checkout_id" # Checkout session ID
+ADK_PAYMENT_STATE = "__payment_data__" # Payment instrument
+ADK_UCP_METADATA_STATE = "__ucp_metadata__" # Capabilities
+ADK_EXTENSIONS_STATE_KEY = "__session_extensions__"
+ADK_LATEST_TOOL_RESULT = "temp:LATEST_TOOL_RESULT"
+```
+
+### State Flow
+
+1. **Request arrives** → Executor builds initial state delta
+2. **Tools execute** → Read/write state via `tool_context.state`
+3. **Callbacks fire** → Capture results in state
+4. **Response sent** → State persisted in session
+
+## ADK → A2A Bridge
+
+`ADKAgentExecutor` bridges the protocols:
+
+```python
+# agent_executor.py
+class ADKAgentExecutor:
+ async def execute(self, context, event_queue):
+ # 1. Activate extensions
+ self._activate_extensions(context)
+
+ # 2. Prepare UCP metadata
+ ucp_metadata = UcpRequestProcessor.prepare_ucp_metadata(context)
+
+ # 3. Extract input
+ query, payment_data = self._prepare_input(context)
+
+ # 4. Build message content
+ content = types.Content(
+ role="user", parts=[types.Part.from_text(text=query)]
+ )
+
+ # 5. Build state delta
+ state_delta = self._build_initial_state_delta(
+ context, ucp_metadata, payment_data
+ )
+
+ # 6. Run agent (async iterator)
+ async for event in self.runner.run_async(
+ user_id=user_id,
+ session_id=session_id,
+ new_message=content,
+ state_delta=state_delta,
+ ):
+ if event.is_final_response():
+ # Process final response parts
+ result_parts = self._process_event(event)
+
+ # 7. Enqueue response
+ event_queue.enqueue(result_parts)
+```
+
+## Prompt Engineering
+
+### Current System Instruction
+
+The agent uses a single instruction (`agent.py:441-454`):
+
+```python
+instruction=(
+ "You are a helpful agent who can help user with shopping actions such"
+ " as searching the catalog, add to checkout session, complete checkout"
+ " and handle order placed event. Given the user ask, plan ahead and"
+ " invoke the tools available to complete the user's ask. Always make"
+ " sure you have completed all aspects of the user's ask. If the user"
+ " says add to my list or remove from the list, add or remove from the"
+ " cart, add the product or remove the product from the checkout"
+ " session. If the user asks to add any items to the checkout session,"
+ " search for the products and then add the matching products to"
+ " checkout session. If the user asks to replace products,"
+ " use remove_from_checkout and add_to_checkout tools to replace the"
+ " products to match the user request"
+)
+```
+
+### Improving the Instruction
+
+For production agents, consider structured prompting with explicit tool ordering and error handling:
+
+```python
+instruction="""
+You are a shopping assistant for Cymbal Retail. Help users find products
+and complete purchases.
+
+TOOLS (use in this order when applicable):
+1. search_shopping_catalog - Always search first when user asks for products
+2. add_to_checkout - Add items after finding them
+3. update_customer_details - Collect email and address before payment
+4. start_payment - Begin payment when customer info is complete
+5. complete_checkout - Finalize after payment is confirmed
+
+RULES:
+- Always search before adding items (don't guess product IDs)
+- Never assume addresses or payment methods - ask the user
+- If a tool returns an error, explain it clearly and suggest next steps
+- Confirm quantities and prices before proceeding to payment
+
+ERROR HANDLING:
+- "Product not found" → Ask user to clarify product name or show alternatives
+- "Missing address" → Politely ask for shipping address
+- "Checkout not found" → Help user add items first
+- "Payment declined" → Explain and offer to try different payment method
+"""
+```
+
+### Model Configuration
+
+| Setting | Current Value | Purpose |
+|---------|---------------|---------|
+| `model` | `gemini-3-flash-preview` | Fast, accurate tool calling |
+| `temperature` | Default (not set) | Balanced creativity vs determinism |
+| `max_tokens` | Default (not set) | Response length limit |
+
+**Model Selection Guide:**
+
+| Model | Best For | Tradeoff |
+|-------|----------|----------|
+| Gemini 3.0 Flash | Tool-heavy agents (this sample) | Fastest, 99% tool accuracy |
+| Gemini 2.0 Pro | Complex reasoning, ambiguous queries | Slower, better nuanced understanding |
+
+To change the model, edit `agent.py:437`:
+
+```python
+root_agent = Agent(
+ model="gemini-2-flash", # or other model ID
+ ...
+)
+```
+
+### Tool Docstring Best Practices
+
+The LLM uses tool docstrings to decide when to call each tool. Clear docstrings improve tool selection accuracy:
+
+```python
+# GOOD: Clear, specific docstring
+def search_shopping_catalog(tool_context: ToolContext, query: str) -> dict:
+ """Search the product catalog for items matching the query.
+
+ Use this tool when the user asks about products, wants to browse items,
+ or needs to find something to buy.
+
+ Args:
+ query: Product name, category, or description to search for.
+ Examples: "cookies", "chocolate chip", "snacks under $5"
+
+ Returns:
+ List of matching products with names, prices, and availability.
+ """
+
+# BAD: Vague docstring
+def search_shopping_catalog(tool_context: ToolContext, query: str) -> dict:
+ """Search products.""" # LLM won't know when to use this
+```
+
+## Adding a New Tool
+
+1. **Define function** with `ToolContext` parameter:
+
+```python
+def my_new_tool(tool_context: ToolContext, param: str) -> dict:
+ """Description for LLM reasoning."""
+ # Implementation
+ return {UCP_CHECKOUT_KEY: result.model_dump(mode="json")}
+```
+
+2. **Add to agent**:
+
+```python
+root_agent = Agent(
+ ...
+ tools=[...existing..., my_new_tool],
+)
+```
+
+3. **Update instruction** if needed to guide LLM usage.
diff --git a/a2a/docs/03-ucp-integration.md b/a2a/docs/03-ucp-integration.md
new file mode 100644
index 0000000..38d002a
--- /dev/null
+++ b/a2a/docs/03-ucp-integration.md
@@ -0,0 +1,195 @@
+# UCP Integration
+
+## TL;DR
+
+- **UCP** provides standardized commerce data types (Checkout, LineItem, Payment)
+- **Capability negotiation** finds common features between client and merchant
+- **Dynamic types** generated based on negotiated capabilities
+
+## Why Negotiate Capabilities?
+
+Different commerce platforms support different features. A basic merchant might only support checkout, while an advanced one offers loyalty points, subscriptions, and gift cards.
+
+**Without negotiation**:
+- Client assumes all features available → breaks when merchant lacks support
+- Merchant sends all data → client can't render unknown fields
+- Tight coupling between specific client and merchant versions
+
+**With negotiation**:
+- Client declares what it supports: "I can handle checkout, fulfillment, discounts"
+- Merchant declares what it offers: "I support checkout, fulfillment"
+- Intersection becomes the contract: "We'll use checkout + fulfillment"
+- Both sides know exactly what data structures to expect
+
+This enables any UCP-compliant client to work with any UCP-compliant merchant.
+
+## UCP Capabilities
+
+| Capability | Purpose | Extends |
+|------------|---------|---------|
+| `dev.ucp.shopping.checkout` | Base checkout session | - |
+| `dev.ucp.shopping.fulfillment` | Shipping address, delivery options | checkout |
+| `dev.ucp.shopping.discount` | Promotional codes | checkout |
+| `dev.ucp.shopping.buyer_consent` | Consent management | checkout |
+
+## Profile Structure
+
+### Merchant Profile (`data/ucp.json`)
+
+```json
+{
+ "ucp": {
+ "version": "2026-01-11",
+ "services": {
+ "dev.ucp.shopping": {
+ "version": "2026-01-11",
+ "spec": "https://ucp.dev/specs/shopping",
+ "a2a": {
+ "endpoint": "http://localhost:10999/.well-known/agent-card.json"
+ }
+ }
+ },
+ "capabilities": [
+ {
+ "name": "dev.ucp.shopping.checkout",
+ "version": "2026-01-11",
+ "spec": "https://ucp.dev/specs/shopping/checkout",
+ "schema": "https://ucp.dev/schemas/shopping/checkout.json"
+ },
+ {
+ "name": "dev.ucp.shopping.fulfillment",
+ "version": "2026-01-11",
+ "extends": "dev.ucp.shopping.checkout"
+ }
+ ]
+ },
+ "payment": {
+ "handlers": [{
+ "id": "example_payment_provider",
+ "name": "example.payment.provider",
+ "version": "2026-01-11"
+ }]
+ }
+}
+```
+
+**Note**: Merchant currently supports 2 capabilities (checkout, fulfillment). Client may request more, but negotiation finds the intersection.
+
+### Client Profile (`chat-client/profile/agent_profile.json`)
+
+```json
+{
+ "ucp": {
+ "version": "2026-01-11",
+ "capabilities": [
+ {"name": "dev.ucp.shopping.checkout"},
+ {"name": "dev.ucp.shopping.fulfillment"},
+ {"name": "dev.ucp.shopping.discount"},
+ {"name": "dev.ucp.shopping.buyer_consent"}
+ ]
+ }
+}
+```
+
+## Capability Negotiation
+
+
+

+
Figure 1: UCP capability negotiation flow — Chat Client sends request with UCP-Agent header, ProfileResolver fetches client profile, validates versions, finds common capabilities, and returns UcpMetadata for dynamic type generation.
+
+
+### Negotiation Steps
+
+1. **Extract header**: Parse `UCP-Agent: profile=""` from request
+2. **Fetch profile**: HTTP GET client's profile URL
+3. **Validate version**: Client version must be ≤ merchant version
+4. **Find intersection**: Common capabilities between client and merchant
+5. **Create metadata**: `UcpMetadata` with shared capabilities
+
+```python
+# ucp_profile_resolver.py
+def get_ucp_metadata(self, client_profile) -> UcpMetadata:
+ client_caps = {(c.name, c.version) for c in client_capabilities}
+ common = [m for m in merchant_caps if (m.name, m.version) in client_caps]
+ return UcpMetadata(capabilities=common, ...)
+```
+
+## Dynamic Type Generation
+
+Based on negotiated capabilities, a custom `Checkout` class is created:
+
+```python
+# helpers/type_generator.py
+def get_checkout_type(ucp_metadata: UcpMetadata) -> type[Checkout]:
+ active = {cap.name for cap in ucp_metadata.capabilities}
+ bases = []
+
+ if "dev.ucp.shopping.fulfillment" in active:
+ bases.append(FulfillmentCheckout)
+ if "dev.ucp.shopping.buyer_consent" in active:
+ bases.append(BuyerConsentCheckout)
+ if "dev.ucp.shopping.discount" in active:
+ bases.append(DiscountCheckout)
+
+ if not bases:
+ return Checkout
+
+ return create_model("DynamicCheckout", __base__=tuple(bases))
+```
+
+### Type Hierarchy
+
+
+

+
Figure 2: Checkout type hierarchy — Base Checkout class is extended by FulfillmentCheckout, BuyerConsentCheckout, and DiscountCheckout based on negotiated capabilities. The get_checkout_type() function dynamically combines these at runtime.
+
+
+**Dynamic composition**: If both `fulfillment` and `discount` capabilities are negotiated, `get_checkout_type()` creates a class that inherits from both.
+
+## UCP SDK Usage
+
+### Import Patterns
+
+```python
+# Checkout types
+from ucp_sdk.models.schemas.shopping.checkout_resp import CheckoutResponse
+from ucp_sdk.models.schemas.shopping.checkout import Checkout, FulfillmentCheckout
+
+# Common types
+from ucp_sdk.models.schemas.shopping.types.line_item import LineItem
+from ucp_sdk.models.schemas.shopping.types.payment_response import PaymentResponse
+from ucp_sdk.models.schemas.shopping.types.order_confirmation import OrderConfirmation
+
+# Fulfillment
+from ucp_sdk.models.schemas.shopping.types.fulfillment_option_response import FulfillmentOptionResponse
+from ucp_sdk.models.schemas.shopping.types.postal_address import PostalAddress
+```
+
+### Response Keys
+
+```python
+# constants.py
+UCP_CHECKOUT_KEY = "a2a.ucp.checkout" # Checkout data
+UCP_PAYMENT_DATA_KEY = "a2a.ucp.checkout.payment_data" # Payment instrument
+UCP_RISK_SIGNALS_KEY = "a2a.ucp.checkout.risk_signals" # Risk data
+```
+
+## Adding a New Capability
+
+1. **Update merchant profile** (`data/ucp.json`):
+```json
+{
+ "capabilities": [
+ ...,
+ {"name": "dev.ucp.shopping.new_capability", "extends": "checkout"}
+ ]
+}
+```
+
+2. **Update type generator** (`helpers/type_generator.py`):
+```python
+if "dev.ucp.shopping.new_capability" in active:
+ bases.append(NewCapabilityCheckout)
+```
+
+3. **Handle in tools** if new fields need processing
diff --git a/a2a/docs/04-commerce-flows.md b/a2a/docs/04-commerce-flows.md
new file mode 100644
index 0000000..f8c18c0
--- /dev/null
+++ b/a2a/docs/04-commerce-flows.md
@@ -0,0 +1,201 @@
+# Commerce Flows
+
+## TL;DR
+
+- **Checkout states**: `incomplete` → `ready_for_complete` → `completed`
+- **Payment flow**: Select method → Get token → Complete checkout
+- **Totals**: Recalculated on every change (subtotal, tax, shipping)
+
+## Why 3 States?
+
+The checkout state machine prevents common e-commerce errors:
+
+| State | Purpose |
+|-------|---------|
+| `incomplete` | Cart mode - freely add/remove items, no commitment yet |
+| `ready_for_complete` | Validation gate - all required info collected, price locked |
+| `completed` | Finalized - order placed, no modifications possible |
+
+**Why not just "in cart" and "ordered"?**
+
+The `ready_for_complete` state serves as a critical checkpoint:
+- Validates buyer email exists (for order confirmation)
+- Validates shipping address (for fulfillment)
+- Locks in pricing (prevents race conditions during payment)
+- Allows payment provider to assess risk before charging
+
+Without this intermediate state, you'd risk creating orders with missing shipping info or charging cards for outdated prices.
+
+## Checkout State Machine
+
+
+

+
Figure 1: Checkout state machine showing the 3 states — incomplete (cart mode), ready_for_complete (validation gate), and completed (finalized). Transitions are triggered by tool calls: add_to_checkout(), start_payment(), and complete_checkout().
+
+
+### State Definitions
+
+| State | Meaning | Missing |
+|-------|---------|---------|
+| `incomplete` | Cart has items but missing info | Buyer email or fulfillment address |
+| `ready_for_complete` | All info collected | Awaiting payment confirmation |
+| `completed` | Order placed | - |
+
+### Transition Triggers
+
+| From | To | Tool | Condition |
+|------|----|----- |-----------|
+| - | incomplete | `add_to_checkout` | First item added |
+| incomplete | ready_for_complete | `start_payment` | Buyer + address present |
+| ready_for_complete | completed | `complete_checkout` | Payment validated |
+
+## Checkout Object Structure
+
+```json
+{
+ "id": "checkout-uuid",
+ "status": "incomplete",
+ "currency": "USD",
+
+ "line_items": [{
+ "id": "item-uuid",
+ "item": {
+ "id": "PROD-001",
+ "title": "Product Name",
+ "price": 499,
+ "image_url": "http://localhost:10999/images/product.jpg"
+ },
+ "quantity": 2,
+ "totals": [{"type": "subtotal", "amount": 998}]
+ }],
+
+ "totals": [
+ {"type": "subtotal", "display_text": "Subtotal", "amount": 998},
+ {"type": "tax", "display_text": "Tax (10%)", "amount": 100},
+ {"type": "shipping", "display_text": "Shipping", "amount": 500},
+ {"type": "total", "display_text": "Total", "amount": 1598}
+ ],
+
+ "fulfillment": {
+ "destination": {"address": {...}},
+ "groups": [{"selected_method": {...}}]
+ },
+
+ "payment": {
+ "handlers": [{"id": "provider", "name": "example.payment.provider"}]
+ },
+
+ "order": {
+ "id": "order-uuid",
+ "permalink_url": "https://example.com/order?id=order-uuid"
+ }
+}
+```
+
+**Note**: Prices are in **cents** (e.g., `499` = $4.99)
+
+## Payment Flow
+
+
+

+
Figure 2: End-to-end payment flow showing 3 phases — Payment Method Selection (UI ↔ CredentialProviderProxy), Get Payment Token, and Complete Checkout (UI → A2A Server → ADK Agent → MockPaymentProcessor). The agent calls place_order() after payment validation.
+
+
+### Payment Components
+
+| Component | Location | Role |
+|-----------|----------|------|
+| CredentialProviderProxy | `chat-client/mocks/` | Mock payment method provider |
+| PaymentMethodSelector | `chat-client/components/` | UI for method selection |
+| MockPaymentProcessor | `business_agent/payment_processor.py` | Simulates payment validation |
+
+### PaymentInstrument Structure
+
+```json
+{
+ "type": "card",
+ "last_digits": "1111",
+ "brand": "AMEX",
+ "expiry": "12/2026",
+ "handler_id": "example_payment_provider",
+ "handler_name": "example.payment.provider",
+ "credential": {
+ "type": "card_token",
+ "token": "mock_token_abc123"
+ }
+}
+```
+
+## Total Calculation
+
+`_recalculate_checkout()` in `store.py` updates all totals:
+
+```python
+def _recalculate_checkout(self, checkout: Checkout) -> None:
+ subtotal = 0
+
+ # 1. Line item totals
+ for item in checkout.line_items:
+ item_total = item.item.price * item.quantity
+ item.totals = [Total(type="subtotal", amount=item_total)]
+ subtotal += item_total
+
+ # 2. Tax (10% flat rate, only if address set)
+ tax = subtotal // 10 if has_fulfillment_address else 0
+
+ # 3. Shipping (from selected method)
+ shipping = selected_method.price if selected_method else 0
+
+ # 4. Grand total
+ total = subtotal + tax + shipping
+
+ # 5. Update checkout.totals
+ checkout.totals = [
+ Total(type="subtotal", amount=subtotal),
+ Total(type="tax", amount=tax),
+ Total(type="shipping", amount=shipping),
+ Total(type="total", amount=total),
+ ]
+```
+
+### Fulfillment Options
+
+```python
+# store.py:525 - _get_fulfillment_options()
+[
+ FulfillmentOptionResponse(
+ id="standard",
+ title="Standard Shipping",
+ description="Arrives in 4-5 days",
+ carrier="USPS",
+ totals=[
+ Total(type="subtotal", display_text="Subtotal", amount=500),
+ Total(type="tax", display_text="Tax", amount=0),
+ Total(type="total", display_text="Total", amount=500),
+ ],
+ ),
+ FulfillmentOptionResponse(
+ id="express",
+ title="Express Shipping",
+ description="Arrives in 1-2 days",
+ carrier="FedEx",
+ totals=[
+ Total(type="subtotal", display_text="Subtotal", amount=1000),
+ Total(type="tax", display_text="Tax", amount=0),
+ Total(type="total", display_text="Total", amount=1000),
+ ],
+ ),
+]
+```
+
+## OrderConfirmation
+
+Created in `place_order()` when checkout completes (store.py:498):
+
+```python
+checkout.status = "completed"
+checkout.order = OrderConfirmation(
+ id=order_id,
+ permalink_url=f"https://example.com/order?id={order_id}",
+)
+```
diff --git a/a2a/docs/05-frontend.md b/a2a/docs/05-frontend.md
new file mode 100644
index 0000000..afd4c58
--- /dev/null
+++ b/a2a/docs/05-frontend.md
@@ -0,0 +1,236 @@
+# Frontend Reference
+
+## TL;DR
+
+- **React 19** + TypeScript + Vite + Tailwind
+- **App.tsx** handles A2A messaging and state
+- **Components** render UCP data types (Checkout, Product, Payment)
+
+## Why a Mock Payment Provider?
+
+Real payment flows involve sensitive credentials and complex integrations. The `CredentialProviderProxy` mock lets you:
+
+- **Test the full checkout flow** without real payment credentials
+- **Understand the data contracts** before implementing real providers
+- **Demonstrate UCP patterns** without external dependencies
+
+In production, you'd replace this with your actual payment SDK (Stripe, Adyen, etc.).
+
+## Request/Response Cycle
+
+
+

+
Figure 1: Request/response cycle — React App sends POST /api with UCP-Agent header, Vite Proxy rewrites path and forwards to A2A Server at :10999, ADK Agent returns response with parts[] containing text and structured data (checkout, products).
+
+
+## Component Hierarchy
+
+
+

+
Figure 2: React component tree — App.tsx manages state and A2A messaging, with Header, ChatMessage, and ChatInput as children. ChatMessage contains UCP data components (green): ProductCard, Checkout, PaymentMethodSelector, and PaymentConfirmation.
+
+
+## App.tsx - State & Handlers
+
+### State
+
+```typescript
+const [messages, setMessages] = useState([initialMessage]);
+const [isLoading, setIsLoading] = useState(false);
+const [contextId, setContextId] = useState(null);
+const [taskId, setTaskId] = useState(null);
+```
+
+### Handler Functions
+
+| Handler | Purpose |
+|---------|---------|
+| `handleSendMessage(content, options)` | Send A2A message, parse response |
+| `handleAddToCheckout(product)` | Add product to cart |
+| `handleStartPayment()` | Initiate payment flow |
+| `handlePaymentMethodSelection(checkout)` | Fetch available methods |
+| `handlePaymentMethodSelected(method)` | Get payment token |
+| `handleConfirmPayment(instrument)` | Complete checkout |
+
+## A2A Communication
+
+### Request Format
+
+```typescript
+const request = {
+ jsonrpc: "2.0",
+ id: crypto.randomUUID(),
+ method: "message/send",
+ params: {
+ message: {
+ role: "user",
+ parts: [{type: "text", text: "show me cookies"}],
+ contextId: contextId, // From previous response
+ taskId: taskId, // For multi-turn tasks
+ },
+ configuration: { historyLength: 0 }
+ }
+};
+
+fetch("/api", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-A2A-Extensions": "https://ucp.dev/specification/reference?v=2026-01-11",
+ "UCP-Agent": `profile="http://localhost:3000/profile/agent_profile.json"`
+ },
+ body: JSON.stringify(request)
+});
+```
+
+### Response Parsing
+
+```typescript
+const data = await response.json();
+
+// Extract context for next request
+setContextId(data.result?.contextId);
+
+// Parse response parts
+for (const part of data.result?.status?.message?.parts || []) {
+ if (part.text) {
+ message.text += part.text;
+ }
+ if (part.data?.["a2a.product_results"]) {
+ message.products = part.data["a2a.product_results"].results;
+ }
+ if (part.data?.["a2a.ucp.checkout"]) {
+ message.checkout = part.data["a2a.ucp.checkout"];
+ }
+}
+```
+
+## Key Components
+
+| Component | Props | Renders |
+|-----------|-------|---------|
+| `ProductCard` | `product`, `onAddToCart` | Product image, name, price, stock |
+| `Checkout` | `checkout`, `onCheckout`, `onCompletePayment` | Line items, totals, action buttons |
+| `PaymentMethodSelector` | `paymentMethods`, `onSelect` | Radio list of methods |
+| `PaymentConfirmation` | `paymentInstrument`, `onConfirm` | Confirm button |
+| `ChatMessage` | `message`, handlers | Combines all above based on data |
+
+## Types (types.ts)
+
+```typescript
+interface ChatMessage {
+ id: string;
+ sender: Sender; // USER | MODEL
+ text: string;
+ products?: Product[];
+ isLoading?: boolean;
+ paymentMethods?: PaymentMethod[];
+ isUserAction?: boolean;
+ checkout?: Checkout;
+ paymentInstrument?: PaymentInstrument;
+}
+
+interface Checkout {
+ id: string;
+ line_items: CheckoutItem[];
+ currency: string;
+ continue_url?: string | null;
+ status: string; // incomplete | ready_for_complete | completed
+ totals: CheckoutTotal[];
+ order_id?: string;
+ order_permalink_url?: string;
+ payment?: Payment;
+}
+
+interface Product {
+ productID: string;
+ name: string;
+ image: string[];
+ brand: { name: string };
+ offers: {
+ price: string;
+ priceCurrency: string;
+ availability: string;
+ };
+ url: string;
+ description: string;
+ size: { name: string };
+}
+
+interface PaymentMethod {
+ id: string;
+ type: string;
+ brand: string;
+ last_digits: string;
+ expiry_month: number;
+ expiry_year: number;
+}
+```
+
+## CredentialProviderProxy
+
+Mock payment provider in `mocks/credentialProviderProxy.ts`:
+
+```typescript
+class CredentialProviderProxy {
+ handler_id = 'example_payment_provider';
+ handler_name = 'example.payment.provider';
+
+ // Returns mock payment methods (wrapped in object)
+ async getSupportedPaymentMethods(
+ user_email: string,
+ config: any
+ ): Promise<{payment_method_aliases: PaymentMethod[]}> {
+ return {
+ payment_method_aliases: [
+ { id: "instr_1", type: "card", brand: "amex",
+ last_digits: "1111", expiry_month: 12, expiry_year: 2026 },
+ { id: "instr_2", type: "card", brand: "visa",
+ last_digits: "8888", expiry_month: 12, expiry_year: 2026 },
+ ]
+ };
+ }
+
+ // Converts method to PaymentInstrument with token
+ async getPaymentToken(
+ user_email: string,
+ payment_method_id: string
+ ): Promise {
+ return {
+ ...payment_method,
+ handler_id: this.handler_id,
+ handler_name: this.handler_name,
+ credential: { type: "token", token: `mock_token_${uuid}` }
+ };
+ }
+}
+```
+
+## Configuration
+
+### Vite Proxy (`vite.config.ts`)
+
+```typescript
+server: {
+ port: 3000,
+ proxy: {
+ "/api": {
+ target: "http://localhost:10999",
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, "")
+ }
+ }
+}
+```
+
+### App Config (`config.ts`)
+
+```typescript
+export const appConfig = new AppProperties(
+ "Business Agent",
+ "Your personal shopping assistant.",
+ "/images/logo.jpg",
+ "Hello, I am your Business Agent...",
+ "Shop with Business Agent"
+);
+```
diff --git a/a2a/docs/06-extending.md b/a2a/docs/06-extending.md
new file mode 100644
index 0000000..33dfcdf
--- /dev/null
+++ b/a2a/docs/06-extending.md
@@ -0,0 +1,509 @@
+# Extending the Sample
+
+## TL;DR
+
+- **Quick wins**: Add products, modify checkout flow, change tax/shipping
+- **Medium effort**: Add new tools, custom payment handlers
+- **Advanced**: New UCP capabilities, replace mock store, multi-agent patterns
+
+## Which Extension Do You Need?
+
+
+

+
Figure 1: Extension decision tree — Choose your path based on what you want to add. New products require only JSON edits, while new capabilities need profile updates, type generation, and tool handling.
+
+
+---
+
+## Part 1: Quick Customizations
+
+### Add Products
+
+**File**: `data/products.json`
+
+```json
+{
+ "productID": "SNACK-007",
+ "sku": "SNACK-007",
+ "name": "Organic Trail Mix",
+ "@type": "Product",
+ "image": ["http://localhost:10999/images/trail_mix.jpg"],
+ "brand": {"name": "Nature's Best", "@type": "Brand"},
+ "offers": {
+ "price": "6.99",
+ "priceCurrency": "USD",
+ "availability": "InStock",
+ "itemCondition": "NewCondition",
+ "@type": "Offer"
+ },
+ "description": "A healthy mix of nuts and dried fruits",
+ "category": "Food > Snacks > Trail Mix"
+}
+```
+
+Add image to `data/images/`, restart server.
+
+### Modify Tax Calculation
+
+**File**: `store.py` - `_recalculate_checkout()` method
+
+```python
+# Current: flat 10% tax
+tax = subtotal // 10
+
+# Custom: location-based tax
+tax_rate = self._get_tax_rate(checkout.fulfillment.destination.address)
+tax = int(subtotal * tax_rate)
+
+def _get_tax_rate(self, address: PostalAddress) -> float:
+ state_rates = {"CA": 0.0725, "NY": 0.08, "TX": 0.0625}
+ return state_rates.get(address.addressRegion, 0.05)
+```
+
+### Add Minimum Order
+
+**File**: `store.py` - `start_payment()` method (line 463)
+
+```python
+def start_payment(self, checkout_id: str) -> Checkout | str:
+ checkout = self._checkouts.get(checkout_id)
+
+ # Add minimum order check
+ subtotal = next(t for t in checkout.totals if t.type == "subtotal").amount
+ if subtotal < 1000: # $10 minimum (cents)
+ return "Minimum order is $10.00"
+
+ # ... rest of validation
+```
+
+### Custom Shipping Options
+
+**File**: `store.py` - `_get_fulfillment_options()` method (line 525)
+
+```python
+def _get_fulfillment_options(self, address: PostalAddress) -> list:
+ options = [
+ FulfillmentOptionResponse(
+ id="standard", title="Standard", price=500,
+ description="4-5 business days"
+ ),
+ FulfillmentOptionResponse(
+ id="express", title="Express", price=1000,
+ description="1-2 business days"
+ ),
+ ]
+ # Add same-day for local addresses
+ if self._is_local(address):
+ options.append(FulfillmentOptionResponse(
+ id="same_day", title="Same Day", price=1500
+ ))
+ return options
+```
+
+---
+
+## Part 2: Adding Tools (ADK Perspective)
+
+### Tool Architecture
+
+Every ADK tool follows this pattern:
+
+
+

+
Figure 2: Tool execution pattern — User query flows through Agent → LLM (tool selection) → Tool (with ToolContext) → Store. Each tool follows: get state → validate → execute → return UCP response.
+
+
+### Template: Creating a New Tool
+
+```python
+# agent.py
+
+def my_new_tool(tool_context: ToolContext, param: str) -> dict:
+ """Docstring becomes the LLM's understanding of this tool.
+
+ Args:
+ param: Description helps LLM know what to pass
+
+ Returns:
+ Description of what the tool returns
+ """
+ # 1. Get state
+ checkout_id = tool_context.state.get(ADK_USER_CHECKOUT_ID)
+ metadata = tool_context.state.get(ADK_UCP_METADATA_STATE)
+
+ # 2. Validate
+ if not checkout_id:
+ return _create_error_response("No active checkout")
+
+ # 3. Execute business logic
+ try:
+ result = store.my_method(checkout_id, param)
+ except ValueError as e:
+ return _create_error_response(str(e))
+
+ # 4. Return UCP-formatted response
+ return {UCP_CHECKOUT_KEY: result.model_dump(mode="json")}
+```
+
+### Example: Apply Discount Tool
+
+```python
+def apply_discount(tool_context: ToolContext, promo_code: str) -> dict:
+ """Apply a promotional code to the current checkout.
+
+ Args:
+ promo_code: The promotional code to apply (e.g., "SAVE10")
+ """
+ checkout_id = tool_context.state.get(ADK_USER_CHECKOUT_ID)
+ if not checkout_id:
+ return _create_error_response("No active checkout")
+
+ try:
+ checkout = store.apply_discount(checkout_id, promo_code)
+ return {UCP_CHECKOUT_KEY: checkout.model_dump(mode="json")}
+ except ValueError as e:
+ return _create_error_response(str(e))
+
+# Add to agent tools list
+root_agent = Agent(..., tools=[...existing..., apply_discount])
+```
+
+### Example: Order Tracking Tool
+
+```python
+def get_order_status(tool_context: ToolContext, order_id: str) -> dict:
+ """Get the status of a placed order.
+
+ Args:
+ order_id: The order ID from order confirmation
+ """
+ order = store.get_order(order_id)
+ if not order:
+ return _create_error_response("Order not found")
+
+ return {
+ "order": {
+ "id": order.order.id,
+ "status": "shipped", # or "processing", "delivered"
+ "tracking_number": "1Z999AA10123456784",
+ "estimated_delivery": "2026-01-25",
+ "permalink_url": order.order.permalink_url,
+ }
+ }
+```
+
+### Example: Product Recommendations Tool
+
+```python
+def get_recommendations(
+ tool_context: ToolContext,
+ rec_type: str = "popular"
+) -> dict:
+ """Get product recommendations for the customer.
+
+ Args:
+ rec_type: Type of recommendations - "popular", "similar", "cart_based"
+ """
+ checkout_id = tool_context.state.get(ADK_USER_CHECKOUT_ID)
+
+ if rec_type == "cart_based" and checkout_id:
+ checkout = store.get_checkout(checkout_id)
+ product_ids = [item.item.id for item in checkout.line_items]
+ products = store.get_related_products(product_ids)
+ elif rec_type == "popular":
+ products = store.get_popular_products(limit=4)
+ else:
+ products = store.search_products("").results[:4]
+
+ return {"a2a.product_results": {"results": products}}
+```
+
+---
+
+## Part 3: UCP Capabilities
+
+### Why Capabilities?
+
+UCP capabilities let you extend checkout data in a standardized way. The client and merchant negotiate which capabilities they both support.
+
+
+

+
Figure 3: Capability extension hierarchy — Base Checkout class extended by FulfillmentCheckout, DiscountCheckout (existing), and LoyaltyCheckout, WishlistCheckout (new capabilities you can add).
+
+
+### Adding a New Capability
+
+**Step 1**: Update merchant profile (`data/ucp.json`)
+
+```json
+{
+ "capabilities": [
+ ...existing...,
+ {
+ "name": "dev.ucp.shopping.loyalty",
+ "version": "2026-01-11",
+ "extends": "dev.ucp.shopping.checkout"
+ }
+ ]
+}
+```
+
+**Step 2**: Create checkout type extension
+
+```python
+# helpers/type_generator.py or models.py
+
+from pydantic import BaseModel
+
+class Reward(BaseModel):
+ id: str
+ name: str
+ points_required: int
+
+class LoyaltyCheckout(Checkout):
+ loyalty_points: int | None = None
+ rewards: list[Reward] | None = None
+```
+
+**Step 3**: Update type generator (`helpers/type_generator.py`)
+
+```python
+def get_checkout_type(ucp_metadata: UcpMetadata) -> type[Checkout]:
+ active = {cap.name for cap in ucp_metadata.capabilities}
+ bases = []
+
+ if "dev.ucp.shopping.fulfillment" in active:
+ bases.append(FulfillmentCheckout)
+ if "dev.ucp.shopping.loyalty" in active: # NEW
+ bases.append(LoyaltyCheckout)
+ # ... other capabilities
+
+ if not bases:
+ return Checkout
+ return create_model("DynamicCheckout", __base__=tuple(bases))
+```
+
+**Step 4**: Handle in tools (if needed)
+
+```python
+def apply_loyalty_points(tool_context: ToolContext, points: int) -> dict:
+ """Apply loyalty points to reduce checkout total."""
+ checkout_id = tool_context.state.get(ADK_USER_CHECKOUT_ID)
+ checkout = store.apply_loyalty_points(checkout_id, points)
+ return {UCP_CHECKOUT_KEY: checkout.model_dump(mode="json")}
+```
+
+### Example: Wishlist Capability
+
+```python
+# 1. Profile: {"name": "dev.ucp.shopping.wishlist", "extends": "checkout"}
+
+# 2. Type
+class WishlistCheckout(Checkout):
+ wishlist_items: list[str] | None = None # Product IDs
+
+# 3. Tool
+def add_to_wishlist(tool_context: ToolContext, product_id: str) -> dict:
+ """Save a product to the customer's wishlist."""
+ wishlist = store.add_to_wishlist(user_id, product_id)
+ return {"wishlist": wishlist}
+
+def move_to_checkout(tool_context: ToolContext, product_id: str) -> dict:
+ """Move a wishlist item to the checkout."""
+ # ...
+```
+
+---
+
+## Part 4: Payment Customization
+
+### Payment Flow with Custom Handler
+
+
+

+
Figure 4: Custom payment flow — Replace CredentialProviderProxy with your payment provider and MockPaymentProcessor with your StripeProcessor (or other provider). Shows the complete flow from payment method selection through Stripe API to OrderConfirmation.
+
+
+### Step 1: Update Profiles
+
+**Merchant** (`data/ucp.json`):
+```json
+{
+ "payment": {
+ "handlers": [{
+ "id": "stripe_handler",
+ "name": "stripe.payment.provider",
+ "version": "2026-01-11",
+ "config": {"business_id": "acct_123456"}
+ }]
+ }
+}
+```
+
+**Client** (`chat-client/profile/agent_profile.json`):
+```json
+{
+ "payment": {
+ "handlers": [{
+ "id": "stripe_handler",
+ "name": "stripe.payment.provider"
+ }]
+ }
+}
+```
+
+### Step 2: Implement Payment Processor
+
+```python
+# payment_processor.py
+
+import stripe
+
+class StripePaymentProcessor:
+ def __init__(self, api_key: str):
+ stripe.api_key = api_key
+
+ async def process_payment(
+ self,
+ payment_data: PaymentInstrument,
+ amount: int,
+ currency: str,
+ risk_data: dict | None = None
+ ) -> Task:
+ try:
+ result = stripe.PaymentIntent.create(
+ amount=amount,
+ currency=currency.lower(),
+ payment_method=payment_data.credential.token,
+ confirm=True
+ )
+ return Task(
+ state=TaskState.completed
+ if result.status == "succeeded"
+ else TaskState.failed
+ )
+ except stripe.error.CardError as e:
+ return Task(state=TaskState.failed, message=str(e))
+```
+
+### Step 3: Update Frontend Mock
+
+Replace `CredentialProviderProxy` in `chat-client/mocks/`:
+
+```typescript
+class StripeCredentialProvider {
+ handler_id = 'stripe_handler';
+ handler_name = 'stripe.payment.provider';
+
+ async getSupportedPaymentMethods(email: string) {
+ // Call your payment service to get saved methods
+ const response = await fetch(`/api/payment-methods?email=${email}`);
+ return response.json();
+ }
+
+ async getPaymentToken(email: string, method_id: string) {
+ // Generate Stripe token
+ const { token } = await stripe.createToken(card);
+ return {
+ ...method,
+ credential: { type: "token", token: token.id }
+ };
+ }
+}
+```
+
+---
+
+## Part 5: User Journeys
+
+### Journey: Order Tracking
+
+**User Goal**: "Where's my order?"
+
+```
+User: "Where's my order?"
+→ Agent: Asks for order ID or shows recent orders
+User: "Order #ORD-12345"
+→ Agent: get_order_status("ORD-12345")
+→ Agent: "Your order shipped via FedEx. Tracking: 1Z999..."
+User: "Can I change the delivery address?"
+→ Agent: Checks if order is shipped
+→ Agent: update_delivery() if not shipped, else "Sorry, already shipped"
+```
+
+**Tools needed**: `get_order_status`, `get_recent_orders`, `update_delivery`
+
+### Journey: Returns & Refunds
+
+**User Goal**: "I want to return this item"
+
+```
+User: "I want to return the cookies I ordered"
+→ Agent: get_recent_orders() - finds order with cookies
+→ Agent: "Found cookies in order #ORD-12345. Why the return?"
+User: "They arrived damaged"
+→ Agent: initiate_return(order_id, item_id, reason="damaged")
+→ Agent: "Return approved. Shipping label sent to your email.
+ Refund of $4.99 will process when we receive the item."
+```
+
+**Tools needed**: `get_recent_orders`, `initiate_return`, `get_return_status`
+
+### Journey: Smart Recommendations
+
+**User Goal**: Discover related products
+
+```
+User: Adds cookies to cart
+→ Agent: After add_to_checkout callback
+→ Agent: get_recommendations("cart_based")
+→ Agent: "Customers who bought these cookies also liked:
+ - Organic Milk ($3.99)
+ - Hot Cocoa Mix ($5.99)"
+User: "Show me similar cookies"
+→ Agent: get_recommendations("similar", product_id="COOKIE-001")
+→ Agent: Shows 4 similar cookie products
+```
+
+**Tools needed**: `get_recommendations` (with types: popular, similar, cart_based)
+
+### Journey: Guest vs Returning Customer
+
+**Guest User**:
+```
+User: Adds items, enters email: new@example.com
+→ Agent: No saved addresses, asks for full address
+→ Agent: No saved payment methods, shows all options
+```
+
+**Returning Customer**:
+```
+User: Adds items, enters email: returning@example.com
+→ Agent: get_saved_addresses(email)
+→ Agent: "Ship to 123 Main St (your default)?"
+User: "Yes"
+→ Agent: get_saved_payment_methods(email)
+→ Agent: "Pay with Visa ending 4242?"
+```
+
+**Tools needed**: `get_saved_addresses`, `get_saved_payment_methods`
+
+---
+
+## Part 6: Replacing the Mock Store
+
+See [Architecture: Mock Store](./01-architecture.md#mock-store-architecture) for:
+- Store structure diagram
+- Key methods to implement
+- Interface definition
+- Adapter pattern example
+- What to keep vs replace
+
+Quick summary:
+
+| Keep (UCP/ADK patterns) | Replace (Mock specifics) |
+|-------------------------|--------------------------|
+| Tool signatures | Data storage |
+| State management | Product catalog |
+| Type generation | Tax/shipping logic |
+| Response formatting | Payment processing |
diff --git a/a2a/docs/07-testing-guide.md b/a2a/docs/07-testing-guide.md
new file mode 100644
index 0000000..0516b35
--- /dev/null
+++ b/a2a/docs/07-testing-guide.md
@@ -0,0 +1,265 @@
+# Testing & Development Guide
+
+## TL;DR
+
+- Start backend (port 10999), then frontend (port 3000)
+- Happy path: search → add to cart → checkout → pay
+- Debug with verbose logging and endpoint verification
+
+## Setup Flow
+
+
+

+
Figure 1: Setup and test flow — Start Backend (port 10999), then Frontend (port 3000), verify endpoints, then proceed to testing: Search Products → Add to Cart → Complete Payment.
+
+
+## Quick Start
+
+### 1. Start Backend
+
+```bash
+cd a2a/business_agent
+uv sync
+cp env.example .env # Add GOOGLE_API_KEY
+uv run business_agent # Starts on :10999
+```
+
+### 2. Start Frontend
+
+```bash
+cd a2a/chat-client
+npm install
+npm run dev # Starts on :3000
+```
+
+### 3. Verify Endpoints
+
+```bash
+# Agent card (A2A discovery)
+curl -s http://localhost:10999/.well-known/agent-card.json | jq .
+
+# UCP profile (merchant capabilities)
+curl -s http://localhost:10999/.well-known/ucp | jq .
+
+# Client profile (client capabilities)
+curl -s http://localhost:3000/profile/agent_profile.json | jq .
+```
+
+## Testing Workflows
+
+### Happy Path (Complete Purchase)
+
+1. Open http://localhost:3000
+2. Type "show me cookies"
+3. Click "Add to Checkout" on a product
+4. Enter email when prompted: `test@example.com`
+5. Enter address: `123 Main St, San Francisco, CA 94105`
+6. Click "Complete Payment"
+7. Select a payment method
+8. Click "Confirm Purchase"
+9. Verify order confirmation appears with order ID and permalink
+
+**Expected State Transitions**:
+- After step 3: `status: "incomplete"`
+- After step 5: `status: "incomplete"` (ready for payment start)
+- After step 6: `status: "ready_for_complete"`
+- After step 8: `status: "completed"`
+
+### Error Scenarios
+
+| Scenario | How to Test | Expected Behavior |
+|----------|-------------|-------------------|
+| No checkout exists | Call `get_checkout` without adding items | "Checkout not created" error |
+| Missing address | Skip address, call `start_payment` | Agent prompts for address |
+| Missing email | Skip email, call `start_payment` | Agent prompts for email |
+| Invalid product | `add_to_checkout("INVALID-ID", 1)` | "Product not found" error |
+| Quantity update | Add item, then `update_checkout` with qty=0 | Item removed from checkout |
+
+## Debugging Guide
+
+### Debug Strategy
+
+When something breaks, follow this systematic approach:
+
+
+

+
Figure 2: Debug strategy decision tree — Systematically check backend, frontend, browser console, UCP-Agent header, and capability negotiation to isolate issues.
+
+
+### Enable Verbose Logging
+
+```python
+# main.py - add at top
+import logging
+logging.basicConfig(level=logging.DEBUG)
+```
+
+### Inspect Tool State
+
+```python
+# In any tool - add temporarily for debugging
+def my_tool(tool_context: ToolContext, param: str) -> dict:
+ print("=== DEBUG ===")
+ print("State keys:", list(tool_context.state.keys()))
+ print("Checkout ID:", tool_context.state.get(ADK_USER_CHECKOUT_ID))
+ print("UCP Metadata:", tool_context.state.get(ADK_UCP_METADATA_STATE))
+ # ... rest of tool
+```
+
+### Check A2A Messages
+
+```typescript
+// App.tsx - in handleSendMessage, add before fetch
+console.log("Request:", JSON.stringify(request, null, 2));
+
+// After response
+console.log("Response:", JSON.stringify(data, null, 2));
+```
+
+### Test A2A Directly (bypass UI)
+
+```bash
+curl -X POST http://localhost:10999/ \
+ -H "Content-Type: application/json" \
+ -H "UCP-Agent: profile=\"http://localhost:3000/profile/agent_profile.json\"" \
+ -d '{
+ "jsonrpc": "2.0",
+ "id": "1",
+ "method": "message/send",
+ "params": {
+ "message": {
+ "role": "user",
+ "parts": [{"type": "text", "text": "show me products"}]
+ }
+ }
+ }'
+```
+
+## Common Issues
+
+| Issue | Likely Cause | Fix |
+|-------|--------------|-----|
+| Server won't start | Missing `GOOGLE_API_KEY` | Add key to `.env` file |
+| "Profile fetch failed" | Frontend not running | Start chat-client on :3000 |
+| "Version unsupported" | Profile version mismatch | Align `version` in both `ucp.json` and `agent_profile.json` |
+| "Checkout not found" | Session expired or no items | Call `add_to_checkout` first |
+| UI not updating | Missing contextId | Check `contextId` in response, ensure it's passed to next request |
+| "Missing UCP metadata" | Header not sent | Verify `UCP-Agent` header in request |
+| Payment methods empty | CredentialProviderProxy issue | Check browser console for mock provider errors |
+
+## Troubleshooting Guide
+
+### Setup Failures
+
+| Error Message | Cause | Solution |
+|---------------|-------|----------|
+| `Address already in use :10999` | Agent already running or port in use | `kill $(lsof -t -i:10999)` or use different port |
+| `GOOGLE_API_KEY not found` | Missing or empty .env file | Create `.env` from `env.example`, add your key |
+| `No module named 'business_agent'` | Not in virtualenv or deps not installed | Run `uv sync` in `business_agent/` directory |
+| `npm ERR! ENOENT package.json` | Wrong directory | `cd chat-client` before running `npm install` |
+| `Connection refused :10999` | Backend not running | Start backend first with `uv run business_agent` |
+
+### Runtime Errors
+
+| Symptom | Debug Steps |
+|---------|-------------|
+| **"Checkout not found"** | 1. Check `contextId` is passed from previous response
2. Verify session hasn't expired
3. Add an item first with `add_to_checkout` |
+| **Products not returning** | 1. Enable DEBUG logging
2. Check if `search_shopping_catalog` tool is being called
3. Verify products.json exists and is valid JSON |
+| **Payment flow hangs** | 1. Check browser console for CredentialProviderProxy errors
2. Verify mock payment methods are returned
3. Check `start_payment` was called successfully |
+| **UI not updating after action** | 1. Verify `contextId` threading in App.tsx
2. Check response structure in browser DevTools
3. Look for React state update issues |
+
+### Step-by-Step Diagnosis
+
+**Problem: Agent returns generic errors**
+
+```bash
+# Step 1: Enable verbose logging
+# Edit main.py, add at top:
+import logging
+logging.basicConfig(level=logging.DEBUG)
+
+# Step 2: Restart agent and check logs
+uv run business_agent
+
+# Step 3: Look for specific error messages in output
+```
+
+**Problem: Capability negotiation failing**
+
+```bash
+# Step 1: Verify both profiles are accessible
+curl -s http://localhost:10999/.well-known/ucp | jq .version
+curl -s http://localhost:3000/profile/agent_profile.json | jq .version
+
+# Step 2: Ensure versions match
+# Both should return the same version string
+
+# Step 3: Check UCP-Agent header is being sent
+# In browser DevTools > Network, look for requests to localhost:10999
+# Verify UCP-Agent header contains profile URL
+```
+
+**Problem: Tools not being called by LLM**
+
+```python
+# Check 1: Tool is registered
+# In agent.py, verify tool is in tools=[] list
+
+# Check 2: Tool docstring is clear
+# LLM needs clear description to know when to use the tool
+@tool
+def my_tool(tool_context: ToolContext, query: str) -> dict:
+ """Search for products matching the query.
+
+ Args:
+ query: Product name or description to search for
+ """
+ # ...
+
+# Check 3: Add debug output to confirm tool is called
+def my_tool(tool_context: ToolContext, query: str) -> dict:
+ print(f"=== TOOL CALLED: my_tool({query}) ===")
+ # ...
+```
+
+### Browser Debugging
+
+**Check Network Requests:**
+1. Open DevTools (F12) → Network tab
+2. Filter by "localhost:10999"
+3. Click on request → Headers tab
+4. Verify `UCP-Agent` header is present
+5. Click Response tab to see A2A response
+
+**Check Console Errors:**
+1. Open DevTools → Console tab
+2. Look for red error messages
+3. Common issues:
+ - CORS errors → Backend not running
+ - JSON parse errors → Malformed response
+ - TypeError → Missing data in response
+
+## Reference
+
+### Ports & URLs
+
+| Service | Port | Endpoints |
+|---------|------|-----------|
+| Backend | 10999 | `/` (A2A), `/.well-known/agent-card.json`, `/.well-known/ucp` |
+| Frontend | 3000 | `/`, `/profile/agent_profile.json` |
+
+### Environment Variables
+
+| Variable | Required | Purpose |
+|----------|----------|---------|
+| `GOOGLE_API_KEY` | Yes | Gemini API access for LLM |
+
+### Key Files for Debugging
+
+| Symptom | Check This File | What to Look For |
+|---------|-----------------|------------------|
+| Tool not called | `agent.py` | Tool in `tools=[]` list |
+| State issues | `constants.py` | State key names |
+| Checkout errors | `store.py` | State machine logic |
+| UCP negotiation | `ucp_profile_resolver.py` | Version/capability matching |
+| Frontend errors | `App.tsx` | Request/response handling |
diff --git a/a2a/docs/08-production-notes.md b/a2a/docs/08-production-notes.md
new file mode 100644
index 0000000..e7da6f2
--- /dev/null
+++ b/a2a/docs/08-production-notes.md
@@ -0,0 +1,260 @@
+# Production Notes
+
+> **WARNING**: This sample is NOT production-ready. This document explains what changes are needed before deploying to production.
+
+## TL;DR
+
+- In-memory state loses data on restart
+- No authentication or rate limiting
+- No health check endpoints
+- See deployment checklist below
+
+---
+
+## Architecture Limitations
+
+
+

+
Figure 1: In-memory sample components vs production-ready replacements
+
+
+| Component | Current State | Production Requirement |
+|-----------|---------------|------------------------|
+| Session Storage | `InMemorySessionService` (lost on restart) | Redis or database |
+| Task Storage | `InMemoryTaskStore` (lost on restart) | Persistent task store |
+| Checkout Storage | `RetailStore._checkouts` dict (in-memory) | PostgreSQL or similar |
+| Order Storage | `RetailStore._orders` dict (in-memory) | PostgreSQL or similar |
+| Concurrency | No locking on `_checkouts` | Checkout-level locks |
+| Authentication | None (trusts `context_id`) | JWT or API key validation |
+| Secrets | Plaintext .env | Secret Manager (GCP/AWS) |
+
+**Key insight**: The `RetailStore` class (in `store.py`) stores all business data in plain Python dictionaries:
+- `self._checkouts = {}` — All active checkout sessions
+- `self._orders = {}` — All completed orders
+- `self._products` — Product catalog (loaded from JSON)
+
+These are lost on restart and have no concurrency protection.
+
+---
+
+## Security Gaps
+
+
+

+
Figure 2: Four critical security vulnerabilities and their required fixes
+
+
+### 1. No Authentication
+
+Any client can claim any `context_id`. In production, validate user identity from JWTs or API keys.
+
+```python
+# Current (insecure)
+user_id = context.context_id # Trusts client-provided value
+
+# Production
+user_id = validate_jwt(context.headers.get("Authorization"))
+```
+
+### 2. Profile URL Not Validated
+
+The `UCP-Agent` header can point to any URL. A malicious client could point to:
+- Internal services (SSRF attack)
+- Slow servers (DoS the agent startup)
+
+```python
+# Production: Whitelist allowed domains
+ALLOWED_DOMAINS = ["localhost", "trusted-partner.com"]
+parsed = urlparse(client_profile_url)
+if parsed.netloc not in ALLOWED_DOMAINS:
+ raise ValueError("Untrusted profile URL")
+```
+
+### 3. No Rate Limiting
+
+Unlimited requests are accepted. Add rate limiting per user/IP.
+
+### 4. Payment Data Unencrypted
+
+Payment instruments are stored as plaintext in session state. If logs capture state, card data leaks.
+
+```python
+# Production: Never log payment data
+logger.info(f"Processing payment for checkout {checkout_id}")
+# NOT: logger.info(f"Payment data: {payment_data}")
+```
+
+---
+
+## Concurrency Issues
+
+### Race Condition in Checkout
+
+Between `start_payment()` and `complete_checkout()`, another request could:
+- Add items (changing the total)
+- Change the address (affecting tax)
+- Call `start_payment()` again
+
+**Fix**: Add checkout-level locking.
+
+```python
+# Production pattern
+async def add_to_checkout(checkout_id: str, ...):
+ async with self.get_lock(checkout_id):
+ # Safe from concurrent modifications
+ checkout = self._checkouts[checkout_id]
+ checkout.line_items.append(...)
+```
+
+### Duplicate Items on Retry
+
+If a network error causes the client to retry `add_to_checkout`, items are duplicated.
+
+**Fix**: Implement idempotency via client-provided keys.
+
+```python
+def add_to_checkout(
+ tool_context: ToolContext,
+ product_id: str,
+ quantity: int,
+ idempotency_key: str | None = None # Client-provided
+) -> dict:
+ if idempotency_key and idempotency_key in self.processed_requests:
+ return self.processed_requests[idempotency_key]
+ # ... proceed with add ...
+```
+
+---
+
+## Deployment Checklist
+
+### Required Before Production
+
+**Infrastructure:**
+- [ ] Create Dockerfile with non-root user
+- [ ] Add `/health` endpoint (liveness probe)
+- [ ] Add `/ready` endpoint (readiness probe)
+- [ ] Configure Kubernetes manifests or Cloud Run
+
+**State Management:**
+- [ ] Replace `InMemorySessionService` with Redis-backed store
+- [ ] Replace `InMemoryTaskStore` with persistent store
+- [ ] Replace `RetailStore._checkouts` dict with database (PostgreSQL)
+- [ ] Replace `RetailStore._orders` dict with database
+- [ ] Add session TTL and cleanup
+
+**Security:**
+- [ ] Add request authentication (JWT/API key)
+- [ ] Validate profile URLs against whitelist
+- [ ] Move `GOOGLE_API_KEY` to Secret Manager
+- [ ] Add rate limiting per user
+
+**Observability:**
+- [ ] Add structured JSON logging
+- [ ] Add Prometheus metrics endpoint
+- [ ] Add request tracing (OpenTelemetry)
+
+**Reliability:**
+- [ ] Add checkout-level locking
+- [ ] Implement idempotency for state-changing operations
+- [ ] Add circuit breaker for Gemini API calls
+
+---
+
+## Scaling Considerations
+
+### Session Affinity
+
+If running multiple replicas, all requests for a session must go to the same instance (or use shared storage).
+
+```yaml
+# Kubernetes: Enable session affinity
+spec:
+ sessionAffinity: ClientIP
+```
+
+### LLM Call Blocking
+
+Each `runner.run_async()` call blocks until the LLM responds (2-5 seconds typical). With synchronous execution, you need one thread per concurrent user.
+
+**Consider**: Async runner with connection pooling.
+
+### Gemini API Rate Limits
+
+The Gemini API has rate limits. Add a circuit breaker to fail fast when limits are hit.
+
+---
+
+## Sample Dockerfile
+
+```dockerfile
+FROM python:3.13-slim
+
+# Create non-root user
+RUN useradd -m agent
+
+WORKDIR /app
+
+# Copy files
+COPY --chown=agent:agent . .
+
+# Install dependencies
+RUN pip install uv && uv sync
+
+# Switch to non-root user
+USER agent
+
+# Expose port
+EXPOSE 10999
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
+ CMD curl -f http://localhost:10999/health || exit 1
+
+# Run
+CMD ["uv", "run", "business_agent"]
+```
+
+---
+
+## Sample Health Check Endpoint
+
+Add to `main.py`:
+
+```python
+@app.route("/health", methods=["GET"])
+async def health(request: Request) -> JSONResponse:
+ return JSONResponse({"status": "healthy"})
+
+@app.route("/ready", methods=["GET"])
+async def ready(request: Request) -> JSONResponse:
+ # Check dependencies here
+ try:
+ # Verify store is accessible
+ _ = store.products
+ return JSONResponse({"status": "ready"})
+ except Exception as e:
+ return JSONResponse({"status": "not_ready", "error": str(e)}, status_code=503)
+```
+
+---
+
+## Environment Variables
+
+Externalize these hardcoded values:
+
+| Variable | Current | Purpose |
+|----------|---------|---------|
+| `AGENT_HOST` | `localhost` | Bind address |
+| `AGENT_PORT` | `10999` | Listen port |
+| `LOG_LEVEL` | `INFO` | Logging verbosity |
+| `GOOGLE_API_KEY` | `.env` file | Gemini API access |
+| `SESSION_TTL` | Unlimited | Session expiration (seconds) |
+| `REDIS_URL` | N/A | Distributed session storage |
+
+---
+
+## Related Documentation
+
+- [Architecture](01-architecture.md) - System design
+- [Testing Guide](07-testing-guide.md) - Debugging and troubleshooting