Skip to main content

Sandbox Orchestration

Sandbox Orchestration provides a secure, isolated environment for executing Python code using the Monty interpreter (a Rust-based Python sandbox). This implementation allows for fine-grained resource control and safe execution of untrusted or complex orchestration logic without the overhead of full container isolation for every step.

The core of this system is the SandboxedTaskTemplate (found in src/flyte/sandbox/_task.py), which manages the lifecycle of a sandboxed execution, from source extraction to resource-limited runtime.

Defining Sandboxed Tasks

Sandboxed tasks are typically defined using the orchestrator property on a TaskEnvironment. This API provides three primary ways to create sandboxed execution units:

1. Bare Decorator

The simplest usage is decorating a standard Python function. This creates a SandboxedTaskTemplate with default configuration.

import flyte

env = flyte.TaskEnvironment(name="my-env")

@env.sandbox.orchestrator
def process_data(x: int, y: int) -> int:
# This body executes inside the Monty sandbox
return (x + y) * 2

2. Decorator Factory

To customize resource limits or timeouts, use the decorator as a factory by passing configuration arguments.

@env.sandbox.orchestrator(timeout_ms=5_000, max_memory=100 * 1024 * 1024)
def heavy_orchestration(data: list[int]) -> int:
return sum(data)

3. Code Strings

For dynamic execution, you can pass a raw Python code string directly. This returns a CodeTaskTemplate (a subclass of SandboxedTaskTemplate).

dynamic_task = env.sandbox.orchestrator(
"x + y",
inputs={"x": int, "y": int},
output=int,
name="dynamic-adder"
)

Configuration and Resource Limits

The SandboxedConfig class (in src/flyte/sandbox/_config.py) defines the constraints applied to the Monty interpreter. When using the decorator factory, these parameters are passed directly to the underlying configuration object:

ParameterDefaultDescription
max_memory50 MBMaximum memory the sandbox can allocate.
max_stack_depth256Maximum recursion/stack depth.
timeout_ms30,000Execution timeout in milliseconds.
type_checkTrueWhether to validate input/output types against the interface.

Type Support and Validation

To ensure safety and compatibility with the Monty interpreter, the sandbox enforces strict type boundaries. The validate_sandboxed_interface function in src/flyte/sandbox/_type_boundary.py checks that all task inputs and outputs belong to the following allowed categories:

  • Primitives: int, float, str, bool, bytes, None.
  • Collections: list, dict, tuple, set.
  • Flyte IO Types: flyte.io.File, flyte.io.Dir, and flyte.io.DataFrame.

Custom classes or complex objects that cannot be natively handled by Monty or marshaled via the IO bridge will trigger a TypeError during task initialization.

External Task Orchestration

One of the most powerful features of the sandbox is its ability to orchestrate other Flyte tasks. The SandboxedTaskTemplate uses _discover_external_refs to inspect the function's globals and identify references to other TaskTemplate objects.

The Async Bridge

When a sandboxed task calls an external task, it doesn't execute it directly. Instead, it uses the ExternalFunctionBridge (in src/flyte/sandbox/_bridge.py).

  1. Marshaling: The bridge converts flyte.io types into Monty-compatible dictionaries using _to_monty.
  2. Suspension: The Monty interpreter pauses execution when it encounters an external call, producing a FunctionSnapshot.
  3. Async Dispatch: The bridge executes the external task (using its .aio() method) and awaits the result.
  4. Resumption: Once the external task completes, the result is marshaled back into the sandbox, and Monty resumes from the snapshot.

Parallel Execution with flyte_map

The sandbox provides a built-in flyte_map function that allows for parallel execution of tasks within the sandboxed environment. The bridge intercepts calls to flyte_map and delegates them to the core flyte.map.aio implementation, ensuring that concurrency limits and group tracking are respected.

Local Execution and Debugging

Because the sandbox extracts source code and runs it in a separate interpreter, standard debugging tools (like breakpoints) may not work inside the sandboxed body. To facilitate local testing, SandboxedTaskTemplate provides a .forward() method.

# This runs inside the Monty sandbox
result = process_data(1, 2)

# This bypasses the sandbox and calls the Python function directly
# Useful for local unit tests and debugging
debug_result = process_data.forward(1, 2)

The forward method executes the original function in the current Python process, bypassing all sandbox constraints and the Monty interpreter entirely.