Programming CentralIf you've spent any time building with LangChain, you know the power of chains. You string together...
If you've spent any time building with LangChain, you know the power of chains. You string together prompts, LLMs, and tools in a neat, linear fashion. It’s perfect for simple tasks like summarizing a document or answering a single question. But when you try to build an agent that can reason, plan, and correct itself, you hit a wall. Chains are linear; they execute once and terminate. They lack the ability to reflect.
To build truly autonomous agents, we need a computational structure that allows for loops, memory, and decision-making. Enter LangGraph.js.
This guide breaks down the architectural shift from linear chains to cyclical graphs, providing a "Hello World" multi-agent workflow you can run today.
To understand LangGraph, we first need to look at the limitations of standard LangChain.
A standard LangChain Chain is like a Serverless Function (e.g., AWS Lambda or Vercel Edge Function). It is:
This is great for isolated tasks, but terrible for agents. An agent needs to think, act, observe, and repeat.
LangGraph.js introduces the Cyclical Graph. It is like a Microservice Architecture orchestrated by a workflow engine (like AWS Step Functions).
This structure enables the ReAct pattern (Reasoning and Acting), the foundation of modern autonomous agents.
A LangGraph is built from three primitive components.
A node is a function that performs work. In an agent context, a node could be:
Edges define which node runs next.
A → B (Always go from A to B).A → B or A → C (Decide the next step based on the current state).This is the most critical concept. In a linear chain, data flows from component to component. In a cyclical graph, we need a central repository that persists across loops.
LangGraph typically uses Zod to define a state schema. This schema acts as a contract, ensuring every node knows what data is available. The state holds conversation history, tool results, and the agent's internal thoughts.
Let's build a simulation of a SaaS dashboard where two agents collaborate: a Reasoner and an Actor. The Reasoner analyzes the user request, and the Actor executes actions. They loop until the task is finished.
The graph follows a cyclical flow:
This code is self-contained. It uses @langchain/core for the graph primitives and zod for state validation.
/**
* SaaS Multi-Agent Workflow: "Hello World" Example
* Dependencies: @langchain/core, zod
*/
import { z } from "zod";
import { StateGraph, START, END } from "@langchain/core/graphs/state_graph";
import { Annotation } from "@langchain/core/langgraph";
// ==========================================
// 1. Define the Shared State Schema
// ==========================================
const GraphState = Annotation.Root({
input: Annotation<string>({
reducer: (curr, update) => update ?? curr,
default: () => "",
}),
steps: Annotation<string[]>({
reducer: (curr, update) => [...curr, update], // Accumulate history
default: () => [],
}),
status: Annotation<string>({
reducer: (curr, update) => update,
default: () => "idle",
}),
shouldContinue: Annotation<boolean>({
reducer: (curr, update) => update,
default: () => false,
}),
});
// ==========================================
// 2. Define Node Logic
// ==========================================
// The Reasoner: Analyzes input and decides if action is needed
const reasonerNode = (state: typeof GraphState.State): Partial<typeof GraphState.State> => {
console.log("🤖 [Reasoner] Thinking...");
const needsAction = state.input.toLowerCase().includes("search");
return {
steps: [...state.steps, `Reasoning: ${needsAction ? 'Action needed' : 'No action needed'}`],
status: needsAction ? "acting" : "finished",
shouldContinue: needsAction,
};
};
// The Actor: Executes the external action
const actorNode = (state: typeof GraphState.State): Partial<typeof GraphState.State> => {
console.log("⚡ [Actor] Executing action...");
// Simulate an API call or tool execution
const result = "Found result: 'LangGraph.js Documentation'";
return {
steps: [...state.steps, `Action: ${result}`],
status: "reasoning", // Loop back to reasoning
shouldContinue: true,
};
};
// ==========================================
// 3. Define Conditional Edges
// ==========================================
const decideNextStep = (state: typeof GraphState.State): string => {
if (state.status === "finished") return END;
if (state.status === "acting") return "actor_node";
return "reasoner_node";
};
// ==========================================
// 4. Build and Compile the Graph
// ==========================================
const workflow = new StateGraph(GraphState);
workflow.addNode("reasoner_node", reasonerNode);
workflow.addNode("actor_node", actorNode);
workflow.addEdge(START, "reasoner_node");
// The Cyclical Logic: Actor -> Decision -> (Actor or Reasoner)
workflow.addConditionalEdges("actor_node", decideNextStep);
workflow.addConditionalEdges("reasoner_node", (state) =>
state.shouldContinue ? "actor_node" : END
);
const app = workflow.compile();
// ==========================================
// 5. Execution
// ==========================================
const runSaaSWorkflow = async () => {
console.log("🚀 SaaS Dashboard: Initializing Agent Workflow...\n");
const initialState = {
input: "Search for latest LangGraph updates",
steps: [],
status: "idle",
shouldContinue: false,
};
// Stream outputs for real-time updates
const stream = await app.stream({ input: initialState.input });
for await (const output of stream) {
const nodeName = Object.keys(output)[0];
const nodeState = output[nodeName];
console.log(`--- Step: ${nodeName} ---`);
console.log(`Status: ${nodeState.status}`);
console.log(`Log: ${nodeState.steps[nodeState.steps.length - 1]}\n`);
}
console.log("✅ Workflow Complete.");
};
runSaaSWorkflow().catch(console.error);
Notice the reducer property in the GraphState definition:
steps: Annotation<string[]>({
reducer: (curr, update) => [...curr, update],
...
})
In a cyclical graph, state accumulates. Without a reducer, the steps array would be overwritten every time the graph loops. The reducer tells LangGraph how to merge new data into the existing state.
LangGraph introduces the Checkpointer. This is an abstraction that saves the complete graph state to a database (Redis, SQL, etc.) after every node execution.
In the code, we used app.stream().
app.invoke() in a serverless environment (like Vercel) will cause timeouts if the agent loops too many times.stream() yields data after every node. In a Next.js app, you can pipe this directly to the frontend, creating a live "thought process" UI for your users.The move from LangChain to LangGraph is the difference between a simple script and a robust application.
LangGraph.js turns AI agents from brittle prototypes into production-ready systems. By treating your agent as a graph of nodes and edges, you gain the control and reliability needed to build the next generation of AI applications.
The concepts and code demonstrated here are drawn directly from the comprehensive roadmap laid out in the book Autonomous Agents. Building Multi-Agent Systems and Workflows with LangGraph.js Amazon Link of the AI with JavaScript & TypeScript Series.
The ebook is also on Leanpub.com: https://leanpub.com/JSTypescriptAutonomousAgents.