Quickstart
From nothing to a scheduled job.
About ten minutes. You will create an account, describe one workload, put the controller on the machine that does the work, and let the first night run. The canonical version of these instructions ships with the code — see the repository README.
Before you start
- A machine that runs the work — Linux, Python 3.12 or newer. It does not need a GPU; without one, energy is simply not measured.
- An NVIDIA GPU is what makes the numbers good: exact energy comes from the hardware counter via NVML.
- A workload that has a deadline rather than a start time. If it has to run the moment you ask, this is the wrong tool.
- 01
Create an account
Accounts live only on Async Energy — you do not need a Hungry Machines login. The response carries the
access_tokenyou will use as a bearer token for everything below.bashcurl -s -X POST https://api.async.energy/auth/signup \ -H 'Content-Type: application/json' \ -d '{"email":"you@example.com","password":"a-good-long-password"}' # Save the token from the response: export TOKEN="<access_token>" - 02
Set your timezone and prices
Deadlines like
"by 7am"are read in your timezone, so set it before you register anything. Your price location decides which curve the optimizer plans against; leave itnullto take the deployment default.bashcurl -s -X PUT https://api.async.energy/api/v1/settings \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"location": null, "timezone": "America/New_York"}' # See which price locations exist: curl -s -H "Authorization: Bearer $TOKEN" \ https://api.async.energy/api/v1/settings | jq .available_locations - 03
Describe the workload
A workflow is what recurs plus the constraint. The optimizer only needs enough to schedule it — the runnable definition stays on your box in the next step.
bashcurl -s -X POST https://api.async.energy/api/v1/workflows \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{ "name": "nightly-embeddings", "framework": "ollama", "request": {"model": "nomic-embed-text"}, "recurrence": "daily", "earliest_start": "22:00", "deadline": "by 7am", "nameplate_watts": 350, "est_duration_s": 1800 }' | jq .id-
recurrence—nonefor one-shot, ordaily/weekly. -
earliest_start/deadline— plain strings in your timezone:"22:00","by 7am","2026-08-20T09:00". -
nameplate_watts/est_duration_s— optional first guesses, used only until real runs exist. Measurements replace them.
-
- 04
Install the controller
This is the only piece that touches your hardware, and it is open source. Install it on the machine that does the work.
bashgit clone https://github.com/boringbots/async-energy-controller.git cd async-energy-controller python -m venv .venv && source .venv/bin/activate pip install -e ".[gpu]" # NVIDIA box — adds NVML for exact energy # pip install -e . # no GPU — installs and runs fine, energy stays null # Puts async-energy-controller on your PATH # (hm-async-controller also works — same program, legacy name)Then point it at the API with a
.envbeside it:.envHM_ASYNC_API_URL=https://api.async.energy HM_ASYNC_EMAIL=you@example.com HM_ASYNC_PASSWORD=a-good-long-password CONTROLLER_ID=workstation-1 # optional; defaults to the hostname - 05
Say what actually runs
jobs.jsonmaps the workflow id from step 03 to the command this particular box executes. This is the trust boundary — the controller only ever runs something it finds in this file. A scheduled workflow with no entry here is skipped with a warning, not fetched from the server.jobs.json{ "<workflow-id-from-step-03>": { "framework": "ollama", "request": { "model": "nomic-embed-text", "prompt_file": "/data/queue/embed-batch.txt" } } }Three frameworks ship today:
command
Run anything. This is how you schedule agent pipelines, robot charging, or any script.
ollama
A blocking call to a local Ollama server.
openai
Any OpenAI-compatible server — vLLM, LM Studio, llama.cpp.
- 06
Start it
Smoke-test with a single tick first — it logs in, pulls the schedule, and exits. Then hand it to systemd; the repo ships an example unit with the recommended dedicated-user and venv layout.
bashasync-energy-controller --once # one tick, then exit async-energy-controller --poll-interval 30 # the run loop # As a service: sudo cp deploy/hm-async-controller.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now hm-async-controller journalctl -u hm-async-controller -f - 07
Watch the first night
The optimizer replans nightly at 02:00 UTC, and immediately whenever you change a workflow or a setting. Read tonight's plan and, in the morning, what it cost.
bash# Tonight's plan curl -s -H "Authorization: Bearer $TOKEN" https://api.async.energy/api/v1/schedule | jq # What actually ran curl -s -H "Authorization: Bearer $TOKEN" "https://api.async.energy/api/v1/runs?limit=10" | jqExpect the first few nights to be rough. With no run history the optimizer is working from your nameplate guesses, and it flags those predictions as
priorrather than pretending to know. Once real runs land it switches to the median of measured energy and duration, and placement quality jumps. - 08
Automate it
The bearer token above expires, which is fine at a terminal but wrong for an agent that registers and reads workloads on its own. Mint a long-lived API key in the dashboard (Sign in → API keys) and send it in an
X-API-Keyheader instead of a password. It is shown once at creation; revoke it from the same screen the moment you need to.bashcurl -s https://api.async.energy/api/v1/schedule \ -H "X-API-Key: <your-api-key>" | jq
When something looks wrong
Signup returned no access token
Email confirmation is on for your account. Confirm the address from the email, then log in for a token.
available_locations is empty
The nightly price sync has not run yet. Plans still price against a live fetch in the meantime.
Schedule says degraded: true
Prices were stale or unreachable when the plan was made. The plan is still deadline-safe — it is just honest that the numbers behind it were not fresh.
The window passed and nothing ran
Either the controller is not running, or the workflow has no entry in jobs.json. Check journalctl -u hm-async-controller for the skip warning.
energy_wh is null on every run
The box has no GPU telemetry — install the [gpu] extra on NVIDIA hardware. Scheduling still works on duration alone; the field stays null rather than being filled with a guess.
A job shows feasible: false
It cannot fit between its earliest start and its deadline at the duration we predict. Widen the window or split the work — it will not be run late.
Still stuck? Open an issue — include the controller log and the schedule JSON.