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:
- Receive webhook POST from WhatsApp
- Verify signature
- Extract
user_id(phone number) andtext - Call
rivet.get_actor(ShoppingAgentActor, user_id)— this creates the actor if it’s new - Send the message to the actor via
actor.on_message(text) - Return
200 OKto 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
- Actor-per-user — One durable Rivet actor per WhatsApp user
- Thin gateway — FastAPI routes only, no agent logic
- Automatic creation — Actors created on first webhook hit, no registration
- Tool-first — Agents execute Shopify MCP tools, not just text generation
- Stateful — Durable state survives sleep/wake/restarts
- E-commerce native — Official
@shopify/dev-mcp, Storefront-scoped - Defense in depth — Rate limits, injection detection, sanitization at every layer
- 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 |