{"id":2977,"date":"2026-11-09T10:43:32","date_gmt":"2026-11-09T10:43:32","guid":{"rendered":"https:\/\/hostry.com\/blog\/?p=2977"},"modified":"2026-11-09T10:43:33","modified_gmt":"2026-11-09T10:43:33","slug":"langgraph-explained-vs-google-adk","status":"publish","type":"post","link":"https:\/\/hostry.com\/blog\/langgraph-explained-vs-google-adk\/","title":{"rendered":"What Is LangGraph? Architecture, Core Concepts and Google ADK Compared"},"content":{"rendered":"\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"675\" src=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-cover.webp\" alt=\"What is LangGraph: an AI agent drawn as a graph of nodes and edges, with a shared state record passed from step to step\" class=\"wp-image-2969\" srcset=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-cover.webp 1200w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-cover-300x169.webp 300w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-cover-1024x576.webp 1024w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-cover-450x253.webp 450w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-cover-768x432.webp 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-preformatted\" style=\"font-size:14px\"><em><strong>LangGraph is an MIT-licensed library from LangChain Inc that runs an AI agent as a graph: one typed state object, nodes that update it and edges that choose the next node. A checkpointer stores the state after every step, so a run can stop, wait for a person and continue later from the same point.<\/strong><\/em><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you are picking an agent framework in late 2026, the other name on the shortlist is usually Google&#8217;s Agent Development Kit, which added graph workflows of its own in ADK 2.0. Neither framework needs a GPU on your side: the model runs at the provider, and a CPU-only <strong><a href=\"https:\/\/hostry.com\/products\/vps\/\">KVM VPS with root access<\/a><\/strong> is enough to host the agent and its database.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is LangGraph, and what it is not<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <strong><a href=\"https:\/\/docs.langchain.com\/oss\/python\/langgraph\/overview\" target=\"_blank\" rel=\"noopener\">LangGraph overview<\/a><\/strong> calls it a low-level orchestration framework and runtime for long-running, stateful agents. In practice it is a Python package (with a JavaScript twin) that executes a graph you define and keeps the graph&#8217;s state between steps and between requests. It ships no prompts and no agent architecture: a node can call an LLM or run three lines of plain Python, and one graph can mix both.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The short answer on LangGraph vs Google ADK:<\/strong> choose LangGraph when the job is a business process with some LLM steps inside and you want one explicit, typed state you can inspect after every step. Choose ADK when the job is mainly an LLM agent with tools and delegation, when you deploy on Google Cloud, or when part of the team works in Go, Java or Kotlin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">LangGraph is not a model and not a hosted service, and it is separate from LangChain, the same company&#8217;s older library of model wrappers; the docs say LangGraph works without it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Its README credits Google&#8217;s Pregel and Apache Beam for the execution model and NetworkX for the shape of the public API. LangSmith, the company&#8217;s commercial tracing and deployment platform, is optional.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How LangGraph works: state, nodes and edges<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A LangGraph program has three parts, and the API names match them closely.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>State<\/strong> is a schema, usually a <span class=\"highlighted\">TypedDict<\/span>, that every node reads. Each key has a reducer that decides how an update merges in. Without one, a new value overwrites the old; <span class=\"highlighted\">Annotated[list, operator.add]<\/span> appends instead, and the prebuilt <span class=\"highlighted\">add_messages<\/span> appends chat messages and replaces any with a matching ID.<\/li><li>Nodes are functions that take the current state and return a partial update, a dict with only the keys they changed.<\/li><li>Edges decide what runs next. A normal edge always goes to the same node; a conditional edge calls a routing function and follows whichever node name it returns. <span class=\"highlighted\">START<\/span> and <span class=\"highlighted\">END<\/span> mark the entry and exit.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">You add these to a <span class=\"highlighted\">StateGraph<\/span> builder and call <span class=\"highlighted\">compile()<\/span>, which the docs make mandatory: compiling checks the structure and is where you attach a checkpointer. The example below routes support tickets without any model; the keyword test in <span class=\"highlighted\">classify<\/span> stands in for what would be an LLM call in a real agent. This and every other Python snippet here ran on November 9, 2026 with langgraph 1.2.20, langgraph-checkpoint-postgres 3.2.0, google-adk 2.14.0, Python 3.13 and PostgreSQL 16 on Ubuntu 24.04, and none needs a model API key.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import operator\nfrom typing import Annotated, TypedDict\n\nfrom langgraph.graph import StateGraph, START, END\n\n\nclass TicketState(TypedDict):\n    text: str\n    priority: str\n    log: Annotated[list[str], operator.add]  # reducer: append, do not overwrite\n\n\ndef classify(state: TicketState) -&gt; dict:\n    urgent = \"down\" in state[\"text\"].lower()\n    return {\"priority\": \"high\" if urgent else \"normal\", \"log\": [\"classify\"]}\n\n\ndef page_on_call(state: TicketState) -&gt; dict:\n    return {\"log\": [\"page_on_call\"]}\n\n\ndef queue_ticket(state: TicketState) -&gt; dict:\n    return {\"log\": [\"queue_ticket\"]}\n\n\ndef route(state: TicketState) -&gt; str:\n    return \"page_on_call\" if state[\"priority\"] == \"high\" else \"queue_ticket\"\n\n\nbuilder = StateGraph(TicketState)\nbuilder.add_node(\"classify\", classify)\nbuilder.add_node(\"page_on_call\", page_on_call)\nbuilder.add_node(\"queue_ticket\", queue_ticket)\nbuilder.add_edge(START, \"classify\")\nbuilder.add_conditional_edges(\"classify\", route, [\"page_on_call\", \"queue_ticket\"])\nbuilder.add_edge(\"page_on_call\", END)\nbuilder.add_edge(\"queue_ticket\", END)\ngraph = builder.compile()\n\nprint(graph.invoke({\"text\": \"API is down in eu-west\", \"log\": []}))\nprint(graph.invoke({\"text\": \"Typo on the pricing page\", \"log\": []}))<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{'text': 'API is down in eu-west', 'priority': 'high', 'log': ['classify', 'page_on_call']}\n{'text': 'Typo on the pricing page', 'priority': 'normal', 'log': ['classify', 'queue_ticket']}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In that output, the <span class=\"highlighted\">log<\/span> key shows the reducer at work: each node returned a one-item list and the runtime concatenated them. Without the <span class=\"highlighted\">operator.add<\/span> annotation you would see only the last node&#8217;s entry.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Super-steps: how the runtime executes the graph<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Under the builder sits a message-passing engine modeled on Pregel. Execution advances in <strong>super-steps<\/strong>. A node becomes active when a new state update arrives on one of its incoming edges; all active nodes run within that super-step, parallel branches included, and their updates are merged through the reducers before the next super-step starts. The run ends when no node is active and no update is in transit. A recursion limit in the run config stops a graph whose loop never reaches <span class=\"highlighted\">END<\/span>, which matters once an LLM decides whether to loop again.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A conditional edge can also return a list of <span class=\"highlighted\">Send<\/span> objects, one per item, to fan work out when the number of branches is only known at run time (map-reduce). A node can return a <span class=\"highlighted\">Command<\/span> that updates state and names the next node in one value, which is how agent handoffs are usually written.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"800\" src=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-architecture.webp\" alt=\"LangGraph architecture: START to an LLM node, a conditional edge to a tool node or END, the tool node looping back, and a checkpointer writing each super-step to PostgreSQL under a thread_id\" class=\"wp-image-2975\" srcset=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-architecture.webp 1200w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-architecture-300x200.webp 300w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-architecture-1024x683.webp 1024w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-architecture-450x300.webp 450w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-architecture-768x512.webp 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><figcaption>The graph only moves state between nodes; the checkpointer stores one snapshot per super-step under the run&#8217;s thread_id.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Persistence, memory and human-in-the-loop<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Compile with a checkpointer and pass a <span class=\"highlighted\">thread_id<\/span> in the config, and LangGraph saves a checkpoint of the whole state at every super-step of that thread. The <strong>LangGraph checkpointer<\/strong> is the base for most of the framework&#8217;s other features. A crashed run resumes from the last saved step instead of starting over. A conversation continues when the next request arrives with the same thread ID. <span class=\"highlighted\">graph.get_state(config)<\/span> shows the current values and which node runs next; <span class=\"highlighted\">get_state_history<\/span> lists earlier checkpoints, and you can resume or fork from any of them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Three savers cover most needs. <span class=\"highlighted\">InMemorySaver<\/span> (the old name <span class=\"highlighted\">MemorySaver<\/span> still works as an alias) keeps everything in RAM and loses it on restart. <span class=\"highlighted\">SqliteSaver<\/span>, from the separate <span class=\"highlighted\">langgraph-checkpoint-sqlite<\/span> package, writes to a local file and suits development. <span class=\"highlighted\">PostgresSaver<\/span> and its async twin <span class=\"highlighted\">AsyncPostgresSaver<\/span> come in the separate <span class=\"highlighted\">langgraph-checkpoint-postgres<\/span> package and are what the docs mean by a persistent checkpointer for production. Keep thread IDs under 255 characters with Postgres. Checkpoints pile up over long threads, so plan a retention job; the Postgres saver can trim a thread to its newest checkpoint or delete it outright (async code uses <span class=\"highlighted\">aprune<\/span> and <span class=\"highlighted\">adelete_thread<\/span>):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from langgraph.checkpoint.postgres import PostgresSaver\n\nDB_URI = \"postgresql:\/\/agent:change-me@127.0.0.1:5432\/langgraph\"\n\nwith PostgresSaver.from_conn_string(DB_URI) as checkpointer:\n    checkpointer.prune([\"T-1001\"], strategy=\"keep_latest\")  # keep only the newest checkpoint\n    checkpointer.delete_thread(\"T-1002\")  # drop a closed ticket completely<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That is short-term memory, scoped to one thread. <strong>Long-term memory<\/strong> across threads, such as facts about a user that every conversation should see, goes into a store (<span class=\"highlighted\">InMemoryStore<\/span> for tests, a database-backed store in production), which nodes read and write explicitly. Items live under a namespace tuple:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from langgraph.store.memory import InMemoryStore\n\nstore = InMemoryStore()\nstore.put((\"users\", \"u-77\"), \"contact\", {\"channel\": \"email\"})\nprint(store.search((\"users\", \"u-77\"))[0].value)  # {'channel': 'email'}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Pausing for a person with interrupt()<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Human-in-the-loop is built on the same checkpoints. Calling <span class=\"highlighted\">interrupt()<\/span> inside a node saves the state and stops the run; the payload you pass appears under <span class=\"highlighted\">__interrupt__<\/span> in the result. Invoking the graph again on the same thread with <span class=\"highlighted\">Command(resume=value)<\/span> continues it, and <span class=\"highlighted\">value<\/span> becomes the return value of <span class=\"highlighted\">interrupt()<\/span>. Here a refund waits for approval:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from typing import TypedDict\n\nfrom langgraph.checkpoint.memory import InMemorySaver\nfrom langgraph.graph import StateGraph, START, END\nfrom langgraph.types import Command, interrupt\n\n\nclass RefundState(TypedDict):\n    order_id: str\n    amount: float\n    approved: bool\n\n\ndef ask_human(state: RefundState) -&gt; dict:\n    answer = interrupt({\"question\": f\"Refund {state['amount']} for {state['order_id']}?\"})\n    return {\"approved\": answer == \"yes\"}\n\n\ndef issue_refund(state: RefundState) -&gt; dict:\n    print(\"refund sent\" if state[\"approved\"] else \"refund declined\")\n    return {}\n\n\nbuilder = StateGraph(RefundState)\nbuilder.add_node(\"ask_human\", ask_human)\nbuilder.add_node(\"issue_refund\", issue_refund)\nbuilder.add_edge(START, \"ask_human\")\nbuilder.add_edge(\"ask_human\", \"issue_refund\")\nbuilder.add_edge(\"issue_refund\", END)\ngraph = builder.compile(checkpointer=InMemorySaver())\n\nconfig = {\"configurable\": {\"thread_id\": \"refund-1042\"}}\nfirst = graph.invoke({\"order_id\": \"A-1042\", \"amount\": 89.0, \"approved\": False}, config)\nprint(first[\"__interrupt__\"][0].value)\nprint(graph.get_state(config).next)\n\ngraph.invoke(Command(resume=\"yes\"), config)\nprint(graph.get_state(config).values)<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>{'question': 'Refund 89.0 for A-1042?'}\n('ask_human',)\nrefund sent\n{'order_id': 'A-1042', 'amount': 89.0, 'approved': True}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">With a durable checkpointer, the approval can arrive a day later from a web form, after a restart, and nothing is lost. The <strong><a href=\"https:\/\/docs.langchain.com\/oss\/python\/langgraph\/interrupts\" target=\"_blank\" rel=\"noopener\">interrupts documentation<\/a><\/strong> sets four rules:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>On resume, the node runs again <strong>from its first line<\/strong>, so anything before <span class=\"highlighted\">interrupt()<\/span> executes twice. Keep writes and API calls after it or in a separate node, or make them idempotent.<\/li><li>Do not wrap <span class=\"highlighted\">interrupt()<\/span> in a bare <span class=\"highlighted\">try\/except<\/span>: the pause is raised as an exception and a broad handler swallows it.<\/li><li>With several interrupts in one node, resume values are matched by position, so keep their order the same on every run.<\/li><li>Pass only simple, JSON-serializable values to <span class=\"highlighted\">interrupt()<\/span>; a function or other complex object may not survive the checkpointer&#8217;s serializer.<\/li><\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Static breakpoints (<span class=\"highlighted\">interrupt_before<\/span>, <span class=\"highlighted\">interrupt_after<\/span>) are meant for debugging. A reviewer can also edit the saved state with <span class=\"highlighted\">update_state<\/span> before letting the run continue.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Multi-agent patterns in LangGraph<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">At its simplest, an agent is still a loop: a model node decides whether to call a tool, a tool node runs it, and a conditional edge sends control back to the model until it answers without a tool call. <span class=\"highlighted\">langgraph.prebuilt<\/span> keeps <span class=\"highlighted\">ToolNode<\/span> and <span class=\"highlighted\">tools_condition<\/span> for writing that loop by hand, and LangChain&#8217;s <span class=\"highlighted\">create_agent<\/span> builds it for you. Both need a model provider key, so neither is run here. The lineage runs both ways: LangChain 1.0&#8217;s <span class=\"highlighted\">create_agent<\/span> is built on LangGraph, and <span class=\"highlighted\">langgraph.prebuilt.create_react_agent<\/span> carries a deprecation warning that points to it. Deep Agents, the company&#8217;s harness for planning and subagents, sits on LangGraph too.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For <strong>LangGraph multi-agent<\/strong> systems, the building block is the <strong>subgraph<\/strong>: a compiled graph used as a node of another graph. If parent and child share state keys, pass the compiled subgraph straight to <span class=\"highlighted\">add_node<\/span>. If their schemas differ, call it from inside a node function and map the state in and out. A subgraph inherits the parent&#8217;s checkpointer and keeps its own checkpoint namespace, so interrupts inside a sub-agent still work. LangChain&#8217;s multi-agent guide names the patterns built on top:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Subagents as tools<\/strong>, the supervisor shape: one main agent calls specialized agents the way it calls tools, and all routing passes through it.<\/li><li>Handoffs, where the active agent passes control to another through a tool call, usually a <span class=\"highlighted\">Command<\/span>.<\/li><li>A router step that classifies the input and merges the answers of the agents it picked.<\/li><li>Skills, where one agent stays in charge and loads extra prompts on demand.<\/li><li>A custom workflow, your own graph in which any of the above can be a node.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Our advice is to begin with one agent and a good tool set and split it only when prompts or tools for different jobs start to interfere with each other, because every extra agent adds model calls and one more prompt to debug.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What Google ADK is, and what changed in ADK 2.0<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Google&#8217;s Agent Development Kit is an open-source, Apache 2.0 framework for building and deploying agents, with SDKs for Python, TypeScript, Go, Java and Kotlin. Its README says it is optimized for Gemini yet model-agnostic and deployment-agnostic. The Python package is <span class=\"highlighted\">google-adk<\/span>; 2.14.0 was released on November 7, 2026 and needs Python 3.11 or newer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">ADK starts from the agent. An <span class=\"highlighted\">LlmAgent<\/span> (exported as <span class=\"highlighted\">Agent<\/span> too) is configured with a model and an instruction, then given tools and optional <span class=\"highlighted\">sub_agents<\/span>; the model decides when to transfer control to a sub-agent, and <span class=\"highlighted\">AgentTool<\/span> wraps an agent so another can call it like a function. Tools can be Python functions, OpenAPI specs or MCP servers; the Model Context Protocol is an open standard for connecting AI applications to outside tools and data. Remote agents are reached over A2A, the Agent2Agent protocol, an open standard for calls between agents.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">According to the <strong><a href=\"https:\/\/adk.dev\/2.0\/\" target=\"_blank\" rel=\"noopener\">ADK 2.0 release page<\/a><\/strong>, ADK Python 2.0 became generally available on May 19, 2026, Go on June 30 and TypeScript on August 21. The headline change is a workflow runtime in which agents and plain functions run as graph nodes. The <strong><a href=\"https:\/\/adk.dev\/graphs\/\" target=\"_blank\" rel=\"noopener\">ADK graph workflows guide<\/a><\/strong> shows the Python API: a <span class=\"highlighted\">Workflow<\/span> with an <span class=\"highlighted\">edges<\/span> list, function nodes that return <span class=\"highlighted\">Event(route=&#8230;)<\/span>, and a dict that maps each route to the next node, with <span class=\"highlighted\">DEFAULT_ROUTE<\/span> as the fallback. A node that yields <span class=\"highlighted\">RequestInput<\/span> pauses for human input, and <span class=\"highlighted\">RetryConfig<\/span> sets retries per node. The ticket router from the first example, rewritten for ADK, ran with an <span class=\"highlighted\">InMemoryRunner<\/span> and no model:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><span class=\"highlighted\">Workflow(name=&#8221;ticket_router&#8221;, edges=[(&#8220;START&#8221;, classify), (classify, {&#8220;high&#8221;: page_on_call, &#8220;normal&#8221;: queue_ticket})])<\/span><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here <span class=\"highlighted\">classify<\/span> returns <span class=\"highlighted\">Event(route=&#8221;high&#8221;, output=node_input)<\/span>, and each node&#8217;s output becomes the next node&#8217;s input; nothing is written to a shared state schema. Code written for ADK 1.x can need changes, among them:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><span class=\"highlighted\">BaseAgent<\/span> now subclasses <span class=\"highlighted\">BaseNode<\/span>, and custom session stores must save the new event fields.<\/li><li>A broad <span class=\"highlighted\">except Exception<\/span> inside a tool hides failures from the new automatic retries, and catching <span class=\"highlighted\">BaseException<\/span> breaks human-input pauses.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">In google-adk 2.14.0, <span class=\"highlighted\">SequentialAgent<\/span>, <span class=\"highlighted\">ParallelAgent<\/span> and <span class=\"highlighted\">LoopAgent<\/span> are marked deprecated in favor of <span class=\"highlighted\">Workflow<\/span>, and the deprecation notice adds that a <span class=\"highlighted\">Workflow<\/span> cannot yet be an <span class=\"highlighted\">LlmAgent<\/span> sub-agent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Persistence works through sessions. A session holds the event history and a state dict for one conversation, and a session service stores it: <span class=\"highlighted\">InMemorySessionService<\/span> for tests, <span class=\"highlighted\">DatabaseSessionService<\/span> for PostgreSQL, MySQL or SQLite through an async SQLAlchemy URL (install <span class=\"highlighted\">google-adk[db]<\/span>), or <span class=\"highlighted\">VertexAiSessionService<\/span> on Google Cloud. The CLI adds <span class=\"highlighted\">adk web<\/span> (a dev UI), <span class=\"highlighted\">adk api_server<\/span>, <span class=\"highlighted\">adk eval<\/span> and <span class=\"highlighted\">adk deploy<\/span>, with targets <span class=\"highlighted\">agent_engine<\/span>, <span class=\"highlighted\">cloud_run<\/span>, <span class=\"highlighted\">gke<\/span> and <span class=\"highlighted\">docker<\/span>. Tracing uses OpenTelemetry, which is a core dependency.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">LangGraph vs Google ADK side by side<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every row was checked against the installed packages and the official docs on November 9, 2026.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Dimension<\/th><th>LangGraph<\/th><th>Google ADK<\/th><\/tr><\/thead><tbody><tr><td>License<\/td><td>MIT<\/td><td>Apache 2.0<\/td><\/tr><tr><td>Version checked<\/td><td>langgraph 1.2.20 (Nov 5, 2026); 1.0.0 was Oct 17, 2025<\/td><td>google-adk 2.14.0 (Nov 7, 2026); Python 2.0 GA May 19, 2026<\/td><\/tr><tr><td>Languages<\/td><td>Python 3.10+, JavaScript\/TypeScript<\/td><td>Python 3.11+, TypeScript, Go, Java, Kotlin<\/td><\/tr><tr><td>Starting point<\/td><td>A graph: <span class=\"highlighted\">StateGraph<\/span>, nodes, edges<\/td><td>An agent: <span class=\"highlighted\">LlmAgent<\/span> with tools and sub-agents; <span class=\"highlighted\">Workflow<\/span> for graphs<\/td><\/tr><tr><td>Control flow<\/td><td>Normal and conditional edges, cycles, <span class=\"highlighted\">Send<\/span> fan-out, <span class=\"highlighted\">Command<\/span><\/td><td>Workflow edges with routes and <span class=\"highlighted\">DEFAULT_ROUTE<\/span>, join nodes, per-node retries; LLM-driven transfer between agents<\/td><\/tr><tr><td>State<\/td><td>One typed schema with per-key reducers<\/td><td>Session state dict plus event history; node output feeds the next node<\/td><\/tr><tr><td>Persistence<\/td><td>Checkpoint per super-step per <span class=\"highlighted\">thread_id<\/span>: InMemorySaver, SqliteSaver, PostgresSaver<\/td><td>Session service: in-memory, database (SQLAlchemy async URL) or Vertex AI; workflows resume from events<\/td><\/tr><tr><td>Human-in-the-loop<\/td><td><span class=\"highlighted\">interrupt()<\/span> anywhere in a node, <span class=\"highlighted\">Command(resume=&#8230;)<\/span>, state editable before resuming<\/td><td><span class=\"highlighted\">RequestInput<\/span> node in workflows; confirmation step for tool actions<\/td><\/tr><tr><td>History and replay<\/td><td><span class=\"highlighted\">get_state_history<\/span>, resume or fork from any checkpoint<\/td><td>Session rewind<\/td><\/tr><tr><td>Multi-agent<\/td><td>Subgraphs, subagents as tools, handoffs, routers<\/td><td><span class=\"highlighted\">sub_agents<\/span> with transfer, <span class=\"highlighted\">AgentTool<\/span>, A2A<\/td><\/tr><tr><td>Models<\/td><td>Whatever your node calls; no model layer of its own<\/td><td>Gemini, and Claude on Google&#8217;s Agent Platform, by name; LiteLLM, Ollama, vLLM and others via connectors<\/td><\/tr><tr><td>Dev tooling and tracing<\/td><td>LangSmith tracing and Studio (opt-in)<\/td><td><span class=\"highlighted\">adk web<\/span>, <span class=\"highlighted\">adk eval<\/span>, OpenTelemetry<\/td><\/tr><tr><td>Vendor hosting<\/td><td>LangSmith Deployment: cloud, hybrid, self-hosted<\/td><td>Agent Runtime on Google Cloud, Cloud Run, GKE<\/td><\/tr><tr><td>Self-hosting without the vendor<\/td><td>Your own service around <span class=\"highlighted\">invoke<\/span>\/<span class=\"highlighted\">stream<\/span>; the standalone Agent Server needs a license key<\/td><td><span class=\"highlighted\">adk api_server<\/span> or a container from <span class=\"highlighted\">adk deploy docker<\/span><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In LangGraph you describe the control flow and the state first, then decide which nodes call a model. ADK hands you an agent with model calls and delegation already wired, and graph workflows are the newer layer for the parts that must be deterministic. An LLM-with-tools agent takes less code in ADK, whereas a business process with a few LLM steps inside maps more directly onto LangGraph&#8217;s explicit state and edges. The two ticket routers show the consequence: in LangGraph each node returned a partial update that the reducer merged into the shared <span class=\"highlighted\">log<\/span>, while in ADK <span class=\"highlighted\">classify<\/span> handed its output straight to the next node.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Persistence follows from that. LangGraph stores <strong>a full state snapshot per step<\/strong>, so inspecting, editing or forking a run is a database read and a write. ADK stores <strong>the event stream of a session<\/strong>, and the 2.14.0 source rebuilds a workflow&#8217;s node runs from those events on resume. Both survive restarts with a database behind them, and both let a run wait for a person. LangGraph&#8217;s model is easier to reason about when you need to debug &#8220;what did the state look like before step 7&#8221;.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">ADK&#8217;s paved roads lead to Google Cloud, although <span class=\"highlighted\">adk api_server<\/span> and the Docker target run anywhere; note that its help text says the server&#8217;s endpoints are unauthenticated and belong behind your own auth layer. LangGraph&#8217;s paved road is LangSmith Deployment, whose self-hosted options are tied to paid plans. The library itself is free, but serving it yourself means you own the HTTP layer, authentication, TLS, connection pooling, backups and checkpoint retention. We recommend that route to readers who are willing to run that stack.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"800\" src=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-compare.webp\" alt=\"LangGraph vs Google ADK comparison card: starting abstraction, languages, state, persistence, human input, multi-agent, models and hosting\" class=\"wp-image-2974\" srcset=\"https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-compare.webp 1200w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-compare-300x200.webp 300w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-compare-1024x683.webp 1024w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-compare-450x300.webp 450w, https:\/\/hostry.com\/blog\/wp-content\/uploads\/2026\/11\/langgraph-explained-vs-google-adk-compare-768x512.webp 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><figcaption>LangGraph and Google ADK side by side: starting point, state, persistence, human input, multi-agent, models, hosting and license.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">When LangGraph is the right choice (and when it is not)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Restart recovery and human approval do not separate the two, since both frameworks resume from a database and both can pause for a person. State, agent design and deployment do.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Pick LangGraph<\/strong> when you want one typed state schema with per-key reducers that every step reads and writes, and when you need to inspect, edit or fork a run at any checkpoint. It also suits teams that want to own the serving layer, since the library has no hosting tie-in.<\/li><li><strong>Pick ADK<\/strong> when the work is a configured agent (model, instruction, tools) that delegates to sub-agents, when part of the team writes Go, Java or Kotlin, or when you plan to deploy on Agent Runtime, Cloud Run or GKE.<\/li><li>For a single model call with one or two tools, skip both and use the provider&#8217;s SDK; a framework adds a dependency tree without adding control.<\/li><li>If you already use LangChain&#8217;s model wrappers and retrievers, LangGraph is the natural next layer, and <span class=\"highlighted\">create_agent<\/span> already runs on it.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For a Python team that self-hosts and has no Google Cloud commitment, we would choose LangGraph, for its checkpoint model and a self-hosted path that needs only Postgres. A team already on Vertex AI with Gemini quotas should start with ADK.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Where CrewAI, AutoGen and n8n fit<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">CrewAI works one level higher: you describe agents by role and goal, group them into crews, and use its event-driven Flows for the state and control around them. It gets a role-based team running quickly, at the cost of the step-level control LangGraph exposes. Microsoft&#8217;s AutoGen has agents talk to each other in two-agent or group chats. Its README now says the project is archived and no longer maintained, with no bug or security fixes, and sends new users to Microsoft Agent Framework (agent-framework 1.25.0 on PyPI, November 7, 2026), which adds graph-based workflows and state for long-running, human-in-the-loop runs in .NET and Python. Evaluate that successor when your team writes .NET or works on Azure. n8n is a visual workflow tool with AI agent nodes, suited to integrations that a non-developer should be able to edit.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Self-host LangGraph without LangSmith on a VPS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A LangGraph agent is ordinary Python plus HTTPS calls to a model API, so the server needs CPU and RAM, never a GPU. A small local model on CPU works but is slow, so an agent that answers people should call a hosted API.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are two ways to <strong>self-host LangGraph without the LangSmith platform<\/strong>. The first is LangChain&#8217;s Agent Server in standalone mode: <span class=\"highlighted\">langgraph build<\/span> produces a Docker image, and the container needs <span class=\"highlighted\">DATABASE_URI<\/span> (Postgres) and <span class=\"highlighted\">REDIS_URI<\/span>. Per the <strong><a href=\"https:\/\/docs.langchain.com\/langsmith\/deploy-standalone-server\" target=\"_blank\" rel=\"noopener\">standalone server docs<\/a><\/strong>, you also need <span class=\"highlighted\">LANGGRAPH_CLOUD_LICENSE_KEY<\/span>, checked once at startup. Per the CLI reference, <span class=\"highlighted\">langgraph dev<\/span> needs no key but is for development only, and <span class=\"highlighted\">langgraph up<\/span> needs a license key in production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Route two is a small web app with the Postgres checkpointer behind it. On Ubuntu 24.04, as the sudo user <span class=\"highlighted\">deploy<\/span> (replace the user and password with your own):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update &amp;&amp; sudo apt install -y postgresql python3-venv\nsudo -u postgres psql -c \"CREATE USER agent WITH PASSWORD 'change-me';\"\nsudo -u postgres createdb -O agent langgraph\nmkdir -p ~\/agent &amp;&amp; python3 -m venv ~\/agent\/.venv\n~\/agent\/.venv\/bin\/pip install langgraph langgraph-checkpoint-postgres \"psycopg[binary]\" fastapi uvicorn<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ubuntu 24.04 installs PostgreSQL 16. On Ubuntu 22.04 the same command gives PostgreSQL 14, which reaches end of life on November 12, 2026, so a new checkpoint database there is worth starting on a supported release; our <strong><a href=\"https:\/\/hostry.com\/blog\/postgresql-14-end-of-life-upgrade\/\">PostgreSQL 14 to 18 upgrade steps<\/a><\/strong> cover moving an existing one. Save the first example&#8217;s graph code without the last four lines as <span class=\"highlighted\">~\/agent\/tickets.py<\/span>, then add <span class=\"highlighted\">app.py<\/span>, a demo that runs on a single database connection:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import os\nfrom contextlib import asynccontextmanager\n\nfrom fastapi import FastAPI\nfrom langgraph.checkpoint.postgres.aio import AsyncPostgresSaver\nfrom pydantic import BaseModel\n\nfrom tickets import builder  # the StateGraph from the first example\n\nDB_URI = os.environ[\"DB_URI\"]  # postgresql:\/\/agent:...@127.0.0.1:5432\/langgraph\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    async with AsyncPostgresSaver.from_conn_string(DB_URI) as saver:\n        await saver.setup()\n        app.state.graph = builder.compile(checkpointer=saver)\n        yield\n\n\napp = FastAPI(lifespan=lifespan)\n\n\nclass Ticket(BaseModel):\n    text: str\n\n\n@app.post(\"\/tickets\/{ticket_id}\")\nasync def run_ticket(ticket_id: str, ticket: Ticket):\n    config = {\"configurable\": {\"thread_id\": ticket_id}}\n    result = await app.state.graph.ainvoke({\"text\": ticket.text, \"log\": []}, config)\n    return {\"priority\": result[\"priority\"], \"steps\": result[\"log\"]}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><span class=\"highlighted\">setup()<\/span> creates four tables on first start (<span class=\"highlighted\">checkpoints<\/span>, <span class=\"highlighted\">checkpoint_blobs<\/span>, <span class=\"highlighted\">checkpoint_writes<\/span> and <span class=\"highlighted\">checkpoint_migrations<\/span>). Start it with <span class=\"highlighted\">DB_URI=&#8230; ~\/agent\/.venv\/bin\/uvicorn app:app &#8211;host 127.0.0.1 &#8211;port 8000<\/span> from <span class=\"highlighted\">~\/agent<\/span> and post the same ticket ID twice; the second answer carries the steps of both runs, read back from Postgres:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -s -X POST http:\/\/127.0.0.1:8000\/tickets\/T-1001 -H 'Content-Type: application\/json' -d '{\"text\": \"Checkout is down\"}'\n{\"priority\":\"high\",\"steps\":[\"classify\",\"page_on_call\"]}\ncurl -s -X POST http:\/\/127.0.0.1:8000\/tickets\/T-1001 -H 'Content-Type: application\/json' -d '{\"text\": \"Still down after restart\"}'\n{\"priority\":\"high\",\"steps\":[\"classify\",\"page_on_call\",\"classify\",\"page_on_call\"]}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><span class=\"highlighted\">AsyncPostgresSaver<\/span> runs each database call under an <span class=\"highlighted\">asyncio.Lock<\/span>, so within one process overlapping requests take turns on checkpoint reads and writes, whether it holds one connection or a pool; for more parallel throughput, run several uvicorn workers. A pool still matters for real traffic, because a connection that breaks, for example after a PostgreSQL restart, is discarded and replaced instead of failing every later request. Create a psycopg <span class=\"highlighted\">AsyncConnectionPool<\/span> with <span class=\"highlighted\">open=False<\/span>, open it in the FastAPI lifespan and close it on shutdown, as the <strong><a href=\"https:\/\/www.psycopg.org\/psycopg3\/docs\/advanced\/pool.html\" target=\"_blank\" rel=\"noopener\">psycopg connection pool docs<\/a><\/strong> show, then pass it to <span class=\"highlighted\">AsyncPostgresSaver(pool)<\/span> with the <span class=\"highlighted\">kwargs<\/span> that <span class=\"highlighted\">from_conn_string<\/span> uses: <span class=\"highlighted\">autocommit=True<\/span>, <span class=\"highlighted\">prepare_threshold=0<\/span> and <span class=\"highlighted\">row_factory=dict_row<\/span>. To keep the service running across reboots, a systemd unit is enough:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \/etc\/systemd\/system\/langgraph-agent.service\n[Unit]\nDescription=LangGraph ticket agent\nWants=network-online.target\nAfter=network-online.target postgresql.service\n\n[Service]\nUser=deploy\nWorkingDirectory=\/home\/deploy\/agent\nEnvironmentFile=\/home\/deploy\/agent\/.env\nExecStart=\/home\/deploy\/agent\/.venv\/bin\/uvicorn app:app --host 127.0.0.1 --port 8000\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Put <span class=\"highlighted\">DB_URI<\/span> and the model provider key in <span class=\"highlighted\">.env<\/span>, <span class=\"highlighted\">chmod 600<\/span> it, then run <span class=\"highlighted\">sudo systemctl enable &#8211;now langgraph-agent<\/span>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Exposure, sizing and running costs<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Uvicorn binds to loopback and Ubuntu&#8217;s PostgreSQL listens on <span class=\"highlighted\">localhost<\/span> by default, so neither is reachable from outside. Put Caddy or Nginx in front for TLS on 443 and add authentication there or in FastAPI, because the example endpoint has none and anyone who reaches it spends your tokens. In the panel firewall or ufw, open 443, plus 80 if your certificate client answers the HTTP challenge, limit 22 to your own address, and block everything else.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">LangChain publishes no RAM figure for a self-built service. In a test container, one uvicorn process running the demo measured about 95 MB RSS after a few requests, with PostgreSQL on top of that. Our estimate is <strong>2 GB of RAM<\/strong> for one such service plus PostgreSQL, and <strong>4 GB<\/strong> for several worker processes or for the standalone Agent Server, which adds Redis and its own queue. Disk grows with checkpoint history, so schedule the retention calls shown earlier and a nightly <span class=\"highlighted\">pg_dump<\/span> to storage off the server. Expect the API bill, not the server, to dominate the monthly cost, so set a spending limit on the provider key before the agent goes live.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This FastAPI service and its Postgres checkpointer fit a <strong><a href=\"https:\/\/hostry.com\/products\/vps\/\">Hostry KVM VPS with Ubuntu 24.04<\/a><\/strong>, deployed within seconds in one of 18+ locations; scale it up when you add workers. The Agent Server route also needs its separate license key.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Related reading: our fixes for <strong><a href=\"https:\/\/hostry.com\/blog\/n8n-queue-mode-workers-not-picking-up-jobs\/\">n8n workers that stop taking jobs<\/a><\/strong>, and <strong><a href=\"https:\/\/hostry.com\/blog\/hermes-agent-vs-openclaw-vps\/\">Hermes Agent and OpenClaw compared on a VPS<\/a><\/strong> for a ready-made assistant instead of a framework.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Is LangGraph free?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yes, the library is free and MIT-licensed, including commercial use. What costs money is LangSmith: its docs list a Plus plan or above for cloud deployment and the Enterprise plan for the self-hosted control plane.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need LangChain to use LangGraph?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. You can call any model SDK, such as OpenAI&#8217;s or Anthropic&#8217;s, directly inside a node. The package installs <span class=\"highlighted\">langchain-core<\/span> for shared types, but you never have to import LangChain&#8217;s chains, prompt templates or model wrappers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can LangGraph run without LangSmith?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yes. Tracing to LangSmith only starts when you set <span class=\"highlighted\">LANGSMITH_TRACING=true<\/span> with an API key; without those variables the bundled <span class=\"highlighted\">langsmith<\/span> client sends nothing. For observability without it, log state transitions from your own service or use the <span class=\"highlighted\">stream()<\/span> API, which emits each node&#8217;s update as it happens.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Does Google ADK only work with Gemini?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. Gemini models, and Claude models on Google&#8217;s Agent Platform, can be named directly; the Anthropic API, OpenAI and other providers go through the <span class=\"highlighted\">LiteLlm<\/span> wrapper, which needs <span class=\"highlighted\">pip install google-adk[extensions]<\/span>, and local servers through the Ollama or vLLM connectors. The managed Agent Runtime, <span class=\"highlighted\">VertexAiSessionService<\/span> and Claude models named directly need Google Cloud; <span class=\"highlighted\">DatabaseSessionService<\/span> and <span class=\"highlighted\">adk api_server<\/span> run on any server.<\/p>\n<!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons generic via filter on the_content -->","protected":false},"excerpt":{"rendered":"<p>LangGraph runs an AI agent as a graph of typed state, nodes and edges, and saves a checkpoint after every step so a run can pause, wait for a person a&#8230;<\/p>\n","protected":false},"author":6,"featured_media":2970,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13,78],"tags":[],"class_list":["post-2977","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","category-apps"],"read_time":15,"_links":{"self":[{"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/posts\/2977","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/comments?post=2977"}],"version-history":[{"count":1,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/posts\/2977\/revisions"}],"predecessor-version":[{"id":2981,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/posts\/2977\/revisions\/2981"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/media\/2970"}],"wp:attachment":[{"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/media?parent=2977"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/categories?post=2977"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hostry.com\/blog\/wp-json\/wp\/v2\/tags?post=2977"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}