Flow engineering
Flow engineering means designing the sequence of steps, or state machine, a task moves through, instead of handing it to one big prompt. Use the probabilistic model only where judgement is needed. Decompose the task into stages, make the stages that need no model deterministic nodes, and add branching or routing between them. No one-shot big prompt.
In LangGraph
The steps are nodes in a state graphState graphsAn agent drawn as nodes that read and write one shared state, joined by edges that say what runs next.Opens in a new tab, and how the state and edges are shaped is graph engineeringGraph engineeringDesigning the graph itself: what lives in state, how big a node is, where it loops and where it stops.Opens in a new tab.
Each LLM step gets one small job and a small prompt. They are the only places the flow can go wrong in a new way. The policy lookup and the escalation are code, so they behave the same every time and can be unit tested. The branch is an ordinary function on the state: routing is a decision made in code on top of a classification the model made.
from typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, END class State(TypedDict): ticket: str category: str policy: str reply: str def classify(state: State) -> dict: # LLM: judgement out = llm.invoke(f"Answer billing or other: {state['ticket']}") return {"category": out.content.strip().lower()} def lookup_policy(state: State) -> dict: # code: no model needed return {"policy": POLICIES["billing"]} def draft(state: State) -> dict: # LLM: wording out = llm.invoke(f"Policy: {state['policy']}\nReply to: {state['ticket']}") return {"reply": out.content} def escalate(state: State) -> dict: # code return {"reply": "Passed to a human agent."} def branch(state: State) -> str: return "lookup_policy" if state["category"] == "billing" else "escalate" flow = ( StateGraph(State) .add_node("classify", classify) .add_node("lookup_policy", lookup_policy) .add_node("draft", draft) .add_node("escalate", escalate) .add_edge(START, "classify") .add_conditional_edges("classify", branch, ["lookup_policy", "escalate"]) .add_edge("lookup_policy", "draft") .add_edge("draft", END) .add_edge("escalate", END) .compile())Why a flow
Anthropic separates workflows, where models and tools follow predefined code paths, from agents, where the model directs its own process, and advises adding complexity only when it demonstrably helps. Prompt chaining suits a task that splits cleanly into fixed steps. Routing allows separation of concerns and more specialised prompts, since tuning one prompt for every kind of input hurts some of them. The multi-agent version of routing is the router patternRouter patternA manager that only decides which specialist handles the request.Opens in a new tab.
A flow is also what makes an agent checkable: every node has an input and an output to log and test (see traceabilityTraceabilityLog the whole reasoning trace, so any answer can be explained afterwards.Opens in a new tab and agent evaluationAgent evaluationTest the flow an agent took and what it left behind, against the goal and the guardrails.Opens in a new tab).
Sources
- Workflows and agents, LangGraph Docs · accessed October 5, 2026
- Building effective agents, Anthropic Engineering · accessed October 5, 2026