Machine
Machines: an always-on computer for agents that should never sleep.
box = noclick.Machine.from_name("support-box", create_if_missing=True, size="medium", region="eu",
max_monthly_usd=120)
support = noclick.Agent.from_name("support", create_if_missing=True, instructions="...",
harness="claude_code", model_connection="claude", machine=box)
print(box.exec("df -h /").stdout)An agent on a machine runs its harness there instead of in a sandbox per conversation: nothing is reaped when it goes quiet, and there's no per-minute sandbox charge, only the machine's flat monthly price. Each conversation gets its own container on the machine, so threads never see each other's files. Model and tool calls go through NoClick's credential proxy with temporary keys: your real keys are never on the machine. NoClick manages its image and updates, restarts it if it fails (rebuilding it on a new host from its last backup if it must), and attached volumes outlive it.
class MachineAn always-on computer of a fixed size in one region, billed a flat
monthly price. Agents created with machine= run their harness on it, each
conversation in a container of its own; exec, files and expose reach
the machine itself. status is provisioning, running, restarting,
updating, stopped or failed (status_reason says why).
- Like every live object, it also has hydrate(), refresh() and raw.
| id | str | |
| name | str | |
| size | str |
|
| cpu | int | None | vCPUs (hardware threads). |
| memory_gb | int | None | |
| disk_gb | int | None | |
| region | str | None | The region it runs in ( |
| status | str | None | |
| status_reason | str | None | Why it's stopped or failed, or why it restarted. |
| monthly_usd | float | None | What it costs a month, in US dollars, running or stopped. |
| max_monthly_usd | float | None | The most you agreed it may cost a month. |
| network | Any | What its own commands may reach: |
| volumes | dict[str, str] | None | The volumes it mounts, by path: |
| agent_count | int | None | How many agents run on it. |
| image | str | None | The NoClick-managed image it runs, with its harness versions. |
| restarts | int | None | How many times it restarted after failing, ever. |
| last_restart_at | Any | |
| user_id | str | None | |
| created_at | Any | |
| files | MachineFiles | |
| ports | list[MachinePort] | The ports it serves. |
| console_url | str | None | This machine in the developer console (None off the hosted API). |
sizesclassmethodasync
noclick.Machine.sizes(*, client=None) -> list[MachineSize]What a machine can be, smallest first.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Each size's name (what size= takes), its processors, memory and disk,
its monthly_usd and about how many conversations can work on it at once.
for size in noclick.Machine.sizes():
print(size.name, size.cpu, size.memory_gb, size.monthly_usd)createclassmethodasync
noclick.Machine.create(
name, *, size='small', region=None, volumes=None, network=None, max_monthly_usd,
user=..., client=None
) -> MachineStart an always-on machine called name (unique to its user; a taken
name raises noclick.Conflict with existing_id). size is one of
Machine.sizes(); region (noclick.regions()) is where it runs, for
good, else your account's default region, else us-east. volumes
attaches Volumes (or their ids or names) at absolute paths: each
conversation sees a volume as it was when its turn started and saves its
writes when the turn ends. network is what the machine's own commands may
reach: True (the default: anywhere the machines usage policy allows, so no
mail ports, mining pools or private addresses) or a list of hosts (HTTPS to them,
"*.github.com" for subdomains, and name lookups). It's charged to your
account from the day it first runs, a thirtieth of its monthly price a day
while it exists, running or stopped, and refused (noclick.PriceExceeded,
nothing started) if its size costs more than max_monthly_usd. An account's
first machine needs its company vetted by NoClick (noclick.ActionRequired
until it's approved).
'small'NoneNoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The machine, provisioning until it's ready (running).
A machine in Europe for a team of agents, with their shared files:
box = noclick.Machine.create(
"support-box", size="medium", region="eu", max_monthly_usd=120,
volumes={"/shared": noclick.Volume.from_name("support-kb", create_if_missing=True)},
)
print(box.status, box.monthly_usd)from_nameclassmethod
noclick.Machine.from_name(
name, *, create_if_missing=False, size=None, region=None, volumes=None,
network=None, max_monthly_usd=None, user=..., client=None
) -> MachineA lazy reference to the machine called name in the current (or given)
user. With create_if_missing=True it's started on first use with the
fields create takes (max_monthly_usd is required then), and an
existing machine whose size, volumes or network differ is changed
to match (a new size restarts it). Its region is fixed: one in
another region raises noclick.Conflict naming its region.
FalseNoneNoneNoneNoneNoneWho the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A machine that loads (or is started, or brought in step with your definition) the first time it's used. Nothing is sent yet.
box = noclick.Machine.from_name("support-box", create_if_missing=True, size="medium", max_monthly_usd=120)
print(box.status)from_idclassmethod
noclick.Machine.from_id(machine_id, *, client=None, user=...) -> MachineA lazy reference to a machine by id.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
A machine that loads the first time you read a field. Nothing is sent yet.
box = noclick.Machine.from_id("3b9e2c71-4f0d-4a68-9c15-7e2a8d1f6b40")
print(box.exec("uptime").stdout)getclassmethodasync
noclick.Machine.get(machine_id, *, user=..., client=None) -> MachineLoad a machine by id now; an unknown id raises noclick.NotFound.
Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The machine, loaded.
box = noclick.Machine.get("3b9e2c71-4f0d-4a68-9c15-7e2a8d1f6b40")
print(box.name, box.status, box.agent_count)listclassmethodasync
noclick.Machine.list(
*, limit=None, user_filter=None, user=..., client=None
) -> list[Machine]The current user's machines (the account's own outside as_user, or those user_filter covers).
Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching machine, newest first, fetched page by page.
for box in noclick.Machine.list(user_filter=noclick.ALL_USERS):
print(box.name, box.region, box.status, box.monthly_usd)list_pageclassmethodasync
noclick.Machine.list_page(
*, cursor=None, limit=None, user_filter=None, user=..., client=None
) -> PageOne page of machines; pass next_cursor back as cursor.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A page whose data holds the machines and whose next_cursor is None on the last page.
page = noclick.Machine.list_page(limit=50)
print([box.name for box in page.data], page.next_cursor)execasync
machine.exec(command, *, background=False, timeout_s=None) -> ExecResult | OperationRun a shell command on the machine itself (not in a conversation's
container) and wait for it: timeout_s is 60 by default, at most 90.
background=True runs it as an operation, up to an hour.
FalseNoneWhat the command printed and how it exited, or with background=True an Operation
whose wait() gives that result.
box = noclick.Machine.from_name("support-box")
print(box.exec("df -h /").stdout)
backup = box.exec("tar czf /shared/backup.tgz /home/noclick", background=True, timeout_s=1800)
print(backup.wait().exit_code)exposeasync
machine.expose(port, *, public=False) -> MachinePortServe port at an HTTPS URL on NoClick's domain: a dashboard or a webhook
receiver your agents run. A private port asks for an API key with the
machines scope, which the app never sees; public=True answers anyone.
FalseThe port's url.
box = noclick.Machine.from_name("support-box")
dashboard = box.expose(8080)
print(dashboard.url)close_portasync
machine.close_port(port) -> NoneStop serving port; its URL stops answering.
noclick.Machine.from_name("support-box").close_port(8080)agentsasync
machine.agents() -> list[Any]The agents that run on it.
Each Agent, loaded.
box = noclick.Machine.from_name("support-box")
print([agent.name for agent in box.agents()])updateasync
machine.update(
*, name=..., size=..., volumes=..., network=..., max_monthly_usd=...
) -> MachineRename it, change its size, volumes, network or price ceiling. New volumes and
network apply without a restart (each conversation's from its next turn). A larger
size restarts it once running turns finish (updating); the new price applies
from the next day. A smaller size moves a running machine to a new host, since no
host shrinks a disk in place: its user's home is copied over and each conversation
loads its files from where they're saved, a longer restart (what's on its disk
must fit the smaller one; a move that fails leaves it on its old host).
This machine, updated.
box = noclick.Machine.from_name("support-box")
box.update(size="large", max_monthly_usd=240)restartasync
machine.restart() -> OperationRestart it now, without waiting for running turns (they fail as interrupted).
Its disk and volumes are kept; a machine.restarted event follows.
box = noclick.Machine.from_name("support-box")
print(box.restart().wait().status) # runningstopasync
machine.stop() -> MachineStop it: its agents take no turns (a message raises noclick.NotAvailable)
until start(). It's still charged, since it stays yours.
This machine, stopped.
noclick.Machine.from_name("support-box").stop()startasync
machine.start() -> MachineStart a stopped machine. One stopped because the balance couldn't pay it
is charged what it owes first (noclick.PaymentRequired while it can't).
This machine, provisioning until it's running.
noclick.Machine.from_name("support-box").start()deleteasync
machine.delete() -> NoneDelete it, with its disk; attached volumes stay. Refused
(noclick.Conflict) while agents run on it: move them first
(agent.update(machine=None)). The charge stops that day.
box = noclick.Machine.from_name("support-box")
for agent in box.agents():
agent.update(machine=None)
box.delete()MachineSizeinvite-only beta
class MachineSizeOne of Machine.sizes(): its processors, memory and disk, and its flat monthly price.
| name | str | What |
| cpu | int | vCPUs (hardware threads), as hosts sell them. |
| memory_gb | int | |
| disk_gb | int | The machine's own disk. |
| monthly_usd | float | Its price per month in US dollars, whatever it runs and whether it's busy. |
| working_conversations | int | About how many coding-agent conversations can work on it at the same moment. |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
MachinePortinvite-only beta
class MachinePortA port a machine serves. url reaches it over HTTPS; a private port asks
for an API key with the machines scope (Authorization: Bearer), a public
one answers anyone.
| port | int | |
| url | str | |
| public | bool | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
MachineFilesinvite-only beta
class MachineFilesmachine.files: read and write files on the machine's own disk.
writeasync
machine.files.write(path, data) -> NoneWrite data (bytes, or text as UTF-8) to path (relative paths are under /home/).
box = noclick.Machine.from_name("support-box")
box.files.write("/etc/noclick/notes.md", "Restart the worker after deploys.")readasync
machine.files.read(path) -> bytesRead the file at path; a missing file raises noclick.NotFound.
The file's bytes, exactly as stored.
box = noclick.Machine.from_name("support-box")
print(box.files.read("/var/log/worker.log").decode()[-2000:])