Skip to course content
Free LangGraph course

LangGraph for Agentic Workflows

Unit 01.01: Branching, cycles, and resuming

Here is the smallest graph that does something a chain cannot: it writes a draft, reviews it, and goes back to writing if the review is not satisfied.

A cycle, and the counter that terminates it

The graph has three pieces beyond the nodes: an entry point, a conditional edge that reads state and returns a destination, and a state field that the cycle increments.

Read decide first. It is a function from state to a node name, and it is where the cycle is either bounded or not.

from typing import TypedDict
from langgraph.graph import StateGraph, START, END


class State(TypedDict):
    draft: str
    attempts: int


def write(state: State):
    return {"draft": "draft v" + str(state["attempts"] + 1),
            "attempts": state["attempts"] + 1}


def review(state: State):
    return state  # a real reviewer would set a flag here


def decide(state: State):
    return END if state["attempts"] >= 3 else "write"


g = StateGraph(State)
g.add_node("write", write)
g.add_node("review", review)
g.add_edge(START, "write")
g.add_edge("write", "review")
g.add_conditional_edges("review", decide, {"write": "write", END: END})

print(g.compile().invoke({"draft": "", "attempts": 0}))

# The cycle write -> review -> write is the thing a chain cannot express.
# Note `attempts`: a cycle without a counter is an infinite loop, and the
# counter has to live in the state because that is the only thing that moves.

The edge review -> write is the cycle. Nothing else in the graph is unusual, and that single edge is what a chain cannot express.

attempts is doing the essential work. Without it, decide has nothing to test and the graph loops until LangGraph's recursion limit stops it - which surfaces as an exception rather than as the give-up path you wanted. A cycle needs a counter, and the counter has to live in the state because the state is the only thing that survives from one node to the next.

The mistake this prevents

The mistake is putting the counter in a Python variable outside the graph. It works in a single-process test and fails the moment the run is checkpointed and resumed, because the variable was never part of what got saved. Anything a router reads must be in the state.

Takeaway

A cycle is one conditional edge pointing backwards. Every cycle needs a counter in the state and a ceiling checked in the router, or it terminates by exception rather than by design.