Memory & Time Travel

A core differentiator of LangGraphGo is its deep support for state persistence. This not only enables basic "memory" capabilities but also unlocks powerful "time travel" features, allowing developers to manipulate application history just like Git.

1. Memory

Background & Functionality

Stateless LLMs cannot remember context. To build chatbots or multi-turn applications, we need to persist state between turns. LangGraphGo offloads this responsibility from the application layer to the framework layer. You don't need to manually manage database reads and writes, just configure a Checkpointer.

Implementation Principle

Checkpointer is an interface responsible for saving and loading Checkpoints. A Checkpoint is a complete state snapshot of the graph at the end of a specific step (Superstep). Every time the state updates, LangGraphGo automatically creates a new Checkpoint and saves it to the underlying storage (e.g., Postgres, Redis, SQLite).

Using ThreadID, we can associate a series of Checkpoints with the same conversation thread.

Code Showcase

// 1. Initialize Checkpointer (using Memory as example, use Postgres/Redis for production)
checkpointer := memory.NewSaver()

// 2. Enable Checkpointer when compiling graph
runnable, _ := g.Compile(graph.WithCheckpointer(checkpointer))

// 3. Specify ThreadID when invoking
config := &graph.Config{
    ThreadID: "conversation-123",
}

// First conversation
runnable.Invoke(ctx, "Hello", config)

// Second conversation, framework automatically loads previous state (memory)
runnable.Invoke(ctx, "My name is Bob", config)

2. Time Travel

Background & Functionality

Since we save a snapshot at every step, we can "travel back in time" at any moment. This is valuable for debugging (reproducing errors), human intervention (correcting intermediate steps), and exploratory execution (trying different branches).

Implementation Principle

Time travel is essentially state loading based on Checkpoint ID. When you specify a historical CheckpointID, LangGraphGo loads the state at that moment instead of the latest state. If you continue execution from that state, the system creates a new branch (Fork), keeping the original history intact.

Code Showcase: Viewing History

// List all historical Checkpoints for this thread
checkpoints, _ := runnable.ListCheckpoints(ctx, config)

for _, cp := range checkpoints {
    fmt.Printf("Step: %d, ID: %s, Time: %s\n", 
        cp.Metadata["step"], cp.ID, cp.Timestamp)
    // You can inspect the state data at that time
    // fmt.Println(cp.State)
}

Code Showcase: Forking

// Suppose we want to go back to the second to last step
pastCheckpoint := checkpoints[1]

forkConfig := &graph.Config{
    ThreadID: "conversation-123",
    CheckpointID: pastCheckpoint.ID, // Specify ID to restore
}

// Continue execution from past state, this will create a new branch
runnable.Invoke(ctx, "Let's try a different approach", forkConfig)

Code Showcase: Modifying History (UpdateState)

Going further, you can even modify past states. This is useful in human intervention scenarios, for example, when a user corrects an Agent's wrong memory at a certain step.

// Suppose we found the Agent remembered the wrong name at a Checkpoint
// We manually update the state
runnable.UpdateState(ctx, config, map[string]interface{}{
    "user_name": "Alice", // Correct the name
})

// If config contains CheckpointID, it modifies history;
// If not, it modifies the current latest state.
// After modification, the next Invoke will run based on this corrected state.