<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Korey M. Stafford on Medium]]></title>
        <description><![CDATA[Stories by Korey M. Stafford on Medium]]></description>
        <link>https://medium.com/@koreymstafford?source=rss-d9f163361a74------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*4gMLrGzRYKO7ybxB.</url>
            <title>Stories by Korey M. Stafford on Medium</title>
            <link>https://medium.com/@koreymstafford?source=rss-d9f163361a74------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 17:56:01 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@koreymstafford/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[LangGraph State Basics]]></title>
            <link>https://medium.com/@koreymstafford/langgraph-state-basics-f2852b315849?source=rss-d9f163361a74------2</link>
            <guid isPermaLink="false">https://medium.com/p/f2852b315849</guid>
            <category><![CDATA[langchain]]></category>
            <category><![CDATA[agentic-ai]]></category>
            <category><![CDATA[langgraph]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Korey M. Stafford]]></dc:creator>
            <pubDate>Wed, 23 Jul 2025 14:45:37 GMT</pubDate>
            <atom:updated>2025-07-23T14:45:37.509Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article I’ll cover some of the basic concepts of LangGraph’s State, including:</p><ul><li><strong>Parallel Execution</strong></li><li><strong>Reducer Functions</strong></li><li><strong>State Schema and Channels</strong></li><li><strong>The Messages Channel</strong></li><li><strong>State Types: Pydantic, Dataclasses, TypedDict</strong></li></ul><p>So, what exactly is <em>State</em>? LangGraph defines it as:</p><p><em>“A shared data structure that represents the current snapshot of your application. It can be any Python type, but is typically a TypedDict or Pydantic BaseModel.”</em></p><p>To expand on this: <strong>State</strong> is the mechanism you use as a developer to track what your agents are doing. More specifically, it’s how you observe and control the evolution of data throughout your graph execution.</p><p>Now, lets cover a few basics.</p><h3>Parallel Execution</h3><p>We don’t need to address it in too much detail here, but it is very easy to create a graph where two nodes are executed in parallel, for instance the following code snippet has two nodes, a and b, which are both executed at the same time.</p><p>When nodes are executed in parallel, issues arise when both nodes try to update the State at the same time.</p><pre>from typing_extensions import TypedDict, Annotated<br>from langgraph.graph import START, StateGraph<br><br>class State(TypedDict):<br>    node_calls: list = []<br><br>builder = StateGraph(State)<br><br>def node_a(state: State):<br>    return {&quot;node_calls&quot;: [&#39;node_a&#39;]}<br><br>def node_b(state: State):<br>    return {&quot;node_calls&quot;: [&#39;node_b&#39;]}<br><br>builder.add_node(&quot;a&quot;, node_a)<br>builder.add_node(&quot;b&quot;, node_b)<br><br>builder.add_edge(START, &quot;a&quot;)<br>builder.add_edge(START, &quot;b&quot;)<br><br>graph = builder.compile()<br><br>graph_response = graph.invoke({&quot;input&quot;:&quot;10&quot;})</pre><p>If you run the above snippet you will get the following error:</p><pre>InvalidUpdateError: At key &#39;node_calls&#39;: Can receive only one value per step. Use an Annotated key to handle multiple values.<br>For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE_called&#39;: Can receive only one value per step. Use an Annotated key to handle multiple values.</pre><p>This is because, for the first step, which executes both nodes in parallel we are trying to write to the same property at the same time. Now in reality the functions which are nodes a and b actually don’t execute at the exact same time, but we consider them the same step in the graph execution, so we need to treat them as writing at the same time. This depicts the issue:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/930/1*LIUnNdbVbOBbxsXjNR2Sxg.png" /></figure><h3><strong>Reducer Functions</strong></h3><p>Reducer functions to the rescue! They are used to solve this problem, and they are used to tell LangGraph how you would like information added to the state in case of parallel execution. For example, you can do this:</p><pre>def reducer(input_list:list, list_to_add:list) -&gt; list:<br>    return input_list + list_to_add<br><br>class State(TypedDict):<br>    node_calls: Annotated[list, reducer]<br><br>builder = StateGraph(State)<br><br>def node_a(state: State):<br>    return {&quot;node_calls&quot;: [&#39;node_a&#39;]}<br><br>def node_b(state: State):<br>    return {&quot;node_calls&quot;: [&#39;node_b&#39;]}<br><br>builder.add_node(&quot;a&quot;, node_a)<br>builder.add_node(&quot;b&quot;, node_b)<br><br>builder.add_edge(START, &quot;a&quot;)<br>builder.add_edge(START, &quot;b&quot;)<br><br>graph = builder.compile()<br>graph_response = graph.invoke({&quot;input&quot;:&quot;10&quot;})<br>graph_response</pre><p>Which will print out this:</p><pre>{&#39;node_calls&#39;: [&#39;node_a&#39;, &#39;node_b&#39;]}</pre><p>To make this easier, you can use the add operator like so.</p><pre>class State(TypedDict):<br>    node_calls: Annotated[list, operator.add]</pre><h3>State Schema &amp; Channels</h3><p>The <strong>state schema</strong> defines the structure of the <strong>state object</strong> that your LangGraph workflow will use. Each schema consists of what LangGraph calls <strong>channels</strong>.</p><p><strong>Channels</strong> are the individual fields (or keys) in the state schema that can hold data. These channels can be <strong>read from or written to</strong> by nodes in your graph, depending on how you configure each node’s inputs and outputs.</p><p>This is simply the terminology LangGraph uses to describe common Python constructs. The <strong>state schema</strong> itself is typically defined using a TypedDict, dataclass, or Pydantic model.</p><p>In practical terms, <strong>channels are just the keys or attributes</strong> in those Python objects.</p><pre># this is the state schema<br>class State(TypedDict):<br>    # this is a channel called node_calls<br>    node_calls: Annotated[list, operator.add]</pre><h3>Messages Channel</h3><p>A common channel or key that is used in LangGraph State is the <strong>Messages</strong> channel. If you are unfamiliar with messages within LangChain/LangGraph, I suggest you take a look at this:</p><p><a href="https://python.langchain.com/docs/concepts/messages/">https://python.langchain.com/docs/concepts/messages/</a></p><p>This message will be used to store the messages from models output like so:</p><pre>class State(TypedDict):<br>    node_calls: Annotated[list, operator.add]<br>    messages: Annotated[list, operator.add]<br><br>builder = StateGraph(State)<br><br>def node_a(state: State):<br>    model_response = model.invoke(&quot;Is a channel in LangGraph State really just a key?&quot;)<br>    return {&quot;node_calls&quot;: [&#39;node_a&#39;], &quot;messages&quot;: [model_response]}<br><br>def node_b(state: State):<br>    model_response = model.invoke(&quot;Are reducer functions in LangGraph required because of the parallel execution in a super step?&quot;)<br>    return {&quot;node_calls&quot;: [&#39;node_b&#39;], &quot;messages&quot;: [model_response]}<br><br>builder.add_node(&quot;a&quot;, node_a)<br>builder.add_node(&quot;b&quot;, node_b)<br><br>builder.add_edge(START, &quot;a&quot;)<br>builder.add_edge(START, &quot;b&quot;)<br><br>graph = builder.compile()<br>graph_response = graph.invoke({&quot;input&quot;:&quot;10&quot;})<br>graph_response</pre><pre>{ &#39;messages&#39;: [ AIMessage(content=&#39;Yes, in LangGraph, a channel is essentially just a key in the state dictionary. When you define a channel with `add_node` or through other methods, you\&#39;re specifying a key that will be used to store and retrieve data in the graph\&#39;s state.\n\nFor example, when you define something like:\n\n```python\n@graph.node\ndef process_data(state):\n    # Do something with state[&quot;input_data&quot;]\n    return {&quot;processed_result&quot;: result}\n```\n\nThe &quot;processed_result&quot; is a channel (key) in the state dictionary. When you connect nodes together, you\&#39;re establishing a flow of data where the output channel from one node becomes the input channel for another node.\n\nThe channel concept helps with organizing the flow of data through the graph, but fundamentally it\&#39;s implemented as a key in the state dictionary that gets passed between nodes in the execution flow.&#39;, additional_kwargs={}, response_metadata={&#39;id&#39;: &#39;msg_01XX8c1ybrB3xxy69ipaGP3k&#39;, &#39;model&#39;: &#39;claude-3-7-sonnet-20250219&#39;, &#39;stop_reason&#39;: &#39;end_turn&#39;, &#39;stop_sequence&#39;: None, &#39;usage&#39;: {&#39;cache_creation_input_tokens&#39;: 0, &#39;cache_read_input_tokens&#39;: 0, &#39;input_tokens&#39;: 20, &#39;output_tokens&#39;: 193, &#39;server_tool_use&#39;: None, &#39;service_tier&#39;: &#39;standard&#39;}, &#39;model_name&#39;: &#39;claude-3-7-sonnet-20250219&#39;}, id=&#39;run--f46501a6-ff96-4a5b-88d7-b02d47871175-0&#39;, usage_metadata={&#39;input_tokens&#39;: 20, &#39;output_tokens&#39;: 193, &#39;total_tokens&#39;: 213, &#39;input_token_details&#39;: {&#39;cache_read&#39;: 0, &#39;cache_creation&#39;: 0}}),<br>                AIMessage(content=&#39;# Reducer Functions in LangGraph and Parallel Execution\n\nYes, reducer functions in LangGraph are specifically designed to handle the challenge of aggregating results from parallel operations during a super step in the model\&#39;s Pregel-inspired execution pattern.\n\n## Why Reducers Are Needed\n\nWhen a node in a LangGraph workflow fans out to process multiple branches in parallel (such as processing multiple agents or documents simultaneously), you need a way to combine these parallel results back into a coherent state. This is precisely what reducer functions do.\n\nThe key reasons reducers are required:\n\n1. **Parallel Execution Aggregation**: They combine the outputs from parallel branches that execute during the same super step\n\n2. **State Management**: They ensure the graph\&#39;s state remains consistent after parallel operations\n\n3. **Data Transformation**: They transform multiple outputs into a format suitable for downstream processing\n\n## Example Use Case\n\nConsider a multi-agent system where you send the same query to several specialist agents in parallel:\n\n```python\n@graph(name=&quot;multi_agent_system&quot;)\ndef multi_agent_workflow():\n    query = MessageState()\n    \n    # Fan out to multiple agents in parallel\n    agent_responses = query.map(process_with_agents)\n    \n    # Need a reducer to combine all agent responses\n    final_answer = agent_responses.reduce(combine_agent_responses)\n    \n    return final_answer\n```\n\nWithout a reducer, there would be no defined way to reconcile the multiple parallel outputs back into a single coherent state for the workflow to continue.\n\n## Reducer Implementation\n\nA reducer typically takes a list of outputs from parallel branches and returns a single combined result:\n\n```python\ndef combine_agent_responses(responses: List[str]) -&gt; str:\n    &quot;&quot;&quot;Combine multiple agent responses into a single coherent answer.&quot;&quot;&quot;\n    # Logic to synthesize, prioritize, or otherwise combine responses\n    return synthesized_answer\n```\n\nThis pattern is fundamental to the Pregel computational model that LangGraph builds upon for distributed processing of graph-structured workflows.&#39;, additional_kwargs={}, response_metadata={&#39;id&#39;: &#39;msg_012FKSFKLU4KQjk6XUtSiCVL&#39;, &#39;model&#39;: &#39;claude-3-7-sonnet-20250219&#39;, &#39;stop_reason&#39;: &#39;end_turn&#39;, &#39;stop_sequence&#39;: None, &#39;usage&#39;: {&#39;cache_creation_input_tokens&#39;: 0, &#39;cache_read_input_tokens&#39;: 0, &#39;input_tokens&#39;: 25, &#39;output_tokens&#39;: 453, &#39;server_tool_use&#39;: None, &#39;service_tier&#39;: &#39;standard&#39;}, &#39;model_name&#39;: &#39;claude-3-7-sonnet-20250219&#39;}, id=&#39;run--3d261f69-b45c-42c5-99b6-b3a184eed049-0&#39;, usage_metadata={&#39;input_tokens&#39;: 25, &#39;output_tokens&#39;: 453, &#39;total_tokens&#39;: 478, &#39;input_token_details&#39;: {&#39;cache_read&#39;: 0, &#39;cache_creation&#39;: 0}})],<br>  &#39;node_calls&#39;: [&#39;node_a&#39;, &#39;node_b&#39;]}</pre><p>LangGraph has provided an easier way to do this than manually adding a channel and a reducer function. You can do this instead:</p><pre>from langgraph.graph import MessagesState<br><br>class State(TypedDict, MessagesState):<br>    node_calls: Annotated[list, operator.add]<br><br>builder = StateGraph(State)<br><br>def node_a(state: State):<br>    model_response = model.invoke(&quot;Is a channel in LangGraph State really just a key?&quot;)<br>    return {&quot;node_calls&quot;: [&#39;node_a&#39;], &quot;messages&quot;: [model_response]}<br><br>def node_b(state: State):<br>    model_response = model.invoke(&quot;Are reducer functions in LangGraph required because of the parallel execution in a super step?&quot;)<br>    return {&quot;node_calls&quot;: [&#39;node_b&#39;], &quot;messages&quot;: [model_response]}<br><br>builder.add_node(&quot;a&quot;, node_a)<br>builder.add_node(&quot;b&quot;, node_b)<br><br>builder.add_edge(START, &quot;a&quot;)<br>builder.add_edge(START, &quot;b&quot;)<br><br>graph = builder.compile()<br>graph_response = graph.invoke({&quot;input&quot;:&quot;10&quot;})</pre><h3><strong>State Types: Pydantic, Dataclasses, TypedDict</strong></h3><p>You can inherit from Pydantic models to form your State class. This is helpful if you want to use features built in to Pydantic like data validation. For instance:</p><pre>class State(BaseModel):<br>    input: str<br>    node_calls: Annotated[list, operator.add]<br><br>builder = StateGraph(State)<br><br>def node_a(state: State):<br>    model_response = model.invoke(&quot;Is a channel in LangGraph State really just a key?&quot;)<br>    return {&quot;node_calls&quot;: [&#39;node_a&#39;], &quot;messages&quot;: [model_response]}<br><br>def node_b(state: State):<br>    model_response = model.invoke(&quot;Are reducer functions in LangGraph required because of the parallel execution in a super step?&quot;)<br>    return {&quot;node_calls&quot;: [&#39;node_b&#39;], &quot;messages&quot;: [model_response]}<br><br>builder.add_node(&quot;a&quot;, node_a)<br>builder.add_node(&quot;b&quot;, node_b)<br><br>builder.add_edge(START, &quot;a&quot;)<br>builder.add_edge(START, &quot;b&quot;)<br><br>graph = builder.compile()<br>graph_response = graph.invoke({&quot;input&quot;:&quot;10&quot;,&quot;node_calls&quot;:[]})<br>pprint.pprint(graph_response)</pre><pre>{&#39;input&#39;: &#39;10&#39;, &#39;node_calls&#39;: [&#39;node_a&#39;, &#39;node_b&#39;]}</pre><p>But, if I pass 10 as a int — I’ll get a error:</p><pre>graph_response = graph.invoke({&quot;input&quot;:10,&quot;node_calls&quot;:[]})</pre><pre>ValidationError: 1 validation error for State<br>input<br>  Input should be a valid string [type=string_type, input_value=10, input_type=int]<br>    For further information visit https://errors.pydantic.dev/2.11/v/string_type<br>Before task with name &#39;a&#39; and path &#39;(&#39;__pregel_pull&#39;, &#39;a&#39;)&#39;</pre><p>Now, dealing with messages is a little bit different with Pydantic, you can’t use the MessageState class shown above, it is not compatible at least with Pydantic’s BaseModel. There are a few different ways to deal with this, but one way is to just create an annotated list and use AnyMessage, with the out of box reducer for messages LangGraph gives you, add_mesages.</p><pre>from langchain_core.messages import AnyMessage<br>from langgraph.graph.message import add_messages<br><br>class State(BaseModel):<br>    input: str<br>    messages: Annotated[list[AnyMessage], add_messages]<br>    node_calls: Annotated[list, operator.add]<br><br>builder = StateGraph(State)<br><br>def node_a(state: State):<br>    model_response = model.invoke(&quot;Is a channel in LangGraph State really just a key?&quot;)<br>    return {&quot;node_calls&quot;: [&#39;node_a&#39;], &quot;messages&quot;: [model_response]}<br><br>def node_b(state: State):<br>    model_response = model.invoke(&quot;Are reducer functions in LangGraph required because of the parallel execution in a super step?&quot;)<br>    return {&quot;node_calls&quot;: [&#39;node_b&#39;], &quot;messages&quot;: [model_response]}<br><br>builder.add_node(&quot;a&quot;, node_a)<br>builder.add_node(&quot;b&quot;, node_b)<br><br>builder.add_edge(START, &quot;a&quot;)<br>builder.add_edge(START, &quot;b&quot;)<br><br>graph = builder.compile()<br>graph_response = graph.invoke({&quot;input&quot;:&quot;10&quot;,&quot;node_calls&quot;:[]})<br>pprint.pprint(graph_response)</pre><pre>{&#39;input&#39;: &#39;10&#39;,<br> &#39;messages&#39;: [AIMessage(content=&#39;Yes, in LangGraph, a channel in the State object is essentially just a key in a dictionary-like structure.\n\nWhen you define a State class with channels, like:\n\n```python\nclass MyState(State):\n    my_channel: list[str] = []\n    another_channel: str = &quot;&quot;\n```\n\nYou\&#39;re basically creating a structured dictionary where &quot;my_channel&quot; and &quot;another_channel&quot; are keys that can be accessed to retrieve or update values.\n\nThe State object provides a convenient API to:\n1. Access these channels (via dot notation or dict-like syntax)\n2. Update them in an immutable manner with methods like `set`\n3. Keep track of changes in a way that works with LangGraph\&#39;s state management\n\nSo while channels provide structure and type hints for your workflow state, at their core, they are indeed just keys in a state dictionary that LangGraph manages for you.&#39;, additional_kwargs={}, response_metadata={&#39;id&#39;: &#39;msg_019hKgST9BoEUB5ELq5KY4BT&#39;, &#39;model&#39;: &#39;claude-3-7-sonnet-20250219&#39;, &#39;stop_reason&#39;: &#39;end_turn&#39;, &#39;stop_sequence&#39;: None, &#39;usage&#39;: {&#39;cache_creation_input_tokens&#39;: 0, &#39;cache_read_input_tokens&#39;: 0, &#39;input_tokens&#39;: 20, &#39;output_tokens&#39;: 201, &#39;server_tool_use&#39;: None, &#39;service_tier&#39;: &#39;standard&#39;}, &#39;model_name&#39;: &#39;claude-3-7-sonnet-20250219&#39;}, id=&#39;run--21b1f29b-d0bb-4145-b171-f0b5363e199a-0&#39;, usage_metadata={&#39;input_tokens&#39;: 20, &#39;output_tokens&#39;: 201, &#39;total_tokens&#39;: 221, &#39;input_token_details&#39;: {&#39;cache_read&#39;: 0, &#39;cache_creation&#39;: 0}}),<br>              AIMessage(content=&quot;## Are Reducer Functions in LangGraph Required Due to Parallel Execution?\n\nYes, that&#39;s exactly right. Reducer functions in LangGraph are primarily needed because of the parallel execution model in supersteps.\n\nIn LangGraph&#39;s implementation of the Bulk Synchronous Parallel (BSP) model:\n\n1. During a superstep, multiple operations can execute in parallel\n2. These parallel operations all work on their own separate copies of state\n3. After the parallel execution phase, the results need to be combined back together\n\nThe reducer function serves this critical role of merging the results from parallel executions into a consistent state. Without reducers, there would be no defined way to reconcile potentially conflicting state changes from parallel operations.\n\nThis is particularly important for:\n- Graph operations where multiple nodes might update simultaneously\n- Maintaining consistency when multiple agents operate independently\n- Ensuring deterministic behavior despite parallel execution\n\nIf you were using LangGraph without parallel execution, the reducer functions would be much less important since there wouldn&#39;t be multiple state versions to combine.&quot;, additional_kwargs={}, response_metadata={&#39;id&#39;: &#39;msg_015kGCaGmJNjCnGA3uisw9qU&#39;, &#39;model&#39;: &#39;claude-3-7-sonnet-20250219&#39;, &#39;stop_reason&#39;: &#39;end_turn&#39;, &#39;stop_sequence&#39;: None, &#39;usage&#39;: {&#39;cache_creation_input_tokens&#39;: 0, &#39;cache_read_input_tokens&#39;: 0, &#39;input_tokens&#39;: 25, &#39;output_tokens&#39;: 230, &#39;server_tool_use&#39;: None, &#39;service_tier&#39;: &#39;standard&#39;}, &#39;model_name&#39;: &#39;claude-3-7-sonnet-20250219&#39;}, id=&#39;run--5833a444-80f2-4f2c-b57a-7a802a01ab5d-0&#39;, usage_metadata={&#39;input_tokens&#39;: 25, &#39;output_tokens&#39;: 230, &#39;total_tokens&#39;: 255, &#39;input_token_details&#39;: {&#39;cache_read&#39;: 0, &#39;cache_creation&#39;: 0}})],<br> &#39;node_calls&#39;: [&#39;node_a&#39;, &#39;node_b&#39;]}</pre><h3>Dataclasses</h3><p>You can also use Python built in dataclasses. They are faster than Pydantic, but won’t perform the same type of data validation. For instance:</p><pre>import dataclasses<br><br>@dataclasses.dataclass<br>class State():<br>    input: str<br>    messages: Annotated[list[AnyMessage], add_messages]<br>    node_calls: Annotated[list, operator.add]<br><br>&quot;&quot;&quot;<br>Graph building code<br>&quot;&quot;&quot;<br><br>graph_response = graph.invoke({&quot;input&quot;:10,&quot;node_calls&quot;:[]})<br>pprint.pprint(graph_response)</pre><p>This works, it will return the same result as above, but you can see that input is passed as an int, when we’ve used the str type hint above.</p><p>Well, that covers the basics of State. I’ll post a few more articles which will delve deeper into state.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f2852b315849" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>