Documentation · v0.9.2

How GPU for Agents works

A coding agent can only read and write text. With its own GPU it can make images and voices, transcribe audio, run open models and test GPU code on real hardware. GPU for Agents gives it that machine with the controls built in: a budget it can't exceed, a log of every step, and a watchdog on the machine that stops it when the work stops.

On this page
OverviewQuickstartArchitectureProcess flowLifecycleWatchdogShutdown ruleData flowAudit and costCoding agentsApplicationsServingJobsCLI outputExit codesMCP toolsWatchdog APIProvidersConfigSecurityReference

01Overview

One command, gpu, rents a GPU from a cloud provider, serves a model or runs code on it, and makes sure it stops. Agents reach it through a skill and an MCP server. Applications call the same command with --json.

Controllable

Limits in a file

gpu.yaml sets the hourly price cap, the session budget and the maximum lifetime. The agent can't go past them without editing the file you can read.

Auditable

Every step logged

Each rent, start, idle period, stop and refusal is one JSON line in events.jsonl, with the reason. gpu events reads it.

Watched from inside

A watchdog on the GPU

The machine checks itself every 60 seconds and calls the provider to stop itself after 15 idle minutes, even if your laptop is closed.

Two planes

PlaneWhatWho uses it, and how
Controlthe gpu command (and the MCP tools that wrap it)an agent through MCP; an app as a subprocess with --json, reading stdout and the exit code
Dataan OpenAI-compatible HTTP API on the rented machineany OpenAI SDK or plain HTTP, with the base_url, api_key and model that gpu setup printed

You never have to stop the GPU yourself. The one thing a client has to handle is a machine that has been stopped: it calls gpu setup again and gets a fresh endpoint.

02Quickstart

Let the agent install it

Paste this into Claude Code, Codex, Gemini CLI or Cursor, naming the one you use:

prompt
Set up GPU for Agents for me. I'm using Claude Code. Fetch https://gpu4agents.jambu.ai/install.md with curl and follow its steps.

The agent runs the installer, gives itself the skill and the MCP server, and checks your provider account. Nothing is rented during installation.

Connect the account that pays for the GPUs

When the agent asks, open your terminal app (not the chat) and type gpu login. It takes about two minutes, and the key never passes through the conversation.

your terminal: gpu login
Connect a GPU provider, so your agent can rent GPUs on its own.
Your key stays on this computer; the agent never sees it.

  1) Vast.ai  built in; GPUs by the hour from a few cents, pay as you go
  2) Another provider I already use  RunPod, Lambda, my company's servers, ...: your agent adds it

Choose [1]: 1

Vast.ai, in 3 steps:
  1. Create a Vast.ai account: https://cloud.vast.ai/
  2. Add some credit ($5 to $10 is plenty to try it): https://cloud.vast.ai/billing/
     You pay only while a GPU runs, and idle ones are shut down for you.
  3. Create a key and copy it: https://cloud.vast.ai/manage-keys/
Already have an account? Skip to the key.

Paste your key here and press Enter (it stays hidden):

The key is checked with the provider before it is saved in ~/.gpu/credentials (mode 600). Pick "Another provider" and the command tells you what to ask your agent: it writes the adapter, then you run gpu login --provider <name>. gpu login --check says whether an account is connected, without changing anything.

If gpu runs something else. Oh My Zsh's git plugin, on by default, makes gpu a shortcut for git push upstream, and a shortcut wins over the command, in your terminal and in your agent's. The installer finds it and turns it off for new terminals, with a marked block at the end of ~/.zshrc or ~/.bashrc that you can delete to undo it (--keep-gpu-alias skips this). The same command is always installed as gpu4agents too, so gpu4agents login works either way, and the skill makes the agent check type gpu before using it.

Or by hand

shell
$ curl -fsSL https://gpu4agents.jambu.ai/install.sh | sh -s -- --agent auto
# or: uv tool install --python 3.12 gpu4agents  /  pipx install gpu4agents
$ gpu init --model Qwen/Qwen2.5-7B-Instruct   # writes gpu.yaml
$ gpu validate                                # config, credentials, real capacity; fails before renting
$ gpu offers                                  # matching machines and their hourly price
$ gpu setup                                   # rent, install the watchdog, serve, wait until ready
$ eval "$(gpu env)"                           # OPENAI_BASE_URL, OPENAI_API_KEY, JAMBU_MODEL_ID
Vast.ai is built in. Any other provider (RunPod, Lambda, a company cluster, your own servers) is one adapter file, and the agent can write it from the provider's API docs: see Provider adapters.

