AI Agents LangGraph

Breakpoints

Intermediate

Breakpoints

This post explains Breakpoints in LangGraph and how they help debug workflows by pausing execution at specific points. It covers inspecting state at runtime, step-by-step debugging, handling breakpoints in cyclic and streaming workflows, using them effectively during development, common pitfalls, and best practices for reliable debugging.

What Are Breakpoints?

Breakpoints in LangGraph are a debugging and control feature that allows you to temporarily pause graph execution at specific nodes so you can inspect the current state, debug logic, or manually intervene. While Interrupts are primarily designed for Human-in-the-Loop production workflows, Breakpoints are especially useful during development, testing, and debugging. They give you fine-grained control similar to a debugger in traditional programming.

Debugging LangGraph Workflows

LangGraph workflows can become complex with cycles, parallel branches, and multi-agent coordination. Breakpoints help you:
  • Understand what’s happening inside each node
  • Inspect intermediate state
  • Debug conditional routing logic
  • Fix issues in loops
  • Validate tool outputs
from langgraph.graph import StateGraph, START, END

graph = StateGraph(AgentState)

graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)

# Add breakpoint for debugging
app = graph.compile(
    checkpointer=MemorySaver(),
    interrupt_before=["tools"]   # Pause before tools node
)

Pausing Execution for Inspection

You can pause execution before or after specific nodes:
app = graph.compile(
    checkpointer=MemorySaver(),
    interrupt_before=["agent", "tools"],      # Pause before these nodes
    # interrupt_after=["retriever"]           # Or after these nodes
)
When the graph hits a breakpoint, it stops and returns control to you.

Inspecting State at Runtime

config = {"configurable": {"thread_id": "debug_001"}}

# Run until breakpoint
result = app.invoke(inputs, config)

# Inspect current state
current_state = app.get_state(config)

print("Next node to run:", current_state.next)
print("Current state:", current_state.values)
print("Messages so far:", len(current_state.values["messages"]))
You can also update state manually during a breakpoint:
app.update_state(config, {
    "messages": [AIMessage(content="Manual correction...")],
    "debug_info": "Fixed tool call error"
})

Step-by-Step Workflow Debugging

def debug_step(config):
    snapshot = app.get_state(config)
    print(f"▶️  Next: {snapshot.next}")
    print(f"State keys: {list(snapshot.values.keys())}")
    
    # Continue to next step
    return app.invoke(None, config)   # None = resume with no new input

# Run step by step
result = app.invoke(initial_input, config)

while result.get("__interrupt__"):
    print("Breakpoint hit. Inspecting...")
    result = debug_step(config)

Breakpoints in Cyclic Workflows

Breakpoints are extremely useful in loops to prevent infinite execution during development:
app = graph.compile(
    checkpointer=MemorySaver(),
    interrupt_before=["agent"]   # Pause every time agent runs (useful for debugging loops)
)

# You can limit breakpoints to specific iterations if needed
This allows you to step through each iteration of a ReAct loop manually.

Breakpoints with Streaming

You can combine breakpoints with streaming for powerful debugging:
for chunk in app.stream(inputs, config, stream_mode="values"):
    print("Received chunk:", chunk)
    
    # You can also manually trigger breakpoints during streaming
Or use astream_events :
async for event in app.astream_events(inputs, config, version="v2"):
    print(f"Event: {event['event']} | Node: {event.get('name')}")

Using Breakpoints During Development

Recommended Development Pattern:
# Development mode with frequent breakpoints
app = graph.compile(
    checkpointer=MemorySaver(),
    interrupt_before=["tools", "final_answer"],   # Strategic breakpoints
    debug=True
)

# Run and step through
result = app.invoke(inputs, config)

while app.get_state(config).next:
    print("\n--- Paused at", app.get_state(config).next, "---")
    input("Press Enter to continue...")
    result = app.invoke(None, config)   # Resume

Common Breakpoint Mistakes

  • Forgetting to use a checkpointer (state is lost when paused)
  • Placing too many breakpoints (workflow becomes tedious)
  • Not resuming correctly (invoke(None, config))
  • Using breakpoints in production without proper controls
  • Not inspecting state before resuming

Best Practices for Breakpoints

  1. Use breakpoints liberally during development, sparingly in production
  2. Always use a persistent checkpointer
  3. Place breakpoints at decision points and before critical actions
  4. Clearly label breakpoint nodes (debug_review, human_check, etc.)
  5. Combine with streaming for maximum visibility
  6. Document expected state at each breakpoint
  7. Remove or conditionalize breakpoints before deploying to production
Pro Tip:
# Conditional breakpoints for development
if os.getenv("DEBUG_MODE") == "true":
    app = graph.compile(
        checkpointer=checkpointer,
        interrupt_before=["agent", "tools"]
    )
else:
    app = graph.compile(checkpointer=checkpointer)
Breakpoints turn LangGraph from a black box into a transparent, debuggable system, giving you full visibility and control during development.

AI agent LangChain LangGraph Python

← All training