System Architecture

February 17, 2026 · View on GitHub

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

Cymbal Retail Agent System Architecture

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

ComponentFileResponsibility
A2A Servermain.pyHTTP server, routing, static files
Agent Executoragent_executor.pyBridge A2A ↔ ADK, session management
Profile Resolverucp_profile_resolver.pyUCP capability negotiation
ADK Agentagent.pyLLM reasoning, tool execution
Retail Storestore.pyProducts, checkouts, orders
Payment Processorpayment_processor.pyMock payment handling

Frontend

ComponentFileResponsibility
AppApp.tsxState management, A2A messaging
ChatMessagecomponents/ChatMessage.tsxMessage rendering
Checkoutcomponents/Checkout.tsxCheckout display
ProductCardcomponents/ProductCard.tsxProduct cards
PaymentMethodSelectorcomponents/PaymentMethodSelector.tsxPayment selection

Request Flow

Request Flow Sequence Diagram

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 pathafter_agent_callback processes final response, returns Parts[] to client

Layer Responsibilities

LayerInputOutputKey Class
A2A ServerHTTP requestHTTP responseA2AStarletteApplication
Agent ExecutorA2A contextEvent queueADKAgentExecutor
ADK AgentUser query + stateTool resultsAgent (google.adk)
Retail StoreMethod callsDomain objectsRetailStore

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

Mock Store Architecture - What to Keep vs Replace

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)
StorageTypePurpose
_productsdict[str, Product]Product catalog (loaded from products.json)
_checkoutsdict[str, Checkout]Active shopping sessions
_ordersdict[str, Checkout]Completed orders

Key Methods

MethodLineCalled ByPurpose
search_products()100search_shopping_catalog toolKeyword search in catalog
add_to_checkout()186add_to_checkout toolCreate/update checkout session
get_checkout()244get_checkout toolRetrieve current checkout state
start_payment()463start_payment toolValidate checkout for payment
place_order()498complete_checkout toolFinalize order, generate confirmation

Replacing with Real Backend

To connect a real commerce platform (Shopify, Magento, custom API):

1. Create interface (recommended for clean separation):

# 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:

# 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):

# 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 signaturesData storage layer
State management via ToolContextProduct catalog source
Checkout type generationTax/shipping calculation
Response formatting with UCP keysPayment processing
A2A/ADK bridgingOrder persistence

Discovery Endpoints

EndpointPurposeSource
/.well-known/agent-card.jsonA2A agent capabilitiesdata/agent_card.json
/.well-known/ucpUCP merchant profiledata/ucp.json
/images/*Product imagesdata/images/