Workspace and Session
ASession is one session’s handle: shell runs a line as it, glob matches as it, and vfs is the file API run as it, so every call answers under the session’s profile, with its working directory, environment, mount modes and hides.
ws.session(id) creates the session when the id is new and adopts it when it exists. Pass profile= to create it under one of the workspace’s profiles, or mounts= to narrow its mount modes. A profile, mounts or permissions for an existing session raise ValueError, since a profile is set once, when the session is created. ws.shell(line, session_id=...) and ws.glob(pattern, session_id=...) are the same calls without the handle; without a session id they act as the workspace’s default session.
session.shell
It answers an
IOResult: exit_code, and await stdout_str() / await stderr_str() for the text (stdout and stderr are the lazy byte streams). A non-zero exit is a result, not an exception.
Cancel a line
Set the event, or cancel the task awaiting the line. The line stops at whatever await it is in, and$? goes back to what it was before the line ran. Setting the event raises MirageAbortError from the call:
124.
session.vfs
The file API, run as the session. Paths are absolute workspace paths, and bytes arebytes.
A missing path raises
FileNotFoundError, and a refused one PermissionError, as the os module does.
Beside your own event loop
An app that already runs its own loop, such as a FastAPI or aiohttp server, can pin a workspace to a thread and loop of its own, so a slow call inside the workspace never stalls the app.runner.call runs a workspace coroutine there from any loop; the Mirage server hosts every workspace this way.
The agent tool table
MirageToolOperations is the seven agent tools (shell, read, write, edit, ls, grep, glob) as a class, answered for one session. MCP, RPC’s tools/call, the HTTP tool routes and the CLI’s tool verbs all call it, so an agent loop of your own gets the same answers:
Each tool is also a method:
await tools.read("/data/report.csv", offset=0, limit=20), await tools.grep("todo", "/src", ignore_case=True). call takes the arguments as the schema reads them, and is what every interface uses. mirage.agents.tool_descriptions holds each tool’s description and JSON schema (READ_DESCRIPTION, READ_INPUT, …) to hand your model, and the agent adapters wrap the table for each framework.
Embed the MCP server
The MCP server the Mirage server runs is a class you can run yourself, over any MCP transport, without the Mirage server:server is the SDK’s low-level Server, so it also mounts behind StreamableHTTPSessionManager in your own ASGI app. mirage.server.mcp.server.TOOLS is the tool list it advertises.
Embed the RPC server
MirageRpcServer serves one session over JSON-RPC, with the same methods the Mirage server answers. handle answers one message, for a route of your own; serve answers a newline-delimited stream until it ends, running each request as its own task so $/cancelRequest reaches a running one:
In-app,
shell runs the line directly on the session; the Mirage server runs it as a job instead.
Reach a server from code
When the workspace lives in a server, any HTTP client reaches it. The token is in~/.mirage/auth_token in the default local mode:
POST /rpc carries the whole Session API in one route; the HTTP page lists the rest, and the RPC page shows a mirage rpc client over stdio.