03Architecture

Three places are involved: your machine, the provider's API, and the rented GPU machine. Control goes through the gpu command and the watchdog. Data goes straight from your client to the model on the GPU.

Architecture: your machine, the provider API and the rented GPU machine YOUR MACHINE RENTED GPU MACHINE Coding agent or your app Claude Code · Codex · Cursor skill + MCP server (gpu mcp) gpu CLI --json · exit codes · file lock gpu.yaml limits, model no credentials .gpu/ state.json logs/events.jsonl model server :8000 vLLM · hf · custom · none /v1 OpenAI routes · /metrics watchdog :8777 policy check every 60 s activity, workloads, locks, jobs X-Jambu-Token auth supervisor (respawn loop) /var/lib/jambu/activity.json Provider API Vast.ai built in any other: adapter data: /v1 OpenAI API · Bearer api_key control · X-Jambu-Token rent · stop · destroy stops itself reads /metrics respawns
Control and data stay apart. Prompts and outputs travel from your client to the model on the GPU and never through a third-party inference API. The watchdog holds a provider key so it can stop its own machine.

What is in the package

ModuleDoes
core/policy.pyThe only shutdown rule. Pure Python, no dependencies. Its source is copied into the watchdog, so the machine and gpu status apply exactly the same rule.
core/engine.pysetup, restart, reconcile, stop, destroy, jobs; serialized by a file lock per project
core/state.py, core/events.py.gpu/state.json and the append-only event log
providers/the provider contract (base.py), the registry, the Vast.ai adapter and the skeleton for new ones
runtime/, serve/how the model runs: vLLM, gpu's own server for any Hugging Face model, or your server
watchdog/the agent that runs on the machine (agent_body.py), its builder, and the jobs extension
agent/the skill, the MCP server and gpu agent install

The core never checks which provider it is talking to. It checks what the provider can do (provider.capabilities), and a test fails the build if core/ ever mentions a provider by name.

04Process flow: gpu setup

What happens between "rent a GPU" and a working endpoint. The first rent usually takes 5 to 15 minutes, mostly the image pull and the model download. Restarting a stopped machine took 41 seconds for a small model.

Sequence of gpu setup between the agent, the CLI, the provider, the watchdog and the model server Agent / app gpu CLI Provider API Watchdog Model server 1 gpu_offers / gpu offers 2 find offers (VRAM, disk, CUDA) cheapest first, with $/h price shown; user says yes 3 gpu_setup over max_hourly_cost_usd? refuse: exit 10, nothing rented 4 rent: image, label jambu-…, ports, onstart 5 boot, pull image, run onstart pull stalls 10 min or boot past 30 min: host_failed, destroy, next host (up to 3) 6 watchdog up first, then model 7 POST /identify ($/h, policy) · /starting 8 poll GET /health until 200 (startup_timeout 10 min) server died (runtime_alive false): exit 11 + log tail, destroyed 9 POST /ready → runtime_ready event 10 {base_url, api_key, model} 11 /v1/chat/completions, /v1/images/generations, … (Bearer api_key) every request counts as activity
setup is idempotent. Ten processes calling it at once rent one machine; the other nine wait on the project's file lock and get the same answer. On a stopped machine it restarts it instead of renting a new one.

Through MCP, setup never blocks the agent

A first rent outlasts any client's tool timeout, so gpu_setup starts setup as a background job, waits up to 45 seconds, and answers status: "running" until the model is ready. Calling it again re-attaches to the same job and never rents a second machine. The job outlives the MCP server, so a restarted client finds it again.

05Lifecycle states

Every provider's states are mapped onto one set, so the CLI and the rule behave the same on any provider.

State machine of an instance provisioning starting ready busy failed idle stopped destroyed /ready workload no activity request, job, heartbeat, lock idle_timeout max_lifetime or session budget stopped_ttl gpu destroy boot fails gpu setup on a stopped machine: restart (41 s measured) cleanup_on_setup_failure: destroyed, setup rents another host
idle and stopped are where cost hides: an idle machine bills GPU time, a stopped one still bills its disk on Vast.ai. The watchdog ends the first; stopped_ttl (24 h) ends the second.
EventWhat a client seesWhat it does
first setupblocks 5 to 15 minutes while the machine is rented and the model downloadswaits; setup bounds itself
idle for idle_timeoutconnection refused or a timeoutcalls setup again, retries once
setup on a stopped machinerestart, no new rent, no new download; the IP and port may changeuses the new base_url
stopped for stopped_ttlthe machine is gonethe next setup rents a new one, with a new key
max_lifetime reachedstopped even under trafficsame as idle

