Skip to content

Supporting Services ​

Most of Uranus runs on Cloudflare's edge — Workers, Durable Objects, D1, R2, Vectorize, Queues. But a few capabilities can't run inside a Worker and are deployed as standalone services. This page documents every deployable unit in the stack and how the pieces connect.

Deployment topology ​

┌─────────────────────── Cloudflare ───────────────────────┐
│                                                           │
│  manage.uranus.im      agent.uranus.im                    │
│  ┌──────────────┐      ┌──────────────┐                   │
│  │ manage worker│      │  agent API   │                   │
│  │ + Dashboard  │      │ (A2A/OpenAI) │                   │
│  │ + DashboardAgent DO ◄──────┘ shares the same DO         │
│  └──────┬───────┘                                         │
│         │  D1 · R2 · Vectorize · Queues · Workers AI       │
│  docs.uranus.im   (landing, oneagentcompany — CF Pages)    │
└─────────┼─────────────────────────────────────────────────┘
          │ webhooks / REST (HTTPS, often via CF Tunnel)
          │
   ┌──────┴───────┬───────────────┬────────────────┐
   ▼              ▼               ▼                ▼
┌────────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────────────┐
│imap-listener│ │ EMQX     │ │ Steel       │ │ workflow-deployer│
│ (Railway)   │ │ broker   │ │ browser     │ │ (Node service)   │
│ Node + IMAP │ │ (Docker) │ │ (Docker)    │ │ wraps wrangler   │
└────────────┘ └──────────┘ └─────────────┘ └──────────────────┘
ModuleRuntimeHosted onPublic URL
manageCF Worker + DOCloudflaremanage.uranus.im
agentCF WorkerCloudflareagent.uranus.im
docs-siteVitePress staticCF Pagesdocs
landing / oneagentcompanyStaticCF Pagesmarketing sites
imap-listenerNode.js (Docker)Railwayinternal
mqtt (EMQX)Docker containerself-hosted, CF Tunnelinternal
browser (Steel)Docker containerself-hostedinternal
workflow-deployerNode.jsself-hostedinternal
oac-api / oneagentcompany-web / oac-storefrontCF Workers + own D1Cloudflareapi.oneagentcompany.com, oneagentcompany.com — a product composed on the platform API (below)

agent — Public Agent API ​

A separate Cloudflare Worker at agent.uranus.im that exposes Uranus agents to third parties. It does not contain its own agent logic — it binds to the same DashboardAgent Durable Object as the manage worker.

Provides:

  • A2A protocol — Agent-to-Agent JSON-RPC endpoints (/.well-known/agent.json, /a2a, /a2a/stream)
  • OpenAI-compatible chat API — /v1/chat/completions style endpoints so existing OpenAI SDK clients can talk to a Uranus agent
  • Agent discovery — /v1/agents

Because it shares the DO, anything an agent learns via the manage dashboard (memory, goals, contacts) is immediately visible through the public API and vice versa.

imap-listener — Inbound email ​

Cloudflare Workers cannot hold a long-lived IMAP IDLE connection. The imap-listener is a small always-on Node.js service that does.

What it does:

  • Connects to one or more IMAP accounts with IMAP IDLE for instant push (no polling delay)
  • On a new message, forwards it as a webhook to the manage worker's email gateway endpoint
  • Periodically syncs gateway configs from the manage API (POLL_INTERVAL, default 60s) so new email gateways are picked up without a redeploy
  • Auto-reconnects on disconnect

Deployment: Docker image, deployed on Railway (railway.json, restart-on-failure, 10 retries).

Key env vars:

VariableDefaultPurpose
MANAGE_API_URLhttps://manage.uranus.imWhere to fetch configs / post webhooks
MANAGE_API_KEY—Auth for the manage API
PORT3000Health/status HTTP server
POLL_INTERVAL60000Config-sync interval (ms)

HTTP API: GET /health (service health + gateway count), GET /status (per-connection state).

The manage worker's email gateway uses HMAC-SHA256 signed Reply-To addresses for thread continuity — the imap-listener just relays; thread matching happens on the manage side.

mqtt — EMQX broker ​

The MQTT gateway needs a broker. Uranus self-hosts EMQX 5.8 via docker-compose.yml.

Configuration highlights:

  • WebSocket listener on 0.0.0.0:8083 (path /mqtt) — all real traffic arrives here via a Cloudflare Tunnel
  • Plain TCP 1883 and dashboard 18083 bound to 127.0.0.1 only — no public exposure
  • Dashboard admin credentials set via EMQX_DASHBOARD__DEFAULT_* env vars
  • Persistent volumes for emqx-data and emqx-log

The manage worker publishes to and receives from EMQX via the broker's REST API (over the tunnel), not a direct MQTT TCP connection — Workers can't hold MQTT sockets either. Inbound device messages are delivered to the manage worker as webhooks, the same pattern as every other gateway.

See Gateways for how MQTT fits the unified gateway pipeline.

browser — Steel browser service ​

Browser automation runs against a Steel browser deployment (the open-source steel-dev/steel-browser). This is the backend behind browser sessions, snapshots, and browser_action.

