async.energy

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.
  1. 01

    Create an account

    Accounts live only on Async Energy — you do not need a Hungry Machines login. The response carries the access_token you will use as a bearer token for everything below.

    bash
    curl -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>"
  2. 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 it null to take the deployment default.

    bash
    curl -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
  3. 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.

    bash
    curl -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
    • recurrencenone for one-shot, or daily / 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.
  4. 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.

    bash
    git 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 .env beside it:

    .env
    HM_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
  5. 05

    Say what actually runs

    jobs.json maps 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.

  6. 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.

    bash
    async-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
  7. 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" | jq

    Expect 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 prior rather than pretending to know. Once real runs land it switches to the median of measured energy and duration, and placement quality jumps.

  8. 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-Key header instead of a password. It is shown once at creation; revoke it from the same screen the moment you need to.

    bash
    curl -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.