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.