Skip to main content
Most of the time an agent does not need a sandbox, only a terminal-like interface. An embedded terminal runs inside an application server. Each terminal is a Mirage session with its own working directory, environment and history, and Monty runs Python in-process, so a terminal starts with no container or VM. Mirage is async native, so one server process runs many terminals at once on its event loop.

Installation

Server

One endpoint creates a session, another runs a command line in it.
app.py

Usage

The run prints 45. A later call to session a starts in /work, where the cd left it.
  • Sessions share the workspace’s mounts, so a file one writes under /work is visible to the others. Give each session its own directory, or its own workspace, to keep files apart.
  • Monty supports a subset of Python and has no network or host access. For more, see Hybrid Terminal.
  • The server runs any line for any caller that names a session. Put authentication and a check that the caller owns the session in front of it before it listens beyond localhost.

Async

ws.shell is a coroutine in Python and a promise in TypeScript, and it never blocks the event loop. The handlers above are async, so while one terminal waits on a file read, a runtime or a slow command, the server keeps serving every other terminal, with no thread or process per terminal.
The three lines finish together in about a second; 20 sessions take the same. Within one session, lines run one after another in order, as in a real terminal, so three sleep 1 lines on session a take three seconds.

Hybrid Terminal

A hybrid terminal keeps light Python on Monty and sends the rest to a Docker container: lines that use numpy or pandas by route policy, and every program Mirage does not have itself, such as ffmpeg, by @external. Redis holds the files, sessions, jobs and processes, so a terminal outlives the server process that runs it. Build the container’s image with sshfs, which serves the workspace into it, and the programs the lines need, and start Redis:
Dockerfile
A container serves one session, so each terminal gets a workspace and a container of its own. The server starts the container when it creates the terminal:
app.py
  • Send each terminal’s requests to one replica, for example by hashing the terminal id at the load balancer. A replica keeps a session in memory once it has loaded it, so two replicas serving one terminal would each keep their own working directory. After a restart, the replica reads the terminal back from Redis, in the directory it left.
  • Every terminal’s /work is the same Redis keys, so terminals share files. Give each RedisVFS its own key_prefix to keep them apart.
  • Remove a terminal’s container with docker rm -f box-<id> when the terminal ends.
  • ws.explain(line) shows which runtime serves each command.