Appearance
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 │
└────────────┘ └──────────┘ └─────────────┘ └──────────────────┘| Module | Runtime | Hosted on | Public URL |
|---|---|---|---|
manage | CF Worker + DO | Cloudflare | manage.uranus.im |
agent | CF Worker | Cloudflare | agent.uranus.im |
docs-site | VitePress static | CF Pages | docs |
landing / oneagentcompany | Static | CF Pages | marketing sites |
imap-listener | Node.js (Docker) | Railway | internal |
mqtt (EMQX) | Docker container | self-hosted, CF Tunnel | internal |
browser (Steel) | Docker container | self-hosted | internal |
workflow-deployer | Node.js | self-hosted | internal |
oac-api / oneagentcompany-web / oac-storefront | CF Workers + own D1 | Cloudflare | api.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/completionsstyle 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:
| Variable | Default | Purpose |
|---|---|---|
MANAGE_API_URL | https://manage.uranus.im | Where to fetch configs / post webhooks |
MANAGE_API_KEY | — | Auth for the manage API |
PORT | 3000 | Health/status HTTP server |
POLL_INTERVAL | 60000 | Config-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
1883and dashboard18083bound to127.0.0.1only — no public exposure - Dashboard admin credentials set via
EMQX_DASHBOARD__DEFAULT_*env vars - Persistent volumes for
emqx-dataandemqx-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-apion ports 3000 (REST) and 9223 (CDP)ui—ghcr.io/steel-dev/steel-browser-uifor 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:
- The manage worker generates Workflow worker code (
workflow-codegen.ts) from the visual node/edge definition - It POSTs the generated code to the workflow-deployer
- The deployer writes the code + a
wrangler.tomlwith the right Workflow bindings, runswrangler deploy, and returns the deployment status
Key env vars:
| Variable | Purpose |
|---|---|
CLOUDFLARE_API_TOKEN | Token with "Workers Scripts: Edit" |
CLOUDFLARE_ACCOUNT_ID | Target account |
PORT | HTTP port (default 3456) |
AGENT_BASE_URL | Agent 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:
- Uranus owns platform concepts only — agents, tenants, conversations, contacts, tasks, memory, knowledge, workflows, gateways, policies.
- Products own their domain (OAC: businesses, modules, storefronts, payments) in their own Worker and database.
- Products push; Uranus never pulls. The manage worker has no binding to any product database.
- The API is product-neutral:
https://manage.uranus.im/api/internal/v1/*, bearerPLATFORM_INTERNAL_SECRET(alias of the olderOAC_FRONTDOOR_SECRET).
| Route | Purpose |
|---|---|
POST tenants {id, name?, source?, agentIds?, policy?} | create / update a tenant and link agents |
GET tenants/:id | read 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:
- Email arrives →
imap-listener(Railway) catches it via IMAP IDLE - imap-listener POSTs a webhook →
manageworker email gateway - manage worker routes into the
DashboardAgentDO → agentic loop runs - Agent calls
execute_workflow→ manage worker triggers the deployed Workflow worker - The Workflow worker (deployed earlier via
workflow-deployer) runs its steps - A step calls back via HMAC RPC →
manageworker/workflow-rpc→ agent memory/tools - If a step needs a browser, the agent drives Cloudflare Browser Run (or the
browserSteel 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.
Related
- Cloudflare Deployment — deploying the Worker-based modules
- Environment Variables — full env var reference
- Gateways — the unified inbound/outbound message pipeline
- Gateway Processing — how webhooks become agent turns
