---
title: Take input control
description: Hand the keyboard from an agent to a person, and back, without the two interleaving.
---

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, and every input
carries the lease generation, so anything from the previous holder is dropped at the gateway
rather than landing late.

Reads are never leased. A viewer that cannot type can still see.

## The agent side

Nothing to do. Both SDKs open a session and acquire the lease on the first call that types or
clicks, and renew it at half its TTL:

```ts
await machine.keyboard.type("hello"); // acquires the lease if it does not hold one
machine.holdsInput; // true
```

## Taking it from a person

The lease route is on the contract client, since the SDKs manage the lease themselves:

```ts
const session = await client.api.sessions.create({
  body: { machine_id: machine.id, type: "human" },
  headers: {},
});

const lease = await client.api.inputLeases.acquire({
  params: { machine_id: machine.id },
  body: { session_id: session.id, ttl_seconds: 60, force: true },
});
```

`force: true` is what takes the lease from its current holder. Without it, an agent already
driving the machine causes a `conflict` naming the holder — which is the right answer for a
program, and the wrong one for a person who has decided to intervene.

## What happens to the agent

Its next renewal fails, and it stops:

```ts
machine.holdsInput; // false
await machine.keyboard.type("more");
// RasterError: conflict — someone else holds the input lease
```

**The SDKs never re-acquire a lease they lost.** 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. Catch the `conflict` and decide deliberately.

## Handing it back

```ts
await client.api.inputLeases.release({
  params: { machine_id: machine.id },
  body: { session_id: session.id },
});
```

Closing the session drops the lease with it. The agent's next input call acquires it again
normally — the lease is free, so no force is needed.

## Watching without taking

A screen stream opens a session but never acquires the lease, so a viewer cannot evict whoever is
driving:

```ts
const stream = await machine.screen.stream();
```

Terminals are outside the lease entirely. Two people holding separate ptys is normal — the lease
governs the desktop, where there is one mouse and one keyboard.

## Further

[Sessions and input control](/docs/reference/sessions) for the fence and how enforcement works,
and [the realtime gateway](/docs/reference/realtime) for the input channel itself.
