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

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, which resolve flow files and forward them to LangflowRunnerExperimental in 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 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) 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 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 (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.

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 (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 (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. The runner captures this in execution_result.error (see lines 185-193 in 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

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

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

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

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 (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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →