Workflow Graphs
State machines and graphs for deterministic-agentic orchestration. When to graph vs free-loop.
Workflow graphs
A workflow graph is a state machine where nodes are steps (LLM calls, tools, conditions) and edges are transitions. Unlike a free-form agent loop, the graph defines which steps can follow which — giving you determinism where you need it and flexibility where you want it.
LangGraph is the canonical framework: you define nodes (functions), edges (transitions), and conditional edges (branching). State is passed through the graph and checkpointed for human-in-the-loop.
Why graphs beat free loops
Free agent loops are non-deterministic — same input, different paths. That's great for research, terrible for production workflows where you need: auditability (which steps ran), retryability (resume from a checkpoint), human-in-the-loop (pause before a step), and composability (reuse sub-graphs). Graphs give you all four.
Graph architecture
START → node → conditional_edge → node → ... → END. Each node is a function (state) → state. Conditional edges route based on state. Checkpoints serialise state for pause/resume. Sub-graphs compose into larger graphs. Compiled graph = runnable, observable, resumable.
A LangGraph workflow
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
class State(TypedDict):
query: str
retrieved: list[str]
answer: str
needs_human: bool
def retrieve(state: State) -> State:
docs = vector_store.search(state["query"], k=5)
return {"retrieved": [d["text"] for d in docs]}
def generate(state: State) -> State:
context = "\n\n".join(state["retrieved"])
answer = call_llm(f"Context: {context}\n\nQuestion: {state['query']}")
needs_human = "I'm not sure" in answer or len(state["retrieved"]) == 0
return {"answer": answer, "needs_human": needs_human}
def human_review(state: State) -> State:
# Pauses here — state checkpointed
# Human approves or edits the answer
approved = request_human_approval(state["answer"])
return {"answer": approved}
def route(state: State) -> str:
return "human_review" if state["needs_human"] else END
# Build the graph
workflow = StateGraph(State)
workflow.add_node("retrieve", retrieve)
workflow.add_node("generate", generate)
workflow.add_node("human_review", human_review)
workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "generate")
workflow.add_conditional_edges("generate", route)
workflow.add_edge("human_review", END)
app = workflow.compile(checkpointer=memory_checkpointer)
# Run with checkpointing — can pause at human_review and resume
config = {"configurable": {"thread_id": "session-123"}}
result = app.invoke({"query": "What is our refund policy?"}, config=config)
# If needs_human, pauses at human_review; resume after approval
# result = app.invoke(None, config=config) # resumeExperiment: graph vs loop
Compare a graph-based workflow vs a free agent loop on a task that needs human approval.
What to observe
Workflow graphs win when you need pause/resume (human-in-the-loop), auditability, retryability, or composability. Free loops win for always-on agents where flexibility matters more than determinism. Match the orchestration to the requirement.
Production workflows
Production graphs: checkpointing for pause/resume, sub-graphs for composability, conditional edges for branching, state versioning for migrations, full trace of node executions, and timeout per node. Use graphs for any workflow with human-in-the-loop, audit, or retry requirements.
Challenge
Design a graph for a refund-approval workflow: retrieve order, classify (auto-approve, review, reject), if review → human approval → execute refund → notify customer. Where do you checkpoint?
Production checklist
Production checklist
0 of 8 checked
Knowledge check
When should you use a workflow graph instead of a free agent loop?
Complete
You can now build deterministic-agentic workflows with graphs. Next: multi-agent systems.
Mark this chapter as complete
Track your progress and unlock the next chapter.