State Management

State is the core of LangGraphGo. Unlike many stateless orchestration frameworks, LangGraphGo explicitly defines and manages state. Understanding State Schema and Reducers is key to building complex graphs.

1. State Schema & Reducers

Background & Functionality

In a graph, multiple nodes might run in parallel and try to update state. If simply overwriting, it leads to race conditions and data loss. Reducers define how to safely merge updates from different nodes.

For example, for chat history, we usually want to append new messages (Append), rather than overwrite old messages (Overwrite).

Implementation Principle

State Schema is a Map defining field names and corresponding merge functions (Reducers). When the runtime receives output from a node, it looks up the corresponding Reducer to handle the update.

Code Showcase

// Define Schema
schema := &graph.StateSchema{
    "messages": graph.AppendReducer, // Message list: Append mode
    "summary":  graph.OverwriteReducer, // Summary: Overwrite mode
    "count": func(old, new interface{}) interface{} { // Custom: Accumulate mode
        return old.(int) + new.(int)
    },
}

// Apply Schema to Graph
g.SetSchema(schema)

2. Ephemeral Channels

Background & Functionality

Not all state needs to be persisted. Sometimes you need to pass a temporary signal or error message that is valid only for the current step and should disappear in the next. This is similar to local variables in programming languages.

Implementation Principle

LangGraphGo provides `EphemeralReducer`. Fields using this Reducer are automatically cleared at the end of each Superstep.

Code Showcase

// Register ephemeral field
schema.RegisterReducer("error_signal", graph.EphemeralReducer)

// Node A sends error
return map[string]interface{}{"error_signal": "something went wrong"}, nil

// Node B (in same step or immediate next step) can read error_signal
// But in subsequent steps, error_signal will be empty

3. Smart Messages

Background & Functionality

When handling chat history, simple appending is not always enough. For example, when an Agent regenerates a reply, we want to replace the previous old reply instead of appending a new one. Or, we want to update the content of a historical message.

Implementation Principle

`MessagesStateGraph` comes with a smart `AddMessages` Reducer. It merges based on message ID: if the new message ID already exists, it updates the old message (Upsert); if not, it appends.

Code Showcase

// Use prebuilt MessagesStateGraph
g := graph.NewMessagesStateGraph()

// First generation
g.AddNode("ai", func(...) {
    return []llms.MessageContent{{ID: "msg_1", Content: "Thinking..."}}, nil
})

// Second generation (Update)
g.AddNode("ai_update", func(...) {
    // ID is same, content will be replaced
    return []llms.MessageContent{{ID: "msg_1", Content: "The answer is 42."}}, nil
})

4. Command API

Background & Functionality

Usually, control flow is defined by graph edges. But in some complex scenarios, nodes might need finer-grained control, such as "update state and immediately jump to node X", or "update state and end graph".

Implementation Principle

A node can return a special `graph.Command` object. The runtime will prioritize instructions in this object (such as `Goto`, `Update`), overriding default routing logic.

Code Showcase

func myNode(ctx context.Context, state interface{}) (interface{}, error) {
    // ... Logic check ...
    
    // Dynamic jump
    return graph.Command{
        Goto: "node_c", // Force jump to node_c
        Update: map[string]interface{}{"key": "value"}, // Update state simultaneously
    }, nil
}