.claude/skills/langgraph-patterns/SKILL.md
# LangGraph Patterns ## When to Load This Skill Load when working with: LangGraph state machines, agent nodes, tool definitions, checkpointers, human-in-the-loop interrupts, multi-agent coordination. ## Current Version LangGraph `>=0.2.0` (langgraph-checkpoint for persistence). Always pin exact version in `pyproject.toml`. ## Core Concepts LangGraph models agent workflows as directed graphs: - **State**: typed dict passed between nodes (the single source of truth) - **Nodes**: Python async
npx skillsauth add pyramidheadshark/ml-claude-infra .claude/skills/langgraph-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Load when working with: LangGraph state machines, agent nodes, tool definitions, checkpointers, human-in-the-loop interrupts, multi-agent coordination.
LangGraph >=0.2.0 (langgraph-checkpoint for persistence).
Always pin exact version in pyproject.toml.
LangGraph models agent workflows as directed graphs:
src/{project_name}/
├── agents/
│ ├── __init__.py
│ ├── graph.py # graph assembly
│ ├── state.py # TypedDict state definition
│ ├── nodes/
│ │ ├── __init__.py
│ │ ├── analyst.py
│ │ └── writer.py
│ └── tools/
│ ├── __init__.py
│ └── search.py
from typing import Annotated
from typing_extensions import TypedDict
import operator
class AgentState(TypedDict):
messages: Annotated[list[dict], operator.add]
user_input: str
retrieved_context: list[str]
final_answer: str | None
error: str | None
iteration_count: int
Use Annotated[list, operator.add] for lists that nodes append to.
Use plain types for values that nodes replace entirely.
from langchain_core.messages import AIMessage
from src.project_name.agents.state import AgentState
from src.project_name.adapters.llm.claude_adapter import ClaudeAdapter
async def analyst_node(state: AgentState) -> dict:
adapter = ClaudeAdapter()
response = await adapter.invoke(
system="You are a precise analyst. Answer based only on retrieved context.",
messages=state["messages"],
context=state["retrieved_context"],
)
return {
"messages": [AIMessage(content=response)],
"final_answer": response,
}
Nodes return ONLY the fields they modify — LangGraph merges the rest automatically.
from langgraph.graph import END, StateGraph
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
from src.project_name.agents.state import AgentState
from src.project_name.agents.nodes.analyst import analyst_node
from src.project_name.agents.nodes.retriever import retriever_node
def should_continue(state: AgentState) -> str:
if state.get("error"):
return "handle_error"
if state.get("final_answer"):
return END
return "analyst"
def build_graph(checkpointer=None) -> StateGraph:
graph = StateGraph(AgentState)
graph.add_node("retriever", retriever_node)
graph.add_node("analyst", analyst_node)
graph.set_entry_point("retriever")
graph.add_edge("retriever", "analyst")
graph.add_conditional_edges("analyst", should_continue)
return graph.compile(checkpointer=checkpointer)
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
async def get_checkpointer(connection_string: str) -> AsyncPostgresSaver:
checkpointer = AsyncPostgresSaver.from_conn_string(connection_string)
await checkpointer.setup()
return checkpointer
For local development, use AsyncSqliteSaver:
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
checkpointer = AsyncSqliteSaver.from_conn_string("checkpoints.db")
from langgraph.types import interrupt
async def approval_node(state: AgentState) -> dict:
decision = interrupt({
"question": "Approve this action?",
"proposed_action": state["planned_action"],
})
return {"approved": decision == "yes"}
Resume after human input:
graph.invoke(
None,
config={"configurable": {"thread_id": thread_id}},
command={"resume": "yes"},
)
from anthropic import AsyncAnthropic
from src.project_name.core.config import settings
class ClaudeAdapter:
def __init__(self) -> None:
self._client = AsyncAnthropic()
async def invoke(
self,
system: str,
messages: list[dict],
context: list[str] | None = None,
) -> str:
system_prompt = system
if context:
system_prompt += f"\n\nContext:\n" + "\n".join(context)
response = await self._client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system=system_prompt,
messages=messages,
)
return response.content[0].text
from langchain_core.tools import tool
@tool
async def search_knowledge_base(query: str) -> str:
"""Search the knowledge base for relevant documents.
Args:
query: The search query in natural language.
Returns:
Relevant document excerpts as a single string.
"""
results = await kb_adapter.search(query, top_k=5)
return "\n\n".join(r.text for r in results)
from fastapi import APIRouter
from langgraph.types import Command
router = APIRouter()
_graph = None
def get_graph():
global _graph
if _graph is None:
_graph = build_graph(checkpointer=get_checkpointer(...))
return _graph
@router.post("/chat")
async def chat(request: ChatRequest) -> ChatResponse:
graph = get_graph()
config = {"configurable": {"thread_id": request.thread_id}}
result = await graph.ainvoke(
{"user_input": request.message, "messages": []},
config=config,
)
return ChatResponse(answer=result["final_answer"])
resources/streaming.md — SSE streaming responses from LangGraphresources/multi-agent.md — supervisor pattern for multi-agent coordinationresources/testing-agents.md — testing LangGraph graphs with pytesttesting
# Design Doc Creator ## When to Load This Skill Load when: design documents, requirements, new project start. Short fixture skill for testing (optional/meta skill).
development
# Windows Developer Guide ## When to Load Automatically loaded on Windows (`platform_trigger: "win32"`). Applies to: `.py`, `.ps1`, `.bat`, `.cmd` files and any Windows-specific workflow. ## Python on Windows ### Encoding (CRITICAL) Windows defaults to `cp1251` / `cp1252` for file I/O. Always specify UTF-8 explicitly: ```python with open("file.txt", "r", encoding="utf-8") as f: content = f.read() Path("file.txt").read_text(encoding="utf-8") Path("file.txt").write_text(content, encodin
development
# Test-First Patterns ## When to Load This Skill Load when writing tests, creating `.feature` files, setting up conftest, discussing test strategy, or reviewing coverage. ## Philosophy Tests are written BEFORE code. Always. No exceptions. The order is: Design Doc → BDD Scenarios → Unit Tests → Implementation. BDD scenarios come from the design document's use cases section — they are a direct translation of business requirements into executable specifications. This makes tests the living do
testing
# Skill: Supply Chain Auditor ## When to Load Auto-load when: adding dependencies, reviewing packages, updating versions, or discussing `requirements.txt`, `pyproject.toml`, `package.json`. Triggers on `dependency`, `install`, `package`, `CVE`, `audit`, `vulnerable` (≥2 keywords). ## Core Rules Every new dependency addition must pass this checklist before merging: 1. **Pinned** — exact version in production (`==1.2.3` for pip, `"1.2.3"` for npm, not `^` or `~`). 2. **Maintained** — last com