Skip to content
chatAgent
Esc
↑↓navigate↵open⌘Jpreview
On this page

Architecture

Architecture Overview

System at a Glance

graph TB
    subgraph "External"
        USER[WhatsApp User]
        WA[WhatsApp Cloud API]
        SHOPIFY[Shopify Store]
        LLM[LLM Provider]
    end

    subgraph "Gateway (FastAPI)"
        WEBHOOK[Webhook Handler<br/>Thin, non-blocking]
    end

    subgraph "Rivet Actor Cluster"
        RIVET[Rivet Runtime]
        A1["Actor: User A<br/>(AgentOS + Pi Agent)"]
        A2["Actor: User B<br/>(AgentOS + Pi Agent)"]
        AN["Actor: User N<br/>(AgentOS + Pi Agent)"]
    end

    subgraph "Storage"
        DURABLE[(Rivet Durable State)]
    end

    USER -->|message| WA
    WA -->|webhook| WEBHOOK
    WEBHOOK -->|get_actor| RIVET
    RIVET --> A1
    RIVET --> A2
    RIVET --> AN

    A1 -->|Shopify MCP| SHOPIFY
    A1 -->|LLM calls| LLM
    A1 -->|send reply| WA
    A1 -->|persist| DURABLE

    WA -->|deliver| USER

    style RIVET fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
    style A1 fill:#c8e6c9,stroke:#388e3c
    style A2 fill:#c8e6c9,stroke:#388e3c
    style AN fill:#c8e6c9,stroke:#388e3c

Component Responsibilities

Layer Component Responsibility
Messaging WhatsApp Cloud API Receive/send messages, media, interactive messages
Gateway FastAPI Thin webhook handler, immediately returns 200, routes to Rivet
Orchestration Rivet Durable actor lifecycle, routing, distribution, sleep/wake
Sandbox AgentOS Per-actor V8 isolate with POSIX-compatible OS
Agent Runtime Pi Agent Core LLM orchestration, tool execution, streaming, events
Extensions Custom MCP client, WhatsApp formatter, state manager, analytics
E-commerce @shopify/dev-mcp Product search, cart, checkout, orders, policies
State Rivet Durable State Persistent conversation + user state

The Thin Gateway Pattern

FastAPI is intentionally dumb. Its only jobs:

  1. Receive webhook POST from WhatsApp
  2. Verify signature
  3. Extract user_id (phone number) and text
  4. Call rivet.get_actor(ShoppingAgentActor, user_id) — this creates the actor if it’s new
  5. Send the message to the actor via actor.on_message(text)
  6. Return 200 OK to WhatsApp immediately (so WhatsApp doesn’t retry)

Everything else happens inside the actor. No agent logic, no message queuing, no state management lives in the gateway.

@app.post("/webhook")
async def webhook(request: Request, background_tasks: BackgroundTasks):
    body = await request.json()
    for msg in extract_whatsapp_messages(body):
        user_id = msg["sender"]
        actor = await rivet.get_actor(ShoppingAgentActor, user_id)
        background_tasks.add_task(actor.on_message, msg["text"])
    return {"status": "ok"}

That’s it. That’s the entire gateway.

The Actor Pattern

Each user gets one durable actor. The actor:

  • Is created automatically on first webhook hit
  • Contains an AgentOS V8 isolate
  • Runs a Pi Agent Core instance
  • Has Shopify MCP tools registered
  • Handles its own rate limiting, injection detection, state persistence
  • Sleeps after 5 minutes of inactivity
  • Wakes up when a new message arrives
  • Terminates after 1 hour idle
  • State persists across sleep/wake/restarts
@rivet.actor
class ShoppingAgentActor(Actor):
    async def on_init(self):
        # Create Pi Agent, register tools

    async def on_message(self, text) -> str:
        # Rate limit → inject check → process → save state → return

    async def on_wake(self):
        # Reconnect resources

    async def on_terminate(self):
        # Final cleanup

Data Flow — Message Lifecycle

sequenceDiagram
    actor U as User
    participant W as WhatsApp
    participant G as FastAPI Gateway
    participant R as Rivet
    participant A as Actor (per user)
    participant P as Pi Agent
    participant L as LLM
    participant M as @shopify/dev-mcp
    participant S as Shopify

    U->>W: Message
    W->>G: Webhook POST
    G-->>W: 200 OK
    G->>R: get_actor(user_id)

    alt New user
        R->>A: Create + on_init()
    else Existing user
        R->>A: Wake if sleeping
    end

    R-->>G: actor
    G->>A: on_message(text)
    A->>P: agent.prompt(text)
    P->>L: Stream
    L-->>P: Tool call
    P->>M: Execute Shopify tool
    M->>S: GraphQL
    S-->>M: Data
    M-->>P: Result
    P->>L: Continue
    L-->>P: Response
    P-->>A: Result
    A->>A: Save state (durable)
    A-->>G: Response
    G->>W: Send reply
    W-->>U: Reply

    Note over A: Sleeps after 5min idle

Key Design Principles

  1. Actor-per-user — One durable Rivet actor per WhatsApp user
  2. Thin gateway — FastAPI routes only, no agent logic
  3. Automatic creation — Actors created on first webhook hit, no registration
  4. Tool-first — Agents execute Shopify MCP tools, not just text generation
  5. Stateful — Durable state survives sleep/wake/restarts
  6. E-commerce native — Official @shopify/dev-mcp, Storefront-scoped
  7. Defense in depth — Rate limits, injection detection, sanitization at every layer
  8. Horizontally scalable — Rivet distributes actors across cluster

Documents

Document Scope
Architecture This overview
Agent Architecture Agent stack + layers
Rivet Actor Model Actor implementation + code
Agent Lifecycle Message → actor flow
AgentOS Configuration Per-actor sandbox
Pi Agent Setup Pi Agent Core install
Pi Agent Core API Reference Python API
Building Pi Extensions MCP, formatter, state, analytics
Concurrency & Security Rate limiting, injection
Shopify Integration Research MCP decision
WhatsApp Business Cloud API WhatsApp integration
Integration Guide End-to-end wiring

Last updated on July 26, 2026