<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Agents &amp; MCP – Victor Grösser</title><link>https://victorgrosser.nl/blog/category/agents-mcp/</link><description>AI agents, tool use and the Model Context Protocol in practice.</description><language>en</language><atom:link href="https://victorgrosser.nl/blog/category/agents-mcp/rss.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Tue, 20 Oct 2026 09:00:00 +0000</lastBuildDate><item><title>Building Agents with LangGraph: State, Nodes, Edges, Memory and Human-in-the-Loop</title><link>https://victorgrosser.nl/blog/building-agents-with-langgraph-key-concepts/</link><guid isPermaLink="true">https://victorgrosser.nl/blog/building-agents-with-langgraph-key-concepts/</guid><pubDate>Tue, 20 Oct 2026 09:00:00 +0000</pubDate><description>Key LangGraph concepts from LangChain Academy&#x27;s free Intro to LangGraph course: state, nodes, edges, memory and human-in-the-loop, plus when to use them.</description><category>Agents &amp; MCP</category><category>langgraph</category><category>ai-agents</category><category>langchain-academy</category><category>agent-memory</category><category>human-in-the-loop</category><category>python</category><content:encoded><![CDATA[<p>A free-form agent loop, where a model calls tools until it decides it's done, is easy to demo and hard to trust. You can't easily see why it took a step, stop it before a risky action, or resume it tomorrow. <a href="https://langchain-ai.github.io/langgraph/" rel="noopener">LangGraph</a> addresses this by making the agent an explicit graph. I recently completed LangChain Academy's free <a href="https://academy.langchain.com/courses/intro-to-langgraph" rel="noopener">Introduction to LangGraph</a> course (Python). These are the concepts that matter most, along with my view on when each one is worth using.</p>
<h3 id="the-core-model-state-nodes-edges">The core model: state, nodes, edges</h3>
<p>A LangGraph application is a <code>StateGraph</code> made of three things.</p>
<p><strong>State</strong> is the shared data structure that flows through the graph. You define it as a <code>TypedDict</code>, a dataclass or a Pydantic model. Pydantic adds runtime validation. Every node reads the state and returns <em>updates</em> to it.</p>
<p><strong>Nodes</strong> are plain Python functions. A node might call a chat model, run a tool or transform data. It returns a partial state update and doesn't need to know about the rest of the graph.</p>
<p><strong>Edges</strong> connect nodes. Normal edges always go from A to B. <em>Conditional edges</em> run a function that picks the next node based on the state. You wire up <code>START</code> and <code>END</code>, call <code>compile()</code>, and run it with <code>invoke()</code>.</p>
<p>The detail that's easy to miss is <strong>reducers</strong>. By default, a node's update overwrites a key. With <code>Annotated[list, operator.add]</code> updates are appended instead. The built-in <code>add_messages</code> reducer appends messages or updates them by id, and supports <code>RemoveMessage</code>. Reducers become essential when nodes run in parallel and write to the same key.</p>
<h3 id="from-chain-to-agent-in-a-few-steps">From chain to agent in a few steps</h3>
<p>The course builds up in steps that make the design choices visible:</p>
<ol><li><strong>Chain</strong>: state is a list of messages (<code>MessagesState</code>), with a chat model that has tools bound via <code>bind_tools</code>.</li><li><strong>Router</strong>: the model either answers directly or emits a tool call. A <code>ToolNode</code> plus the prebuilt <code>tools_condition</code> edge handles the routing.</li><li><strong>Agent (ReAct)</strong>: add an edge from the tool node back to the model, so it loops until there are no more tool calls.</li></ol>
<p>So an "agent" is a router with a loop, and nothing more mysterious than that. Once you see it this way, you can decide exactly where the loop is allowed and where it isn't.</p>
<h3 id="memory-short-term-and-long-term">Memory: short-term and long-term</h3>
<p>LangGraph separates two kinds of memory.</p>
<p><strong>Short-term (thread) memory</strong> comes from a <strong>checkpointer</strong>. Compile the graph with <code>MemorySaver</code> (or <code>SqliteSaver</code> for persistence), pass a <code>thread_id</code> in the config, and the state is saved after every step. The next invocation on the same thread continues where the last one stopped. Conversations grow, so the course shows how to keep the context bounded: remove messages, pass only the last N, use <code>trim_messages</code> by token count, or keep a running summary.</p>
<p><strong>Long-term memory</strong> works <em>across</em> threads through a <strong>Store</strong>, such as <code>InMemoryStore</code>, keyed by a namespace tuple plus a key. You decide on a memory schema. It can be a <em>profile</em>, which is one JSON document per user, or a <em>collection</em>, a growing list of items. The final example in the module is an assistant that decides for itself when to update the user's profile, a to-do collection or its own instructions.</p>
<p><strong>When to use what:</strong> a checkpointer is almost always worth adding, because it also enables human-in-the-loop and debugging. Long-term memory only makes sense when you actually need personalisation across sessions. It is also a data-protection question, so design what you store deliberately.</p>
<h3 id="human-in-the-loop">Human-in-the-loop</h3>
<p>This is where LangGraph clearly beats an opaque loop. Because state is checkpointed, you can pause a graph, inspect it, change it and resume:</p>
<ul><li><strong>Breakpoints</strong>: <code>interrupt_before=[&quot;tools&quot;]</code> stops before a node, for example before a tool runs. Resuming with <code>invoke(None, config)</code> continues from the checkpoint.</li><li><strong>Editing state</strong>: <code>graph.update_state(config, values, as_node=...)</code> lets a human correct the state, for example to fix a tool argument, before continuing.</li><li><strong>Dynamic breakpoints</strong>: a node can raise an interrupt conditionally, for example only when an amount exceeds a threshold.</li><li><strong>Time travel</strong>: <code>get_state_history</code> lets you replay from an earlier checkpoint, or <em>fork</em> by updating an old one and running from there.</li></ul>
<p><strong>When to use it:</strong> any action with real consequences, such as sending a message, changing a record or spending money, should go through an explicit approval point. In legal or educational contexts, I'd consider this the default rather than an optional extra.</p>
<h3 id="scaling-the-graph-parallelism-sub-graphs-map-reduce">Scaling the graph: parallelism, sub-graphs, map-reduce</h3>
<p>Module 4 builds a research assistant. It generates analyst personas (with human feedback on the personas), runs parallel interviews using web and Wikipedia search, writes sections, and combines them into a report. The patterns along the way:</p>
<ul><li><strong>Fan-out/fan-in parallelism</strong>, which needs reducers on shared keys.</li><li><strong>Sub-graphs</strong> with their own state, which talk to the parent through overlapping keys.</li><li><strong>Map-reduce with the <code>Send</code> API</strong>, which spawns a dynamic number of parallel nodes and then aggregates the results.</li></ul>
<p><strong>When to use them:</strong> only when the task really splits into independent pieces. Every extra branch adds coordination and evaluation work. Start with a single agent and tools, and add structure when a trace shows you need it.</p>
<h3 id="deployment-and-streaming">Deployment and streaming</h3>
<p>The course also covers the operational side. You can stream either state <code>updates</code> or full <code>values</code>, and stream tokens with <code>astream_events</code>. LangGraph Studio is useful for visual debugging. For deployment, a <code>langgraph.json</code> plus a Docker image runs on LangGraph Server, which handles the API, a task queue and Postgres persistence. There are clients through the SDK or <code>RemoteGraph</code>. Two ideas I hadn't considered before: <strong>double-texting strategies</strong> (reject, enqueue, interrupt or rollback when a user sends a new message mid-run) and <strong>assistants</strong>, which are versioned configurations of the same graph.</p>
<h3 id="when-langgraph-is-the-right-tool">When LangGraph is the right tool</h3>
<ul><li><strong>Use it</strong> when you need control over the flow, durable state, approvals or resumable runs. That usually means multi-step agents that act on real systems.</li><li><strong>Skip it</strong> for a single prompt-and-answer or a fixed linear pipeline, where plain code is simpler.</li><li><strong>Keep it simple first.</strong> A predefined workflow is easier to test than a free agent, and a single agent is easier than several. The graph makes that trade-off explicit, which is exactly why I like it.</li></ul>
<h3 id="getting-started">Getting started</h3>
<p>The course is free. The code is in the public <a href="https://github.com/langchain-ai/langchain-academy" rel="noopener">langchain-academy repository</a> as Jupyter notebooks, one folder per module, with a <code>studio</code> folder you can run with <code>langgraph dev</code>. You'll need Python 3.11+ and an API key for a chat model. Tracing through LangSmith is optional but very helpful. Work through the modules in order: each one adds a single concept on top of the last.</p>
<p><strong>Links:</strong> <a href="https://academy.langchain.com/courses/intro-to-langgraph" rel="noopener">Introduction to LangGraph – LangChain Academy</a> · <a href="https://langchain-ai.github.io/langgraph/" rel="noopener">LangGraph documentation</a> · <a href="https://github.com/langchain-ai/langchain-academy" rel="noopener">Course notebooks on GitHub</a></p>]]></content:encoded></item></channel></rss>
