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, or macOS on Apple Silicon, 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. On an Apple Silicon Mac the equivalent comes from IOReport with no driver and no sudo — seeAPPLE-SILICON.md. Intel Macs are not supported.
  • 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
    • recurrence — none for 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.
  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
    python3 -m venv .venv && source .venv/bin/activate
    
    pip install -e .          # NVML ships in the base install: exact energy on an NVIDIA box,
                              # and it installs and runs fine with no GPU — energy stays null.
                              # On an Apple Silicon Mac this also pulls the IOReport backend.
    
    # 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.

    The unit files are Linux-only. On macOS there is no equivalent in the repo yet — run the loop in the foreground, or drive--once from cron or a launchdagent of your own.

    bash
    async-energy-controller --check               # validate catalog, login, schedule; exit non-zero if wrong
    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 thedashboard(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 usable NVML — no NVIDIA driver, or no NVIDIA GPU. There is no extra to install; the profiler falls back to nvidia-smi, then to a null profiler. Scheduling still works on duration alone.

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.