LangGraph Shared State Tutorial: Control Context in Multi-Agent Systems

LangGraph Shared State Tutorial: Control Context in Multi-Agent Systems

LangGraph shared state is where a multi-agent workflow becomes predictable. A supervisor can choose the right specialist and still produce a weak answer if every worker receives the same noisy conversation history. The next step is deciding what each agent can read, what it may return, and what must remain private.

This tutorial continues the LangGraph multi-agent systems tutorial series. It builds on the supervisor architecture by defining a state contract: a small, explicit agreement about the data that moves through your graph.

What shared state means in LangGraph

In LangGraph, state is the current snapshot of an application. Nodes read that snapshot, do their work, and return updates. The graph applies those updates using each field’s reducer. This is more useful than treating state as one large “memory” object: it makes the data flow inspectable and gives you a place to control ownership.

For a supervisor workflow, separate four ideas:

  • Shared state: facts needed by more than one node, such as the user request, task status, validated findings, and the final answer.
  • Worker-private context: scratch notes, intermediate reasoning, or tool output that is useful to one worker but should not leak into every later prompt.
  • Runtime context: dependencies and configuration supplied when the graph runs, such as a user ID, permissions, a database client, or a feature flag.
  • Long-term memory: information intentionally retained across conversations. It is not a substitute for the short-lived state of one workflow.

Design a LangGraph shared state contract

Start with the smallest schema that lets downstream nodes do their jobs. Store raw facts, not preformatted prompt text. A research worker might need the task and a short brief; an analysis worker may need only approved findings. Neither needs every message, tool trace, or scratchpad from the rest of the graph.

import operator
from typing import Annotated

from langgraph.graph import MessagesState


class ResearchState(MessagesState):
    task: str
    research_brief: str
    findings: Annotated[list[str], operator.add]
    analysis: str
    final_answer: str

MessagesState provides a message list with LangGraph’s message-aware reducer. The findings field uses operator.add, so multiple updates are appended instead of one worker silently replacing another worker’s work. Fields without a reducer are replaced by the newest update, which is often the right behaviour for a single status or final answer.

Return partial updates; do not mutate the snapshot

A node should return only the fields it changes. That makes each transition easier to test and avoids accidental coupling between workers.

def research_worker(state: ResearchState) -> dict:
    source = "LangGraph state is a graph-level snapshot."
    new_findings = list((source,))
    return {"findings": new_findings}


def analysis_worker(state: ResearchState) -> dict:
    summary = " ".join(state["findings"])
    return {"analysis": f"Approved findings: {summary}"}

Do not append directly to state["findings"] and then return the whole object. Treat the incoming state as a snapshot. Returning a small update lets LangGraph apply the reducer consistently, and it makes parallel branches safer to reason about.

LangGraph shared state update diagram showing a node returning a partial update which a reducer merges into the next state snapshot.
Nodes return small updates. Reducers define how LangGraph merges them into the next state snapshot.

Give each worker a narrow view of context

Shared state is not a reason to give every agent unrestricted access. A useful design is to create a small adapter for each worker: it reads the graph state, selects the fields that matter, and builds a concise worker input.

def research_input(state: ResearchState) -> str:
    return (
        f"Task: {state['task']}\n"
        f"Brief: {state['research_brief']}\n"
        "Return concise, evidence-backed findings."
    )


def analysis_input(state: ResearchState) -> str:
    findings = "\n".join(f"- {item}" for item in state["findings"])
    return (
        f"Task: {state['task']}\n"
        f"Approved findings:\n{findings}\n"
        "Explain the implications without adding unsupported claims."
    )

This boundary improves more than token use. It reduces the chance that a worker mistakes irrelevant history for instruction, prevents internal tool noise from contaminating another prompt, and makes it clear which facts are trusted enough to pass forward.

Separate shared, private, and runtime data

Use LangGraph shared state for workflow facts that must survive from one graph step to the next. Keep private, node-specific work out of the shared contract unless another node genuinely needs it. Pass infrastructure dependencies and per-run configuration through runtime context rather than storing them in state.

For example, a database connection is runtime context; a user’s request is graph state; a reusable preference deliberately saved for future sessions belongs in long-term memory. Keeping those lifetimes separate prevents a graph from becoming an unstructured container of everything your application happens to know.

Common state-design mistakes

  • Passing full chat history to every worker. Start with the smallest useful slice, then add context only when a worker demonstrably needs it.
  • Saving rendered prompts in state. Save raw task data and format it inside the node that uses it.
  • Using one mutable “context” dictionary. Prefer named, typed fields with explicit reducers and owners.
  • Persisting secrets or clients in graph state. Keep credentials, service clients, and permissions in runtime context.
  • Allowing parallel branches to overwrite one field. Use a reducer when several branches must contribute values; otherwise design the graph so there is one clear writer.

Test the LangGraph shared state contract

Before adding routing or parallel execution, test the state contract in isolation. Invoke each node with a minimal state snapshot and assert only the intended fields change. Then test a two-worker run and inspect the final state. If you cannot explain where a field came from, which reducer merged it, and which worker can read it, the contract needs to be smaller.

The official LangGraph Graph API guide explains state schemas and reducers in depth. The context-engineering guide is also useful when deciding what belongs in model context, tool context, and the agent lifecycle.

Next: route work without losing control

With a clear state contract, the supervisor can make routing decisions from reliable inputs and workers can return structured updates. The next tutorial covers routing and parallel execution: choosing the right worker, running independent branches safely, and combining their results.

For the previous implementation step, see LangGraph Supervisor Tutorial: Build a Multi-Agent System.

Leave a Comment

Are you human? Please solve:Captcha


Alpesh Kumar
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.