Skip to content

How it works

What one install is made of, how the duty cycle works, and what wakes it.

Updated

One install

One install on your account is:

  • one Worker, the front door;
  • one Control Durable Object (Control DO), which holds config, the message inbox, alerts and the record of every operation;
  • one Runtime DO for each epoch. An epoch is one image plus one Worker code version, and one epoch is active at a time. The Runtime DO owns the container;
  • one container, 2 vCPU, 6 GiB memory, 20 GB disk by default, that runs OpenClaw;
  • one sandbox container for agent tools, only when sandbox is on;
  • one R2 bucket, oc-host-<inst> by default.

The Worker is oc-host by default, at https://<name>.<subdomain>.workers.dev. With Cloudflare Access on, the URL is your custom domain.

Inside the container, a supervisor (ocsup) restores state, starts OpenClaw and runs the sleep steps. It reaches the Worker through gates, private addresses that the Worker serves (Security lists them).

The owner console

The owner console is the page at your install URL (/), the same on a custom domain and on workers.dev. Without the owner cookie, it shows the login form and nothing else.

With the owner cookie, it shows a state card and three tabs. The top bar has the install name, Open Control UI (/chat) and Sign out. The state card shows the state (running, asleep, waking, retrying) and for how long, the last activity, the next scheduled wake, the projected month cost at the last 24 hours’ pace, the check count, and one state button:

State Button
Asleep Wake now
Starting Waking, 20 s, disabled
Running Sleep now. When a rule keeps the install awake, the rule shows beside the button.
Going to sleep Going to sleep, disabled. There is no cancel.
Backoff, or every restore refused Retry now, with the fix line of the open alert beside it
Stopped by you Start

Each tab is its own page, and the tab names are links: Overview is /, Alerts and log is /?tab=log, and Setup is /?tab=setup. An item shows in one tab only, and buttons are only in Setup and Alerts and log.

Tab What it holds
Overview Cost: this month at the last 24 hours’ pace, the always-on month, percent saved, and the cost guard. An alerts summary that links to Alerts and log. Budgets: each daily cap with a bar, and Raise when one is at 80 percent. Connected checks: model (provider, model id, gateway, last test time), web search, browser and web fetch, media, each channel (transport, webhook, last platform error, whether it blocks sleep), the install (CLI, image, OpenClaw, upgrade available), config render, wake list and held inbox. Backups: newest archive age and verified flag, snapshots and chain length, replica lag, next maintenance.
Alerts and log Open alerts first: severity badge, detail, fix line, and Acknowledge (closes the alert). For doctor-failed, it also shows the tail of the Doctor output. Then the log of wakes, sleeps, restarts and archives of the last 30 days, newest first, with the time and how long each took. Filter the log by kind with the links above the table. Then closed alerts.
Setup Things to connect and set up, one row each with its state and one button, one control, or the command to run: Telegram, pairing requests with Approve, other channels, the alert channel (a URL field with Save, Test and Off), Access (a command: it needs a Cloudflare API token on your computer), the sandbox (a list of off, on and all, with Apply), the model (a list of the models in the config, with Apply), an upgrade or config render when one is due, and a daily limit near its cap with Raise. It also has Archive now and Restore drill.

Notes:

  • Approve does the same as ochost pairing approve telegram <code>. Raise doubles a limit, up to 1,000,000,000. A second click on the same page does not double it again.
  • The three controls run the same /ops actions as the CLI. Apply on the sandbox saves the choice in the Control DO and restarts the install, with no deploy. The row says “Applies at the next start” until the next boot. Apply on the model posts a new config version (source owner) with agents.defaults.model.primary changed, then restarts. If the install sleeps, it does not wake: the change applies at the next start.
  • Save on the alert channel stores the URL in the Control DO. The Worker cannot set its own secret, so the sender uses this URL first and the ALERT_URL secret second. Off stops all alerts, even when the secret is set. Test sends the same test alert as ochost alerts test and shows its code; run ochost alerts ack <code> to confirm it arrived. ochost alerts setup still works: it sets the secret, which applies when no URL is saved in the console.
  • A choice made in the console wins over the value in oc-host.jsonc. After you use the sandbox control, ochost sandbox on|off and a deploy no longer change the sandbox, so keep using the console.
  • A status is a dot and a word: OK, Warning or Failing. A warning or failing row has the fix line that ochost doctor prints.
  • After a button, the page returns to the tab that holds the button.
  • The page follows the light or dark setting of your system.
  • Opening the page does not wake the install (see Security). The model test and the Telegram webhook check run at most once a minute. While the install sleeps, check rows show the last values and say so.
  • GET /ops/doctor serves the same JSON, and ochost doctor prints it, so the page and the CLI agree.
  • The page works with JavaScript off. Each tab is a link and each button is a form. A small script reloads the current tab every 30 seconds (5 seconds while the state changes).

The duty cycle

The duty cycle is the default mode, sleep.mode: auto: the container sleeps when it is idle and wakes when something needs it.

Setting Default Meaning
sleep.idleMinutes 5 Idle time before sleep. It doubles after a quick re-wake, up to 60.
sleep.minAwakeMinutes 2 Stay awake at least this long after a boot.
sleep.minSleepMinutes 10 The next known wake must be at least this far away.
sleep.uiIdleMinutes 15 An open Control UI tab is closed after this long without user input.
sleep.uiMaxMinutes 60 A tab is closed after this long without a chat message or a write (a change made from the UI).
sleep.maxAwakeHours 16 Past this uptime, the install restarts when idle.
sleep.proactive 2h@08-22 OpenClaw’s proactive check runs every 2 hours from 08:00 to 22:00 local time.

