Opereo AI

Engines

The engine contract — and Hermes, the first engine kind.

Engines

An engine is an agent runtime a project runs. Opereo AI doesn't assume what a runtime looks like — each engine kind brings its own substrate, its own panels, and its own API namespace (/api/<kind>/…). The contract every kind implements is deliberately small:

  • Identity — id, kind, name
  • nodes() — the agents it hosts, drawn on the project canvas
  • A wing — that kind's entire surface (config, chat, tools, everything), used only by its own routes and panels

There are no plugin registries or capability flags: each kind is wired explicitly — the node it draws, the panels it opens, the routes it answers. One kind ships today.

Hermes

A hermes engine is a Hermes runtime on a box from one of three providers — same Hermes, same dashboard API, same UI. Opereo AI provisions the box, drives it over the runtime's own dashboard HTTP API, and hands you the box's full dashboard — one click, opens in its own tab. Everything a Hermes engine can do is exposed under /api/hermes/{engineId}/….

Hermes calls its agents profiles. Each profile is independent and owns:

  • Soul — its instructions and persona
  • Skills — instruction packs it can load
  • Tools — toolsets and MCP servers it can call
  • Integrations — connected third-party apps it acts through
  • Channels — where it can be reached (Telegram, Slack, email, and more)
  • Automations — scheduled prompts that run on a cron
  • Memory — what it remembers across sessions
  • Secrets — credentials scoped to it

New profiles start on Hermes' anonymous free model tier — no platform key ever touches the box. Connect your own providers in the Models panel for stronger models.

Names and descriptions

An agent's name is a free-text display label (up to 64 characters); its description can contain up to 500 characters. Create, duplicate, and hub install generate a stable internal profile slug. Editing the name or description updates display_name, description, and description_auto: false together in the profile's profile.yaml, preserving other fields. It leaves the directory, profile UUID, gateway configuration, and relay routing intact. The default profile can have a display name too. Existing profiles without one show their original slug until edited.

Providers

Where the box runs is chosen per engine at create time, through the API's type field. It defaults to hetzner, and a provider is only offered when its credentials are configured.

  • hetzner — a Hetzner Cloud VM running the official Hermes container behind Caddy. Isolation: a whole VM.
  • railway — the same container as a Railway service with a persistent volume. Isolation: a container.
  • upstash — an Upstash Box restored from a baked snapshot. Isolation: a box.

The Upstash snapshot

Only the upstash provider bakes an image. Restoring a snapshot that already has the runtime, its dependencies, a browser engine and the built dashboard is the difference between a box starting in seconds and in half an hour. The other two providers pull the published Hermes image instead. Baking is a maintenance task run when the upstream version moves — see Self-hosting.

Versions and updates

Nothing updates itself. GET /api/hermes/{engineId}/version reports the box's current Hermes version alongside the latest published one, and POST /api/hermes/{engineId}/update moves that one engine to it — pulling the new image over SSH on hetzner, redeploying the service on railway, running the dashboard's own updater on upstash.

Talking to the box

Opereo AI speaks to each box through the runtime's own dashboard HTTP API. The client is generated from the box's OpenAPI spec, so the control plane is typed end to end against the exact runtime version it provisions. Upgrades are cheap: if a new runtime version doesn't move the API surface, the generated client doesn't change.

The box dashboard

Each box also serves the runtime's full dashboard UI. The Dashboard tab hands you its URL and credentials and opens it in a new tab — the box is its own site with its own session.

WhatsApp via the relay

Channels are configured on the box itself, one credential per platform. WhatsApp is the exception: a self-hosted Opereo AI can run the optional relay, a Cloudflare Worker that fronts one shared WhatsApp number for every engine. The box holds no WhatsApp credential and opens no inbound port — its Hermes gateway dials out to the relay over a single WebSocket. Opereo AI enrolls the engine with the relay and the provider stamps the resulting GATEWAY_RELAY_* variables into the box when the agent is created.

A user connects by asking for a link code in the app and texting link <code> to the number. The relay binds that WhatsApp sender to one profile, and from then on their messages reach that profile and its replies come back over the same socket. A stranger who texts the number only gets the instruction to link. Texting a new code moves the phone to another agent. While an agent is offline the relay keeps its messages for when it returns and tells the sender so, including how to link a different agent.

On WhatsApp the agent can send and receive text and media, react, show a typing indicator, and ask for a decision with native buttons or a list — so approvals work. WhatsApp has no message editing, so replies arrive as whole messages rather than streaming drafts, and it has no threads.

Setup is in Self-hosting.

Adding an engine kind

Engine kinds are code, not configuration: a new kind means an entry in ENGINE_KINDS, a pack directory implementing the contract (laid out like hermes/), explicit canvas wiring for its node and panels, and an /api/<kind>/… namespace for its operations. The full recipe — and the shorter one for adding a Hermes provider — lives in CONTRIBUTING.md.

On this page