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 Machine

An 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).

Attributes
idstr
namestr
sizestr

small, medium or large (Machine.sizes()).

cpuint | None

vCPUs (hardware threads).

memory_gbint | None
disk_gbint | None
regionstr | None

The region it runs in (noclick.regions()), fixed when it was created.

statusstr | None
status_reasonstr | None

Why it's stopped or failed, or why it restarted.

monthly_usdfloat | None

What it costs a month, in US dollars, running or stopped.

max_monthly_usdfloat | None

The most you agreed it may cost a month.

networkAny

What its own commands may reach: True (anywhere the machines usage policy allows) or the hosts.

volumesdict[str, str] | None

The volumes it mounts, by path: {path: volume id}.

agent_countint | None

How many agents run on it.

imagestr | None

The NoClick-managed image it runs, with its harness versions.

restartsint | None

How many times it restarted after failing, ever.

last_restart_atAny
user_idstr | None
created_atAny
filesMachineFiles
portslist[MachinePort]

The ports it serves.

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

Parameters
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[MachineSize]

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.

Usage
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
) -> Machine

Start 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).

Parameters
namestrrequired
sizestrdefault 'small'
regionstr | Nonedefault None
volumesMapping[str, Any] | Nonedefault None
networkbool | list[str] | Nonedefault None
max_monthly_usdfloatrequired
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Machine

The machine, provisioning until it's ready (running).

Usage

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
) -> Machine

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

Parameters
namestrrequired
create_if_missingbooldefault False
sizestr | Nonedefault None
regionstr | Nonedefault None
volumesMapping[str, Any] | Nonedefault None
networkbool | list[str] | Nonedefault None
max_monthly_usdfloat | Nonedefault None
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Machine

A machine that loads (or is started, or brought in step with your definition) the first time it's used. Nothing is sent yet.

Usage
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=...) -> Machine

A lazy reference to a machine by id.

Parameters
machine_idstrrequired
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
Returns
Machine

A machine that loads the first time you read a field. Nothing is sent yet.

Usage
box = noclick.Machine.from_id("3b9e2c71-4f0d-4a68-9c15-7e2a8d1f6b40")
print(box.exec("uptime").stdout)

getclassmethodasync

noclick.Machine.get(machine_id, *, user=..., client=None) -> Machine

Load a machine by id now; an unknown id raises noclick.NotFound.

Parameters
machine_idstrrequired
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Machine

The machine, loaded.

Usage
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).

Parameters
limitint | None

Return at most this many.

(default None)
user_filterAny

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.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[Machine]

Every matching machine, newest first, fetched page by page.

Usage
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
) -> Page

One page of machines; pass next_cursor back as cursor.

Parameters
cursorstr | None

The next_cursor of the page before; omit it for the first page.

(default None)
limitint | None

Return at most this many.

(default None)
user_filterAny

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.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Page

A page whose data holds the machines and whose next_cursor is None on the last page.

Usage
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 | Operation

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

Parameters
commandstrrequired
backgroundbooldefault False
timeout_sint | Nonedefault None
Returns
ExecResult | Operation

What the command printed and how it exited, or with background=True an Operation whose wait() gives that result.

Usage
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) -> MachinePort

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

Parameters
portintrequired
publicbooldefault False
Returns
MachinePort

The port's url.

Usage
box = noclick.Machine.from_name("support-box")
dashboard = box.expose(8080)
print(dashboard.url)

close_portasync

machine.close_port(port) -> None

Stop serving port; its URL stops answering.

Parameters
portintrequired
Usage
noclick.Machine.from_name("support-box").close_port(8080)

agentsasync

machine.agents() -> list[Any]

The agents that run on it.

Returns
list[Any]

Each Agent, loaded.

Usage
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=...
) -> Machine

Rename 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).

Parameters
nameAnyomit to leave it unchanged
sizeAnyomit to leave it unchanged
volumesAnyomit to leave it unchanged
networkAnyomit to leave it unchanged
max_monthly_usdAnyomit to leave it unchanged
Returns
Machine

This machine, updated.

Usage
box = noclick.Machine.from_name("support-box")
box.update(size="large", max_monthly_usd=240)

restartasync

machine.restart() -> Operation

Restart it now, without waiting for running turns (they fail as interrupted). Its disk and volumes are kept; a machine.restarted event follows.

Returns
Operation

An Operation whose wait() gives the machine, running again.

Usage
box = noclick.Machine.from_name("support-box")
print(box.restart().wait().status)   # running

stopasync

machine.stop() -> Machine

Stop it: its agents take no turns (a message raises noclick.NotAvailable) until start(). It's still charged, since it stays yours.

Returns
Machine

This machine, stopped.

Usage
noclick.Machine.from_name("support-box").stop()

startasync

machine.start() -> Machine

Start a stopped machine. One stopped because the balance couldn't pay it is charged what it owes first (noclick.PaymentRequired while it can't).

Returns
Machine

This machine, provisioning until it's running.

Usage
noclick.Machine.from_name("support-box").start()

deleteasync

machine.delete() -> None

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

Usage
box = noclick.Machine.from_name("support-box")
for agent in box.agents():
    agent.update(machine=None)
box.delete()

MachineSizeinvite-only beta

class MachineSize

One of Machine.sizes(): its processors, memory and disk, and its flat monthly price.

Attributes
namestr

What size= takes: small, medium or large.

cpuint

vCPUs (hardware threads), as hosts sell them.

memory_gbint
disk_gbint

The machine's own disk.

monthly_usdfloat

Its price per month in US dollars, whatever it runs and whether it's busy.

working_conversationsint

About how many coding-agent conversations can work on it at the same moment.

rawdict[str, Any]

The response JSON exactly as the API sent it.

MachinePortinvite-only beta

class MachinePort

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

Attributes
portint
urlstr
publicbool
rawdict[str, Any]

The response JSON exactly as the API sent it.

MachineFilesinvite-only beta

class MachineFiles

machine.files: read and write files on the machine's own disk.

writeasync

machine.files.write(path, data) -> None

Write data (bytes, or text as UTF-8) to path (relative paths are under /home/noclick).

Parameters
pathstrrequired
databytes | strrequired
Usage
box = noclick.Machine.from_name("support-box")
box.files.write("/etc/noclick/notes.md", "Restart the worker after deploys.")

readasync

machine.files.read(path) -> bytes

Read the file at path; a missing file raises noclick.NotFound.

Parameters
pathstrrequired
Returns
bytes

The file's bytes, exactly as stored.

Usage
box = noclick.Machine.from_name("support-box")
print(box.files.read("/var/log/worker.log").decode()[-2000:])