Machines

Run agents on an always-on machine at a flat monthly price, instead of a sandbox per conversation.

A machine is an always-on computer of a fixed size in one region, billed a flat monthly price. Agents you put on it run their harness there instead of in a sandbox per conversation: nothing is shut down when they go quiet, and there's no per-minute sandbox charge.

Start one

import noclick

box = noclick.Machine.from_name(
    "support-box",
    create_if_missing=True,
    size="medium",
    region="eu",
    max_monthly_usd=120,
)
print(box.status, box.monthly_usd)   # provisioning 62.4

max_monthly_usd is required: once the company is vetted, a size that costs more raises noclick.PriceExceeded and nothing starts. region is fixed for the machine's life; without it the machine takes your account's default region, else us-east. With create_if_missing=True your code is the definition, so a different size, volumes or network later changes the machine to match (a new size restarts it).

size=vCPUsMemoryDiskPer monthConversations working at once
small24 GB80 GB$31.202
medium48 GB160 GB$62.404
large816 GB320 GB$124.808

noclick.Machine.sizes() lists them. A vCPU is a hardware thread, as every host sells them. The price covers the machine, its disk and its daily backups.

A machine is provisioning until it's ready (a minute or two), then running. status_reason says why one is stopped or failed.

Put agents on it

claude = noclick.Connection.from_name("claude", integration="anthropic", create_if_missing=True)
support = noclick.Agent.from_name(
    "support",
    create_if_missing=True,
    instructions="You answer support tickets from our docs and the customer's account.",
    harness="claude_code",
    model_connection=claude,
    machine=box,
)
print([agent.name for agent in box.agents()])   # ['support']

An agent on a coding harness runs whole on the machine. An sdk agent's shell doesn't run on a machine yet: machine= with one raises noclick.NotAvailable. Many agents share one machine (at most 50), and each conversation gets a container of its own on it, so threads never see each other's files. An agent on a machine runs in the machine's region: a different region raises noclick.InvalidRequest, and an agent whose region is set moves only onto a machine in it. An agent can use its own user's machines and the account's.

agent.update(machine=None) moves an agent back to a sandbox per conversation, and machine=other to another machine. Each conversation moves between turns and keeps its files.

Your keys stay off it

The machine never holds your connections' or model keys. A coding harness on it calls its model through NoClick's credential proxy with temporary credentials, which make the calls with the real key and are revoked when the conversation's container stops, and its tools reach your connections through NoClick, under your policy, exactly as in a sandbox. So a coding agent on a machine needs a model connection holding an Anthropic, OpenAI or OpenRouter key: a person's Claude or ChatGPT plan runs on sandboxes only for now, and machine= with one raises noclick.NotAvailable.

The machine itself

print(box.exec("df -h /").stdout)
box.files.write("notes.md", "Restart the worker after deploys.")
dashboard = box.expose(8080)          # private: asks for an API key
print(dashboard.url)
box.exec("tar czf /home/noclick/backup.tgz /home/noclick/app", background=True, timeout_s=1800).wait()

exec and files reach the machine itself, not a conversation's container. They act as the machine's user, noclick, in /home/noclick (a relative path is under it); it has no administrator rights, so install what your agents need through their own containers.

expose(port) serves a port at an HTTPS URL on NoClick's domain. A private port asks for an API key with the machines scope (Authorization: Bearer), which NoClick checks and removes before the request reaches your app; public=True answers anyone, for a webhook receiver your agents run. close_port(port) stops serving it. A machine serves at most 10 ports.

Files and volumes

A conversation's files (its /workspace and its harness's session) live on the machine's disk and are saved at the end of every turn, before its reply reaches you, so a conversation survives anything that happens to the machine and moves to a sandbox or another machine with its files. A single file over 32 MB stays on the machine and isn't saved: keep large outputs in a bucket.

The machine's own files, the ones you write with exec and files, are kept across restarts. NoClick backs up the machine's disk every day; after a hardware failure the machine is rebuilt on a new host from its last backup (a machine.restored event), so files written on the machine itself since then are lost. Keep what matters in a volume or a bucket.

