Sessions
Spawn, attach, write to, resize, rename, and kill terminal sessions on the host.
A session is one live PTY the host server owns. client.sessions drives it.
Spawn
const { sessionId } = await client.sessions.spawn({
cwd: "/path/to/worktree",
cols: 120,
rows: 40,
// optional shell profile, command, agent id, title, environment
});spawn posts POST /v1/sessions and returns the session id immediately; output flows
through the event stream. Agent sessions (spawned with an agent id from the catalog)
carry startup/prefill handling and get PRAGMA_FANOUT_* variables inside fanout
attempts.
Attach — the event stream
for await (const event of client.sessions.attach(sessionId)) {
switch (event.type) {
case "replay":
// scrollback replay; event.cursor is the absolute output-byte cursor
break;
case "output":
// event.data: Uint8Array of raw PTY output
break;
case "title":
case "exit":
}
}The stream is NDJSON over GET /v1/sessions/{id}/events. The first event is a
replay: the server re-sends as much scrollback as it can cover, and reset: true
means the connection's cursor fell outside the retained window. Reconnecting clients send
their last cursor back on attach to resume seamlessly.
Input, resize, rename, kill
await client.sessions.write(sessionId, new TextEncoder().encode("cargo test\r"));
await client.sessions.resize(sessionId, { cols: 100, rows: 30 });
await client.sessions.rename(sessionId, "build"); // renames the hosting tab
await client.sessions.kill(sessionId);
await client.sessions.killForCwd("/path/to/worktree"); // kill every session in a cwdwrite posts raw octet-stream bytes to POST /v1/sessions/{id}/input — no JSON, no
escaping.
Sharing a session's size: viewport leases
A session has one PTY grid, shared by every client showing it. A small client — a phone —
cannot just resize it to fit, or the desktop showing the same terminal is reflowed under
the user. Borrow the grid with a lease instead:
const lease = await client.sessions.acquireViewport(sessionId, { cols: 48, rows: 30 });
// { leaseId, cols, rows, previousCols?, previousRows?, generation, expiresInMs }
const timer = setInterval(
() => client.sessions.renewViewport(sessionId, lease.leaseId),
lease.expiresInMs / 3,
);
// While leased, only the holder's resizes apply; anyone else's is remembered as the
// size to restore. `resizeLeased` returns whether the host applied it.
await client.sessions.resizeLeased(sessionId, { cols: 52, rows: 30 }, lease.leaseId);
clearInterval(timer);
await client.sessions.releaseViewport(sessionId, lease.leaseId); // restores the owner's sizeThe host sweeps expired leases on a timer, so a client that is suspended or loses its
connection never strands the grid at phone size. client.sessions.info(sessionId) reports the
grid, liveness, and whether the viewport is currently leased.
Use cases
- Headless agents: spawn a session per attempt and read its output from a script.
- Custom clients: the mobile app is built exactly on these routes.
- Supervision:
killForCwdis how tooling tears down everything a worktree started.