Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Design and optimize production-grade multi-agent systems with LangGraph, LangChain, and DeepAgents for complex AI workflows.
.claude/skills/multi-agent-architect/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 39% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 89% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 99% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 43% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 92% | 0% |
This skill turns Claude into a Senior AI Multi-Agent Architect specialized in LangGraph, LangChain, and DeepAgents. It provides structured workflows for creating and updating production-grade multi-agent systems — including supervisor agents, planners, researchers, coders, and memory-backed autonomous pipelines. Use it whenever you need to design, build, debug, or scale any multi-agent AI system.
If this skill adapts material from an external GitHub repository, declare both:
source_repo: owner/reposource_type: official or source_type: communityBefore writing any code, clarify:
All agents share a typed state object passed through the graph:
pythonfrom typing import TypedDict class AgentState(TypedDict): user_goal: str tasks: list[str] completed_tasks: list[str] next_agent: str context: dict step_count: int # guards against infinite loops error: str | None
Each agent is an async function that reads from state and returns an updated state:
pythonimport logging from langchain_openai import ChatOpenAI logger = logging.getLogger(__name__) async def research_node(state: AgentState) -> AgentState: logger.info("research_node: starting") llm = ChatOpenAI(model="gpt-4o") result = await llm.bind_tools(research_tools).ainvoke(state["user_goal"]) state["context"]["research"] = result.content state["next_agent"] = "coder" return state
Wire nodes together with edges and conditional routing:
pythonfrom langgraph.graph import StateGraph, END from langgraph.prebuilt import ToolNode def build_graph() -> StateGraph: graph = StateGraph(AgentState) graph.add_node("supervisor", supervisor_node) graph.add_node("research", research_node) graph.add_node("coder", coding_node) graph.add_node("validator", validation_node) graph.add_node("tools", ToolNode(all_tools)) graph.set_entry_point("supervisor") graph.add_conditional_edges( "supervisor", route_next, {"research": "research", "coder": "coder", "end": END} ) graph.add_edge("research", "supervisor") graph.add_edge("coder", "validator") graph.add_edge("validator", "supervisor") return graph.compile() def route_next(state: AgentState) -> str: if state["step_count"] > 20: return "end" return state["next_agent"]
pythonfrom langchain_community.chat_message_histories import RedisChatMessageHistory def get_memory(session_id: str): return RedisChatMessageHistory( session_id=session_id, url=os.getenv("REDIS_URL"), ttl=3600 )
pythonasync def run(user_goal: str, session_id: str): graph = build_graph() initial_state = AgentState( user_goal=user_goal, tasks=[], completed_tasks=[], next_agent="supervisor", context={}, step_count=0, error=None, ) return await graph.ainvoke(initial_state)
pythonfrom fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class RunRequest(BaseModel): goal: str session_id: str @app.post("/run") async def run_agent(req: RunRequest): result = await run(req.goal, req.session_id) return {"result": result}
When the user wants to update or debug an existing agent, structure the response as:
## Existing Issue
[Describe the current problem]
## Root Cause
[Identify why it's happening in the architecture]
## Proposed Update
[Outline the changes at architecture level]
## Updated Code
[Generate only the changed modules]
## Migration Notes
[What breaks, what's backward-compatible]
## Performance Impact
[Latency / token / memory delta]Always generate code in this layout:
multi_agent_system/
├── agents/ # One file per agent role
├── tools/ # Tool definitions and wrappers
├── memory/ # Redis, VectorDB, LangChain memory helpers
├── prompts/ # Prompt templates (one per agent)
├── workflows/ # High-level orchestration logic
├── graphs/ # LangGraph state + compiled graph definitions
├── api/ # FastAPI routes (optional)
├── configs/ # Config loader — no secrets in code
├── tests/ # Unit + integration tests per agent
└── main.pypython# agents/research_agent.py async def research_node(state: AgentState) -> AgentState: llm = ChatOpenAI(model="gpt-4o").bind_tools([web_search, rag_search]) response = await llm.ainvoke( f"Research the following and return structured findings:\n{state['user_goal']}" ) state["context"]["research"] = response.content state["next_agent"] = "coder" return state # agents/coding_agent.py async def coding_node(state: AgentState) -> AgentState: llm = ChatOpenAI(model="gpt-4o").bind_tools([python_repl, github_tool]) response = await llm.ainvoke( f"Given this research:\n{state['context']['research']}\n\nWrite production Python code." ) state["context"]["code"] = response.content state["next_agent"] = "validator" return state
python# agents/supervisor_agent.py DELEGATION_PROMPT = """ You are a supervisor. Given the current state, decide the next agent. Available agents: research, coder, validator, end. Respond with ONLY the agent name. Goal: {goal} Completed: {completed} Context keys available: {context} """ async def supervisor_node(state: AgentState) -> AgentState: state["step_count"] += 1 llm = ChatOpenAI(model="gpt-4o") decision = await llm.ainvoke( DELEGATION_PROMPT.format( goal=state["user_goal"], completed=state["completed_tasks"], context=list(state["context"].keys()), ) ) next_agent = decision.content.strip().lower() # Validate against allowlist before setting allowed = {"research", "coder", "validator", "end"} state["next_agent"] = next_agent if next_agent in allowed else "end" return state
pythonasync def reflection_node(state: AgentState) -> AgentState: llm = ChatOpenAI(model="gpt-4o") critique = await llm.ainvoke( f"Evaluate this output critically:\n{state['context'].get('code', '')}\n" "List any bugs, gaps, or improvements. Be concise." ) state["context"]["critique"] = critique.content state["next_agent"] = "coder" if "bug" in critique.content.lower() else "end" return state
TypedDict for all state schemas — enables type checking and graph validationstep_count guard to prevent infinite routing loopsasync/await throughout — LangGraph supports async nativelyos.getenv()session_idpip show langgraph).python OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") # ✅ correct leaked_openai_token = "[redacted API key]" # ❌ never do this
<!-- security-allowlist: python_repl tool examples are for sandboxed execution environments only -->
session_id and set a TTL to prevent memory leaks across sessions.Solution: Add step_count: int to state; return "end" in route_next() when step_count > N
Solution: Validate the LLM's routing output against a hardcoded allowlist before setting next_agent
Solution: Scope Redis keys to session_id and always set a TTL (ttl=3600)
Solution: Always write tool output into state["context"] and confirm the next node reads it
Solution: Use .bind_tools([only_relevant_tools]) per agent instead of a global tool list
Solution: Wrap LLM calls in retry logic with exponential backoff using tenacity
@langchain-rag - When you need retrieval-augmented generation pipelines specifically@fastapi-backend - When deploying agent systems as production REST APIs@python-async - When deepening async/await patterns used throughout agent nodes| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 43,661 | 17,887 | -59% | 1 | 1 | 0% | 4,945 | 6,874 | +39% | 0 | 0 | — |
case-02 | fail→fail | 21,222 | 15,523 | -27% | 1 | 1 | 0% | 3,857 | 5,910 | +53% | 0 | 0 | — |
case-03 | fail→fail | 29,301 | 21,466 | -27% | 1 | 1 | 0% | 6,207 | 7,590 | +22% | 0 | 0 | — |
case-04 | pass→pass | 12,780 | 9,117 | -29% | 1 | 1 | 0% | 2,705 | 4,932 | +82% | 0 | 0 | — |
case-05 | pass→pass | 11,678 | 8,946 | -23% | 1 | 1 | 0% | 2,441 | 4,712 | +93% | 0 | 0 | — |
case-06 | pass→pass | 12,454 | 6,508 | -48% | 1 | 1 | 0% | 2,494 | 4,226 | +69% | 0 | 0 | — |
case-07 | fail→pass | 13,947 | 9,756 | -30% | 1 | 1 | 0% | 2,437 | 4,617 | +89% | 0 | 0 | — |
case-08 | pass→pass | 11,490 | 10,319 | -10% | 1 | 1 | 0% | 2,146 | 4,904 | +129% | 0 | 0 | — |
case-09 | fail→fail | 13,267 | 10,558 | -20% | 1 | 1 | 0% | 2,183 | 4,913 | +125% | 0 | 0 | — |
case-10 | fail→pass | 13,493 | 11,184 | -17% | 1 | 1 | 0% | 2,516 | 5,016 | +99% | 0 | 0 | — |
case-11 | pass→pass | 15,618 | 13,163 | -16% | 1 | 1 | 0% | 2,754 | 5,583 | +103% | 0 | 0 | — |
case-12 | fail→pass | 26,134 | 4,881 | -81% | 1 | 1 | 0% | 2,621 | 3,760 | +43% | 0 | 0 | — |
case-13 | pass→pass | 11,972 | 8,901 | -26% | 1 | 1 | 0% | 2,145 | 4,565 | +113% | 0 | 0 | — |
case-14 | pass→pass | 10,773 | 4,554 | -58% | 1 | 1 | 0% | 1,795 | 3,644 | +103% | 0 | 0 | — |
case-15 | pass→pass | 10,713 | 8,081 | -25% | 1 | 1 | 0% | 2,087 | 4,561 | +119% | 0 | 0 | — |
case-16 | pass→fail | 16,631 | 9,783 | -41% | 1 | 1 | 0% | 3,299 | 5,012 | +52% | 0 | 0 | — |
case-17 | fail→pass | 13,653 | 11,414 | -16% | 1 | 1 | 0% | 2,734 | 5,237 | +92% | 0 | 0 | — |
case-18 | fail→pass | 12,765 | 14,927 | +17% | 1 | 1 | 0% | 2,132 | 5,595 | +162% | 0 | 0 | — |
case-19 | fail→pass | 17,113 | 6,710 | -61% | 1 | 1 | 0% | 3,141 | 4,264 | +36% | 0 | 0 | — |
case-20 | fail→pass | 12,947 | 9,627 | -26% | 1 | 1 | 0% | 2,440 | 4,769 | +95% | 0 | 0 | — |
case-21 | pass→pass | 15,573 | 12,406 | -20% | 1 | 1 | 0% | 2,885 | 5,309 | +84% | 0 | 0 | — |
case-22 | pass→pass | 15,706 | 13,499 | -14% | 1 | 1 | 0% | 2,370 | 5,425 | +129% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/28/2026 | +45% |
Other measured skills in the registry, with their headline benchmark lift.