[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6EAPJR05Z0RQAMTSS8RN2SH":3,"skill-same:01M6EAPJR05Z0RQAMTSS8RN2SH":62},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":34,"linkOnly":35,"markdownContent":36,"markdownBody":37,"frontMatter":38,"refName":39,"commitSha":40,"skillDirectory":41,"forksCount":42,"repositoryHtmlUrl":43,"repositoryHomepageUrl":44,"ownerHtmlUrl":45,"ownerBlogUrl":6,"ownerDescription":46,"updatedAt":47,"install":48,"resources":61},"01M6EAPJR05Z0RQAMTSS8RN2SH","langgraph-fundamentals",null,"INVOKE THIS SKILL when writing ANY LangGraph code. Covers StateGraph, state schemas, nodes, edges, Command, Send, invoke, streaming, and error handling.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FHarmeet10000\u002Fskills\u002Flanggraph-fundamentals\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33],"langgraph","langgraph fundamentals","writing","code","error handling","state","streaming","command","Nodes","error","2026-11-10 10:12:30",false,"---\nname: langgraph-fundamentals\ndescription: \"INVOKE THIS SKILL when writing ANY LangGraph code. Covers StateGraph, state schemas, nodes, edges, Command, Send, invoke, streaming, and error handling.\"\n---\n\n\u003Coverview>\nLangGraph models agent workflows as **directed graphs**:\n\n- **StateGraph**: Main class for building stateful graphs\n- **Nodes**: Functions that perform work and update state\n- **Edges**: Define execution order (static or conditional)\n- **START\u002FEND**: Special nodes marking entry and exit points\n- **State with Reducers**: Control how state updates are merged\n\nGraphs must be `compile()`d before execution.\n\u003C\u002Foverview>\n\n\u003Cdesign-methodology>\n\n### Designing a LangGraph application\n\nFollow these 5 steps when building a new graph:\n\n1. **Map out discrete steps** — sketch a flowchart of your workflow. Each step becomes a node.\n2. **Identify what each step does** — categorize nodes: LLM step, data step, action step, or user input step. For each, determine static context (prompt), dynamic context (from state), retry strategy, and desired outcome.\n3. **Design your state** — state is shared memory for all nodes. Store raw data, format prompts on-demand inside nodes.\n4. **Build your nodes** — implement each step as a function that takes state and returns partial updates.\n5. **Wire it together** — connect nodes with edges, add conditional routing, compile with a checkpointer if needed.\n\n\u003C\u002Fdesign-methodology>\n\n\u003Cwhen-to-use-langgraph>\n\n| Use LangGraph When | Use Alternatives When |\n|-------------------|----------------------|\n| Need fine-grained control over agent orchestration | Quick prototyping → LangChain agents |\n| Building complex workflows with branching\u002Floops | Simple stateless workflows → LangChain direct |\n| Require human-in-the-loop, persistence | Batteries-included features → Deep Agents |\n\n\u003C\u002Fwhen-to-use-langgraph>\n\n---\n\n## State Management\n\n\u003Cstate-update-strategies>\n\n| Need | Solution | Example |\n|------|----------|---------|\n| Overwrite value | No reducer (default) | Simple fields like counters |\n| Append to list | Reducer (operator.add \u002F concat) | Message history, logs |\n| Custom logic | Custom reducer function | Complex merging |\n\n\u003C\u002Fstate-update-strategies>\n\n\u003Cex-state-with-reducer>\n\u003Cpython>\nDefine state schema with reducers for accumulating lists and summing integers.\n```python\nfrom typing_extensions import TypedDict, Annotated\nimport operator\n\nclass State(TypedDict):\n    name: str  # Default: overwrites on update\n    messages: Annotated[list, operator.add]  # Appends to list\n    total: Annotated[int, operator.add]  # Sums integers\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse StateSchema with ReducedValue for accumulating arrays.\n```typescript\nimport { StateSchema, ReducedValue, MessagesValue } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  name: z.string(),  \u002F\u002F Default: overwrites\n  messages: MessagesValue,  \u002F\u002F Built-in for messages\n  items: new ReducedValue(\n    z.array(z.string()).default(() => []),\n    { reducer: (current, update) => current.concat(update) }\n  ),\n});\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-state-with-reducer>\n\n\u003Cfix-forgot-reducer-for-list>\n\u003Cpython>\nWithout a reducer, returning a list overwrites previous values.\n```python\n# WRONG: List will be OVERWRITTEN\nclass State(TypedDict):\n    messages: list  # No reducer!\n\n# Node 1 returns: {\"messages\": [\"A\"]}\n# Node 2 returns: {\"messages\": [\"B\"]}\n# Final: {\"messages\": [\"B\"]}  # \"A\" is LOST!\n\n# CORRECT: Use Annotated with operator.add\nfrom typing import Annotated\nimport operator\n\nclass State(TypedDict):\n    messages: Annotated[list, operator.add]\n# Final: {\"messages\": [\"A\", \"B\"]}\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nWithout ReducedValue, arrays are overwritten not appended.\n```typescript\n\u002F\u002F WRONG: Array will be overwritten\nconst State = new StateSchema({\n  items: z.array(z.string()),  \u002F\u002F No reducer!\n});\n\u002F\u002F Node 1: { items: [\"A\"] }, Node 2: { items: [\"B\"] }\n\u002F\u002F Final: { items: [\"B\"] }  \u002F\u002F A is lost!\n\n\u002F\u002F CORRECT: Use ReducedValue\nconst State = new StateSchema({\n  items: new ReducedValue(\n    z.array(z.string()).default(() => []),\n    { reducer: (current, update) => current.concat(update) }\n  ),\n});\n\u002F\u002F Final: { items: [\"A\", \"B\"] }\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-forgot-reducer-for-list>\n\n\u003Cfix-state-must-return-dict>\n\u003Cpython>\nNodes must return partial updates, not mutate and return full state.\n```python\n# WRONG: Returning entire state object\ndef my_node(state: State) -> State:\n    state[\"field\"] = \"updated\"\n    return state  # Don't mutate and return!\n\n# CORRECT: Return dict with only the updates\ndef my_node(state: State) -> dict:\n    return {\"field\": \"updated\"}\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nReturn partial updates only, not the full state object.\n```typescript\n\u002F\u002F WRONG: Returning entire state\nconst myNode = async (state: typeof State.State) => {\n  state.field = \"updated\";\n  return state;  \u002F\u002F Don't do this!\n};\n\n\u002F\u002F CORRECT: Return partial updates\nconst myNode = async (state: typeof State.State) => {\n  return { field: \"updated\" };\n};\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-state-must-return-dict>\n\n---\n\n## Nodes\n\n\u003Cnode-function-signatures>\n\nNode functions accept these arguments:\n\n\u003Cpython>\n\n| Signature | When to Use |\n|-----------|-------------|\n| `def node(state: State)` | Simple nodes that only need state |\n| `def node(state: State, config: RunnableConfig)` | Need thread_id, tags, or configurable values |\n| `def node(state: State, runtime: Runtime[Context])` | Need runtime context, store, or stream_writer |\n\n```python\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.runtime import Runtime\n\ndef plain_node(state: State):\n    return {\"results\": \"done\"}\n\ndef node_with_config(state: State, config: RunnableConfig):\n    thread_id = config[\"configurable\"][\"thread_id\"]\n    return {\"results\": f\"Thread: {thread_id}\"}\n\ndef node_with_runtime(state: State, runtime: Runtime[Context]):\n    user_id = runtime.context.user_id\n    return {\"results\": f\"User: {user_id}\"}\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\n\n| Signature | When to Use |\n|-----------|-------------|\n| `(state) => {...}` | Simple nodes that only need state |\n| `(state, config) => {...}` | Need thread_id, tags, or configurable values |\n\n```typescript\nimport { GraphNode, StateSchema } from \"@langchain\u002Flanggraph\";\n\nconst plainNode: GraphNode\u003Ctypeof State> = (state) => {\n  return { results: \"done\" };\n};\n\nconst nodeWithConfig: GraphNode\u003Ctypeof State> = (state, config) => {\n  const threadId = config?.configurable?.thread_id;\n  return { results: `Thread: ${threadId}` };\n};\n```\n\u003C\u002Ftypescript>\n\n\u003C\u002Fnode-function-signatures>\n\n---\n\n## Edges\n\n\u003Cedge-type-selection>\n\n| Need | Edge Type | When to Use |\n|------|-----------|-------------|\n| Always go to same node | `add_edge()` | Fixed, deterministic flow |\n| Route based on state | `add_conditional_edges()` | Dynamic branching |\n| Update state AND route | `Command` | Combine logic in single node |\n| Fan-out to multiple nodes | `Send` | Parallel processing with dynamic inputs |\n\n\u003C\u002Fedge-type-selection>\n\n\u003Cex-basic-graph>\n\u003Cpython>\nSimple two-node graph with linear edges.\n```python\nfrom langgraph.graph import StateGraph, START, END\nfrom typing_extensions import TypedDict\n\nclass State(TypedDict):\n    input: str\n    output: str\n\ndef process_input(state: State) -> dict:\n    return {\"output\": f\"Processed: {state['input']}\"}\n\ndef finalize(state: State) -> dict:\n    return {\"output\": state[\"output\"].upper()}\n\ngraph = (\n    StateGraph(State)\n    .add_node(\"process\", process_input)\n    .add_node(\"finalize\", finalize)\n    .add_edge(START, \"process\")\n    .add_edge(\"process\", \"finalize\")\n    .add_edge(\"finalize\", END)\n    .compile()\n)\n\nresult = graph.invoke({\"input\": \"hello\"})\nprint(result[\"output\"])  # \"PROCESSED: HELLO\"\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nChain nodes with addEdge and compile before invoking.\n```typescript\nimport { StateGraph, StateSchema, START, END } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  input: z.string(),\n  output: z.string().default(\"\"),\n});\n\nconst processInput = async (state: typeof State.State) => {\n  return { output: `Processed: ${state.input}` };\n};\n\nconst finalize = async (state: typeof State.State) => {\n  return { output: state.output.toUpperCase() };\n};\n\nconst graph = new StateGraph(State)\n  .addNode(\"process\", processInput)\n  .addNode(\"finalize\", finalize)\n  .addEdge(START, \"process\")\n  .addEdge(\"process\", \"finalize\")\n  .addEdge(\"finalize\", END)\n  .compile();\n\nconst result = await graph.invoke({ input: \"hello\" });\nconsole.log(result.output);  \u002F\u002F \"PROCESSED: HELLO\"\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-basic-graph>\n\n\u003Cex-conditional-edges>\n\u003Cpython>\nRoute to different nodes based on state with conditional edges.\n```python\nfrom typing import Literal\nfrom langgraph.graph import StateGraph, START, END\n\nclass State(TypedDict):\n    query: str\n    route: str\n    result: str\n\ndef classify(state: State) -> dict:\n    if \"weather\" in state[\"query\"].lower():\n        return {\"route\": \"weather\"}\n    return {\"route\": \"general\"}\n\ndef route_query(state: State) -> Literal[\"weather\", \"general\"]:\n    return state[\"route\"]\n\ngraph = (\n    StateGraph(State)\n    .add_node(\"classify\", classify)\n    .add_node(\"weather\", lambda s: {\"result\": \"Sunny, 72F\"})\n    .add_node(\"general\", lambda s: {\"result\": \"General response\"})\n    .add_edge(START, \"classify\")\n    .add_conditional_edges(\"classify\", route_query, [\"weather\", \"general\"])\n    .add_edge(\"weather\", END)\n    .add_edge(\"general\", END)\n    .compile()\n)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\naddConditionalEdges routes based on function return value.\n```typescript\nimport { StateGraph, StateSchema, START, END } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  query: z.string(),\n  route: z.string().default(\"\"),\n  result: z.string().default(\"\"),\n});\n\nconst classify = async (state: typeof State.State) => {\n  if (state.query.toLowerCase().includes(\"weather\")) {\n    return { route: \"weather\" };\n  }\n  return { route: \"general\" };\n};\n\nconst routeQuery = (state: typeof State.State) => state.route;\n\nconst graph = new StateGraph(State)\n  .addNode(\"classify\", classify)\n  .addNode(\"weather\", async () => ({ result: \"Sunny, 72F\" }))\n  .addNode(\"general\", async () => ({ result: \"General response\" }))\n  .addEdge(START, \"classify\")\n  .addConditionalEdges(\"classify\", routeQuery, [\"weather\", \"general\"])\n  .addEdge(\"weather\", END)\n  .addEdge(\"general\", END)\n  .compile();\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-conditional-edges>\n\n---\n\n## Command\n\nCommand combines state updates and routing in a single return value. Fields:\n- **`update`**: State updates to apply (like returning a dict from a node)\n- **`goto`**: Node name(s) to navigate to next\n- **`resume`**: Value to resume after `interrupt()` — see human-in-the-loop skill\n\n\u003Cex-command-state-and-routing>\n\u003Cpython>\nCommand lets you update state AND choose next node in one return.\n```python\nfrom langgraph.types import Command\nfrom typing import Literal\n\nclass State(TypedDict):\n    count: int\n    result: str\n\ndef node_a(state: State) -> Command[Literal[\"node_b\", \"node_c\"]]:\n    \"\"\"Update state AND decide next node in one return.\"\"\"\n    new_count = state[\"count\"] + 1\n    if new_count > 5:\n        return Command(update={\"count\": new_count}, goto=\"node_c\")\n    return Command(update={\"count\": new_count}, goto=\"node_b\")\n\ngraph = (\n    StateGraph(State)\n    .add_node(\"node_a\", node_a)\n    .add_node(\"node_b\", lambda s: {\"result\": \"B\"})\n    .add_node(\"node_c\", lambda s: {\"result\": \"C\"})\n    .add_edge(START, \"node_a\")\n    .add_edge(\"node_b\", END)\n    .add_edge(\"node_c\", END)\n    .compile()\n)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nReturn Command with update and goto to combine state change with routing.\n```typescript\nimport { StateGraph, StateSchema, START, END, Command } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  count: z.number().default(0),\n  result: z.string().default(\"\"),\n});\n\nconst nodeA = async (state: typeof State.State) => {\n  const newCount = state.count + 1;\n  if (newCount > 5) {\n    return new Command({ update: { count: newCount }, goto: \"node_c\" });\n  }\n  return new Command({ update: { count: newCount }, goto: \"node_b\" });\n};\n\nconst graph = new StateGraph(State)\n  .addNode(\"node_a\", nodeA, { ends: [\"node_b\", \"node_c\"] })\n  .addNode(\"node_b\", async () => ({ result: \"B\" }))\n  .addNode(\"node_c\", async () => ({ result: \"C\" }))\n  .addEdge(START, \"node_a\")\n  .addEdge(\"node_b\", END)\n  .addEdge(\"node_c\", END)\n  .compile();\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-command-state-and-routing>\n\n\u003Ccommand-return-type-annotations>\n\n**Python**: Use `Command[Literal[\"node_a\", \"node_b\"]]` as the return type annotation to declare valid goto destinations.\n\n**TypeScript**: Pass `{ ends: [\"node_a\", \"node_b\"] }` as the third argument to `addNode` to declare valid goto destinations.\n\n\u003C\u002Fcommand-return-type-annotations>\n\n\u003Cwarning-command-static-edges>\n\n**Warning**: `Command` only adds **dynamic** edges — static edges defined with `add_edge` \u002F `addEdge` still execute. If `node_a` returns `Command(goto=\"node_c\")` and you also have `graph.add_edge(\"node_a\", \"node_b\")`, **both** `node_b` and `node_c` will run.\n\n\u003C\u002Fwarning-command-static-edges>\n\n---\n\n## Send API\n\nFan-out with `Send`: return `[Send(\"worker\", {...})]` from a conditional edge to spawn parallel workers. Requires a reducer on the results field.\n\n\u003Cex-orchestrator-worker>\n\u003Cpython>\nFan out tasks to parallel workers using the Send API and aggregate results.\n```python\nfrom langgraph.types import Send\nfrom typing import Annotated\nimport operator\n\nclass OrchestratorState(TypedDict):\n    tasks: list[str]\n    results: Annotated[list, operator.add]\n    summary: str\n\ndef orchestrator(state: OrchestratorState):\n    \"\"\"Fan out tasks to workers.\"\"\"\n    return [Send(\"worker\", {\"task\": task}) for task in state[\"tasks\"]]\n\ndef worker(state: dict) -> dict:\n    return {\"results\": [f\"Completed: {state['task']}\"]}\n\ndef synthesize(state: OrchestratorState) -> dict:\n    return {\"summary\": f\"Processed {len(state['results'])} tasks\"}\n\ngraph = (\n    StateGraph(OrchestratorState)\n    .add_node(\"worker\", worker)\n    .add_node(\"synthesize\", synthesize)\n    .add_conditional_edges(START, orchestrator, [\"worker\"])\n    .add_edge(\"worker\", \"synthesize\")\n    .add_edge(\"synthesize\", END)\n    .compile()\n)\n\nresult = graph.invoke({\"tasks\": [\"Task A\", \"Task B\", \"Task C\"]})\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nFan out tasks to parallel workers using the Send API and aggregate results.\n```typescript\nimport { Send, StateGraph, StateSchema, ReducedValue, START, END } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  tasks: z.array(z.string()),\n  results: new ReducedValue(\n    z.array(z.string()).default(() => []),\n    { reducer: (curr, upd) => curr.concat(upd) }\n  ),\n  summary: z.string().default(\"\"),\n});\n\nconst orchestrator = (state: typeof State.State) => {\n  return state.tasks.map((task) => new Send(\"worker\", { task }));\n};\n\nconst worker = async (state: { task: string }) => {\n  return { results: [`Completed: ${state.task}`] };\n};\n\nconst synthesize = async (state: typeof State.State) => {\n  return { summary: `Processed ${state.results.length} tasks` };\n};\n\nconst graph = new StateGraph(State)\n  .addNode(\"worker\", worker)\n  .addNode(\"synthesize\", synthesize)\n  .addConditionalEdges(START, orchestrator, [\"worker\"])\n  .addEdge(\"worker\", \"synthesize\")\n  .addEdge(\"synthesize\", END)\n  .compile();\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-orchestrator-worker>\n\n\u003Cfix-send-accumulator>\n\u003Cpython>\nUse a reducer to accumulate parallel worker results (otherwise last worker overwrites).\n```python\n# WRONG: No reducer - last worker overwrites\nclass State(TypedDict):\n    results: list\n\n# CORRECT\nclass State(TypedDict):\n    results: Annotated[list, operator.add]  # Accumulates\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse ReducedValue to accumulate parallel worker results.\n```typescript\n\u002F\u002F WRONG: No reducer\nconst State = new StateSchema({ results: z.array(z.string()) });\n\n\u002F\u002F CORRECT\nconst State = new StateSchema({\n  results: new ReducedValue(z.array(z.string()).default(() => []), { reducer: (curr, upd) => curr.concat(upd) }),\n});\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-send-accumulator>\n\n---\n\n## Running Graphs: Invoke and Stream\n\n\u003Cinvoke-basics>\n\nCall `graph.invoke(input, config)` to run a graph to completion and return the final state.\n\n\u003Cpython>\n```python\nresult = graph.invoke({\"input\": \"hello\"})\n# With config (for persistence, tags, etc.)\nresult = graph.invoke({\"input\": \"hello\"}, {\"configurable\": {\"thread_id\": \"1\"}})\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\n```typescript\nconst result = await graph.invoke({ input: \"hello\" });\n\u002F\u002F With config\nconst result = await graph.invoke({ input: \"hello\" }, { configurable: { thread_id: \"1\" } });\n```\n\u003C\u002Ftypescript>\n\n\u003C\u002Finvoke-basics>\n\n\u003Cstream-mode-selection>\n\n| Mode | What it Streams | Use Case |\n|------|----------------|----------|\n| `values` | Full state after each step | Monitor complete state |\n| `updates` | State deltas | Track incremental updates |\n| `messages` | LLM tokens + metadata | Chat UIs |\n| `custom` | User-defined data | Progress indicators |\n\n\u003C\u002Fstream-mode-selection>\n\n\u003Cex-stream-llm-tokens>\n\u003Cpython>\nStream LLM tokens in real-time for chat UI display.\n```python\nfor chunk in graph.stream(\n    {\"messages\": [HumanMessage(\"Hello\")]},\n    stream_mode=\"messages\"\n):\n    token, metadata = chunk\n    if hasattr(token, \"content\"):\n        print(token.content, end=\"\", flush=True)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nStream LLM tokens in real-time for chat UI display.\n```typescript\nfor await (const chunk of graph.stream(\n  { messages: [new HumanMessage(\"Hello\")] },\n  { streamMode: \"messages\" }\n)) {\n  const [token, metadata] = chunk;\n  if (token.content) {\n    process.stdout.write(token.content);\n  }\n}\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-stream-llm-tokens>\n\n\u003Cex-stream-custom-data>\n\u003Cpython>\nEmit custom progress updates from within nodes using the stream writer.\n```python\nfrom langgraph.config import get_stream_writer\n\ndef my_node(state):\n    writer = get_stream_writer()\n    writer(\"Processing step 1...\")\n    # Do work\n    writer(\"Complete!\")\n    return {\"result\": \"done\"}\n\nfor chunk in graph.stream({\"data\": \"test\"}, stream_mode=\"custom\"):\n    print(chunk)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nEmit custom progress updates from within nodes using the stream writer.\n```typescript\nimport { getWriter } from \"@langchain\u002Flanggraph\";\n\nconst myNode = async (state: typeof State.State) => {\n  const writer = getWriter();\n  writer(\"Processing step 1...\");\n  \u002F\u002F Do work\n  writer(\"Complete!\");\n  return { result: \"done\" };\n};\n\nfor await (const chunk of graph.stream({ data: \"test\" }, { streamMode: \"custom\" })) {\n  console.log(chunk);\n}\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-stream-custom-data>\n\n---\n\n## Error Handling\n\nMatch the error type to the right handler:\n\n\u003Cerror-handling-table>\n\n| Error Type | Who Fixes | Strategy | Example |\n|---|---|---|---|\n| Transient (network, rate limits) | System | `RetryPolicy(max_attempts=3)` | `add_node(..., retry_policy=...)` |\n| LLM-recoverable (tool failures) | LLM | `ToolNode(tools, handle_tool_errors=True)` | Error returned as ToolMessage |\n| User-fixable (missing info) | Human | `interrupt({\"message\": ...})` | Collect missing data (see HITL skill) |\n| Unexpected | Developer | Let bubble up | `raise` |\n\n\u003C\u002Ferror-handling-table>\n\n\u003Cex-retry-policy>\n\u003Cpython>\nUse RetryPolicy for transient errors (network issues, rate limits).\n```python\nfrom langgraph.types import RetryPolicy\n\nworkflow.add_node(\n    \"search_documentation\",\n    search_documentation,\n    retry_policy=RetryPolicy(max_attempts=3, initial_interval=1.0)\n)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse retryPolicy for transient errors.\n```typescript\nworkflow.addNode(\n  \"searchDocumentation\",\n  searchDocumentation,\n  {\n    retryPolicy: { maxAttempts: 3, initialInterval: 1.0 },\n  },\n);\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-retry-policy>\n\n\u003Cex-tool-node-error-handling>\n\u003Cpython>\nUse ToolNode from langgraph.prebuilt to handle tool execution and errors. When handle_tool_errors=True, errors are returned as ToolMessages so the LLM can recover.\n```python\nfrom langgraph.prebuilt import ToolNode\n\ntool_node = ToolNode(tools, handle_tool_errors=True)\n\nworkflow.add_node(\"tools\", tool_node)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse ToolNode from @langchain\u002Flanggraph\u002Fprebuilt to handle tool execution and errors. When handleToolErrors is true, errors are returned as ToolMessages so the LLM can recover.\n```typescript\nimport { ToolNode } from \"@langchain\u002Flanggraph\u002Fprebuilt\";\n\nconst toolNode = new ToolNode(tools, { handleToolErrors: true });\n\nworkflow.addNode(\"tools\", toolNode);\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-tool-node-error-handling>\n\n---\n\n## Common Fixes\n\n\u003Cfix-compile-before-execution>\n\u003Cpython>\nMust compile() to get executable graph.\n```python\n# WRONG\nbuilder.invoke({\"input\": \"test\"})  # AttributeError!\n\n# CORRECT\ngraph = builder.compile()\ngraph.invoke({\"input\": \"test\"})\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nMust compile() to get executable graph.\n```typescript\n\u002F\u002F WRONG\nawait builder.invoke({ input: \"test\" });\n\n\u002F\u002F CORRECT\nconst graph = builder.compile();\nawait graph.invoke({ input: \"test\" });\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-compile-before-execution>\n\n\u003Cfix-infinite-loop-needs-exit>\n\u003Cpython>\nProvide conditional path to END to avoid infinite loops.\n```python\n# WRONG: Loops forever\nbuilder.add_edge(\"node_a\", \"node_b\")\nbuilder.add_edge(\"node_b\", \"node_a\")\n\n# CORRECT\ndef should_continue(state):\n    return END if state[\"count\"] > 10 else \"node_b\"\nbuilder.add_conditional_edges(\"node_a\", should_continue)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse conditional edges with END return to break loops.\n```typescript\n\u002F\u002F WRONG: Loops forever\nbuilder.addEdge(\"node_a\", \"node_b\").addEdge(\"node_b\", \"node_a\");\n\n\u002F\u002F CORRECT\nbuilder.addConditionalEdges(\"node_a\", (state) => state.count > 10 ? END : \"node_b\");\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-infinite-loop-needs-exit>\n\n\u003Cfix-common-mistakes>\nOther common mistakes:\n```python\n# Router must return names of nodes that exist in the graph\nbuilder.add_node(\"my_node\", func)  # Add node BEFORE referencing in edges\nbuilder.add_conditional_edges(\"node_a\", router, [\"my_node\"])\n\n# Command return type needs Literal for routing destinations (Python)\ndef node_a(state) -> Command[Literal[\"node_b\", \"node_c\"]]:\n    return Command(goto=\"node_b\")\n\n# START is entry-only - cannot route back to it\nbuilder.add_edge(\"node_a\", START)  # WRONG!\nbuilder.add_edge(\"node_a\", \"entry\")  # Use a named entry node instead\n\n# Reducer expects matching types\nreturn {\"items\": [\"item\"]}  # List for list reducer, not a string\n```\n```typescript\n\u002F\u002F Always await graph.invoke() - it returns a Promise\nconst result = await graph.invoke({ input: \"test\" });\n\n\u002F\u002F TS Command nodes need { ends } to declare routing destinations\nbuilder.addNode(\"router\", routerFn, { ends: [\"node_b\", \"node_c\"] });\n```\n\u003C\u002Ffix-common-mistakes>\n\n\u003Cboundaries>\n### What You Should NOT Do\n\n- Mutate state directly — always return partial update dicts from nodes\n- Route back to START — it's entry-only; use a named node instead\n- Forget reducers on list fields — without one, last write wins\n- Mix static edges with Command goto without understanding both will execute\n\u003C\u002Fboundaries>\n","\u003Coverview>\nLangGraph models agent workflows as **directed graphs**:\n\n- **StateGraph**: Main class for building stateful graphs\n- **Nodes**: Functions that perform work and update state\n- **Edges**: Define execution order (static or conditional)\n- **START\u002FEND**: Special nodes marking entry and exit points\n- **State with Reducers**: Control how state updates are merged\n\nGraphs must be `compile()`d before execution.\n\u003C\u002Foverview>\n\n\u003Cdesign-methodology>\n\n### Designing a LangGraph application\n\nFollow these 5 steps when building a new graph:\n\n1. **Map out discrete steps** — sketch a flowchart of your workflow. Each step becomes a node.\n2. **Identify what each step does** — categorize nodes: LLM step, data step, action step, or user input step. For each, determine static context (prompt), dynamic context (from state), retry strategy, and desired outcome.\n3. **Design your state** — state is shared memory for all nodes. Store raw data, format prompts on-demand inside nodes.\n4. **Build your nodes** — implement each step as a function that takes state and returns partial updates.\n5. **Wire it together** — connect nodes with edges, add conditional routing, compile with a checkpointer if needed.\n\n\u003C\u002Fdesign-methodology>\n\n\u003Cwhen-to-use-langgraph>\n\n| Use LangGraph When | Use Alternatives When |\n|-------------------|----------------------|\n| Need fine-grained control over agent orchestration | Quick prototyping → LangChain agents |\n| Building complex workflows with branching\u002Floops | Simple stateless workflows → LangChain direct |\n| Require human-in-the-loop, persistence | Batteries-included features → Deep Agents |\n\n\u003C\u002Fwhen-to-use-langgraph>\n\n---\n\n## State Management\n\n\u003Cstate-update-strategies>\n\n| Need | Solution | Example |\n|------|----------|---------|\n| Overwrite value | No reducer (default) | Simple fields like counters |\n| Append to list | Reducer (operator.add \u002F concat) | Message history, logs |\n| Custom logic | Custom reducer function | Complex merging |\n\n\u003C\u002Fstate-update-strategies>\n\n\u003Cex-state-with-reducer>\n\u003Cpython>\nDefine state schema with reducers for accumulating lists and summing integers.\n```python\nfrom typing_extensions import TypedDict, Annotated\nimport operator\n\nclass State(TypedDict):\n    name: str  # Default: overwrites on update\n    messages: Annotated[list, operator.add]  # Appends to list\n    total: Annotated[int, operator.add]  # Sums integers\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse StateSchema with ReducedValue for accumulating arrays.\n```typescript\nimport { StateSchema, ReducedValue, MessagesValue } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  name: z.string(),  \u002F\u002F Default: overwrites\n  messages: MessagesValue,  \u002F\u002F Built-in for messages\n  items: new ReducedValue(\n    z.array(z.string()).default(() => []),\n    { reducer: (current, update) => current.concat(update) }\n  ),\n});\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-state-with-reducer>\n\n\u003Cfix-forgot-reducer-for-list>\n\u003Cpython>\nWithout a reducer, returning a list overwrites previous values.\n```python\n# WRONG: List will be OVERWRITTEN\nclass State(TypedDict):\n    messages: list  # No reducer!\n\n# Node 1 returns: {\"messages\": [\"A\"]}\n# Node 2 returns: {\"messages\": [\"B\"]}\n# Final: {\"messages\": [\"B\"]}  # \"A\" is LOST!\n\n# CORRECT: Use Annotated with operator.add\nfrom typing import Annotated\nimport operator\n\nclass State(TypedDict):\n    messages: Annotated[list, operator.add]\n# Final: {\"messages\": [\"A\", \"B\"]}\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nWithout ReducedValue, arrays are overwritten not appended.\n```typescript\n\u002F\u002F WRONG: Array will be overwritten\nconst State = new StateSchema({\n  items: z.array(z.string()),  \u002F\u002F No reducer!\n});\n\u002F\u002F Node 1: { items: [\"A\"] }, Node 2: { items: [\"B\"] }\n\u002F\u002F Final: { items: [\"B\"] }  \u002F\u002F A is lost!\n\n\u002F\u002F CORRECT: Use ReducedValue\nconst State = new StateSchema({\n  items: new ReducedValue(\n    z.array(z.string()).default(() => []),\n    { reducer: (current, update) => current.concat(update) }\n  ),\n});\n\u002F\u002F Final: { items: [\"A\", \"B\"] }\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-forgot-reducer-for-list>\n\n\u003Cfix-state-must-return-dict>\n\u003Cpython>\nNodes must return partial updates, not mutate and return full state.\n```python\n# WRONG: Returning entire state object\ndef my_node(state: State) -> State:\n    state[\"field\"] = \"updated\"\n    return state  # Don't mutate and return!\n\n# CORRECT: Return dict with only the updates\ndef my_node(state: State) -> dict:\n    return {\"field\": \"updated\"}\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nReturn partial updates only, not the full state object.\n```typescript\n\u002F\u002F WRONG: Returning entire state\nconst myNode = async (state: typeof State.State) => {\n  state.field = \"updated\";\n  return state;  \u002F\u002F Don't do this!\n};\n\n\u002F\u002F CORRECT: Return partial updates\nconst myNode = async (state: typeof State.State) => {\n  return { field: \"updated\" };\n};\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-state-must-return-dict>\n\n---\n\n## Nodes\n\n\u003Cnode-function-signatures>\n\nNode functions accept these arguments:\n\n\u003Cpython>\n\n| Signature | When to Use |\n|-----------|-------------|\n| `def node(state: State)` | Simple nodes that only need state |\n| `def node(state: State, config: RunnableConfig)` | Need thread_id, tags, or configurable values |\n| `def node(state: State, runtime: Runtime[Context])` | Need runtime context, store, or stream_writer |\n\n```python\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.runtime import Runtime\n\ndef plain_node(state: State):\n    return {\"results\": \"done\"}\n\ndef node_with_config(state: State, config: RunnableConfig):\n    thread_id = config[\"configurable\"][\"thread_id\"]\n    return {\"results\": f\"Thread: {thread_id}\"}\n\ndef node_with_runtime(state: State, runtime: Runtime[Context]):\n    user_id = runtime.context.user_id\n    return {\"results\": f\"User: {user_id}\"}\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\n\n| Signature | When to Use |\n|-----------|-------------|\n| `(state) => {...}` | Simple nodes that only need state |\n| `(state, config) => {...}` | Need thread_id, tags, or configurable values |\n\n```typescript\nimport { GraphNode, StateSchema } from \"@langchain\u002Flanggraph\";\n\nconst plainNode: GraphNode\u003Ctypeof State> = (state) => {\n  return { results: \"done\" };\n};\n\nconst nodeWithConfig: GraphNode\u003Ctypeof State> = (state, config) => {\n  const threadId = config?.configurable?.thread_id;\n  return { results: `Thread: ${threadId}` };\n};\n```\n\u003C\u002Ftypescript>\n\n\u003C\u002Fnode-function-signatures>\n\n---\n\n## Edges\n\n\u003Cedge-type-selection>\n\n| Need | Edge Type | When to Use |\n|------|-----------|-------------|\n| Always go to same node | `add_edge()` | Fixed, deterministic flow |\n| Route based on state | `add_conditional_edges()` | Dynamic branching |\n| Update state AND route | `Command` | Combine logic in single node |\n| Fan-out to multiple nodes | `Send` | Parallel processing with dynamic inputs |\n\n\u003C\u002Fedge-type-selection>\n\n\u003Cex-basic-graph>\n\u003Cpython>\nSimple two-node graph with linear edges.\n```python\nfrom langgraph.graph import StateGraph, START, END\nfrom typing_extensions import TypedDict\n\nclass State(TypedDict):\n    input: str\n    output: str\n\ndef process_input(state: State) -> dict:\n    return {\"output\": f\"Processed: {state['input']}\"}\n\ndef finalize(state: State) -> dict:\n    return {\"output\": state[\"output\"].upper()}\n\ngraph = (\n    StateGraph(State)\n    .add_node(\"process\", process_input)\n    .add_node(\"finalize\", finalize)\n    .add_edge(START, \"process\")\n    .add_edge(\"process\", \"finalize\")\n    .add_edge(\"finalize\", END)\n    .compile()\n)\n\nresult = graph.invoke({\"input\": \"hello\"})\nprint(result[\"output\"])  # \"PROCESSED: HELLO\"\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nChain nodes with addEdge and compile before invoking.\n```typescript\nimport { StateGraph, StateSchema, START, END } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  input: z.string(),\n  output: z.string().default(\"\"),\n});\n\nconst processInput = async (state: typeof State.State) => {\n  return { output: `Processed: ${state.input}` };\n};\n\nconst finalize = async (state: typeof State.State) => {\n  return { output: state.output.toUpperCase() };\n};\n\nconst graph = new StateGraph(State)\n  .addNode(\"process\", processInput)\n  .addNode(\"finalize\", finalize)\n  .addEdge(START, \"process\")\n  .addEdge(\"process\", \"finalize\")\n  .addEdge(\"finalize\", END)\n  .compile();\n\nconst result = await graph.invoke({ input: \"hello\" });\nconsole.log(result.output);  \u002F\u002F \"PROCESSED: HELLO\"\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-basic-graph>\n\n\u003Cex-conditional-edges>\n\u003Cpython>\nRoute to different nodes based on state with conditional edges.\n```python\nfrom typing import Literal\nfrom langgraph.graph import StateGraph, START, END\n\nclass State(TypedDict):\n    query: str\n    route: str\n    result: str\n\ndef classify(state: State) -> dict:\n    if \"weather\" in state[\"query\"].lower():\n        return {\"route\": \"weather\"}\n    return {\"route\": \"general\"}\n\ndef route_query(state: State) -> Literal[\"weather\", \"general\"]:\n    return state[\"route\"]\n\ngraph = (\n    StateGraph(State)\n    .add_node(\"classify\", classify)\n    .add_node(\"weather\", lambda s: {\"result\": \"Sunny, 72F\"})\n    .add_node(\"general\", lambda s: {\"result\": \"General response\"})\n    .add_edge(START, \"classify\")\n    .add_conditional_edges(\"classify\", route_query, [\"weather\", \"general\"])\n    .add_edge(\"weather\", END)\n    .add_edge(\"general\", END)\n    .compile()\n)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\naddConditionalEdges routes based on function return value.\n```typescript\nimport { StateGraph, StateSchema, START, END } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  query: z.string(),\n  route: z.string().default(\"\"),\n  result: z.string().default(\"\"),\n});\n\nconst classify = async (state: typeof State.State) => {\n  if (state.query.toLowerCase().includes(\"weather\")) {\n    return { route: \"weather\" };\n  }\n  return { route: \"general\" };\n};\n\nconst routeQuery = (state: typeof State.State) => state.route;\n\nconst graph = new StateGraph(State)\n  .addNode(\"classify\", classify)\n  .addNode(\"weather\", async () => ({ result: \"Sunny, 72F\" }))\n  .addNode(\"general\", async () => ({ result: \"General response\" }))\n  .addEdge(START, \"classify\")\n  .addConditionalEdges(\"classify\", routeQuery, [\"weather\", \"general\"])\n  .addEdge(\"weather\", END)\n  .addEdge(\"general\", END)\n  .compile();\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-conditional-edges>\n\n---\n\n## Command\n\nCommand combines state updates and routing in a single return value. Fields:\n- **`update`**: State updates to apply (like returning a dict from a node)\n- **`goto`**: Node name(s) to navigate to next\n- **`resume`**: Value to resume after `interrupt()` — see human-in-the-loop skill\n\n\u003Cex-command-state-and-routing>\n\u003Cpython>\nCommand lets you update state AND choose next node in one return.\n```python\nfrom langgraph.types import Command\nfrom typing import Literal\n\nclass State(TypedDict):\n    count: int\n    result: str\n\ndef node_a(state: State) -> Command[Literal[\"node_b\", \"node_c\"]]:\n    \"\"\"Update state AND decide next node in one return.\"\"\"\n    new_count = state[\"count\"] + 1\n    if new_count > 5:\n        return Command(update={\"count\": new_count}, goto=\"node_c\")\n    return Command(update={\"count\": new_count}, goto=\"node_b\")\n\ngraph = (\n    StateGraph(State)\n    .add_node(\"node_a\", node_a)\n    .add_node(\"node_b\", lambda s: {\"result\": \"B\"})\n    .add_node(\"node_c\", lambda s: {\"result\": \"C\"})\n    .add_edge(START, \"node_a\")\n    .add_edge(\"node_b\", END)\n    .add_edge(\"node_c\", END)\n    .compile()\n)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nReturn Command with update and goto to combine state change with routing.\n```typescript\nimport { StateGraph, StateSchema, START, END, Command } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  count: z.number().default(0),\n  result: z.string().default(\"\"),\n});\n\nconst nodeA = async (state: typeof State.State) => {\n  const newCount = state.count + 1;\n  if (newCount > 5) {\n    return new Command({ update: { count: newCount }, goto: \"node_c\" });\n  }\n  return new Command({ update: { count: newCount }, goto: \"node_b\" });\n};\n\nconst graph = new StateGraph(State)\n  .addNode(\"node_a\", nodeA, { ends: [\"node_b\", \"node_c\"] })\n  .addNode(\"node_b\", async () => ({ result: \"B\" }))\n  .addNode(\"node_c\", async () => ({ result: \"C\" }))\n  .addEdge(START, \"node_a\")\n  .addEdge(\"node_b\", END)\n  .addEdge(\"node_c\", END)\n  .compile();\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-command-state-and-routing>\n\n\u003Ccommand-return-type-annotations>\n\n**Python**: Use `Command[Literal[\"node_a\", \"node_b\"]]` as the return type annotation to declare valid goto destinations.\n\n**TypeScript**: Pass `{ ends: [\"node_a\", \"node_b\"] }` as the third argument to `addNode` to declare valid goto destinations.\n\n\u003C\u002Fcommand-return-type-annotations>\n\n\u003Cwarning-command-static-edges>\n\n**Warning**: `Command` only adds **dynamic** edges — static edges defined with `add_edge` \u002F `addEdge` still execute. If `node_a` returns `Command(goto=\"node_c\")` and you also have `graph.add_edge(\"node_a\", \"node_b\")`, **both** `node_b` and `node_c` will run.\n\n\u003C\u002Fwarning-command-static-edges>\n\n---\n\n## Send API\n\nFan-out with `Send`: return `[Send(\"worker\", {...})]` from a conditional edge to spawn parallel workers. Requires a reducer on the results field.\n\n\u003Cex-orchestrator-worker>\n\u003Cpython>\nFan out tasks to parallel workers using the Send API and aggregate results.\n```python\nfrom langgraph.types import Send\nfrom typing import Annotated\nimport operator\n\nclass OrchestratorState(TypedDict):\n    tasks: list[str]\n    results: Annotated[list, operator.add]\n    summary: str\n\ndef orchestrator(state: OrchestratorState):\n    \"\"\"Fan out tasks to workers.\"\"\"\n    return [Send(\"worker\", {\"task\": task}) for task in state[\"tasks\"]]\n\ndef worker(state: dict) -> dict:\n    return {\"results\": [f\"Completed: {state['task']}\"]}\n\ndef synthesize(state: OrchestratorState) -> dict:\n    return {\"summary\": f\"Processed {len(state['results'])} tasks\"}\n\ngraph = (\n    StateGraph(OrchestratorState)\n    .add_node(\"worker\", worker)\n    .add_node(\"synthesize\", synthesize)\n    .add_conditional_edges(START, orchestrator, [\"worker\"])\n    .add_edge(\"worker\", \"synthesize\")\n    .add_edge(\"synthesize\", END)\n    .compile()\n)\n\nresult = graph.invoke({\"tasks\": [\"Task A\", \"Task B\", \"Task C\"]})\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nFan out tasks to parallel workers using the Send API and aggregate results.\n```typescript\nimport { Send, StateGraph, StateSchema, ReducedValue, START, END } from \"@langchain\u002Flanggraph\";\nimport { z } from \"zod\";\n\nconst State = new StateSchema({\n  tasks: z.array(z.string()),\n  results: new ReducedValue(\n    z.array(z.string()).default(() => []),\n    { reducer: (curr, upd) => curr.concat(upd) }\n  ),\n  summary: z.string().default(\"\"),\n});\n\nconst orchestrator = (state: typeof State.State) => {\n  return state.tasks.map((task) => new Send(\"worker\", { task }));\n};\n\nconst worker = async (state: { task: string }) => {\n  return { results: [`Completed: ${state.task}`] };\n};\n\nconst synthesize = async (state: typeof State.State) => {\n  return { summary: `Processed ${state.results.length} tasks` };\n};\n\nconst graph = new StateGraph(State)\n  .addNode(\"worker\", worker)\n  .addNode(\"synthesize\", synthesize)\n  .addConditionalEdges(START, orchestrator, [\"worker\"])\n  .addEdge(\"worker\", \"synthesize\")\n  .addEdge(\"synthesize\", END)\n  .compile();\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-orchestrator-worker>\n\n\u003Cfix-send-accumulator>\n\u003Cpython>\nUse a reducer to accumulate parallel worker results (otherwise last worker overwrites).\n```python\n# WRONG: No reducer - last worker overwrites\nclass State(TypedDict):\n    results: list\n\n# CORRECT\nclass State(TypedDict):\n    results: Annotated[list, operator.add]  # Accumulates\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse ReducedValue to accumulate parallel worker results.\n```typescript\n\u002F\u002F WRONG: No reducer\nconst State = new StateSchema({ results: z.array(z.string()) });\n\n\u002F\u002F CORRECT\nconst State = new StateSchema({\n  results: new ReducedValue(z.array(z.string()).default(() => []), { reducer: (curr, upd) => curr.concat(upd) }),\n});\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-send-accumulator>\n\n---\n\n## Running Graphs: Invoke and Stream\n\n\u003Cinvoke-basics>\n\nCall `graph.invoke(input, config)` to run a graph to completion and return the final state.\n\n\u003Cpython>\n```python\nresult = graph.invoke({\"input\": \"hello\"})\n# With config (for persistence, tags, etc.)\nresult = graph.invoke({\"input\": \"hello\"}, {\"configurable\": {\"thread_id\": \"1\"}})\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\n```typescript\nconst result = await graph.invoke({ input: \"hello\" });\n\u002F\u002F With config\nconst result = await graph.invoke({ input: \"hello\" }, { configurable: { thread_id: \"1\" } });\n```\n\u003C\u002Ftypescript>\n\n\u003C\u002Finvoke-basics>\n\n\u003Cstream-mode-selection>\n\n| Mode | What it Streams | Use Case |\n|------|----------------|----------|\n| `values` | Full state after each step | Monitor complete state |\n| `updates` | State deltas | Track incremental updates |\n| `messages` | LLM tokens + metadata | Chat UIs |\n| `custom` | User-defined data | Progress indicators |\n\n\u003C\u002Fstream-mode-selection>\n\n\u003Cex-stream-llm-tokens>\n\u003Cpython>\nStream LLM tokens in real-time for chat UI display.\n```python\nfor chunk in graph.stream(\n    {\"messages\": [HumanMessage(\"Hello\")]},\n    stream_mode=\"messages\"\n):\n    token, metadata = chunk\n    if hasattr(token, \"content\"):\n        print(token.content, end=\"\", flush=True)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nStream LLM tokens in real-time for chat UI display.\n```typescript\nfor await (const chunk of graph.stream(\n  { messages: [new HumanMessage(\"Hello\")] },\n  { streamMode: \"messages\" }\n)) {\n  const [token, metadata] = chunk;\n  if (token.content) {\n    process.stdout.write(token.content);\n  }\n}\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-stream-llm-tokens>\n\n\u003Cex-stream-custom-data>\n\u003Cpython>\nEmit custom progress updates from within nodes using the stream writer.\n```python\nfrom langgraph.config import get_stream_writer\n\ndef my_node(state):\n    writer = get_stream_writer()\n    writer(\"Processing step 1...\")\n    # Do work\n    writer(\"Complete!\")\n    return {\"result\": \"done\"}\n\nfor chunk in graph.stream({\"data\": \"test\"}, stream_mode=\"custom\"):\n    print(chunk)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nEmit custom progress updates from within nodes using the stream writer.\n```typescript\nimport { getWriter } from \"@langchain\u002Flanggraph\";\n\nconst myNode = async (state: typeof State.State) => {\n  const writer = getWriter();\n  writer(\"Processing step 1...\");\n  \u002F\u002F Do work\n  writer(\"Complete!\");\n  return { result: \"done\" };\n};\n\nfor await (const chunk of graph.stream({ data: \"test\" }, { streamMode: \"custom\" })) {\n  console.log(chunk);\n}\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-stream-custom-data>\n\n---\n\n## Error Handling\n\nMatch the error type to the right handler:\n\n\u003Cerror-handling-table>\n\n| Error Type | Who Fixes | Strategy | Example |\n|---|---|---|---|\n| Transient (network, rate limits) | System | `RetryPolicy(max_attempts=3)` | `add_node(..., retry_policy=...)` |\n| LLM-recoverable (tool failures) | LLM | `ToolNode(tools, handle_tool_errors=True)` | Error returned as ToolMessage |\n| User-fixable (missing info) | Human | `interrupt({\"message\": ...})` | Collect missing data (see HITL skill) |\n| Unexpected | Developer | Let bubble up | `raise` |\n\n\u003C\u002Ferror-handling-table>\n\n\u003Cex-retry-policy>\n\u003Cpython>\nUse RetryPolicy for transient errors (network issues, rate limits).\n```python\nfrom langgraph.types import RetryPolicy\n\nworkflow.add_node(\n    \"search_documentation\",\n    search_documentation,\n    retry_policy=RetryPolicy(max_attempts=3, initial_interval=1.0)\n)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse retryPolicy for transient errors.\n```typescript\nworkflow.addNode(\n  \"searchDocumentation\",\n  searchDocumentation,\n  {\n    retryPolicy: { maxAttempts: 3, initialInterval: 1.0 },\n  },\n);\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-retry-policy>\n\n\u003Cex-tool-node-error-handling>\n\u003Cpython>\nUse ToolNode from langgraph.prebuilt to handle tool execution and errors. When handle_tool_errors=True, errors are returned as ToolMessages so the LLM can recover.\n```python\nfrom langgraph.prebuilt import ToolNode\n\ntool_node = ToolNode(tools, handle_tool_errors=True)\n\nworkflow.add_node(\"tools\", tool_node)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse ToolNode from @langchain\u002Flanggraph\u002Fprebuilt to handle tool execution and errors. When handleToolErrors is true, errors are returned as ToolMessages so the LLM can recover.\n```typescript\nimport { ToolNode } from \"@langchain\u002Flanggraph\u002Fprebuilt\";\n\nconst toolNode = new ToolNode(tools, { handleToolErrors: true });\n\nworkflow.addNode(\"tools\", toolNode);\n```\n\u003C\u002Ftypescript>\n\u003C\u002Fex-tool-node-error-handling>\n\n---\n\n## Common Fixes\n\n\u003Cfix-compile-before-execution>\n\u003Cpython>\nMust compile() to get executable graph.\n```python\n# WRONG\nbuilder.invoke({\"input\": \"test\"})  # AttributeError!\n\n# CORRECT\ngraph = builder.compile()\ngraph.invoke({\"input\": \"test\"})\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nMust compile() to get executable graph.\n```typescript\n\u002F\u002F WRONG\nawait builder.invoke({ input: \"test\" });\n\n\u002F\u002F CORRECT\nconst graph = builder.compile();\nawait graph.invoke({ input: \"test\" });\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-compile-before-execution>\n\n\u003Cfix-infinite-loop-needs-exit>\n\u003Cpython>\nProvide conditional path to END to avoid infinite loops.\n```python\n# WRONG: Loops forever\nbuilder.add_edge(\"node_a\", \"node_b\")\nbuilder.add_edge(\"node_b\", \"node_a\")\n\n# CORRECT\ndef should_continue(state):\n    return END if state[\"count\"] > 10 else \"node_b\"\nbuilder.add_conditional_edges(\"node_a\", should_continue)\n```\n\u003C\u002Fpython>\n\u003Ctypescript>\nUse conditional edges with END return to break loops.\n```typescript\n\u002F\u002F WRONG: Loops forever\nbuilder.addEdge(\"node_a\", \"node_b\").addEdge(\"node_b\", \"node_a\");\n\n\u002F\u002F CORRECT\nbuilder.addConditionalEdges(\"node_a\", (state) => state.count > 10 ? END : \"node_b\");\n```\n\u003C\u002Ftypescript>\n\u003C\u002Ffix-infinite-loop-needs-exit>\n\n\u003Cfix-common-mistakes>\nOther common mistakes:\n```python\n# Router must return names of nodes that exist in the graph\nbuilder.add_node(\"my_node\", func)  # Add node BEFORE referencing in edges\nbuilder.add_conditional_edges(\"node_a\", router, [\"my_node\"])\n\n# Command return type needs Literal for routing destinations (Python)\ndef node_a(state) -> Command[Literal[\"node_b\", \"node_c\"]]:\n    return Command(goto=\"node_b\")\n\n# START is entry-only - cannot route back to it\nbuilder.add_edge(\"node_a\", START)  # WRONG!\nbuilder.add_edge(\"node_a\", \"entry\")  # Use a named entry node instead\n\n# Reducer expects matching types\nreturn {\"items\": [\"item\"]}  # List for list reducer, not a string\n```\n```typescript\n\u002F\u002F Always await graph.invoke() - it returns a Promise\nconst result = await graph.invoke({ input: \"test\" });\n\n\u002F\u002F TS Command nodes need { ends } to declare routing destinations\nbuilder.addNode(\"router\", routerFn, { ends: [\"node_b\", \"node_c\"] });\n```\n\u003C\u002Ffix-common-mistakes>\n\n\u003Cboundaries>\n### What You Should NOT Do\n\n- Mutate state directly — always return partial update dicts from nodes\n- Route back to START — it's entry-only; use a named node instead\n- Forget reducers on list fields — without one, last write wins\n- Mix static edges with Command goto without understanding both will execute\n\u003C\u002Fboundaries>\n",{"name":5,"description":7},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002FHarmeet10000\u002Fskills\u002Flanggraph-fundamentals",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":49,"prompt":59,"zipAvailable":60},[50,53,56],{"tool":51,"command":52},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill langgraph-fundamentals",{"tool":54,"command":55},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill langgraph-fundamentals",{"tool":57,"command":58},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill langgraph-fundamentals","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FHarmeet10000\u002Fskills\u002Flanggraph-fundamentals\u002FSKILL.md",true,[],[63,77,87,96,102,109],{"skillNo":64,"name":65,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":66,"categories":67,"keywords":73,"publishedAt":34},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[68],{"categoryNo":69,"categoryName":70,"taxonomyType":71,"taxonomyTypeDesc":72},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[74,75,76],"model","model hierarchy","model hierarchy skill",{"skillNo":78,"name":79,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":83,"publishedAt":34},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[82],{"categoryNo":69,"categoryName":70,"taxonomyType":71,"taxonomyTypeDesc":72},[84,85,86],"MCP","zoom","zoom mcp",{"skillNo":88,"name":89,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":34},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[93,94,95,85],"sdk","zoom apps sdk","apps",{"skillNo":97,"name":98,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":99,"categories":100,"keywords":101,"publishedAt":34},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[98],{"skillNo":103,"name":104,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":108,"publishedAt":34},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[107],{"categoryNo":69,"categoryName":70,"taxonomyType":71,"taxonomyTypeDesc":72},[104],{"skillNo":110,"name":111,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":114,"publishedAt":34},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[111]]