06The watchdog

The CLI process can't be in charge of shutdown: close the laptop and a local timer dies while the GPU keeps billing. So setup installs a watchdog on the machine itself, and the machine stops itself.

Where it runs

Inside the rented machine

A small HTTP service on port 8777, started by the boot script before the model server, inside a bash respawn loop. If it crashes, the loop starts it again 5 seconds later.

How often

Every 60 seconds

It evaluates the same rule as gpu status, from core/policy.py, and reads the model server's /metrics every 15 seconds to see traffic.

What it can do

Stop or destroy its own machine

The provider adapter injects two calls, provider_stop and provider_destroy. Nothing else of the provider's API is used from the machine.

What survives

State on disk

/var/lib/jambu/activity.json survives a stop and start. A restart opens a new session with a fresh lease, so a restarted machine isn't killed the moment it boots.

What counts as activity

CountsDoes not count
a running or queued workload with a live heartbeat (gpu run, a job)
model startup
inference traffic: requests running or waiting, completed requests, prompt tokens
an active lock (gpu lock --for 2h)
a healthy server nobody calls
a health check or a metrics scrape
an open SSH session
a dead heartbeat: after heartbeat_grace (5 min) the workload is stale

Traffic is read from the server's own counters. The first read only sets a baseline, so old totals don't keep a machine up, and a counter that goes down means the server restarted, so it rebaselines. A failed probe never counts as activity.

When the provider call fails

  • A failed stop never disarms the guard. It logs provider_error and guard_retry and tries again on the next tick.
  • If the provider accepted the call but the machine is still running after 5 minutes, the watchdog issues it again (guard_reissue).
  • After 30 minutes of failed stops it destroys the machine instead (stop_failed_escalation): a machine that bills forever costs more than a lost disk.

A second line of defense

The same rule runs from outside too. gpu guard evaluates it from cron or CI, and gpu sweep --install-timer runs every 5 minutes over every registered project, then stops orphans running longer than 24 hours and destroys orphans stopped longer than 24 hours. It only ever sees machines labeled jambu-; nothing else in the account is touched. It's a dry run until --apply.

07The shutdown rule

One pure function, evaluate(snapshot, config), decides whether a machine may keep billing. It checks in this order and stops at the first answer. Every automatic shutdown records its reason.

1
Is it already stopped?and stopped longer than stopped_ttl, not locked
DESTROY · stopped_ttlotherwise nothing to do
2
Session older than max_lifetime (6 h)?measured from the last start, not from creation
STOP · max_lifetimeunless a lock is active
3
Session cost over max_session_cost_usd?hourly price × session age
STOP · budget_exceededunless a lock is active
4
Is a lock active?gpu lock --for 2h; indefinite locks only if the project allows them
KEEP · locked
5
Is the model still starting?
KEEP · startup_in_progress
6
Is a workload running or queued?with a heartbeat younger than heartbeat_grace
KEEP · workload_activework in flight is never interrupted
7
Idle longer than idle_timeout (15 min)?
STOP · idle_timeout
8
Anything else
KEEP · within_policy

"STOP" is the cheapest action that ends billing: a stop where a stopped machine is free, a destroy with auto_destroy: true or when the provider can't stop. gpu status prints the decision it would take right now, so you can see the watchdog's next move before it happens.

The invariant. A machine may stay billable only while useful work runs, useful work is queued or due within the idle window, or you locked it. Everything else ends stopped, or destroyed where stopping doesn't end billing.

08Data flow

What lives where, what crosses the network, and which secrets go where.

Data flow between your machine, the provider and the GPU Your client prompts, audio, images job code (≤ 2 GB bundle) receives: completions, images, speech, transcripts, embeddings, job outputs → gpu-outputs/<job id>/ GPU machine model weights (HF cache) /workspace/jobs/<id>/ code · log · outputs /etc/jambu/watchdog.json provider key + token, 0600 /var/log/jambu/events.jsonl pulled into your local log Provider API HTTPS · rent, price, stop :8000 request + Bearer api_key response, straight from your model :8777 control + X-Jambu-Token · job bundle up, outputs down
No third-party inference API. Requests go from your client to the model you rented. The provider sees the machine (image, ports, price) and never your prompts.

