# Langflow Flow Runner Architecture: Handling Loops and Retries in Graph Execution

> Discover Langflow's flow runner architecture, detailing how it manages graph execution, cyclic dependencies, and loops. Learn about its stateless design and fault tolerance with tenacity.

- Repository: [Langflow/langflow](https://github.com/langflow-ai/langflow)
- Tags: architecture
- Published: 2026-02-24

---

**Langflow's flow runner architecture orchestrates graph execution through the stateless `LangflowRunnerExperimental` class, which manages database initialization, tweak processing, and cleanup, while `RunnableVerticesManager` handles cyclic dependencies and loop vertices through predecessor tracking, and individual components implement fault tolerance via the `tenacity` library rather than a centralized retry mechanism.**

The `langflow-ai/langflow` repository implements a sophisticated **flow runner architecture** designed to execute complex data processing graphs with support for cyclic dependencies and resilient external API calls. At its core, the system separates orchestration concerns from component execution, enabling both isolated flow runs and fine-grained control over loop iterations and error recovery.

## Core Components of the Flow Runner Architecture

### API Entry Points and the Experimental Runner

Execution begins at `execute_flow_file` or `execute_flow_file_streaming` in [`src/backend/base/langflow/services/flow/flow_executor.py`](https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/services/flow/flow_executor.py), which resolve flow files and forward them to `LangflowRunnerExperimental` in [`src/backend/base/langflow/services/flow/flow_runner.py`](https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/services/flow/flow_runner.py). This runner is deliberately **stateless**—every invocation creates a fresh graph instance, initializes the database if needed via `should_initialize_db=True`, processes tweak injections, and guarantees cleanup of both cache and temporary database rows after execution.

### Graph Construction and Vertex Management

The `Graph.from_payload` method in [`src/lfx/src/lfx/graph/graph/base.py`](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/graph/graph/base.py) transforms JSON payloads into executable graph objects containing vertices, edges, and cycle information. Each node is represented by a `Vertex` instance (defined in [`src/lfx/src/lfx/graph/vertex/base.py`](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/graph/vertex/base.py)) that exposes an `is_loop` property based on output schema inspection. During `Graph.prepare`, the system builds a list of `cycle_vertices` through depth-first search, allowing self-loops while preventing invalid circular dependencies.

### Execution State Tracking

The `RunnableVerticesManager` in [`src/lfx/src/lfx/graph/graph/runnable_vertices_manager.py`](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/graph/graph/runnable_vertices_manager.py) maintains the run-state, tracking which vertices are ready for execution. Its `are_all_predecessors_fulfilled` method (lines 86-100) implements the core logic for resolving cyclic dependencies and determining when loop-capable vertices may fire.

## Loop Handling in the Flow Runner

### Detecting Loop-Capable Vertices

Vertices indicate looping capability through the `is_loop` property in [`src/lfx/src/lfx/graph/vertex/base.py`](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/graph/vertex/base.py) (lines 125-129). This property inspects the component's output schema for the `allows_loop` flag, returning `True` when the vertex can participate in iterative execution cycles.

### Predecessor Resolution for Cyclic Graphs

The `RunnableVerticesManager` distinguishes between standard and cyclic execution paths in its `are_all_predecessors_fulfilled` method. For vertices within cycles (`cycle_vertices`), the manager applies specific rules:

- **First execution**: Permits running only when `is_loop` is `True` and all pending predecessors belong to the same cycle (`pending_set <= self.cycle_vertices`)
- **Subsequent executions**: After `ran_at_least_once` is set, the vertex waits until no pending or running predecessors remain, preventing infinite spin

### Context Sharing Across Iterations

The `graph.context` dictionary persists across loop iterations, allowing components to accumulate state. For example, a loop body can append results to a shared list that subsequent iterations or downstream vertices can access, verified by test suites like [`test_loop_parser_integration.py`](https://github.com/langflow-ai/langflow/blob/main/test_loop_parser_integration.py).

## Retry Mechanisms and Error Handling

### Component-Level Retry Implementation

Unlike centralized retry systems, Langflow delegates fault tolerance to individual components using the **`tenacity`** library. The HuggingFace component in [`src/lfx/src/lfx/components/huggingface/huggingface.py`](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/components/huggingface/huggingface.py) (lines 151-156) decorates API calls with `@retry(stop=stop_after_attempt(3), wait=wait_fixed(2))`, while the Twelve Labs Pegasus component in [`src/lfx/src/lfx/components/twelvelabs/pegasus.py`](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/components/twelvelabs/pegasus.py) (lines 179-181) implements exponential back-off for resilient external communication.

### Error Propagation and Runner Cleanup

When component exceptions exhaust retry limits, the error propagates through the async execution chain to `run_graph` in [`src/lfx/src/lfx/processing/process.py`](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/processing/process.py). The runner captures this in `execution_result.error` (see lines 185-193 in [`flow_runner.py`](https://github.com/langflow-ai/langflow/blob/main/flow_runner.py)), clears the flow state and user cache, and returns structured error responses or raises exceptions in direct method calls.

## Executing Flows with Loops and External APIs

When a flow combines iterative processing with unreliable external services, the architecture coordinates as follows:

1. **Graph Construction**: The loop node reports `is_loop=True`, and `Graph.prepare` marks surrounding vertices as `cycle_vertices` due to self-referential edges.
2. **Initial Execution**: `RunnableVerticesManager.are_all_predecessors_fulfilled` permits the first iteration because all pending predecessors are within the same cycle.
3. **Resilient Component Execution**: If the loop body calls an external API (e.g., HuggingFace inference), the component's `@retry` wrapper automatically reissues failed requests up to the configured limit before surfacing errors.
4. **Iteration Management**: After the first run, `ran_at_least_once` flags modify the readiness check, ensuring the loop waits for cycle completion before subsequent iterations.
5. **Cleanup**: Upon loop completion (when the data source exhausts or termination conditions meet), the runner extracts final results and executes `clear_flow_state` to remove temporary database entries and cache artifacts.

## Practical Implementation Examples

### Running a Flow with the Experimental Runner

```python
from pathlib import Path
from langflow.services.flow.flow_runner import LangflowRunnerExperimental
from uuid import uuid4

async def run_iterative_flow():
    runner = LangflowRunnerExperimental()
    # Flow JSON contains a Loop component

    result = await runner.run(
        session_id=str(uuid4()),
        flow=Path("workflows/data_processing.json"),
        input_value="initial_token",
        tweaks_values={"api_component": {"timeout": 30}}
    )
    return result

```

### Detecting Loop Vertices Programmatically

```python
from lfx.graph.vertex.base import Vertex

def check_loop_capability(vertex: Vertex) -> bool:
    # Checks output schemas for allows_loop flag

    # Source: src/lfx/src/lfx/graph/vertex/base.py#L125-L129

    return vertex.is_loop

```

### Implementing Component-Level Retries

```python
from tenacity import retry, stop_after_attempt, wait_fixed
import httpx

class ExternalAPIComponent:
    @retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
    async def fetch_data(self, endpoint: str) -> dict:
        async with httpx.AsyncClient() as client:
            resp = await client.get(endpoint)
            resp.raise_for_status()
            return resp.json()

```

### Checking Vertex Readiness with RunnableVerticesManager

```python
def can_execute(manager, vertex_id: str, is_loop: bool) -> bool:
    # Delegates to are_all_predecessors_fulfilled

    # Source: src/lfx/src/lfx/graph/graph/runnable_vertices_manager.py#L86-L100

    return manager.is_vertex_runnable(vertex_id, is_active=True, is_loop=is_loop)

```

## Summary

- **Langflow's flow runner architecture** separates orchestration (`LangflowRunnerExperimental`) from graph execution logic, maintaining statelessness through complete cleanup after each run.
- **Loop handling** relies on `Vertex.is_loop` detection and `RunnableVerticesManager.are_all_predecessors_fulfilled` to manage cyclic dependencies without infinite execution.
- **Retry logic** is decentralized to components using `tenacity` decorators, allowing granular control over external API resilience rather than runner-level blanket policies.
- **State management** uses `graph.context` for data persistence across loop iterations, while the runner handles database initialization and temporary user cleanup automatically.

## Frequently Asked Questions

### What makes Langflow's flow runner stateless?

The `LangflowRunnerExperimental` class creates a fresh `Graph` instance for every execution and explicitly clears both the cache service and temporary database rows upon completion. This design prevents memory leaks between runs and ensures that each flow execution starts from a clean state regardless of previous failures or successes.

### How does Langflow prevent infinite loops in cyclic graphs?

The `RunnableVerticesManager` tracks `ran_at_least_once` flags and requires that subsequent iterations wait for all cycle predecessors to complete before refiring. Additionally, the `Graph.prepare` method distinguishes between valid self-loops (allowed) and invalid circular dependencies (errors), while components themselves typically implement termination logic based on data exhaustion rather than the runner imposing iteration limits.

### Why are retries implemented at the component level instead of the runner level?

Decentralizing retry logic to individual components via `tenacity` allows each external service integration to define appropriate back-off strategies, timeout durations, and retry counts based on specific API reliability characteristics. This avoids the inefficiency of runner-level retries that would re-execute entire graph sections rather than just the failed API call.

### How can I detect if a vertex supports looping programmatically?

Inspect the `is_loop` property on any `Vertex` instance, which evaluates the output schema for `allows_loop` flags. In [`src/lfx/src/lfx/graph/vertex/base.py`](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/graph/vertex/base.py) (lines 125-129), this property returns `True` when any output definition contains `"allows_loop": true`, indicating the component is designed for iterative execution within cycles.