Hello everyone. I’ve been building a fairly complex agent workflow with LangGraph and have hit a persistent state issue that’s been driving me a bit batty for the last two days. I’m hoping someone with a deeper dive into the library’s internals can point out my likely oversight.
In essence, my graph’s `State` seems to be carrying over between completely separate invocations of `graph.invoke()`. I’ve designed this as a request-scoped service, so each new user interaction should start with a fresh state. Instead, I’m finding conversation history and tool call results from a previous session appearing in a brand new session. This suggests state is being stored in the graph object itself, but I was under the impression that `invoke` created a new execution context.
Here’s a simplified version of my graph construction:
```python
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
import operator
class State(TypedDict):
messages: Annotated[list, add_messages]
user_query: str
extracted_data: dict
def node_processor(state: State):
# ... processes state, uses tools
return {"extracted_data": {"new_key": "value"}}
builder = StateGraph(State)
builder.add_node("process", node_processor)
builder.set_entry_point("process")
builder.add_edge("process", END)
graph = builder.compile()
# First invocation
result1 = graph.invoke({"user_query": "First query", "messages": []})
print(f"Run 1 extracted_data: {result1['extracted_data']}")
# Second, intended-to-be-fresh invocation
result2 = graph.invoke({"user_query": "Second query", "messages": []})
print(f"Run 2 extracted_data: {result2['extracted_data']}")
```
In my actual code, `node_processor` modifies state, including the `messages` list. The issue is that when I inspect `result2`, its `extracted_data` sometimes contains data that was only generated during `result1`. The `messages` list also seems to accumulate.
My core question: **Is the compiled `Graph` object inherently stateful across `invoke` calls if your nodes modify the state in-place?** Or should each `invoke` truly start from the input state provided, implying my bug is elsewhere?
I’ve checked for obvious culprits:
* I'm not using a `MemorySaver` for persistence (this is purely in-memory).
* I'm not accidentally reusing a mutable default argument in any function.
* The `State` definition uses `add_messages` for the list, which I know is designed for aggregation. Could this be the root cause? Does `add_messages` have some static registry?
Any insights into the intended lifecycle of the `State` object would be immensely helpful. I’m trying to adhere to best practices for a clean, stateless service pattern, and this has me stumped.
—Felix
Yep, that's a classic one. Your suspicion about state living in the graph object is on point. When you define your State class and `add_messages` function, that becomes the schema for the graph's memory.
The key is that `graph.invoke()` starts from whatever you pass as the *initial* state. If you don't pass a fresh one, it might default to or retain values. Are you calling it like `graph.invoke({"messages": [], "user_query": "..."})` for each new session? If you're just passing `{"user_query": "..."}`, the `messages` and `extracted_data` keys could be hanging around from the previous run's final state.
Could you show how you're actually calling `invoke`? That's usually where the persistence sneaks in.
✌️
Missing the last line of your node_processor return is a red flag. That snippet cuts off, but if you're accidentally returning the full mutated state dictionary instead of just the delta, you'll perpetuate everything.
Check what you're actually returning. The graph merges your return with the existing state. If your node returns `{"messages": [...], "extracted_data": {...}}` with old data still in it, that gets merged back.
Also, look at any checkpoints or persistence config you might have set globally. That's another way state hangs around.
Run it yourself.