Troupeby AliveDocsSign in

Runtime

Sessions

A session holds one device for one holder until it is released or expires, and every call that touches a device names its session. This page follows a session from create to release: the spec, installs, waiting, staying alive and ending.

A session from start to end

TypeScript
import { createClient } from '@alivelabs/troupe-client'

const troupe = createClient({ baseUrl, token })

let session = await troupe.call('sessions.create', {
  spec: { platform: 'ios-simulator', deviceType: 'iPhone 18 Pro', os: '27.0' },
  install: [{ path: '/Users/me/shop/build/Shop.app' }],
})

while (session.status === 'starting') {
  const waited = await troupe.call('sessions.wait', { sessionId: session.sessionId })
  session = waited.session
}
if (session.status !== 'ready') throw new Error(`The session is ${session.status}`)

await troupe.call('apps.launch', { sessionId: session.sessionId, bundleId: 'com.example.shop' })
// ... drive the device ...
await troupe.call('sessions.release', { sessionId: session.sessionId })

The client SDK shows where baseUrl and token come from. The same procedures are MCP tools and REST routes; the test framework calls them for you.

A session's status moves forward only:

StatusMeaning
queuedWaiting for a device. Troupe Cloud only: a local runtime refuses instead
startingThe device is booting, its driver is starting, or apps are being installed
readyThe device takes calls
endedReleased, expired, or the runtime shut down
failedA requested install failed because of the app
erroredTroupe or the machine failed: boot, driver, environment, memory
canceledReleased before it was ready

The last four are final. A final session has a reason, and an errored or failed one has the error.

Create

sessions.create returns at once. The session is ready when a warm device matches the spec and nothing is to be installed. Otherwise it is starting.

FieldWhat it does
specThe device to get. Required
installUp to five apps to install while the session starts
keyA name of your own for the session, for attaching again
holder{ kind, label }: who holds the session. The label shows in Troupe Studio
idleTimeoutMs, maxDurationMsReplace the holder's default timeouts. Up to 24 hours on a local runtime
freshtrue boots a new device even when a warm one matches

A local runtime never queues. With no free device slot or memory, the call fails with CAPACITY_UNAVAILABLE, which carries retryAfterMs: wait that long and call again.

The device spec

FieldDefaultWhat it is
platformRequiredios-simulator, android-emulator or catalyst
deviceTypeThe platform's defaultA device type by id or exact name: 'iPhone 18 Pro', 'pixel_9', 'iphone-17'
os'latest'iOS version '27.0', Android API level '36'. 'latest' is the newest installed that the device type runs
variant'google_apis'Android only: the system image's tag
archThe host's'arm64' or 'x86_64'
locale'en-US'A BCP 47 tag, applied at boot
timeZoneThe host'sAn IANA name
orientation'portrait'The orientation at start
appearance'light''light' or 'dark' at start

The session's resolved field records what the spec came to: the device type's name, the exact OS version and its build. Each platform page lists its device types: iOS, Android and Catalyst.

A spec the platform cannot satisfy is DEVICE_SPEC_INVALID. A missing simulator runtime or system image is an ENVIRONMENT_* error whose data.fix is the command that installs it.

Installs

install takes app sources. The runtime detects the format from the extension.

SourceUse
{ path }An absolute path on the runtime's machine. Only the runtime owner may use it
{ url, sha256 }An https URL, or http to loopback, with the file's SHA-256. At most 4 GiB
{ artifactId }A Troupe Cloud build artifact. A local runtime refuses it

Installs run while the session is starting, so they never count against a test's timeout or the first action. An install the app causes to fail ends the session failed with the reason app-install-failed. The session's installed field lists each app with its bundleId.

To install later, call apps.install.

Wait

sessions.wait answers when the session is ready or final, or when timeoutMs passes (30 seconds by default). A timeout is a normal answer with done: false: call again. A session that has not started within 10 minutes ends errored with the reason start-timeout.

Until ready, device calls answer SESSION_NOT_READY.

The live stream opens earlier, once the device is booted and its screen is captured, while the driver still starts. Wait for that moment with until: 'stream' on a server that advertises the feature:

TypeScript
if (await troupe.supports('sessions.streamBeforeReady')) {
  await troupe.call('sessions.wait', { sessionId, until: 'stream' })
}

Stay alive

A session ends by itself when its holder goes quiet. How depends on the holder's kind.

KindWhoStays alive byIdle timeoutLongest life
test-workertroupe test workersA heartbeat every 10 seconds; it ends 30 seconds after the lastnonenone locally
clientTroupe Studio, the React componentsActivity10 minutes60 minutes
mcpEvery session an MCP tool createsActivity10 minutes60 minutes
apiOther callers. The defaultActivity10 minutes60 minutes

Activity is any call by the holder that names the session, and input sent on the stream by the driver. Watching the stream, reading events and asking for a watch ticket are not activity. sessions.heartbeat counts as activity and returns the two deadlines, which the session also carries as expiresAt and maxEndsAt.

Only the holder may change a session. A call from another principal that would act on the device, keep the session alive or release it fails with SESSION_BUSY. On a local runtime the CLI, troupe test and the MCP bridge all act as the runtime's owner, so this separates paired browser pages from each other and from you. The owner may release any session.

Attach by key

Pass a key to find a session again. sessions.create with the key of a live session of the same caller and holder kind returns that session and resets its timeout, instead of starting a device. A worker that restarts gets its warm device back this way. The same key with a different spec is CONFLICT.

Release

sessions.release ends the session: canceled before it was ready, ended with the reason released after.

  • By default the device goes back to the warm pool: its apps are terminated and it stays booted for a few minutes for the next session with the same spec.
  • With healthy: false the device is shut down and not used again. Use it when the device misbehaved.

A session that takes a warm device finds the apps and the data the last session left on it. Create the session with fresh: true when it must start on a new device. In tests, the appIsolation option decides what each test finds: see App isolation.

Releasing a session that has ended returns it unchanged.

Why a session ended

Statusreason
endedreleased, idle-timeout, heartbeat-timeout, max-duration, runtime-shutdown
canceledreleased
failedapp-install-failed
erroredboot-failed, driver-failed, environment, start-timeout, device-lost, host-pressure

Troupe Cloud adds queue-timeout and host-lost. A call on a session that has ended fails with SESSION_ENDED, which carries the status and the reason.

An app crash does not end a session. The call that hit it fails with APP_CRASHED, and logs.crashes lists the reports.

Follow sessions

sessions.events is an event stream of what happens to one session, or to all of them when you leave sessionId out.

TypeScript
for await (const event of troupe.follow('sessions.events', { sessionId })) {
  if (event.type === 'status') console.log(event.status, event.reason)
  if (event.type === 'streamReady') console.log('the stream can be opened')
}

A local runtime sends status, appState, inputState and streamReady events. It keeps the last 1000 events of a session, and follow resumes from the last one it saw after a dropped connection. A gap event says that events were missed.

Next

esc

    Type to search every page, the protocol reference and the cloud API.