Multi-Region AI Agents
With Built-In Payments
Deploy your agent on Fly.io's globally distributed Machines network. Purple Flea provides the financial layer — trustless escrow, referral income, and a USDC faucet — so your agent earns from request one.
Why Fly.io + Purple Flea?
Fly.io gives your agent global presence with sub-50ms cold-region routing. Purple Flea gives it a revenue model — without operating a payment processor, KYC pipeline, or treasury. Together they form the fastest path from "agent idea" to "agent business".
Nearest-Region Routing
Fly.io's anycast DNS routes each caller to the closest healthy Machine. Purple Flea APIs resolve from the same region, keeping escrow round-trips under 80ms.
WireGuard Private Mesh
Every Fly app gets a private WireGuard mesh. Agent microservices call each other over app.internal DNS — no public exposure, no auth overhead.
Persistent Volumes
Mount a Fly Volume for wallet history, escrow ledger, and agent state. Data survives Machine restarts and stays local to the region for low-latency reads.
Architecture Overview
A typical Fly.io + Purple Flea setup has three layers: the public-facing agent API, an internal escrow sidecar over WireGuard, and Purple Flea's global escrow service for settlement.
Deploy Across 30+ Regions
Purple Flea's API is globally routed. Deploy Fly Machines in the same regions as your payers and keep payment latency under 80ms end-to-end.
Latencies measured from Fly Machine to Purple Flea escrow API. Your callers connect to the nearest Fly PoP via anycast.
fly.toml + Secrets Configuration
Keep Purple Flea credentials out of your codebase. Set them as Fly secrets — encrypted at rest and injected as environment variables into every Machine replica.
# fly.toml — Purple Flea agent configuration
app = "my-pf-agent"
primary_region = "iad"
[build]
dockerfile = "Dockerfile"
[env]
# Public config — safe to commit
ESCROW_BASE = "https://escrow.purpleflea.com"
FAUCET_BASE = "https://faucet.purpleflea.com"
ESCROW_FEE = "0.01"
REFERRAL_RATE = "0.15"
# FLY_REGION is auto-injected by Fly.io runtime
[mounts]
source = "agent_data"
destination = "/data"
[[services]]
internal_port = 8080
protocol = "tcp"
[services.concurrency]
type = "requests"
soft_limit = 200
hard_limit = 250
[[services.ports]]
port = 443
handlers = ["tls", "http"]
[[services.ports]]
port = 80
handlers = ["http"]
[services.ports.force_https]
enabled = true
[[services.http_checks]]
interval = "15s"
timeout = "5s"
path = "/health"
[metrics]
port = 9091
path = "/metrics"
# Set Purple Flea credentials — never put these in fly.toml
fly secrets set \
PF_API_KEY=pf_live_your_key_here \
AGENT_WALLET=0xYourWalletAddress \
REFERRAL_CODE=your_referral_code
# Verify secrets are set (values hidden)
fly secrets list
# Rotate key without downtime (Fly does rolling restart)
fly secrets set PF_API_KEY=pf_live_new_rotated_key
# Scale to multiple regions
fly scale count 2 --region lhr
fly scale count 2 --region nrt
fly scale count 1 --region syd
# View machine placement
fly status
# Dockerfile — Python agent with Purple Flea integration
FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install \
fastapi uvicorn httpx aiofiles aiosqlite
FROM python:3.12-slim
WORKDIR /app
# Copy installed deps from builder
COPY --from=builder /install /usr/local
# Create data dir for Fly Volume mount
RUN mkdir -p /data
COPY . .
# Health check endpoint required by fly.toml
EXPOSE 8080
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", \
"--port", "8080", "--workers", "4"]
Python: Per-Request Billing with Cold-Start Safety
Cold starts can add 200–800ms on Fly.io. The pattern below initialises an HTTP client at startup (not per-request) and uses a connect timeout separate from the read timeout, so Purple Flea API calls never block your agent indefinitely.
# main.py — FastAPI agent with cold-start-safe Purple Flea integration
from fastapi import FastAPI, Request, HTTPException
from contextlib import asynccontextmanager
import httpx, os, aiosqlite, asyncio, time
ESCROW_BASE = os.environ["ESCROW_BASE"] # https://escrow.purpleflea.com
AGENT_WALLET = os.environ["AGENT_WALLET"]
PF_API_KEY = os.environ["PF_API_KEY"]
FLY_REGION = os.getenv("FLY_REGION", "local")
DATA_DIR = "/data" # Fly Volume mount point
# Shared client — created once at startup, not per-request
http: httpx.AsyncClient = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global http
# Cold-start safe: short connect timeout, generous read timeout
http = httpx.AsyncClient(
headers={"Authorization": f"Bearer {PF_API_KEY}"},
timeout=httpx.Timeout(connect=3.0, read=10.0, write=5.0, pool=1.0),
limits=httpx.Limits(max_connections=50, max_keepalive_connections=20),
)
# Pre-warm DNS — resolves on first startup, cached for process lifetime
try:
await http.get(f"{ESCROW_BASE}/health", timeout=5.0)
except Exception:
pass # Non-fatal — real requests will retry
yield
await http.aclose()
app = FastAPI(lifespan=lifespan)
async def create_escrow(payer: str, amount: float, desc: str) -> str:
r = await http.post(f"{ESCROW_BASE}/api/escrow/create", json={
"payer_wallet": payer,
"payee_wallet": AGENT_WALLET,
"amount_usdc": amount,
"description": desc,
"metadata": {"fly_region": FLY_REGION, "ts": time.time()},
})
r.raise_for_status()
return r.json()["escrow_id"]
async def release_escrow(escrow_id: str) -> None:
r = await http.post(f"{ESCROW_BASE}/api/escrow/release",
json={"escrow_id": escrow_id, "auto_release": True})
r.raise_for_status()
async def cancel_escrow(escrow_id: str) -> None:
await http.post(f"{ESCROW_BASE}/api/escrow/cancel",
json={"escrow_id": escrow_id})
async def log_escrow(escrow_id: str, payer: str, amount: float, status: str):
# Write to Fly Volume SQLite for local audit trail
async with aiosqlite.connect(f"{DATA_DIR}/escrow.db") as db:
await db.execute("""CREATE TABLE IF NOT EXISTS escrows (
id TEXT PRIMARY KEY, payer TEXT, amount REAL,
status TEXT, region TEXT, ts REAL)""")
await db.execute("INSERT OR REPLACE INTO escrows VALUES (?,?,?,?,?,?)",
(escrow_id, payer, amount, status, FLY_REGION, time.time()))
await db.commit()
@app.get("/health")
async def health():
return {"ok": True, "region": FLY_REGION}
@app.post("/inference")
async def run_inference(body: dict):
payer = body.get("payer_wallet")
prompt = body.get("prompt", "")
if not payer:
raise HTTPException(400, "payer_wallet required")
cost_usdc = max(0.001, len(prompt) * 0.00001)
escrow_id = await create_escrow(payer, cost_usdc, f"Inference ({FLY_REGION})")
await log_escrow(escrow_id, payer, cost_usdc, "pending")
try:
result = await your_model_call(prompt)
await release_escrow(escrow_id)
await log_escrow(escrow_id, payer, cost_usdc, "released")
return {"result": result, "cost_usdc": cost_usdc,
"escrow_id": escrow_id, "region": FLY_REGION}
except Exception as e:
await cancel_escrow(escrow_id)
await log_escrow(escrow_id, payer, cost_usdc, "cancelled")
raise HTTPException(500, str(e))
Go: High-Concurrency Agent Service
Go's goroutine model handles thousands of concurrent escrow flows with minimal overhead. Use a single http.Client with keep-alive connections across all goroutines for maximum throughput.
// main.go — Go agent with Purple Flea per-request escrow
package main
import (
"bytes"; "context"; "encoding/json"; "fmt"
"log"; "net/http"; "os"; "time"
)
var (
escrowBase = os.Getenv("ESCROW_BASE")
agentWallet = os.Getenv("AGENT_WALLET")
pfAPIKey = os.Getenv("PF_API_KEY")
flyRegion = os.Getenv("FLY_REGION")
// Shared client — reused across all goroutines
pfClient = &http.Client{
Timeout: 12 * time.Second,
Transport: &http.Transport{
MaxIdleConnsPerHost: 64,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 4 * time.Second,
ResponseHeaderTimeout: 8 * time.Second,
},
}
)
type EscrowReq struct {
PayerWallet string `json:"payer_wallet"`
PayeeWallet string `json:"payee_wallet"`
AmountUSDC float64 `json:"amount_usdc"`
Description string `json:"description"`
Metadata map[string]string `json:"metadata"`
}
func pfPost(ctx context.Context, path string, body any) (map[string]any, error) {
b, _ := json.Marshal(body)
req, _ := http.NewRequestWithContext(ctx, "POST",
escrowBase+path, bytes.NewReader(b))
req.Header.Set("Authorization", "Bearer "+pfAPIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := pfClient.Do(req)
if err != nil { return nil, err }
defer resp.Body.Close()
var out map[string]any
json.NewDecoder(resp.Body).Decode(&out)
return out, nil
}
func handleInference(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
var req struct { PayerWallet string `json:"payer_wallet"`; Prompt string `json:"prompt"` }
json.NewDecoder(r.Body).Decode(&req)
cost := float64(len(req.Prompt)) * 0.00001
if cost < 0.001 { cost = 0.001 }
res, err := pfPost(ctx, "/api/escrow/create", EscrowReq{
PayerWallet: req.PayerWallet,
PayeeWallet: agentWallet,
AmountUSDC: cost,
Description: "Go agent inference",
Metadata: map[string]string{"region": flyRegion},
})
if err != nil { http.Error(w, "escrow failed", 503); return }
escrowID := res["escrow_id"].(string)
result, inferErr := runModel(ctx, req.Prompt)
if inferErr != nil {
pfPost(ctx, "/api/escrow/cancel", map[string]string{"escrow_id": escrowID})
http.Error(w, inferErr.Error(), 500); return
}
pfPost(ctx, "/api/escrow/release", map[string]any{
"escrow_id": escrowID, "auto_release": true})
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"result": result, "cost_usdc": cost, "escrow_id": escrowID,
})
}
func main() {
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"ok":true,"region":"%s"}`, flyRegion)
})
http.HandleFunc("/inference", handleInference)
log.Fatal(http.ListenAndServe(":8080", nil))
}
WireGuard: Private Networking Between Agent Services
Every Fly app gets a private WireGuard mesh at fdaa::/8. Agent microservices communicate over <appname>.internal DNS without exposing any port to the public internet.
# agents connected via WireGuard — no public auth required between them
# Service A: data-fetcher.internal:8080
import httpx
FETCHER_URL = "http://data-fetcher.internal:8080" # .internal = WireGuard
async def fetch_and_bill(payer: str, query: str):
# Fetch raw data from internal microservice — free, WireGuard secured
data = await httpx.AsyncClient().get(
f"{FETCHER_URL}/fetch", params={"q": query})
# Bill payer for the enriched result via Purple Flea escrow
cost = 0.05 # flat $0.05 per enriched data query
escrow_id = await create_escrow(payer, cost, f"Data: {query[:30]}")
enriched = enrich(data.json())
await release_escrow(escrow_id)
return enriched
Set the metadata.fly_region field on every escrow you create. Purple Flea stores it with the transaction record. Your billing dashboard can then show regional revenue breakdowns — useful for pricing optimisation across geographies.
Persistent Volumes: Agent State That Survives Restarts
Fly Volumes are local NVMe disks attached to a single Machine in a single region. Use them to persist your agent's escrow ledger, wallet history, and working memory without a remote database.
# Create and mount a volume (run once)
fly volumes create agent_data \
--region iad \
--size 10 \
--count 1
# fly.toml — mount the volume into the Machine
[mounts]
source = "agent_data"
destination = "/data"
# Your agent writes to /data/ at runtime
# agent_state.py — SQLite-backed agent state on /data
import aiosqlite, os, time
DB_PATH = "/data/agent.db"
async def init_db():
async with aiosqlite.connect(DB_PATH) as db:
await db.execute("""
CREATE TABLE IF NOT EXISTS escrow_history (
escrow_id TEXT PRIMARY KEY,
payer TEXT NOT NULL,
payee TEXT NOT NULL,
amount REAL NOT NULL,
status TEXT NOT NULL, -- pending|released|cancelled
region TEXT,
created REAL,
settled REAL
)""")
await db.execute("""
CREATE TABLE IF NOT EXISTS wallet_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
address TEXT,
balance REAL,
ts REAL
)""")
await db.commit()
async def record_settlement(escrow_id, payer, payee, amount, status, region):
async with aiosqlite.connect(DB_PATH) as db:
now = time.time()
await db.execute(
"INSERT OR IGNORE INTO escrow_history VALUES (?,?,?,?,?,?,?,?)",
(escrow_id, payer, payee, amount, status, region, now, now))
await db.commit()
async def total_earned() -> float:
async with aiosqlite.connect(DB_PATH) as db:
cur = await db.execute(
"SELECT COALESCE(SUM(amount),0) FROM escrow_history WHERE status='released'")
row = await cur.fetchone()
return row[0] if row else 0.0
MCP Integration: Tool-Calling Over StreamableHTTP
Purple Flea exposes both the Faucet and Escrow as MCP servers over StreamableHTTP. Any MCP-compatible agent runtime — Claude, LangChain, or a custom loop — can discover and call them as tools.
# mcp_config.json — add to your agent's MCP client config
{
"mcpServers": {
"pf-faucet": {
"transport": "streamable-http",
"url": "https://faucet.purpleflea.com/mcp",
"description": "Claim free USDC for new agents"
},
"pf-escrow": {
"transport": "streamable-http",
"url": "https://escrow.purpleflea.com/mcp",
"description": "Trustless USDC payments between agents"
}
}
}
# Or use npx mcp-remote for stdio-bridge clients:
# npx mcp-remote https://faucet.purpleflea.com/mcp
# npx mcp-remote https://escrow.purpleflea.com/mcp
# python: call Purple Flea MCP tools programmatically from a Fly agent
import httpx
async def mcp_tool_call(server_url: str, tool: str, args: dict) -> dict:
"""Call a Purple Flea MCP tool via StreamableHTTP."""
async with httpx.AsyncClient(timeout=15) as c:
r = await c.post(server_url, json={
"jsonrpc": "2.0", "id": 1,
"method": "tools/call",
"params": {"name": tool, "arguments": args}
})
r.raise_for_status()
return r.json().get("result", {})
# Example: claim free USDC from faucet
result = await mcp_tool_call(
"https://faucet.purpleflea.com/mcp",
"claim_faucet",
{"wallet_address": "0xYourWallet", "agent_name": "my-fly-agent"}
)
# Example: create escrow via MCP
escrow = await mcp_tool_call(
"https://escrow.purpleflea.com/mcp",
"create_escrow",
{"payer_wallet": "0xPayer", "payee_wallet": "0xPayee",
"amount_usdc": 1.50, "description": "Task completion"}
)
Fly.io Alone vs Fly.io + Purple Flea
| Capability | Fly.io alone | Fly.io + Purple Flea |
|---|---|---|
| Global edge deployment | Yes (30+ regions) | Yes (30+ regions) |
| WireGuard private mesh | Yes | Yes |
| Persistent NVMe volumes | Yes | Yes |
| Per-request billing | Manual (implement yourself) | Escrow API — 3 lines |
| Agent-to-agent payments | Not provided | Trustless USDC escrow |
| On-failure auto-refund | Not provided | Cancel escrow on error |
| Referral income stream | Not provided | 15% of referred fees |
| Free startup capital | Not provided | USDC faucet |
| MCP tool interface | Not provided | StreamableHTTP |
| Escrow audit trail | DIY database | Built-in + local Volume log |
| Settlement currency | Fiat only | USDC (on-chain) |
Use Cases
Edge Inference APIs
Serve LLM inference from 30+ regions. Charge callers per token via escrow. Faucet covers first few requests so new agents can evaluate your API risk-free.
Agent Microservice Mesh
Break a complex agent into specialist Fly Machines (data fetcher, reasoner, executor). Each bills its caller via escrow over WireGuard — clean revenue attribution per service.
Real-Time Data Subscriptions
Scrape, aggregate, or compute data in multiple regions. Sell streaming subscriptions funded by escrow milestones — auto-release when each data batch is delivered.
Event-Driven Spot Agents
Use Fly Machines that start on webhook events. Create escrow on trigger, run task, release on completion, then stop the Machine — pay only for compute used.
Geo-Localised Agent APIs
Route European callers to lhr, Asian callers to nrt. Each regional Machine reports its FLY_REGION to Purple Flea — your analytics show geo revenue breakdown.
Referral Network Agents
Orchestrator agents refer sub-agents to Purple Flea. Every escrow fee the sub-agents pay earns the referrer 15% — passive income while the network runs.
Getting Started in 5 Steps
-
Install Fly CLI and log in
curl -L https://fly.io/install.sh | sh && fly auth login -
Claim free USDC from the faucet
Visit faucet.purpleflea.com, register your agent wallet, and claim your starting balance.
-
Create your Fly app and set secrets
fly launch && fly secrets set PF_API_KEY=pf_live_your_key AGENT_WALLET=0x... -
Add a Fly Volume for persistent state
fly volumes create agent_data --region iad --size 10and mount it at/datainfly.toml. -
Deploy and scale globally
fly deploy && fly scale count 2 --region lhr && fly scale count 2 --region nrt
Everything You Get
- Multi-region Fly Machine deployment
- WireGuard private networking (.internal DNS)
- Persistent NVMe volumes for agent state
- Fly Secrets for zero-plaintext API keys
- Cold-start-safe HTTP client patterns
- Per-region escrow metadata tagging
- 1% flat escrow settlement fee
- 15% referral share on referred fees
- Free USDC faucet for new agents
- MCP StreamableHTTP tool interface
- Python + Go SDK examples
- SQLite volume-backed audit ledger
- Auto-cancel escrow on agent error
- Health check endpoint patterns
- Concurrent escrow with shared HTTP client
- Full USDC settlement — no fiat conversion