Opereo AI

Architecture

What Opereo AI stores, what the box owns, and what happens when you create an engine.

Architecture

Opereo AI is a control plane, not a runtime. The agents don't run inside Opereo AI — each engine brings its own substrate (a Hermes engine is a box of its own, on the provider you pick), and Opereo AI drives it over HTTP.

The split

LayerOwns
Opereo AI (Next.js + Postgres)organizations, projects, engines, API keys, canvas layout
The engine's runtimethe agents, their config, sessions, transcripts, skills, memory — all of it
The edge Worker (optional)which hostname label points at which box origin
The relay (optional)which WhatsApp sender is bound to which engine and profile

The important consequence: Opereo AI stores almost nothing about your agents. A profile's skills, channels, automations and instructions live on its box and are read back on demand. There's no sync loop and no second copy to drift.

What Opereo AI does keep is small. Each engine row carries a status (creating, ready, failed), an error, and a meta JSON column typed per kind. For Hermes that's one flat object — { type, name, url, dashboardPassword, dashboardCookie } plus one nullable per-provider field (hetzner: { ip }, railway: { serviceId }, upstash: { boxId }) — everything needed to reach the box, and nothing else.

The two Workers

Both are optional Cloudflare Workers, each its own package under workers/, deployed independently of the app.

The edge Worker (workers/edge/) is a hostname router: it reads the label off <name>.<BOX_DOMAIN>, looks the matching origin up in a KV namespace, and forwards the request. It is provider-blind — the app writes the KV entry when it provisions a box, so the Worker never knows or cares whether the origin is a Hetzner VM, a Railway service or an Upstash Box. Without it, each box is reached on its provider's own origin.

The relay (workers/relay/) is a Hermes Relay connector fronting one shared WhatsApp number for every engine. Nothing dials into a box: the box's Hermes gateway opens a single outbound WebSocket to the relay, held by a Durable Object per engine that hibernates while idle and buffers inbound messages in its own storage until the gateway reconnects and acknowledges them. A D1 database is the directory — per-engine gateway credentials, the shared number, the WhatsApp-sender-to-profile bindings, and pending link codes — and inbound media is re-hosted in R2 behind short-lived capability URLs. The app never touches the socket or the database: it only ever calls the relay's admin API, to enrol an engine and to mint a link code.

Creating an engine

Creating a project provisions nothing; an engine is created on its own, and the call returns before the box exists.

  1. Insert — an engine row lands with status creating and the request returns immediately. The rest runs in the background.
  2. Provision — the provider's create brings up the box (a Hetzner VM, a Railway service, an Upstash Box), and its origin is registered with the edge Worker if public hostnames are configured.
  3. Configure — Opereo AI waits for the box's dashboard, then seeds the default profile: its model on the engine's free tier, the connector plumbing, and its API gateway. No provider keys are injected — users connect their own.
  4. Finish — the row becomes ready, or failed with the error that stopped it.

The UI polls until the row settles. A Hetzner engine is usually ready in 2–3 minutes (it pulls the image cold), Railway in about a minute, Upstash in about 25 seconds. Every step is idempotent — it looks for what it needs before creating it — so re-running a failed creation resumes rather than duplicating.

Talking to an engine

Each engine kind exposes its whole surface through its own API namespace (/api/hermes/{engineId}/… today), and Opereo AI reads agent state back from the runtime on demand. How a kind talks to its substrate is its own business — Hermes' typed dashboard client is described in Engines.

Auth

Organizations, members, and roles come from the auth provider. Every API route resolves the caller first — a browser session or an org-scoped API key — then checks that the caller's org owns the project before touching an engine. Admins reach any project in their org; members are scoped to their own. The full contract is on the API page.

On this page