ProvenPin Install

langgraph js stategraph: working versions (tested 2026-10-17)

Build a LangGraph StateGraph with an Annotation state, two nodes and a conditional edge, compile it with a MemorySaver checkpointer and assert state accumulation and routing across two invocations on one thread.

passed2 passed · 0 failed2026-10-17 19:19 UTC
Status
passed
Last passed
2026-10-17 19:19 UTC
Tests
2 / 2
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
@langchain/langgraph@1.5.1@langchain/core@1.2.18@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 39061946720

The code

Exactly the files that passed. Your agent can pull them with get_recipe("langgraph-js-stategraph") (install), or see them on GitHub.

package.json
{
  "name": "langgraph-js-stategraph",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "@langchain/core": "1.2.18",
    "@langchain/langgraph": "1.5.1"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
src/graph.ts
import {
  Annotation,
  END,
  MemorySaver,
  START,
  StateGraph,
} from "@langchain/langgraph";

export const GraphState = Annotation.Root({
  input: Annotation<string>({
    reducer: (_left, right) => right,
    default: () => "",
  }),
  steps: Annotation<string[]>({
    reducer: (left, right) => left.concat(right),
    default: () => [],
  }),
  route: Annotation<string>({
    reducer: (_left, right) => right,
    default: () => "",
  }),
});

export type GraphStateType = typeof GraphState.State;

export function classify(state: GraphStateType): Partial<GraphStateType> {
  const route = state.input.length % 2 === 0 ? "even" : "odd";
  return { steps: [`classify:${state.input}=${route}`], route };
}

export function handle(state: GraphStateType): Partial<GraphStateType> {
  return { steps: [`handle:${state.input}`] };
}

export function buildGraph(checkpointer: MemorySaver = new MemorySaver()) {
  return new StateGraph(GraphState)
    .addNode("classify", classify)
    .addNode("handle", handle)
    .addEdge(START, "classify")
    .addConditionalEdges("classify", (state: GraphStateType) => state.route, {
      even: "handle",
      odd: END,
    })
    .addEdge("handle", END)
    .compile({ checkpointer });
}
test/graph.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { MemorySaver } from "@langchain/langgraph";
import { buildGraph } from "../src/graph.ts";

test("routes through the conditional edge and accumulates state on one thread", async () => {
  const app = buildGraph(new MemorySaver());
  const config = { configurable: { thread_id: "thread-1" } };

  const first = await app.invoke({ input: "ab" }, config);
  assert.deepEqual(first.steps, ["classify:ab=even", "handle:ab"]);
  assert.equal(first.route, "even");

  const second = await app.invoke({ input: "abc" }, config);
  assert.deepEqual(second.steps, [
    "classify:ab=even",
    "handle:ab",
    "classify:abc=odd",
  ]);
  assert.equal(second.route, "odd");
});

test("keeps separate state per thread", async () => {
  const app = buildGraph(new MemorySaver());

  await app.invoke({ input: "abcd" }, { configurable: { thread_id: "a" } });
  const other = await app.invoke(
    { input: "abcde" },
    { configurable: { thread_id: "b" } },
  );

  assert.deepEqual(other.steps, ["classify:abcde=odd"]);
  assert.equal(other.route, "odd");
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.