Files on your machine

PathHoldsSecret
gpu.yamlwhat to run and how long it may live; credentials are refused hereno
~/.gpu/credentials or .envthe provider key, written by gpu login (mode 600)yes
.gpu/state.jsonthe rented instance, the endpoint key, the watchdog token (mode 600)yes
.gpu/logs/events.jsonlthe event log of this projectno
~/.gpu/projects.jsonprojects registered by setup, guarded by gpu sweepno
~/.gpu/sweep.json, bad_hosts.jsonwhen orphans were first seen stopped; hosts that failed to boot (skipped for 24 h)no

Keep .gpu/ on persistent storage. A container that loses it forgets its instance and rents a second one, leaving the first to the watchdog and the sweep. Add .gpu/ and .env to .gitignore.

09Audit and cost

Every lifecycle action appends one JSON object to the event log, so any GPU cost can be traced back to a decision and a reason. The watchdog keeps its own log on the machine, and gpu status and gpu events merge it into yours.

shell
$ gpu events                 # the lifecycle log, newest last
$ gpu --json status          # state, cost so far, idle time, the watchdog's next move
$ gpu ls                     # every jambu- machine in the account, and which project owns it

Each line carries ts, event and pid, plus the project's context (instance id, provider) and the event's own fields, such as the reason, the price or the decision detail.

A real session, from the event log

sd-turbo on one RTX 3060 at $0.065/h, 4 Nov 2026 (UTC). The machine was rented, served images, went quiet, and stopped itself.

bootingservingidle countdown (15 min)stopped
3 min
rent to model ready
27 min
billed GPU time
$0.03
GPU cost of the session
0
commands needed to stop it
Show as a table
Time (UTC)EventDetail
02:23:25provision_requestedsd-turbo, min 12 GB VRAM
02:23:26instance_createdRTX 3060, $0.065/h
02:25:18watchdog_startedidle_timeout 900 s
02:26:27runtime_readystartup 65.8 s
02:35:18instance_idlepending_workloads 0
02:50:43state_reconciledidle → stopped

What that session cost, against leaving it on

GPU cost over 24 hours at the same $0.065/h. The solid line is what happened; the dashed line is the same machine with nobody watching it.

with the watchdog (real)left running
Show as a table
AfterWith the watchdogLeft running
1 h$0.03$0.07
8 h$0.03$0.52
24 h$0.03$1.56
The cheap card makes the gap look small. The same 24 hours on a $2/h GPU is $48, and the watchdog's rule doesn't depend on the price. A stopped Vast.ai machine still bills a little for its disk until stopped_ttl destroys it; the chart shows GPU time only.

Event vocabulary

GroupEvents
Rent and bootprovision_requested budget_rejected instance_created host_failed runtime_starting runtime_ready setup_failed cleanup_performed
Workworkload_started workload_finished job_submitted job_finished job_outputs_pulled
Lifecycleinstance_idle instance_locked instance_unlocked lifecycle_guard_triggered instance_stopped instance_started instance_destroyed state_reconciled
Watchdogwatchdog_started watchdog_identified session_started runtime_exited guard_retry guard_reissue activity_probe_error
Errors and sweepprovider_error watchdog_error sweep_action (in ~/.gpu/logs/events.jsonl)

10Integrating a coding agent

The agent gets two things: a skill that teaches it the rules, and a local MCP server that exposes the gpu commands as tools.

AgentSkillMCP server
Claude Code.claude/skills/gpu4agents/.mcp.json, or user scope with --global
Codex.agents/skills/gpu4agents/[mcp_servers.gpu] in ~/.codex/config.toml
Gemini CLI.agents/skills/gpu4agents/mcpServers.gpu in .gemini/settings.json
Cursor.agents/skills/gpu4agents/.cursor/mcp.json
Claude Desktop~/.gpu/gpu-skill.zip, uploaded under Settingsclaude_desktop_config.json
shell
$ gpu agent install -t claude            # this project; --global for every project
$ gpu agent install -t auto --global     # every agent found on this machine
$ gpu mcp --project /path/to/project     # any other MCP client, over stdio