Deployment: Docker (docker-compose.yml):

  • api — ghcr.io/steel-dev/steel-browser-api on ports 3000 (REST) and 9223 (CDP)
  • ui — ghcr.io/steel-dev/steel-browser-ui for live-view monitoring
  • DuckDB-backed log storage

The manage worker's BrowserClient (src/lib/browser.ts) drives this over the Steel REST API — creating sessions, running evaluate for accessibility-tree snapshots, navigating, screenshotting.

Provider abstraction

browser.ts defines a BrowserProvider interface with two implementations: browser-cf.ts against Cloudflare's Browser Run (@cloudflare/puppeteer on the BROWSER binding) and the Steel client. The active provider is selected by the BROWSER_PROVIDER env var (steel | cf), default cf — headless agent automation runs on Cloudflare, with hosted Live View, handoff and session recordings; Steel is kept for human-in-the-loop sessions when settings.browser.hitlProvider = "steel". See Browser Sessions → Providers.

See Browser Sessions and Browser Actions for the agent-facing API.

workflow-deployer — Workflow deployment service ​

Cloudflare Workflows require [[workflows]] bindings in wrangler.toml, which the Cloudflare Workers REST API does not support. The only way to deploy a Workflow programmatically is through the wrangler CLI.

The workflow-deployer is a Node.js HTTP service that wraps wrangler deploy:

  1. The manage worker generates Workflow worker code (workflow-codegen.ts) from the visual node/edge definition
  2. It POSTs the generated code to the workflow-deployer
  3. The deployer writes the code + a wrangler.toml with the right Workflow bindings, runs wrangler deploy, and returns the deployment status

Key env vars:

VariablePurpose
CLOUDFLARE_API_TOKENToken with "Workers Scripts: Edit"
CLOUDFLARE_ACCOUNT_IDTarget account
PORTHTTP port (default 3456)
AGENT_BASE_URLAgent API base injected into every deployed Workflow (default https://manage.uranus.im/api/agents)
AGENT_API_KEY (or INTERNAL_API_SECRET)Bearer the deployed Workflow uses to call back into the agent

Since 2026-09 the deployer writes AGENT_BASE_URL and AGENT_API_KEY into the generated wrangler.toml [vars], which is what the AI Agent and Approval Gate nodes use to call …/{agentId}/chat and …/{agentId}/workflows/approvals/request. If neither key variable is set the deploy still succeeds but logs a warning and those nodes fail at runtime. The deployer runs under pm2 on its own host; after pulling the change it needs npm run build and pm2 restart workflow-deployer with the secret in its environment.

Platform API and tenants ​

Uranus is the agent platform; products such as OAC (One Agent Company) compose it rather than share tables with it. OAC — the business layer (modules, storefronts, connectors, payments, its own API and MCP server) — is documented separately at docs.oneagentcompany.com; this page covers only what crosses the boundary. The boundary is written down in manage/docs/PLATFORM_API.md:

  1. Uranus owns platform concepts only — agents, tenants, conversations, contacts, tasks, memory, knowledge, workflows, gateways, policies.
  2. Products own their domain (OAC: businesses, modules, storefronts, payments) in their own Worker and database.
  3. Products push; Uranus never pulls. The manage worker has no binding to any product database.
  4. The API is product-neutral: https://manage.uranus.im/api/internal/v1/*, bearer PLATFORM_INTERNAL_SECRET (alias of the older OAC_FRONTDOOR_SECRET).
RoutePurpose
POST tenants {id, name?, source?, agentIds?, policy?}create / update a tenant and link agents
GET tenants/:idread a tenant
POST tenants/:id/consent {partnerAi}record partner-AI sub-processor consent
POST agents/:agentId/tenant {tenantId | null}link or unlink an agent
POST tenants/provision, tenants/renamed, agents/assign, agents/web-gateway, agents/mcp, agents/prompt, …product-neutral aliases of the legacy /api/internal/oac/* handlers, which remain as aliases

The tenants table (migration 078: id, name, source, partner_ai_consent_at, policy) plus agents.tenant_id is what knowledge scope, task memory and the data-flow policy key on; agent/tenancy.ts is the only reader. The tenant id is whatever the product uses (OAC: its business id). The reverse direction — Uranus calling a product — uses that product's own API (OAC_API_BASE + the shared secret), e.g. the control panel's OAC admin pages.

How a message flows through the supporting services ​

A concrete example — an inbound email triggering a workflow that calls back:

  1. Email arrives → imap-listener (Railway) catches it via IMAP IDLE
  2. imap-listener POSTs a webhook → manage worker email gateway
  3. manage worker routes into the DashboardAgent DO → agentic loop runs
  4. Agent calls execute_workflow → manage worker triggers the deployed Workflow worker
  5. The Workflow worker (deployed earlier via workflow-deployer) runs its steps
  6. A step calls back via HMAC RPC → manage worker /workflow-rpc → agent memory/tools
  7. If a step needs a browser, the agent drives Cloudflare Browser Run (or the browser Steel service for HITL sessions)

Every hop except the imap-listener IDLE connection and the broker/browser sockets is a stateless HTTPS request — which is exactly why imap-listener, mqtt and the Steel browser service live outside the Workers runtime.