> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Route Policy

> Script which runtime serves each command line; route-policy scripts run on the workspace's evaluator runtime.

When several runtimes can run the same command, the **route policy**
picks one per line. Here `monty` runs `python3` in-process (fast,
stdlib-only) and `docker` runs it in your container (your packages, an
exec round-trip):

```yaml theme={null}
mode: exec
runtimes:
  - monty # captures python3 by default: sandboxed, stdlib-only
  - name: docker
    captures: ["python3"] # your container, has pandas installed
    config:
      container: my-box
  - workspace
route_policy: ./policy.py
```

```python policy.py theme={null}
# Jobs under /jobs need the container's packages; anything else
# stays on the fast in-process sandbox. The LAST EXPRESSION is
# the verdict.
stage = ctx["commands"][0]
in_jobs = any(p.startswith("/jobs/") for p in stage["paths"])
"docker" if in_jobs else "monty"
```

`python3 /jobs/train.py` runs in the container; `cat data.csv | python3
-c ...` stays on monty.

## Input: the line's context

The script reads one global, `ctx`:

```json theme={null}
{
  "line": "python3 /jobs/train.py --epochs 3",
  "commands": [
    {
      "command": "python3",
      "words": ["python3", "/jobs/train.py", "--epochs", "3"],
      "builtin": true,
      "paths": ["/jobs/train.py"]
    }
  ],
  "command": "python3",
  "builtin": true,
  "cwd": "/",
  "env": {},
  "session_id": "019fb2fc-19a3-77cf-bbbe-c76069eed523",
  "agent_id": "",
  "mounts": ["/.bash_history/", "/data/", "/dev/", "/"]
}
```

`commands` has one entry per pipeline stage; `command` and `builtin`
mirror the first. It round-trips through JSON (`RouteContext.from_dict`)
for tests.

## Output: the verdict

| Verdict | Effect |
| - | - |
| `"docker"` or `{"runtime": "docker"}` | that runtime serves every command it captures |
| `None` | the first capturer in `runtimes` order serves each command |
| `{"deny": reason}` | refused before it runs: `<cmd>: Permission denied`, exit 126 |
| anything else | the line fails with a routing error |

In code the typed forms are `RouteResult` and `DenyResult`:

```python theme={null}
from mirage.runtime.routing import (DenyResult, RouteContext, RouteOutcome,
                                   RouteResult)

def policy(ctx: RouteContext) -> RouteOutcome | None:
    if any(p.startswith("/prod/") for p in ctx.commands[0].paths):
        return DenyResult("writes under /prod are blocked")
    return RouteResult("docker") if "/jobs/" in ctx.line else None
```

A runtime entry's own `script:` answers a yes or no: will it serve this
line? With no placement, the first willing capturer wins. A runtime a
policy places the line on must also say yes, or the line is refused with
`runtimes.<name>` as the refusal's policy. The caller's `runtime=`
argument skips the scripts.

## Admit, then route

The route policy is the built-in answer to the
[policy's](/home/policy/policies#shell-hooks) `pre_execute` hook, asked before
any coded policy. A coded policy places a line the same way:

```python theme={null}
from mirage.policy import Deny, Policy, Route
from mirage.runtime.routing import RouteContext

class HeavyOnDocker(Policy):
    async def pre_execute(self, ctx: RouteContext) -> Route | Deny | None:
        return Route("docker") if "/jobs/" in ctx.line else None
```

A line the rules refuse is never placed. A Deny beats any Route, and two
policies naming different runtimes refuse the line. Nested lines (`$( )`,
`eval`, `source`, `xargs`) keep the outer line's placement.
`ws.explain(line)` shows each placement answer and each command's
runtime.

## What runs the scripts

A script runs on an **evaluator runtime** from the `runtimes` list: a
`.py` script on the first Python evaluator (monty, or pyodide in
TypeScript), a `.js` one on the first JavaScript evaluator (quickjs).
With no evaluator the first decision fails. A script that runs past 10
seconds fails the line.

In code, `route_policy=` also takes a plain function, sync or async,
called directly; a runtime's `script=` takes `(ctx) -> bool`:

```python theme={null}
from mirage.runtime.routing import RouteContext

def policy(ctx: RouteContext) -> str | None:
    stage = ctx.commands[0]
    in_jobs = any(p.startswith("/jobs/") for p in stage.paths)
    return "docker" if in_jobs else "monty"

ws = Workspace(mounts, runtimes=runtimes, route_policy=policy)
```

## Bring your own

Inherit `EvaluatorMixin` (Python) or implement `Evaluator` with the
`EVALUATOR` brand (TypeScript) on your runtime, and it can evaluate
scripts: `eval(code, inputs=...)` returns the last expression's value.
The docker examples do it by piping a harness to `python3 -`:
[python](https://github.com/strukto-ai/mirage/blob/main/examples/python/runtimes/docker/docker_eval.py),
[typescript](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/runtimes/docker/docker_eval.ts).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.