What keeps the agent in line

  • The skill tells it to show the price and ask before renting, to wait out a slow setup instead of starting over, never to touch the provider key, and to offer stop or destroy when done.
  • The tools are annotated: gpu_destroy is marked destructive and the read-only tools read-only, so your client can let gpu_status run freely and ask for every gpu_setup. In Claude Code: "allow": ["mcp__gpu__gpu_status", "mcp__gpu__gpu_offers", "mcp__gpu__gpu_ls"].
  • gpu.yaml holds the hard limits. The agent can't raise them without editing a file you can see in the diff.
  • The watchdog stops the machine whatever the agent does, and the sweep catches anything left behind.

11Integrating an application

No SDK. An app in any language runs gpu --json setup, reads the JSON, and points its OpenAI client at the result. When a request fails because the machine is gone, it runs setup again and retries once.

Ensuregpu --json setup at boot or on the first request
Callany OpenAI client, timeout 1 to 2 min
Detectconnection refused, reset or timeout
Healsetup again; share one in-flight setup
Retryonce, with the new base_url
python
import json, subprocess
from openai import OpenAI, APIConnectionError

def ensure_gpu(cwd="/srv/my-app"):
    out = subprocess.run(["gpu", "--json", "setup"], cwd=cwd, check=True,
                         capture_output=True, text=True).stdout
    return json.loads(out)

conn = ensure_gpu()
def ask(prompt):
    global conn
    for attempt in (1, 2):
        client = OpenAI(base_url=conn["base_url"], api_key=conn["api_key"], max_retries=0, timeout=120)
        try:
            return client.chat.completions.create(
                model=conn["model"], messages=[{"role": "user", "content": prompt}]
            ).choices[0].message.content
        except APIConnectionError:
            if attempt == 2:
                raise
            conn = ensure_gpu()   # stopped while idle: restart it, then retry

Retry only on connection errors. A 400 or 401 comes from a live server. For TypeScript, examples/typescript-client/src/gpu.ts is one file with no dependencies that does all of this.

Short jobs: wrap them

shell
$ gpu run python score.py --input data.jsonl   # heartbeats keep the machine up while it runs
$ gpu lock --for 3h && ./batch.sh; gpu unlock     # a batch with quiet stretches longer than idle_timeout

gpu run sets OPENAI_BASE_URL, OPENAI_API_KEY, JAMBU_MODEL_ID, JAMBU_GPU_ENDPOINT and the instance identity, so any OpenAI SDK picks the endpoint up by itself.

12Serving models

gpu init --model <repo> reads the Hugging Face model card and picks the engine and the OpenAI routes. Any OpenAI client works unchanged.

The modelEngineRoutes
chat, vision-languagevllm/v1/chat/completions (with tools) · /v1/completions · /v1/responses
images (diffusers)hf/v1/images/generations
voicehf/v1/audio/speech
transcriptionhf/v1/audio/transcriptions · /v1/audio/translations
embeddingshf/v1/embeddings
any other pipeline (detection, depth, …)hf/v1/pipeline
a model none of these loadcustomyours, from a server the agent writes
no server, only jobsnonenone

GET /v1/models lists the model, its task and its routes. Changing the model keeps the machine: edit model.id and run gpu setup, which redeploys on the same GPU. Use models as their licenses and your purpose allow; open models with their own policies are for legitimate work you own, such as research, evaluation, red-teaming your own systems and creative work.

The custom server contract

  1. Listen on 0.0.0.0:$PORT.
  2. Answer GET /health with 503 until the model is loaded, then 200.
  3. Require Authorization: Bearer $JAMBU_API_KEY on every route except /health and /metrics.
  4. Serve /metrics with jambu_requests_running (gauge) and jambu_requests_total (counter). Without them requests don't count as activity, and the machine stops after idle_timeout even while in use.
  5. Use the OpenAI request and response shapes where one exists.
  6. Exit non-zero when loading fails, after printing why. Never print secrets.

13Running jobs

A job is the agent's own code on the GPU: training, a kernel benchmark, a batch over a dataset. gpu job run packs it, runs it, streams the log and brings the outputs back.

