Flyte 2 is the durable runtime built for open source

Flyte 2 uses infrastructure-as-context to recover from infrastructure failures, not just bugs. Scale durable, agent-native workflows to production, on your own infra.

Copied to clipboard!
$ pip install flyte 
$ flyte start devbox

Flyte UI ready at http://localhost:30080

Why Flyte 2?

AI/ML has moved beyond traditional orchestration

Orchestrators were built for linear data pipelines: extract, transform, load, repeat. 

AI workloads don't look like that. They branch based on model output, retry against infrastructure that fails in AI-specific ways (OOM kills, preempted spot nodes, GPUs that vanish mid-run), and mix long training loops with realtime serving.

Flyte 2 AI runtime is built for that.

Flyte SDK downloads to date:

PyPI Downloads

Durable Execution

Recover from infrastructure failures, not just code failures

Most orchestrators retry your function when it throws an exception. Flyte 2 goes further: it's infrastructure-aware, and it automatically recovers when the infrastructure underneath your task disappears, an OOM kill, a preempted spot node, a GPU that vanishes mid-run, without you writing any recovery logic yourself.

Copied to clipboard!
from datetime import timedelta
import flyte

env = flyte.TaskEnvironment(
    name="training",
    resources=flyte.Resources(cpu=4, gpu="A100:2, memory="32Gi")
)

@env.task(
    retries=flyte.RetryStrategy(
        count=5,
        backoff=flyte.Backoff(
            base=timedelta(seconds=10),
            factor=2.0,
            cap=timedelta(minutes=5)
        ),
    ),
    timeout=flyte.Timeout(
        max_runtime=timedelta(hours=6),
        deadline=timedelta(hours=8)
    ),
)
async def train_model(checkpoint: Checkpoint | None = None) -> Checkpoint:
    # If this pod is OOM-killed, preempted, or loses its GPU mid-run,
    # Flyte 2 retries with backoff and resumes from the last checkpoint,
    # not from scratch.
    ...
    return new_checkpoint

Retries with backoff, a per-attempt runtime cap, and an absolute deadline, composable on a single task. Infrastructure-triggered `retries` (node loss, preemption) don't consume your retries budget at all, the platform handles those separately.

Fail fast on the unfixable

Not every failure should be retried. Raise `NonRecoverableError` for a bad input or malformed config so the action fails immediately instead of burning through your retry budget on something retrying will never fix.

Copied to clipboard!
@env.task(retries=3)
async def validate_and_process(x: int) -> str:
    if x < 0:
        raise flyte.errors.NonRecoverableError(
            f"Input x={x} is negative, retrying will not help."
        )
    return f"processed({x})"

Every execution is reproducible

Executions are logged and versioned automatically. Trace exactly what ran, with what inputs, and see precisely when and how a self-healing recovery happened, right in the UI.

Copied to clipboard!
@env.task(cache="auto")
async def expensive_computation(data: str) -> str:
    # This result will be cached and reused for identical inputs
    ...

Authoring

Author dynamic, agent-native workflows

Flyte 2 drops the DSL. Workflows are ordinary Python: tasks calling tasks inside `TaskEnvironments`. Branching, looping, and runtime decisions are just Python control flow, `if`, `for`, `try/except`, not a separate configuration language layered on top.

That's also what makes Flyte 2 a fit for agentic control flow: an agent's decision loop is just async Python, recovering from the same infra failures covered above.

Copied to clipboard!
import flyte

env = flyte.TaskEnvironment(name="research_agent")

@env.task(retries=3)
async def plan_step(query: str) -> Plan:
    ...

@env.task(retries=3)
async def execute_step(action: Action) -> StepResult:
    ...

@env.task
async def agent_loop(query: str) -> str:
    plan = await plan_step(query)
    while not plan.is_done:
        result = await execute_step(plan.next_action)
        plan = await plan_step(query, prior_result=result)
    return plan.final_answer

@env.task
async def main(queries: list[str]) -> list[str]:
    # each query recovers independently
    return list(await flyte.map(agent_loop, queries))

Inference

Batch inference with max GPU utilization

When running batch inference, the single biggest cost driver is idle GPU time: cycles where the GPU sits waiting with nothing to do. Flyte 2 uses `DynamicBatcher` to maximize GPU utilization.

Copied to clipboard!
from flyte.extras import DynamicBatcher

async def process(batch: list[dict]) -> list[str]:
    """Your batch processing function. Must return results in the same order as the input."""
    return [heavy_computation(item) for item in batch]

async with DynamicBatcher(
    process_fn=process,
    target_batch_cost=1000,   # cost budget per batch
    max_batch_size=64,        # hard cap on records per batch
    batch_timeout_s=0.05,     # max wait time before dispatching a partial batch
    max_queue_size=5_000,     # queue size for backpressure
) as batcher:
    futures = []
    for record in my_records:
        future = await batcher.submit(record, estimated_cost=10)
        futures.append(future)
    results = await asyncio.gather(*futures)

Real-time inference and model serving

Flyte 2 adds support for real-time inference for single node models.

Copied to clipboard!
app = FastAPI(title="ML Model API")

# Define request/response models
class PredictionRequest(BaseModel):
    feature1: float
    feature2: float
    feature3: float

class PredictionResponse(BaseModel):
    prediction: float
    probability: float

# Load model (you would typically load this from storage)
model = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global model
    model_path = os.getenv("MODEL_PATH", "/app/models/model.joblib")
    # In production, load from your storage
    if os.path.exists(model_path):
        with open(model_path, "rb") as f:
            model = joblib.load(f)
    yield

@app.post("/predict", response_model=PredictionResponse)
async def predict(request: PredictionRequest):
    # Make prediction
    # prediction = model.predict([[request.feature1, request.feature2, request.feature3]])

    # Dummy prediction for demo
    prediction = 0.85
    probability = 0.92

    return PredictionResponse(
        prediction=prediction,
        probability=probability,
    )

env = FastAPIAppEnvironment(
    name="ml-model-api",
    app=app,
    image=flyte.Image.from_debian_base(python_version=(3, 12)).with_pip_packages(
        "fastapi",
        "uvicorn",
        "scikit-learn",
        "pydantic",
        "joblib",
    ),
    parameters=[
        flyte.app.Parameter(
            name="model_file",
            value=flyte.io.File.from_existing_remote("s3://bucket/models/model.joblib"),
            mount="/app/models",
            env_var="MODEL_PATH",
        ),
    ],
    resources=flyte.Resources(cpu=2, memory="2Gi"),
    requires_auth=False,
)

Sandboxes

Safely execute LLM-generated code

Flyte 2 ships with a built-in sandbox that lets LLMs generate Python orchestration code and execute it safely. Two complementary modes:

  • Sandboxed orchestration, built on Monty, a Rust-based sandboxed Python interpreter. Starts in microseconds, runs pure Python control flow, and dispatches heavy work to full container tasks through the Flyte controller.
  • Code sandboxing: a stateless sandbox that runs arbitrary Python scripts or shell commands inside an ephemeral, disposable container, for when you need full Python capabilities beyond pure control flow.

Programmatic tool calling for agents

Instead of a model making one tool call at a time, with every intermediate result passing back through its context window, the model writes a single block of code that calls multiple tools, transforms data, and applies logic, all inside the sandbox. Only the final result returns to the model.

Copied to clipboard!
import flyte.sandbox

result = await flyte.sandbox.orchestrate_local(
    code,
    inputs={"_unused": 0},
    tasks=list(tools.values()),
)

For production workloads, wrap tools as `@env.task` so the sandbox dispatches them as durable Flyte tasks, the same retry and recovery behavior from above applies to every tool call.

Compute Management

Autoscale long-running apps

Model endpoints, agent services, and other long-running apps scale replicas up and down automatically to match load.

Copied to clipboard!
scaling=flyte.app.Scaling(
    replicas=(min_replicas, max_replicas),
    scaledown_after=idle_ttl_seconds,
)

Spot instances, with automatic fallback

Schedule interruptible workloads on spot or preemptible instances to cut compute costs. When a spot instance is reclaimed, Flyte 2 falls back to on-demand on the final attempt, the same recovery mechanism covered in Durable by default handles the interruption automatically.

Copied to clipboard!
@env.task(interruptible=True)
async def data_processing(batch: Batch) -> Result:
    ...

Migrating from Flyte 1 or another legacy tool?

Check out our migration resources.