Rensei docs

Interactive Sessions

Launch a live, attachable terminal session via a template, drive it from web or iOS, and reconnect without losing history.

Partial. The launch, attach, presence/driver, and reconnect path below is shipped and live-verified. A few edges called out inline (the initial prompt field, recording playback) are still in progress - do not assume they behave like the rest of the page until the callout clears.

The PTY session host and the wire protocol that carries keystrokes and screen updates are OSS - documented at donmai.dev/docs/sessions. This page covers the platform's session lifecycle, quotas, and multi-viewer model built on top of that protocol.

An interactive session is a live, attachable terminal - not a headless agent run you read back afterward. A real command runs under a real pseudo-terminal (PTY) on a worker, and any number of people can watch it live from the web or the iOS app; one of them can drive it at a time. This is the primary way to pair with a running agent, babysit a long build, or hand a terminal to a teammate without a screen-share.

Launching a session

There are two ways to start one:

From a template (recommended). The interactive-session-authored template exposes a typed form: pool, harness, model profile, repository, sandbox substrate, max duration, and an initial prompt. Submitting it fires a manual trigger that runs through a gate.human_query approval step before the session actually launches - so someone signs off on cost/scope before a worker spins up.

Directly. POST /api/interactive-sessions starts a session immediately and hands back a sessionId synchronously, which is what the web start-session screen (/{org}/{project}/interactive-sessions/new) uses under the hood - a workflow trigger is fire-and-forget and can't return an id inline.

Any parameter you leave blank, or set to the project-default sentinel, resolves from your project's defaults at launch time - it is never routed through the model/pool selection engine that ordinary agent dispatch uses. If you want a specific model or pool for this session, say so explicitly; otherwise you get the project default.

The initial prompt field is collected on the launch form but is not yet delivered into the session - there is no wire path for it yet. Type your first command after you attach instead.

Launches are gated by two feature flags (interactive_sessions entitlement + a route flag), both default-deny per organization. If you don't see interactive sessions in your project, ask an admin to enable the feature - see Quotas & Plans for what plans include it.

Attaching

Open a session from the session list or from a direct link:

  • Web - /{org}/{project}/interactive-sessions/[id] renders the terminal with xterm.js and a WebGL renderer. It shows a presence strip, a quota clock, and a stop button alongside the terminal.
  • iOS - the iOS app attaches with a native Metal-rendered terminal and Network.framework transport.

Both clients authenticate with a short-lived per-session token (minted via POST /api/interactive-sessions/{id}/tokens) and speak the same wire protocol, so a web viewer and an iOS viewer see byte-identical output.

Presence and who drives

Everyone attached sees the live terminal. Exactly one person - the driver - can type into it. The rest are viewers.

  • Any user with a driver-capable grant can take the pen at any time: no approval round-trip, just a grab. The person who was driving loses the pen, sees a toast, and drops to viewer.
  • Taking the pen is audit-logged and every input event is stamped with the user who sent it - if two people are debugging together, the activity trail shows who typed what.
  • A short cooldown after a grab prevents a rapid tug-of-war between two people reaching for the pen at once.
  • After a reconnect, stale input from a superseded connection is dropped server-side (an internal generation counter on the pen grant), so a flaky network doesn't let an old, disconnected client's buffered keystrokes land after someone else has taken over.

The presence strip on both web and iOS shows who is watching and who is driving.

Stopping a session is broader than driving it: any project member with access to the session can stop it, not only the current driver. If you can see a session, you can stop it.

Reconnecting

Interactive sessions are designed to survive flaky networks without losing the session:

  • The web client dials a WebSocket first. On repeated dial failures (three failed attempts within 30 seconds) or a failed subprotocol handshake, it automatically falls back to a degraded transport (SSE for output, plain POST for input) rather than disconnecting entirely. There's no automatic upgrade back to the WebSocket once degraded in this version - a manual reload retries the fast path.
  • The iOS client uses a dead-peer watchdog: it pings every 10 seconds and tears down and reconnects if it hears nothing back for 20. A network path change (Wi-Fi to cellular, for example) also triggers an immediate reconnect.
  • On reconnect, the client either replays the missed frames from the server's ring buffer (if the gap is small) or gets a fresh full-screen snapshot repainted (if it isn't, or if it just missed too much). Either way you land back on the correct, current screen state - not a stale one.
  • Backgrounding the iOS app does not keep a socket open. After roughly 15 seconds of background grace, the app cleanly detaches both legs of the connection; the session itself keeps running on the server. Reopening the app re-attaches - minting a fresh token only if the old one expired - and replays or repaints to catch up. This is deliberate: iOS does not grant this class of app a persistent background socket.

Ending a session

A session ends when the command exits, when someone stops it (from the terminal's stop button, rensei tooling, or the admin console), or when it hits its duration budget - see Quotas & Plans for the warning/grace/stop sequence. The terminal shows the final screen and an exit chip once the session ends, so you can read the last output before navigating away.

Recordings

The session record carries a pointer for a recording of the terminal session, visible to operators from the admin console. End-user recording playback and the retention policy for recordings are not finalized as of this page - treat recording as an operator-visible capability for now, not a guaranteed user-facing feature.

Using it from a workflow

Beyond the seeded template, three workflow nodes let you compose interactive sessions into your own automation:

NodeDoes
interactive.session.launchStarts a session from a workflow, resolving any omitted axis from project defaults
interactive.session.injectSends text into a running session's input stream (the same chokepoint the terminal itself uses)
interactive.session.under_quotaA condition node - branch on whether the org still has room under its interactive-session quota before launching

A common pattern: an event trigger (a Linear comment, a GitHub issue, a Sentry alert) runs into a gate.human_query, and approving the gate launches a live session with the approver as the initial driver - "escalate this to a live session" as a one-click action from wherever the event already lives.

Workflow triggers built by hand in the visual editor do not yet persist their typed-parameter validation (trigger_config) - that wiring only exists for the seeded template today. If you build your own launch trigger in the editor, parameter validation at trigger time will not fire.

On this page