Troupeby AliveDocsSign in

Runtime

The runtime

The runtime is the local process that boots devices, holds sessions and serves every operation over REST, RPC, MCP and the stream on one loopback port. This page covers starting and stopping it, what it keeps on disk, how it protects your machine, and how to clean up.

Daemon or in process

The runtime runs in one of two ways.

  • As a daemon. troupe test, troupe install, troupe studio and troupe mcp start it in the background when none answers. It stays up between commands, so a device from the last run is still warm for the next. It exits by itself after 30 minutes with no session and no device.
  • In process. troupe test --no-daemon, or any run with CI set, runs the runtime inside the test command and shuts its devices down at the end. See Running in CI.

Only one runtime serves a $TROUPE_HOME at a time. A --no-daemon run refuses to start while a daemon runs for the same home.

Start, stop and status

Start the daemon yourself when you want to call it from the client or REST:

Shell
bunx troupe runtime start --detach
Output
Started the Troupe runtime at http://127.0.0.1:<port> (pid <pid>).

Without --detach it runs in the foreground, logs to the terminal and stops on Ctrl+C. A detached daemon writes its output to $TROUPE_HOME/logs/daemon.log.

Shell
bunx troupe runtime status
Output
Troupe runtime <version> at http://127.0.0.1:<port> (pid <pid>), up since <time>
  sessions: 1 live
  device <name> (ios-simulator): leased, <session id>
  governor: normal, <n> MB available

A device is creating, booting, ready (warm, with no session), leased (held by a session), shuttingDown or broken. --json prints the same as JSON: the output of runtime.status with running, url and pid added. The command exits 1 when no daemon runs.

Shell
bunx troupe runtime stop

stop refuses while sessions are live. --force ends them, with the reason runtime-shutdown, and shuts their devices down.

What it serves

The daemon binds 127.0.0.1 only, on a port it takes from 47000 to 47999.

PathWhat
/v1/<group>/<operation>REST, one route per procedure
/rpc/v2/<group>/<operation>RPC
/mcpMCP over Streamable HTTP
/v1/streamThe live stream, a WebSocket
/Troupe Studio

Every request passes one gate. The Host header must name the loopback address, an Origin must be the runtime's own or a paired one, and every procedure except runtime.version and pairings.redeem needs a bearer token. Calling the API directly has the details.

What it keeps on disk

Everything is under $TROUPE_HOME, which defaults to ~/.troupe and is readable by you alone.

PathWhat
runtime.jsonThe running daemon's url, its owner token, its pid and its contract version. Written when the daemon listens, removed when it stops
daemon.lockHeld for the runtime's whole life, so two runtimes never share a home
host.jsonHost settings: toolchain paths and limits. troupe install writes it
logs/daemon.logA detached daemon's output
logs/runtime.jsonlThe runtime's structured log, with secrets redacted
state/devices.jsonThe registry of devices Troupe created
state/processes.jsonThe child processes the runtime started
cache/What is built once, such as the XCUITest runner

The owner token in runtime.json holds every scope. Treat it as a password. It is new each time the daemon starts, so read the file when you connect instead of copying the token.

A second directory, ~/.troupe-host, holds a ledger of the ports and homes that all of your runtimes use. Set TROUPE_HOST_HOME to move it.

Host safety

The runtime works on a machine you also use for other things. These rules hold whatever a caller asks.

  • It touches only what it made. Troupe creates simulators and AVDs named troupe-…, records each in its registry, and acts only on registered devices. Anything else is reported at most, never changed.
  • It stays on loopback. Every listener binds 127.0.0.1 on a port from Troupe's range.
  • It limits devices. At most two devices run per platform by default. Set platforms.<id>.maxDevices in host.json to change that.
  • It watches memory. A governor samples memory pressure. Under pressure it shuts down warm devices first. At the critical level it refuses new sessions, and after 30 seconds there it stops Troupe's newest device; that session ends errored with the reason host-pressure.
  • It refuses instead of queueing. When no device slot or memory is free, sessions.create fails with CAPACITY_UNAVAILABLE, a reason (busy or host-pressure) and retryAfterMs. troupe test waits and retries.
  • It keeps released devices warm, briefly. A device released healthy has its apps terminated and stays booted for 5 minutes for the next session with the same spec, with its apps still installed. A warm device gives way before a new session is refused.
  • It cleans up after itself. Each child process runs in its own process group and is recorded. A starting runtime first removes what an earlier one left. A small watcher process, the reaper, does the same 60 seconds after a runtime dies without shutting down, unless another runtime took the home over.

host.json

troupe install writes this file the first time it runs:

JSON
{
  "version": 1,
  "profile": "developer",
  "platforms": {
    "ios-simulator": { "developerDir": "/Applications/Xcode.app/Contents/Developer" }
  }
}

The file is strict: an unknown key stops the CLI with the file and the key in the message. Besides each platform's keys (iOS, Android, Catalyst), a troupe object takes:

KeyDefaultWhat it does
portRange[47000, 47999]The TCP ports Troupe may bind
memoryBudgetMbnoneA ceiling on the memory of all Troupe devices together
reserveForProductionMb2048Free memory the governor keeps for everything that is not Troupe
warmPoolTtlMs300000How long a released device stays warm, at most the default. 0 turns the warm pool off

The developer profile is for your own machine. A shared profile exists for a Mac that also runs other workloads: it reserves their ports and device names and tightens the budgets.

Cleaning up

Normally there is nothing to clean. Two commands exist for when a crash, a forced kill or a power loss left something behind.

Shell
bunx troupe runtime sweep

sweep lists leaked devices, AVDs and process groups that Troupe owns, one per line with what it would do (remove or report). Anything it cannot prove is Troupe's is listed as such and never touched. Add --apply to remove what is listed for removal.

  • Through a running daemon, devices are only reported, because a live session may hold one. --apply stops stray process groups.
  • With no daemon, or with --no-daemon, the sweep runs in the command itself, as a starting runtime sweeps. There --apply deletes leaked devices too.
Shell
bunx troupe runtime kill-all

kill-all stops everything Troupe owns for your user, whether or not a daemon answers. It stops every running runtime, then sweeps every $TROUPE_HOME a runtime of yours has used. --disable also sets enabled: false for every platform in host.json, so nothing starts again until you change it back.

Both commands exit 1 when something Troupe owns is still there afterwards.

Next

esc

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