OpenHands · Automation

Moving an automation from local to cloud (OHE)

Short answer to Rajiv's question: you can carry the code, but you re-register the automation. Here's why, and the way that avoids the problem entirely.

TL;DR. An automation is two things: a definition row in the service's database (schedule, entrypoint, model, trigger) and a code tarball in a file store. The tarball is portable; the definition and its storage/secrets are per-deployment. So there is no clean "copy the tarball and it runs on OHE" — you re-create the definition on the target. The robust fix: keep the code in a git-backed plugin and there's nothing to move.

What an automation actually is

Local and cloud/OHE run the same service (OpenHands/automation), so the API surface is the same. What differs is the state behind it. One automation =

Definition row (Postgres) trigger (cron) · entrypoint model · timeout · enabled org_id · user_id tarball_path ─────────┐ per-deployment state Code tarball (file store) local fs · or gcs/s3 in cloud portable — but path differs per store
The tarball travels; the row and its store/secrets don't.

Can you directly move tarballs between local and OHE?

You can move the tarball — download it (GET /automations/{id}/tarball) and upload it to the target. But that does not move the automation, because the target still has none of:

So: it isn't an API-surface problem (the service is the same code) — it's that a definition + its storage + its secrets are deployment-scoped state, not a portable artifact. Re-registering on the target is the step you can't skip. The good news: it's mechanical — an agent or a small script can do it.

The recommended way: don't move anything

John-Mason's point on the thread is the real answer. Put the smarts outside the automation, in a plugin loaded from a git ref. Plugin-triggered automations load the plugin fresh from the given ref (branch or tag) on every run — so the code is source-controlled, and the automation config is just a schedule plus a pointer.

Then "moving to cloud" stops being a transfer:

  1. Create the same thin automation on OHE (schedule + the same plugin ref).
  2. It pulls the same code from git on run. Nothing to copy, nothing to keep in sync.
This also answers Rajiv's first question (source control / backup): with a git-backed plugin, the code is already in git. The only per-deployment thing left is the schedule, which a tiny API script can dump to a file and re-create anywhere.

If you must move a tarball-based (custom-script) automation

  1. GET /automations/{id}/tarball on the source; keep the setup_script_path and entrypoint from its definition.
  2. On the target (OHE), create the automation via the same create endpoint, uploading that tarball and re-supplying schedule / entrypoint / model.
  3. Re-add secrets and API keys on the target — they don't travel with the tarball.
  4. Dispatch once (POST /automations/{id}/dispatch) to confirm it runs before trusting the schedule.
Watch: don't expect the source tarball_path to be meaningful on the target — the file store is different. And confirm the target's runtime/secrets match what the script assumes, or the first real run fails quietly.

Grounded in OpenHands/automation (models.py: the definition row; storage/factory.py: FILE_STORE local/gcs/s3; automation_router.py: the tarball + dispatch endpoints). Written by smolpaws 🐾 for the #proj-automations thread.