Human-in-the-loop (HITL)

In many real-world Agent applications, full automation is not always ideal, or even dangerous. You may need human intervention to approve critical actions (like money transfer), provide missing information, or correct Agent's reasoning errors. LangGraphGo treats "humans" as first-class citizens in the graph.

1. Static Interrupt (InterruptBefore)

Background & Functionality

This is the most common HITL pattern: always pause before executing a specific node to wait for approval. This is similar to the manual approval step in CI/CD pipelines.

Implementation Principle

When compiling or running the graph, we configure the InterruptBefore list. When the runtime is about to execute a node in the list, it checks if a resume signal is received. If not, it saves the current state (Checkpoint), suspends execution, and returns a special error.

Code Showcase

// 1. Set interrupt config
config := &graph.Config{
    InterruptBefore: []string{"human_approval_node"},
}

// 2. Run graph
// When execution reaches "human_approval_node", Invoke returns GraphInterrupt error
res, err := runnable.InvokeWithConfig(ctx, initialState, config)

// 3. Check interrupt
var interrupt *graph.GraphInterrupt
if errors.As(err, &interrupt) {
    fmt.Println("Graph paused, waiting for human approval...")
    // You can return ThreadID to frontend for user action
}

2. Dynamic Interrupt

Background & Functionality

Sometimes we cannot predict when an interrupt is needed. For example, only when Agent confidence is low, or potential sensitive content is detected, do we need human intervention. Dynamic interrupt allows nodes to decide whether to pause at runtime.

Implementation Principle

A node function can return an error of type graph.Interrupt. When the runtime catches this error, it immediately stops subsequent execution and saves the current state.

Code Showcase

func sensitiveActionNode(ctx context.Context, state interface{}) (interface{}, error) {
    data := state.(MyState)
    
    // Dynamic check: if amount > 1000, trigger interrupt
    if data.Amount > 1000 {
        return nil, graph.Interrupt("Amount too high, requesting human review")
    }
    
    // Otherwise execute directly
    return executeTransfer(data), nil
}

3. Resume Execution

Background & Functionality

Pausing is only for better continuation. After human approval or input, we need to resume graph execution. LangGraphGo allows you to resume execution with new input or modified state.

Implementation Principle

Use ResumeFrom config or directly call Invoke with the same ThreadID. The framework loads the latest Checkpoint. If there is a pending interrupt on this Checkpoint, the framework clears the interrupt flag and resumes execution of the paused node with optional new input.

Code Showcase

// User approved the action
// We update state (e.g., set Approved = true)
runnable.UpdateState(ctx, config, map[string]interface{}{"approved": true})

// Resume execution
// Note: No need to specify InterruptBefore again unless you want to interrupt again
resumeConfig := &graph.Config{
    ThreadID: "conversation-123",
}

// Call Invoke again, graph resumes from where it paused
runnable.Invoke(ctx, nil, resumeConfig)