Architecture
flowchart LR
Client["Client"]
API["Fastify API<br/>Auth + Validation + Limits"]
HTTP["HTTP Engine"]
Queue["BullMQ Queue"]
Worker["Execution Worker"]
Browser["Playwright + Chromium<br/>Isolated Context per Run"]
DB[("PostgreSQL<br/>Runs, Attempts, Sessions,<br/>Usage, Metadata")]
Redis[("Redis<br/>Queue State<br/>Leases<br/>Rate Limits<br/>Idempotency")]
Artifacts[("Artifact Store<br/>Screenshots + Traces")]
Egress["Safe Egress Layer<br/>HTTP Transport + Chromium Proxy"]
Client --> API
API --> HTTP
HTTP --> Upstream["Upstream Services"]
API --> Queue
Queue --> Redis
Queue --> Worker
Worker --> Browser
Browser --> Artifacts
Worker --> DB
API --> DB
Browser --> Egress
PostgreSQL: principals, runs, attempts, actions, sessions, artifact metadata, usage, webhook deliveries
Redis: queue state, leases, rate/idempotency coordination
ArtifactStore: screenshots and traces outside relational rows
Safe egress: pinned HTTP transport + mandatory Chromium proxy
The API owns public contracts and admission control. Execution modules own fetching and deterministic domain behavior. The worker owns browser resources. PostgreSQL repositories and BullMQ adapters sit behind ports, so tests can use in-memory adapters without creating a second production architecture.
One long-lived Chromium process amortizes startup cost. Every run creates a new context, injects only requested state, and closes the context in finally. A disconnected process is discarded and recreated.
Workspace packages declare their own runtime dependencies and publish only dist JavaScript and declarations. A development export condition and TypeScript path map preserve source-level local development without weakening the package boundary.
Failures retain their layer: validation, target policy, upstream navigation, individual action, assertion, browser process, worker, schema, and artifact failures remain distinguishable.