Preloader
Technology
  • Estimated reading time: 4 Minutes

Hands-on LangGraph Development: Building a Cyclic State Machine with Retries

Hands-on LangGraph Development: Building a Cyclic State Machine with Retries

Why retries are a first-class pattern in agent workflows

When you build agent systems for real work, failures are normal. APIs rate-limit. A database query times out. A tool returns an empty response. A model produces an answer that does not pass validation. If your workflow stops at the first error, you will spend more time re-running jobs manually than benefiting from automation.

A cyclic state machine solves this by treating “try again” as part of the design, not an afterthought. In practice, this means (1) tracking what happened, (2) deciding whether a retry is safe and useful, and (3) looping back with small adjustments. This pattern is a core skill in agentic AI training, because it makes the agent resilient without requiring constant human babysitting.

LangGraph mental model: state, nodes, edges, and routing

LangGraph is useful here because it makes your workflow explicit. You create a graph of nodes (steps), edges (transitions), and a shared state object that moves through the graph. The key concept for retries is conditional routing: after a node runs, you evaluate state and choose the next node.

A simple retry loop usually needs these ingredients:

  • State fields: attempt, max_attempts, last_error, result, and status (e.g., “success”, “fail”, “retry”).
  • Attempt node: calls the tool or performs the task.
  • Validator node: checks whether the output is acceptable.
  • Router: decides whether to end, retry, or stop with failure.
  • Guardrails: max attempts, timeouts, and (optionally) backoff.

This structure is easy to test because each node is a small function with a clear input and output: the state.

Designing the retry cycle: what should change on each attempt?

A retry loop is not just “run the same thing again.” A good loop changes something small each time so the agent has a higher chance of succeeding.

Common, practical adjustments include:

  • Backoff: wait longer after repeated failures (useful for rate limits).
  • Tool parameter tweaks: narrower query, smaller batch size, different endpoint.
  • Prompt tightening: require a structured format or add constraints when the validator fails.
  • Fallback strategy: after N failures, switch to a simpler method or request human review.

Also decide up front which failures are retryable. For example:

  • Retryable: network timeouts, 429 rate limits, transient 5xx errors.
  • Not retryable (usually): invalid credentials, “resource not found,” schema mismatch that won’t change.

This decision-making is exactly what separates reliable automation from brittle demos—and it’s a recurring theme in agentic AI training.

Hands-on build: a minimal cyclic LangGraph workflow

Below is a compact example of how the pieces fit together. The code is intentionally minimal so you can adapt it to your tools (search, SQL, ticket creation, summarisation, etc.).

Pseudocode-style LangGraph pattern (names may vary by version)

from typing import TypedDict, Optional

class State(TypedDict):
    attempt: int
    max_attempts: int
    status: str          # “retry” | “success” | “fail”
    result: Optional[str]
    last_error: Optional[str]

def attempt_task(state: State) -> State:
    try:
        # Replace with your real tool call
        output = run_tool_call()
        state["result"] = output
        state["last_error"] = None
        state["status"] = "retry"  # assume retry until validated
    except Exception as e:
        state["result"] = None
        state["last_error"] = str(e)
        state["status"] = "retry"
    return state

def validate(state: State) -> State:
    ok = state["result"] is not None and is_valid(state["result"])
    state["status"] = "success" if ok else "retry"
    return state

def route(state: State) -> str:
    if state["status"] == "success":
        return "end"
    if state["attempt"] >= state["max_attempts"]:
        state["status"] = "fail"
        return "end"
    return "attempt_task"  # loop

Graph wiring conceptually:

START -> attempt_task -> validate -> (route) -> attempt_task OR END

Implementation details vary, but the logic stays the same: after validation, you either terminate or loop back to the attempt node. The state carries the context across cycles. A practical refinement is to increment the attempt right before re-entering the attempt node and to add a wait/backoff step if you are hitting external services.

If you want this to behave well in production, include structured error types and store enough context to debug later (request IDs, tool inputs, response codes). Those operational habits are an important part of agentic AI training, not just a “nice to have.”

Production checks: avoid infinite loops and silent failures

Before you ship a retrying agent, apply these guardrails:

  • Max attempts and global timeout: protect cost and latency.
  • Idempotency: retries should not duplicate side effects (e.g., creating two tickets).
  • Observability: log each attempt with the reason for retry and what changed.
  • Fallback paths: after repeated failure, switch strategies or escalate to a human.
  • Deterministic validation: ensure your validator is clear and consistent, or you will loop due to vague rules.

Conclusion

A cyclic state machine is one of the most practical patterns you can build in LangGraph. It turns failures into controlled loops: attempt, validate, route, retry. By explicitly tracking attempts, classifying errors, and applying safe guardrails, you get workflows that recover automatically instead of failing abruptly. If your goal is dependable agent systems—not one-off demos—this retry loop should be in your default toolkit for agentic AI training.

Our Sponsors

Our blog is proudly supported by industry-leading sponsors.