volumes={"/shared": vol} attaches volumes for the machine and every conversation on it. A conversation sees a volume as it was when its turn started and saves its writes when the turn ends, as an agent's sandbox does. The machine's own copy at that path, owned by its user, is loaded when the machine starts and saved every five minutes and before it restarts or moves. Volumes outlive the machine, and a volume mounted on a machine can't be deleted (noclick.Conflict).

Restarts and updates

NoClick manages the machine's image, with the harnesses at the versions NoClick runs (image names it), and updates it: an update waits for running turns, then replaces each conversation's container between turns. If the machine stops answering, NoClick restarts it; restarts counts these, turns it interrupts fail as interrupted (never replayed), and a machine.restarted event follows. One that stops answering again soon after, or doesn't come back, is rebuilt on a new host from its last backup (a machine.restored event, with the backup's time in restored_from). One that stops answering again after that is failed (a machine.failed event), and box.restart() tries again.

box.restart() restarts it now (an operation that finishes once it runs again). box.stop() powers it off, so its agents take no turns (a message raises noclick.NotAvailable) until box.start().

box.update(size="large") moves it to a larger size once running turns end (updating, a few minutes); the new price applies from the next day. A smaller size moves the machine to a new host, since no host shrinks a disk in place: the machine's user's home (/home/noclick) is copied over and each conversation loads its files from where they're saved, a longer restart. The machine must be running and what's on its disk must fit the smaller one; if the move fails, the machine comes back on its old host at its old size.

Network and the usage policy

Machines follow NoClick's machines usage policy, section 3 of the Acceptable Use Policy: no cryptocurrency mining, network scanning or bulk email, whatever runs on them. Mail ports, known mining pools, other machines' private addresses and the host's metadata service are blocked for everything on a machine. network is what the machine's own commands may reach: True (the default) is anywhere else, and a list of hosts limits them further, like an agent's network: HTTPS to those hosts ("*.github.com" covers its subdomains) and name lookups, nothing else. Each conversation's container keeps to its agent's own network the same way.

Before an account's first machine, NoClick vets the company: the account's owner, signed in, submits its name, website, country and what its machines are for, and accepts the usage policy. Until NoClick approves it, creating a machine raises noclick.ActionRequired; a rejection says why, and you can submit again.

Where it runs

A machine runs in its region and its disk stays there. What NoClick keeps about it, such as its agents' conversation history, stays in NoClick's database in us-east.

What it costs

A machine is charged a thirtieth of its monthly price for each day it's held, from the day it first runs until the day you delete it, running or stopped (one that fails to come up isn't charged). An agent on it pays no sandbox time; its model tokens are billed as they would be anywhere. Machines show as their own line in usage.

If your balance can't pay a day, the day stays owed and you're told (a machine.unpaid event and an email). The machine keeps running for 48 hours, then stops (status_reason is "unpaid") with its disk kept; unpaid for 14 days, it's deleted and its agents move back to sandboxes. Owed days are charged as soon as your balance covers them, and box.start() charges them first.

A coding agent's sandbox is billed about $0.24 for each hour it's up (about $0.42 pinned to us-east or us-west), so one agent that never goes quiet costs about $175 a month in sandbox time. A small machine at $31.20 pays for itself once your agents keep sandboxes up for more than about 130 hours a month, a little over four hours a day. Agents that answer a few messages a day are cheaper on sandboxes; agents that work most of the day belong on a machine.

Sizing

A coding agent's conversation needs about a vCPU and 1.5 to 2 GB while it works and almost nothing while it waits. Size for the conversations that work at the same moment, not for how many there are: past what the size holds at once, a turn waits in queued for one to finish rather than failing. A machine runs at most 50 agents. Builds that need more memory (npm install, tsc on a large project) want a larger size.

Remove it

for agent in box.agents():
    agent.update(machine=None)
box.delete()

A machine with agents on it can't be deleted (noclick.Conflict): move them first. Deleting stops the charge that day and removes its disk; its volumes stay.