Skip to content
Raster
Esc
navigateopen⌘Jpreview
On this page

Sessions and input control

One holder of the keyboard at a time, and how a person takes it back from an agent.

Two objects sit between a client and a machine’s keyboard: a session, which is a connection, and an input lease, which is permission to type. Reads never need either.

Sessions

POST   /v1/sessions                  open one on a machine
GET    /v1/sessions                  list them
DELETE /v1/sessions/{session_id}     close one

A session has a typeagent, human or view — and a state of active, idle or closed. It records which user or API key opened it, which is how the machine’s event log can say who took control.

Both SDKs open one for you on the first call that needs it, and close it in machine.close() or on leaving a Python with block. Do call that: an abandoned session holds a lease until it expires, and nobody else can type in the meantime.

Concurrent sessions are a plan entitlement. See billing.

The input lease

POST   /v1/machines/{id}/input-lease    acquire or renew
GET    /v1/machines/{id}/input-lease    read the current holder
DELETE /v1/machines/{id}/input-lease    release

At most one session may send input to a machine at a time. That is what makes human takeover safe: taking control revokes the agent’s lease rather than interleaving with it.

const lease = await client.api.inputLeases.acquire({
  params: { machine_id: machine.id },
  body: { session_id, ttl_seconds: 60, force: false },
});
lease.fence; // increments on every grant
lease.expires_at;

ttl_seconds is 5 to 3600, default 60. A lease is renewed by acquiring it again.

The fence

Every grant increments a counter, and every input message carries the fence it was sent under. The gateway drops a message whose fence is lower than the machine’s current lease — that client is a former controller — and it drops one that is higher too, so a client cannot promote itself by inventing a number. An expired lease is a lower fence by definition, because the next grant increments it.

That is the whole takeover mechanism, and it is enforced at one point rather than by hoping each client behaves. A client whose fence is stale gets one lease_lost message and must fall back to read-only.

force

force: false is the default and is what an SDK always sends. Taking a lease from its holder is human takeover — a deliberate act by a person, not something a library does on a click. The dashboard sends force: true when a person asks for the keyboard; an agent gets a conflict naming the current holder.

The SDKs never take a lease back

Both SDKs acquire the lease on the first call that types or clicks, renew it at half its TTL, and stop if a renewal fails:

machine.holdsInput; // false after a takeover

Not re-acquiring is the important half. A lost lease means a person took over, and silently taking it back would put an agent and a person on the same keyboard, which is the one thing single-holder input exists to prevent. The next input call surfaces the API’s own conflict, which says who holds it.

Where policy and enforcement live

Lease policy is the API’s: who may hold one, for how long, and whether a force is allowed. Lease enforcement is the gateway’s: the fence check on every input message. Two places that can grant a lease is one more than can be reasoned about.

Terminals are not leased

A terminal is a session of its own. Two people holding separate ptys on one machine is normal, so the terminal is deliberately outside the lease. The lease governs the desktop, where there is one mouse and one keyboard.

Screen streams are not leased either — a viewer that cannot type can still see.

Was this page helpful?