Skip to course content
Free LangGraph course

LangGraph for Agentic Workflows

Unit 04.02: Retries that do not loop forever

Retry is the most common cycle in an agentic graph and the easiest one to get subtly wrong.

Counter, ceiling, and a give-up node

Three pieces are needed together: a counter in the state, a ceiling checked in the router, and a give-up node distinct from the success node.

The code runs a step that succeeds on the third attempt.

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

MAX_ATTEMPTS = 3


class State(TypedDict):
    attempts: int
    ok: bool
    outcome: str


def attempt(state: State):
    n = state["attempts"] + 1
    return {"attempts": n, "ok": n >= 3}      # succeeds on the third try


def decide(state: State):
    if state["ok"]:
        return "succeed"
    if state["attempts"] >= MAX_ATTEMPTS:
        return "give_up"
    return "attempt"


g = StateGraph(State)
g.add_node("attempt", attempt)
g.add_node("succeed", lambda s: {"outcome": f"ok after {s['attempts']}"})
g.add_node("give_up", lambda s: {"outcome": f"failed after {s['attempts']}"})
g.add_edge(START, "attempt")
g.add_conditional_edges("attempt", decide,
                        {"attempt": "attempt", "succeed": "succeed", "give_up": "give_up"})
g.add_edge("succeed", END)
g.add_edge("give_up", END)

print(g.compile().invoke({"attempts": 0, "ok": False, "outcome": ""}))

# Three things make a retry loop safe: a counter in the state, a ceiling checked
# in the router, and a distinct give-up node. Drop any one and the graph either
# runs until the recursion limit or reports failure as success.

The router checks success before the ceiling, so a run that succeeds on the final permitted attempt is reported as success rather than as exhaustion. That ordering matters and is easy to get backwards.

The separate give_up node is what makes the outcome distinguishable downstream. Falling through to the success node after the ceiling produces a run that reports success having achieved nothing - the worst of the available failure modes, because nothing alerts.

The mistake this prevents

The mistake is retrying without asking whether the failure is retryable. A timeout is worth retrying; a validation error on the same input will fail identically three times, costing three times as much and delaying the error by three attempts. Classify the failure before looping on it.

Takeaway

A safe retry needs a counter in the state, a ceiling in the router, and a distinct give-up path. Check success before the ceiling, and only retry failures that could plausibly succeed next time.