These keys are in oc-host.jsonc. Change one with ochost config set <key> <value>, and it applies at the next op, with no deploy.

To sleep, the install also needs an empty inbox and no running turn, cron run or pending delivery. A cost guard keeps the install awake when the last 24 hours of sleep and wake cycles would cost more than always-on.

A sleep takes a snapshot of the container disk, and a wake starts the container from that snapshot. Each wake also gets a new generation (gen) of the SQLite replica, a new R2 path that only the current boot may write. This stops a container from an earlier boot from writing over newer data.

What wakes it

Event Wakes?
A Telegram message from a paired sender Yes. The Worker stores and acknowledges it first, then the container starts and receives it.
A verified Slack (HTTP mode) or Twilio SMS event Yes. Same path as Telegram. There is no wake list for these yet. Twilio delivery receipts do not wake.
A cron alarm (OpenClaw cron jobs and the proactive check) Yes. The Control DO sets a wake alarm before the next job.
You open the Control UI (/chat) with the owner cookie Yes. You see a waking page until the container runs.
You select Wake now in the owner console Yes.
You select Archive now in the owner console Yes. The archive runs in the container.
An ochost command that needs the container, such as wake or backup create Yes.
Daily maintenance Yes, if there was no wake in 24 hours or a held message is 20 hours old.

What does not wake it

Event Result
An anonymous request, a wrong path or a wrong webhook secret 404 or 401. No wake.
You open the owner console at / No. It shows the state and cached checks.
A Telegram message from an unknown sender Stored and acknowledged. Held until the next wake. No wake.
A Control UI tab that was closed for idle The tab shows “offline”. Its reconnect does not wake the install. Reload the page to clear it.

Cold reply time

In tests:

  • The first reply after a cold wake took p50 31.6 s with a mock model (30 runs).
  • From the acknowledgement of a message to a running container took p50 32.9 s (200 sleep and wake cycles).
  • One real reply came 20.8 s after the message. The model took 3.5 s of that.

While Telegram waits, the Worker shows a “Waking up” message (see Channels), and replies stream by message edits. A warm-up call at boot exists but is off, because it made the first reply slower in tests.

A warm reply takes about 1 second plus model time. Limits and tradeoffs states the bound as “about a minute”.

The Worker acknowledges the Telegram webhook at the edge while the container sleeps. In 200 sleep and wake cycles the acknowledgement took p50 72 ms and p99 390 ms. No update was lost.

What keeps it awake

A channel that needs a socket keeps the install always-on. The sleep check reads the live channel status, and a running channel account that does not use the managed webhook blocks sleep. Other blockers are a running cron job, an open ingress event, a pending delivery, an event-driven job and an open ochost import (it fails with import-stalled after 30 minutes without an answer). ochost status names the blocker. See Channels and Cost.

Design choices, and the failures they prevent

oc-workers started from a reading of the earlier Cloudflare template for OpenClaw, cloudflare/moltworker, and the failures its users reported. Four of those reports shaped the design.

Reported failure Cause in the template oc-workers Tested
Stuck at “Container is starting up”. The start runs inside a web request. No supervisor, no time limit, no retry. The Runtime DO owns the start. Each attempt has a time limit (30 s, 300 s on the first pull of a new image). A failed start is a step on the restart ladder, which waits longer before each new try and never gives up: up to 5 minutes between tries for a platform error, and every 30 minutes with a high alert every 6 hours at its last level. A start that goes 40 rounds without a running container ends with start-loop and goes to the ladder too. The console shows the state and the fix. 50 random kills, 0 restart timeouts.
Ran for days, then the start never ends. The backup expires after 7 days and is deleted. A restore can run again under a live gateway. No expiry. An old archive is removed only when a newer one is verified, and the newest verified archive is always kept. The restore runs before the gateway starts, never under it. No empty boot in the loss tests. Rebuilt after a wiped Control DO in 69.3 s.
The gateway runs, no reply comes. No inbox. A message lost in a restart is gone. The Control DO stores and acks every message before it wakes the container. A message accepted and not answered before a start is replayed once. A message not answered for 5 minutes while the gateway runs raises the alert no-reply. 587 messages during 50 kills, 586 acked; 4 unanswered led to the replay rule. 15 of 15 answered once after a kill right after accept.
Data loss, R2 mount failures. State lives on a mounted bucket. A failed mount means an empty home and a fresh setup. No mount. The database streams to R2 through a gate; the container holds no R2 key. Archives are verified by read-back and a monthly restore drill. An install with state refuses to boot empty. See State and backups.

Platform reliability

A 72 hour test on Cloudflare with 24 injected faults showed 0 platform-caused container stops in 3.7 awake instance-days, and the sleep-mode instance had 71 of 71 wakes succeed. After a fault we caused, the container restarted in 224 to 1045 s, of which 180 to 240 s is the restart delay we set. Restore time grew from 18 to 41 s to 215 to 249 s in the last two restarts. Cloudflare gives no uptime guarantee for containers.

Next

Go to Limits and tradeoffs.

Type to search…

↑↓ navigate↵ selectEsc close