Packthe directory, minus .git, venvs, .env (≤ 2 GB)
UploadPUT /jobs/<id>/bundle
Runsetup: then run:, with a timeout
Streamlog by offset; heartbeats count as work
Pull$JAMBU_OUTPUT_DIR → gpu-outputs/<id>/
gpujob.yaml
run: python train.py --epochs 3
setup: pip install -r requirements.txt
env:
  EPOCHS: "3"
timeout: 2h
exclude: [data/raw, "*.ckpt"]
label: lora-qwen-7b
shell
$ gpu init --jobs --min-vram-gb 24
$ gpu job run
$ gpu job run -- python test_kernel.py
$ gpu job status     # state, exit, cost_usd
$ gpu job logs -f
$ gpu job pull       # works mid-run
$ gpu job cancel

Job states: running, succeeded, failed, timeout (exit 124), cancelled (exit 130), lost (the machine restarted under it). The provider credential the watchdog uses is removed from the job's environment.

14Contract: CLI output

Pass --json before the command name. stdout then carries exactly one JSON document; errors go to stderr as one line, error: <message>, with a non-zero exit code.

CommandUse it toBlocksBills
gpu --json setupget a working endpoint: rent, or restart a stopped machine, and wait for the modeluntil readyyes
gpu --json envread the endpoint without starting anythingnono
gpu --json statusfull state, cost so far, the guard's decisionnono
gpu --json stopstop now; disk and model cache keptuntil stoppedends GPU time
gpu --json destroy --yesdelete it; nothing billed afterwardsuntil goneends all
gpu --json lock --for 2h / unlockkeep it up through a quiet period, then return to the rulesnowhile locked
gpu --json setup
{
  "instance_id": "27581234",
  "state": "ready",
  "endpoint": "http://203.0.113.7:41022",
  "base_url": "http://203.0.113.7:41022/v1",
  "api_key": "k6V0…  (43 characters, per machine)",
  "model": "Qwen/Qwen2.5-7B-Instruct",
  "hourly_cost_usd": 0.196
}

The key is per machine: it survives stops and restarts and changes only when a new machine is rented. Take base_url, api_key and model from the latest setup instead of caching them across restarts. gpu env without --json prints shell exports.

15Contract: exit codes

CodeMeaningWhat to do
0success
1unexpected errorlog stderr, retry later
2config error, or config changed since setupfix it; setup --recreate replaces a machine built from the old one
3no instance, no endpointcall setup
4provider API errorretry with backoff
5provider authentication failedthe user runs gpu login
6no capacity matchesretry later, or relax VRAM or the budget
7provisioning failedretry; the failed machine was already destroyed
8provider timed outretry with backoff
9the provider can't do thischange the config
10over budget.max_hourly_cost_usdraise the cap or wait for cheaper offers
11the model server didn't startstderr has its log tail; the machine was destroyed
12another gpu command holds the project lockwait and retry; status answers with "busy": true

16Contract: MCP tools

Each tool runs the CLI with --json in the project directory, so errors carry the same exit_code, plus meaning and next for the agent.

ToolDoesCosts money
gpu_initwrite gpu.yaml for a model, with an optional price cap and VRAMno
gpu_validatecheck config and credentialsno
gpu_offerscheapest matching machines and their priceno
gpu_setuprent or restart, wait for the model (background job, re-attachable)yes
gpu_statusstate, cost so far, idle time, the watchdog's next moveno
gpu_connectionbase_url, api_key, model without starting anythingno
gpu_runrun a local command with the endpoint in its environmentif it has to start the machine
gpu_job_run · _status · _pull · _cancelrun code on the GPU and bring the outputs backyes
gpu_logsruntime, onstart, watchdog or provider logno
gpu_lock / gpu_unlockkeep the machine up through a quiet periodwhile locked
gpu_stopstop now; restart takes about a minuteends GPU time
gpu_destroydelete; needs confirm=trueends all
gpu_lsevery machine in the account, from any projectno

17Contract: the watchdog API

HTTP on port 8777 of the machine. Every route except /health needs the header X-Jambu-Token, compared in constant time. The token lives in .gpu/state.json and is never printed. The CLI is the usual caller; you rarely need these directly.

