Skip to main content
A workspace can hold several runtimes for the same command, with different trade-offs. monty runs python3 in-process, sandboxed, stdlib-only: perfect for quick one-liners and pipe transforms, useless for a script that imports pandas. A docker entry is the opposite: your container, your installed packages, but every line pays the exec round-trip. The route decides per line which one serves it:
route.py
So python3 /jobs/train.py --epochs 3 runs in the container, while cat data.csv | python3 -c "import sys; print(len(sys.stdin.read()))" stays on monty.

Input: the line’s context

The script sees one global, ctx, the parsed line before any routing. This payload is captured from a live run of the config above, routing python3 /jobs/train.py --epochs 3:
  • commands: one entry per pipeline stage (cat x | python3 - has two), each with its full words and its absolute-path operands.
  • command, builtin: mirror the first stage. builtin means mirage has a builtin spec for the command (python3 is a builtin that hands its code to a runtime), as opposed to an unknown name.
  • env: the session environment (empty here, no export yet).
  • session_id: the executing session’s id. agent_id is empty until an agent identity is attached.
  • mounts: the workspace’s mount prefixes, including built-ins like /dev/.
The payload round-trips through JSON (RouteContext.from_dict), so you can store one and replay a decision in tests.

Output: the verdict

  • A runtime name ("docker"): that entry serves every command it captures on this line.
  • None: no opinion; the first capturer in the runtimes list order serves each command (here, monty).
  • Anything else is a routing error; the line fails rather than guessing.
Besides the global route, each runtime entry can carry its own script: with the same ctx input but a boolean verdict: am I willing to serve this line? Unwilling entries step aside and the first willing capturer wins.

What runs the scripts

route.py is never imported: the workspace evaluates its source on the policy engine, the first entry in the runtimes list with the evaluator capability. In the config above that is monty. A config with route scripts but no evaluator entry fails at the first decision. When you build the workspace in code rather than from a config file, route= also accepts a plain function with the same decision contract, sync or async. It is called directly, so no evaluator is involved:
A per-entry script= likewise accepts (ctx) -> bool in code.

Bring your own

The capability is open: inherit EvaluatorMixin (Python) or implement Evaluator with the EVALUATOR brand (TypeScript) on your own runtime, and it becomes eligible as the policy engine. eval(code, inputs=...) returns the last expression’s value; how the value travels is your runtime’s choice. The docker examples give a stock container the capability by piping a harness to python3 -: python, typescript.