{
  "type": "new-runtime-knowledge-guide",
  "version": 1,
  "generated_at": "2026-07-23",
  "volume": 21,
  "slug": "langchain-langgraph-reference",
  "title": "LangChain & LangGraph Under the Hood",
  "description": "This volume is not about “which framework is more fashionable,” but about the modern architecture of the LangChain Inc. stack. In the current version, `LangChain` is a high level with models, messages, tools, middleware and fast `create_agent`, and `LangGraph` is a low-level runtime for stateful agents: `State`, `Nodes`, `Edges`, `Command`, `thread_id`, `checkpoints`, `interrupts`, replay and long-running execution. Below is how this is really connected and what exactly happens during startup.",
  "track": {
    "slug": "agent-systems",
    "title": "Agent Systems",
    "route": "https://newruntime.com/learn/tracks/agent-systems/",
    "position": 4
  },
  "counts": {
    "sections": 7,
    "cards": 32
  },
  "routes": {
    "html": "https://newruntime.com/learn/langchain-langgraph-reference/",
    "json": "https://newruntime.com/learn/langchain-langgraph-reference.json"
  },
  "sections": [
    {
      "number": "01",
      "title": "Where Are They On The Stack?",
      "intro": "",
      "cards": [
        {
          "number": "01",
          "title": "`LangChain` and `LangGraph` today are not competitors, but two layers",
          "tag": "Framing",
          "description": "Up-to-date documentation describes them as a linked stack. LangChain gives a high level: models, messages, tools, middleware, ready-made agent and standard interfaces. LangGraph sits below and is responsible for orchestration runtime: state, transitions, persistence, pauses, renewal and long-lived workflow.",
          "blocks": [
            {
              "kind": "code",
              "label": "Diagram",
              "language": "text",
              "value": "Contemporary Mental Model\n\napp code\n  ↓\nLangChain = comfortable high-level primitives\n  ↓\nLangGraph = graph runtime and orchestration\n  ↓\nmodel / tools / stores / external APIs\n\nLangChain often uses LangGraph under the hood rather than replacing it."
            }
          ]
        },
        {
          "number": "02",
          "title": "Current stack recommendation: Start from above, drop lower in pain",
          "tag": "Framing",
          "description": "The official position is simple: if you want a quick start and a typical agent cycle, it’s wise to start with ‘LangChain.create agent’. If the orchestration becomes non-standard, there are custom branches, hand hops, pauses, replay and a complex state, then they descend on the LangGraph.",
          "blocks": [
            {
              "kind": "list",
              "label": "briefly",
              "items": [
                "Quickly assemble the agent LangChain",
                "Custom runtime by LangGraph",
                "Both are often used in the same project."
              ]
            }
          ]
        },
        {
          "number": "03",
          "title": "It is important to read old materials with revision",
          "tag": "Framing",
          "description": "There are a lot of old tutorials around LangChain. In them you can find \"Chain\", \"AgentExecutor\", old memory classes, LCEL-first style, and LangGraph - the former \"create react agent\". In the current stack, the emphasis has shifted towards 'create agent' in LangChain and low-level graph runtime in LangGraph.",
          "blocks": [
            {
              "kind": "code",
              "label": "What it does it mean practically?",
              "language": "text",
              "value": "I found an old example on the blog.\nCheck what version it is on.\nDo not confuse old abstractions with the current recommended entry point.\nTake a look at agents/memory/graph examples"
            }
          ]
        },
        {
          "number": "04",
          "title": "The main boundary between them: “components” versus “execution”",
          "tag": "Framing",
          "description": "LangChain primarily provides standard components and convenient high-level entry points. ‘LangGraph’ primarily determines how long a state lives, which nodes run further, where checkpoints are placed, how interrupt/resume works, and how the graph goes along the steps.",
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "Question",
                "Who answers more often?"
              ],
              "rows": [
                [
                  "How do you call the model uniformly?",
                  "LangChain"
                ],
                [
                  "How to describe tool and schema?",
                  "LangChain"
                ],
                [
                  "How do you keep the state step by step?",
                  "LangGraph"
                ],
                [
                  "How to pause, resume and replay an agent?",
                  "LangGraph"
                ]
              ]
            }
          ]
        }
      ]
    },
    {
      "number": "02",
      "title": "LangChain: High level",
      "intro": "",
      "cards": [
        {
          "number": "05",
          "title": "In LangChain, the basic unit of context is ‘messages’.",
          "tag": "LangChain",
          "description": "Modern LangChain relies on a list of messages rather than one giant prompt-string. Key types: SystemMessage, HumanMessage, AIMessage, ToolMessage. This is important because tool-calling, memory, streaming, and structured output in an agent live around message history.",
          "blocks": [
            {
              "kind": "code",
              "label": "Diagram",
              "language": "text",
              "value": "MESSAGE HISTORY\n\nsystem: \"You're the assistant...\"\nuser: \"Find flights\"\nai:     tool_call(search_flights, {...})\ntool: \"I found 3 options\"\nai: \"These are the best options...\"\n\nToolMessage is not just a log. This is part of a valid story that the model sees again."
            }
          ]
        },
        {
          "number": "06",
          "title": "Create agent() is the main high-level input",
          "tag": "LangChain",
          "description": "In the current LangChain, the type agent is assembled through ‘create agent(model=..., tools=..., system prompt=..., middleware=...)’. If the list of tools is empty, you get essentially one model node without a tool-calling loop. If tools are available, the agent begins to turn the model/tool cycle to a stop condition.",
          "blocks": [
            {
              "kind": "code",
              "label": "minimum",
              "language": "sql",
              "value": "from langchain.agents import create_agent\n\nagent = create_agent(\n    model=\"provider:model\",\n    tools=[search_docs, get_weather],\n    system_prompt=\"Be concise\",\n)\n\nresult = agent.invoke({\n    \"messages\": [{\"role\": \"user\", \"content\": \"What's going on?\"]\n})"
            }
          ]
        },
        {
          "number": "07",
          "title": "Tool in LangChain is callable with clear schema",
          "tag": "LangChain",
          "description": "Tool is most often created through '@tool'. The model doesn’t “see your Python,” it sees the tool name, description, and schema of arguments. When the agent decides to call the tool, LangChain parses the tool call, calls the Python function, and forms the result as ‘ToolMessage’.",
          "blocks": [
            {
              "kind": "code",
              "label": "typical",
              "language": "sql",
              "value": "from langchain.tools import tool\n\n@tool\ndef search_docs(query: str, limit: int = 3) -> str:\n    \"\"\"Search internal docs.\"\"\"\n    ...\n\nDocstring and signature become part of tool schema"
            }
          ]
        },
        {
          "number": "08",
          "title": "`ToolRuntime` opens access to state, context, store and config",
          "tag": "LangChain",
          "description": "Tools may not be pure functions, but runtime-aware. Through ToolRuntime, the tool receives the short-lived state of the current thread, immutable context of the call, long-term store, stream writer, config and tool call id. This makes the tool not just a utility, but a full member of the agent runtime.",
          "blocks": [
            {
              "kind": "code",
              "label": "signature",
              "language": "sql",
              "value": "from langchain.tools import tool, ToolRuntime\n\n@tool\ndef get_last_question(runtime: ToolRuntime) -> str:\n    messages = runtime.state[\"messages\"]\n    ...\n\nRuntime parameter does not show the model as an argument tool"
            }
          ]
        },
        {
          "number": "09",
          "title": "Structured output and middleware are two very strong points of LangChain.",
          "tag": "LangChain",
          "description": "Through ‘response format’, you can ask the agent to return the typed result. LangChain uses either a native provider structured output or a tool-calling strategy, and the result is structured response. Through middleware, you can dynamically change the model, filter tools, intercept tool errors and embed policy logic.",
          "blocks": [
            {
              "kind": "code",
              "label": "Diagram",
              "language": "text",
              "value": "Two important hooks\n\nresponse_format=MySchema\n  The agent collects the structured response\n  validate\n  Include structured response\n\nmiddleware=[...]\n  Up to model call\n  Around the tool call\n  Dynamic routing / error handling / policy"
            }
          ]
        }
      ]
    },
    {
      "number": "03",
      "title": "LangGraph: Low-level Runtime",
      "intro": "",
      "cards": [
        {
          "number": "10",
          "title": "The three primitives of LangGraph are “State”, “Nodes”, “Edges”.",
          "tag": "LangGraph",
          "description": "LangGraph simulates agent workflow as a graph. 'State' is the current image of the app. Nodes are functions that do something. Edges are the rules that determine which node to run next. Everything else grows out of these three: loops, branches, subgraphs, map-reduce and human-in-the-loop.",
          "blocks": [
            {
              "kind": "code",
              "label": "frame",
              "language": "sql",
              "value": "from langgraph.graph import StateGraph, START, END\n\nbuilder = StateGraph(State)\nbuilder.add_node(\"model\", model_node)\nbuilder.add_node(\"tools\", tool_node)\nbuilder.add_edge(START, \"model\")\nbuilder.add_edge(\"tools\", \"model\")\ngraph = builder.compile()"
            }
          ]
        },
        {
          "number": "11",
          "title": "“State” is a shared snapshot, not just a dict for convenience.",
          "tag": "LangGraph",
          "description": "The state in LangGraph is the central part of the performance. The nodes read the current state and return the update, which freezes back. Therefore, the state scheme should be designed consciously: what keys are, what reducers combine them, what does “message history” mean, which means “pending action”, where intermediate results lie.",
          "blocks": [
            {
              "kind": "list",
              "label": "state-key",
              "items": [
                "messages",
                "user_info",
                "plan",
                "approval",
                "Don't turn the state into a garbage dump."
              ]
            }
          ]
        },
        {
          "number": "12",
          "title": "Reducers are needed where updates should not be erased, but accumulated",
          "tag": "LangGraph",
          "description": "If the node returns a new piece of state, it must somehow be combined with the old one. Message history often uses a reducer like 'add messages', otherwise you run the risk of rewrite the entire story with one new message. That is, the reducer in LangGraph is a rule of merge, not a secondary technique.",
          "blocks": [
            {
              "kind": "code",
              "label": "typical",
              "language": "python",
              "value": "class State(TypedDict):\n    messages: Annotated[list, add_messages]\n\ndef node(state: State):\n    return {\"messages\": [(\"assistant\", \"next step\")]}"
            }
          ]
        },
        {
          "number": "13",
          "title": "“Command” means “update the state and jump on.”",
          "tag": "LangGraph",
          "description": "When one update is not enough, the node can return Command(update=..., goto=...) It is handoff, agent routing and human-in-the-loop resume. Important detail: Command does not cancel the already added static edges. If a node has a static edge and a “goto” return at the same time, the graph can go both ways.",
          "blocks": [
            {
              "kind": "code",
              "label": "example",
              "language": "sql",
              "value": "from langgraph.types import Command\n\ndef route_node(state: State) -> Command[Literal[\"search\", \"answer\"]]:\n    if needs_search(state):\n        return Command(goto=\"search\")\n    return Command(update={\"done\": True}, goto=\"answer\")"
            }
          ]
        },
        {
          "number": "14",
          "title": "Under the hood, LangGraph thinks in “supersteps” rather than “functional challenges.”",
          "tag": "LangGraph",
          "description": "LangGraph is inspired by the Pregel-like execution model. Nodes become active when they receive an incoming message/state update. Parallel branches can be executed within one super-step. When the node has no new inputs, it “votes to stop.” The count stops when all the nodes are inactive and nothing else flies over the ribs.",
          "blocks": [
            {
              "kind": "code",
              "label": "Diagram",
              "language": "text",
              "value": "SUPER-STEP MENTAL MODEL\n\nnode A active\n  Provides updates\n  Send management further\n\nNode B and C receive entry\n  Become active\n  They can run in one super-step.\n\nNo new messages.\n  Inactive nodes\n  graph completed"
            }
          ]
        }
      ]
    },
    {
      "number": "04",
      "title": "What Really Happens During Launch",
      "intro": "",
      "cards": [
        {
          "number": "15",
          "title": "'create agent' under the hood is a graph-based loop",
          "tag": "Runtime",
          "description": "When you call ‘agent.invoke(...)’, there is no ‘one magic call’. The agent moves on the graph: model node reads \"messages\", generates a response; if there is a tool call, management goes to tools node; tools are executed and return \"ToolMessage\"; then the graph again calls the model. This is done before the final answer or the iteration limit.",
          "blocks": [
            {
              "kind": "code",
              "label": "Diagram",
              "language": "text",
              "value": "Agent's type cycle\n\nuser message\n  ↓\nmodel node\n  ↓\nAIMessage\n  If you have tool calls, tools node\n  │                         ↓\n  │                     ToolMessage\n  │                         ↓\n  └────────────────────→ model node\n  ↓\nAIMessage without tool calls\n  ↓\nfinal output"
            }
          ]
        },
        {
          "number": "16",
          "title": "At the message level, a tool call is a strict handshake.",
          "tag": "Runtime",
          "description": "The model is not “itself calling Python.” It returns \"AIMessage\" with the tool request. LangChain reads this query, executes the desired function, and then adds \"ToolMessage\", which refers to the original \"tool call id\". The next model step receives an already extended message history and continues reasoning on its basis.",
          "blocks": [
            {
              "kind": "code",
              "label": "granularly",
              "language": "typescript",
              "value": "1. model node -> AIMessage(tool_calls=[...])\n2. runtime parsite tool calls\n3. calls Python function.\n4. wraps the result in ToolMessage(tool call id=...)\n5. ToolMessage added to state[\"messages\"]\n6. the next model node sees the tool result and decides what to do next"
            }
          ]
        },
        {
          "number": "17",
          "title": "What exactly “triggers the next step”",
          "tag": "Runtime",
          "description": "The next step is not cosine magic, but a change in the graph state and the logic of the ribs. If model node issues a tool call, runtime directs control to tools node. If tools node returns \"ToolMessage\", the graph activates the model node again. If the model returns the final AIMessage without the new tool calls, the agent completes the cycle.",
          "blocks": [
            {
              "kind": "list",
              "label": "stop / continue",
              "items": [
                "There is a tool calls to go to tools",
                "ToolMessage is back in model",
                "No tool calls can be completed.",
                "Iteration limit / runtime guards"
              ]
            }
          ]
        },
        {
          "number": "18",
          "title": "Structured output goes through runtime, not just parser at the end.",
          "tag": "Runtime",
          "description": "When you specify ‘response format’, LangChain either uses provider-native structured output or implements a schema through tool calling. The result is then validated and put into structured response. That is, the final contract is already embedded in the execution of the agent, rather than hanging a separate regex after the response of the model.",
          "blocks": [
            {
              "kind": "table",
              "headers": [
                "Strategy",
                "What's happening?"
              ],
              "rows": [
                [
                  "ProviderStrategy",
                  "Provider returns structured response"
                ],
                [
                  "ToolStrategy",
                  "structure is implemented through tool-calling mechanics"
                ],
                [
                  "Outcome",
                  "Validated object in 'structured response'"
                ]
              ]
            }
          ]
        },
        {
          "number": "19",
          "title": "Middleware is a model/tool runtime intervention point.",
          "tag": "Runtime",
          "description": "Through middleware, you can replace the model on the fly according to the dialog state, filter out available tools, wrap the tool call with custom error processing or policy logic. This is important because in a real project, an agent’s behavior is rarely determined by a prompt.",
          "blocks": [
            {
              "kind": "code",
              "label": "frequent use cases",
              "language": "python",
              "value": "if messages_too_long:\n    switch to stronger model\n\nif tool failed:\n    return friendly ToolMessage\n\nif user has no permission:\n    hide dangerous tools"
            }
          ]
        },
        {
          "number": "20",
          "title": "Streaming provides not only tokens, but also state updates",
          "tag": "Runtime",
          "description": "LangChain/LangGraph streaming isn’t just about printing text piece by piece. You can stream state updates by agent steps, token chunks from model and custom progress events from nodes or tools. Therefore, streaming is useful for both UX and debag.",
          "blocks": [
            {
              "kind": "code",
              "label": "major regimes",
              "language": "text",
              "value": "updates -> State deltas by step\nmessages -> tokens / message chunks + metadata\nTraditional Signals of Progress\ndebug -> maximum detailed flow (LangGraph)"
            }
          ]
        }
      ]
    },
    {
      "number": "05",
      "title": "Persistence, Threads and Human-In-The-Loop",
      "intro": "",
      "cards": [
        {
          "number": "21",
          "title": "`thread id` + checkpointer = memory and restart",
          "tag": "Persistence",
          "description": "When a graph is compiled with a checkpointer, LangGraph stores the checkpoint state on each super-step. All of these checkpoints belong to thread. Therefore, ‘thread id’ is not a decorative parameter, but a key from which runtime understands which story and current state to download.",
          "blocks": [
            {
              "kind": "code",
              "label": "Minimal logic",
              "language": "text",
              "value": "graph = builder.compile(checkpointer=checkpointer)\n\nconfig = {\"configurable\": {\"thread_id\": \"thread-42\"}}\ngraph.invoke(inputs, config=config)\n\nWithout thread id there is nothing to address the saved state"
            }
          ]
        },
        {
          "number": "22",
          "title": "Thread, checkpoint and state snapshot are three different entities.",
          "tag": "Persistence",
          "description": "Thread is the lifeline of a particular conversation or launch. Checkpoint is a snapshot of the state at a particular point in the line. “StateSnapshot” is the content of this image: values, metadata, next tasks and other runtime information. This distinction is important for replay, fork and debugging.",
          "blocks": [
            {
              "kind": "code",
              "label": "Diagram",
              "language": "text",
              "value": "A simple picture\n\nthread-42\n  ├─ checkpoint A\n  ├─ checkpoint B\n  ├─ checkpoint C\n  └─ current state\n\nYou can not only store the “last state”, but also go through the history of execution."
            }
          ]
        },
        {
          "number": "23",
          "title": "'interrupt()' really pauses the graph, and 'Command(resume=...)' really continues it.",
          "tag": "Persistence",
          "description": "Interrupt in LangGraph is a built-in execution pause mechanism for external input. When you call \"interrupt()\", the graph saves the state and returns the payload to \" interrupt  \". To continue, you need to call the graph again with the same \"thread id\" and transmit \"Command(resume=..)\". In this case, the node starts again from the beginning, and the resume value becomes the result of “interrupt()” inside the node code.",
          "blocks": [
            {
              "kind": "code",
              "label": "granularly",
              "language": "text",
              "value": "approved = interrupt(\"Approve?\")\n\nOutside:\nresult = graph.invoke(inputs, config=config)\nprint(result[\"__interrupt__\"])\n\ngraph.invoke(Command(resume=True), config=config)\n\nThe code before interrupt can be executed again, so the side effects before it must be idempotent."
            }
          ]
        },
        {
          "number": "24",
          "title": "Short-lived memory and long-lived memory are not the same thing.",
          "tag": "Persistence",
          "description": "Short-term memory usually lives in the graph state and is tied to thread. Long-term memory lives in a store and can experience conversations, sessions, and threads. This is important: the checkpointer stores the progress of a particular execution, and the store stores more stable user or application data.",
          "blocks": [
            {
              "kind": "list",
              "label": "responsibility",
              "items": [
                "state/messages → current conversation",
                "store/preferences → long-lived data",
                "Do not try to solve both problems by one entity."
              ]
            }
          ]
        }
      ]
    },
    {
      "number": "06",
      "title": "How they're usually combined",
      "intro": "",
      "cards": [
        {
          "number": "25",
          "title": "When one LangChain is enough",
          "tag": "Patterns",
          "description": "If you have a typical agent loop, a comprehensible tool list, a moderate amount of state, and no specific requirements for custom graph management, 'create agent' is usually sufficient. Especially if the goal is to quickly get a production-usable baseline without manual assembly runtime.",
          "blocks": [
            {
              "kind": "list",
              "label": "signal",
              "items": [
                "tool-calling assistant",
                "RAG + tools",
                "structured output agent",
                "We need a quick start."
              ]
            }
          ]
        },
        {
          "number": "26",
          "title": "When is the time to switch to LangGraph?",
          "tag": "Patterns",
          "description": "LangGraph is usually switched to when the standard \"model , tools\" cycle no longer describes a system. For example: many states and manual hops, multiple subagents, approval steps, time travel, long-running jobs, cycles with non-standard stop conditions, custom subgraphs and complex routing.",
          "blocks": [
            {
              "kind": "list",
              "label": "signal",
              "items": [
                "human-in-the-loop",
                "branching",
                "custom state machine",
                "long-lived"
              ]
            }
          ]
        },
        {
          "number": "27",
          "title": "Frequent production pattern: LangChain components within LangGraph nodes",
          "tag": "Patterns",
          "description": "In real systems, you don’t have to choose either-or. Often take 'init chat model', 'messages', tools, structured output and other LangChain amenities, but orchestration is written manually through LangGraph. That is, LangChain supplies building blocks, and LangGraph manages the life cycle and transitions.",
          "blocks": [
            {
              "kind": "code",
              "label": "Diagram",
              "language": "text",
              "value": "Hybrid assembly\n\nLangGraph node\n  ─ calls the LangChain model\n  Works with LangChain messages\n  Uses LangChain tools/schemas\n  Update returns to graph state\n\nThis is essentially the most natural way to use the stack together."
            }
          ]
        },
        {
          "number": "28",
          "title": "The Normal Way of Project Evolution",
          "tag": "Patterns",
          "description": "It’s almost always easier not to “design a perfect graph in advance,” but to go in layers. First, take 'create agent' and check the problem. Then take the policy out to middleware. Then, if this is not enough, build a custom “StateGraph”, leaving the LangChain components inside. This reduces the risk of overengineering.",
          "blocks": [
            {
              "kind": "code",
              "label": "evolution",
              "language": "sql",
              "value": "1. create_agent baseline\n2. middleware / response_format / memory\n3. see where high-level behavior is no longer enough\n4. take out orchestration in StateGraph\n5. LangChain remains a layer of components"
            }
          ]
        }
      ]
    },
    {
      "number": "07",
      "title": "Grabbles, Minimum Code and Output",
      "intro": "",
      "cards": [
        {
          "number": "29",
          "title": "Frequent rakes in LangChain/LangGraph",
          "tag": "Ops",
          "description": "The main problem here is not the AI, but the wrong mental model. People confuse old versions with new ones, think tool call is the magic of the model, don't pass 'thread id', put non-idempotent side effects before 'interrupt', mix static edges and 'Command', or try to debug the graph as a linear function.",
          "blocks": [
            {
              "kind": "list",
              "label": "red flags",
              "items": [
                "Read the old tutorial as the current standard",
                "No 'thread id' with persistence/interruptions",
                "side effects to 'interrupt' without idempotency",
                "Expect Command to eliminate static edge"
              ]
            }
          ]
        },
        {
          "number": "30",
          "title": "Minimum code: High-level agent vs manual graph",
          "tag": "Bridge",
          "description": "These two pieces of code show the difference. In the first case, you describe “what I need”, and runtime is already assembled. In the second, you describe the execution graph itself.",
          "blocks": [
            {
              "kind": "code",
              "label": "LangChain",
              "language": "text",
              "value": "agent = create_agent(\n    model=\"provider:model\",\n    tools=[search_docs],\n    response_format=AnswerSchema,\n)"
            },
            {
              "kind": "code",
              "label": "LangGraph",
              "language": "text",
              "value": "builder = StateGraph(State)\nbuilder.add_node(\"plan\", plan_node)\nbuilder.add_node(\"act\", act_node)\nbuilder.add_node(\"review\", review_node)\nbuilder.add_edge(START, \"plan\")\nbuilder.add_conditional_edges(\"plan\", route)\ngraph = builder.compile(checkpointer=checkpointer)"
            }
          ]
        },
        {
          "number": "31",
          "title": "If you need to explain the stack in one sentence",
          "tag": "Bridge",
          "description": "LangChain helps to quickly assemble agent applications from standard components. LangGraph gives precise control over how these applications are actually executed in time: with state, branching, pauses, memory and renewal.",
          "blocks": [
            {
              "kind": "list",
              "label": "formula",
              "items": [
                "LangChain = components + high-level API",
                "LangGraph = orchestration runtime"
              ]
            }
          ]
        },
        {
          "number": "32",
          "title": "The main idea of the volume",
          "tag": "Bridge",
          "description": "To speak well of LangChain and LangGraph, it’s helpful to stop thinking of them as “two competing libraries for AI.” High-level agent components on top, low-level stateful runtime on bottom. Then it becomes clear why \"create agent\" works quickly, and why StateGraph is even needed.",
          "blocks": [
            {
              "kind": "list",
              "label": "takeaway",
              "items": [
                "Understand the message history",
                "Understand state + edges",
                "Understand thread/checkpoint/interrupt",
                "This is the language of conversation with LangChain/LangGraph engineers."
              ]
            }
          ]
        }
      ]
    }
  ]
}