RoutePurpose
GET /healthliveness, no auth
GET /statestate, idle and session age, workloads, lock, last traffic probe, runtime_alive, and the decision it would take now
GET /events?limit=Nthe machine's event log
GET /logs?name=runtime|onstart|watchdog&lines=Nlog tails without SSH (default 200, at most 2000)
POST /identifythe CLI tells the machine its instance id, hourly price and policy
POST /starting · /readymodel startup began, model answers
POST /activitymark activity now
POST /workload/start · /heartbeat · /endregister work, keep it alive, end it with a return code
POST /lock · /unlock · /extendlocks with a TTL (indefinite only when the project allows it)
POST /shutdown{"action": "stop"|"destroy", "reason"}; remembered and retried if the provider call fails
/jobs, /jobs/<id>/bundle|start|log|outputs|cancelthe jobs extension

18Contract: provider adapters

Vast.ai ships built in. Any other provider is one Python class that implements providers/base.py. The core, the watchdog, the CLI and the MCP tools stay the same.

shell
$ gpu providers new runpod --display-name "RunPod"   # writes gpu_providers/runpod.py
$ gpu init --provider runpod --model Qwen/Qwen2.5-0.5B-Instruct
$ gpu login --provider runpod
$ gpu --json validate && gpu --json offers           # real GPUs, real prices, nothing rented
PartMust do
capabilitiesdeclare only what the API supports; stop_eliminates_billing=False if a stopped machine still bills
tagline, signup_url, setup_stepswhat gpu login shows a first-time user: one line about the provider and the steps from no account to a key, in plain words
verify_credentialscheck the key; return only safe fields such as a balance, never an email, a name or the key
find_offersGPUs that fit VRAM, count, disk and the price cap, cheapest first
setuprent with the image, env, jambu- label, ports and onstart script
statusmap states onto provisioning … destroyed, ports onto model and watchdog
stop, destroysucceed when already stopped or gone
list_instancesonly machines labeled jambu-; never anything else in the account
WATCHDOG_ACTIONSstdlib-only provider_stop(ctx) and provider_destroy(ctx) that run on the machine

An adapter can live in the project (provider.plugin: ./gpu_providers/acme.py:AcmeProvider), in an installed package (entry point group gpu4agents.providers), or in this repository. Errors are the normalized ones from core/errors.py, so exit codes are the same on every provider.

19Configuration

gpu.yaml is the desired state. gpu reads only its gpu_runtime: key. Unknown keys are a hard error, so a typo in idle_timeout can't silently switch off the protection.

gpu.yaml
gpu_runtime:
  version: 1
  model: {id: stabilityai/sd-turbo}
  compute:
    gpu: {min_vram_gb: 12}
  budget:
    max_hourly_cost_usd: 1.00
    max_session_cost_usd: 5.00
  lifecycle:
    idle_timeout: 15m
    max_lifetime: 6h
    stopped_ttl: 24h
FieldDefault
lifecycle.idle_timeout15m
lifecycle.max_lifetime6h
lifecycle.stopped_ttl24h
lifecycle.heartbeat_grace5m
lifecycle.watchdog.interval60s
lifecycle.traffic_counts_as_activitytrue
health.boot_timeout30m
health.pull_stall_timeout10m
compute.host_attempts3
budget.*unset

At least one of idle_timeout and max_lifetime must be set, and idle_timeout may not exceed max_lifetime. Override the price cap once with gpu setup --allow-cost-override. Every field: CONFIG.md.

20Security

  • The model endpoint needs a key. Unless runtime.api_key is set, setup generates one per machine (43 random URL-safe characters). /health and /metrics stay open; they reveal no data.
  • The endpoint is plain HTTP on a public IP and port, so the key and prompts travel unencrypted. For sensitive data, use a private network or a tunnel, or put a TLS proxy in front.
  • The watchdog holds a provider key on the machine (/etc/jambu/watchdog.json, mode 600) so it can stop itself. Use a dedicated, restricted key. Opting out (lifecycle.watchdog.enabled: false) leaves shutdown to gpu guard, and validate warns.
  • The key never goes through the chat. gpu login takes it in a hidden prompt in your terminal, and the skill tells the agent never to ask for it, read it or print it.
  • A shell alias can shadow gpu. With Oh My Zsh's gpu = git push upstream, an agent typing gpu setup in a repository would push. The installer removes the alias for new shells, and the skill has the agent check type gpu first and use command gpu. MCP tools don't go through a shell.
  • Account hygiene. gpu ls and gpu sweep only see machines labeled jambu-.

21Full reference

The pages agents read are served from this site as Markdown. The long-form references live in the repository.

Copied