new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

LangChain · Agents · all subjects

agents/middleware/planning-delegation

25 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Planning and delegation with subagents

Complex tasks can exceed what one context window can handle. Delegation lets the main agent break work into pieces, hand them to subagents that each run in their own isolated context, and stay focused on coordination rather than execution. Work can run in parallel and the main agent's context stays clean.

Subagent isolation benefits

A subagent runs in its own context window so the supervisor sees only the final result, not every tool call along the way. This keeps the main analysis focused and leaves room for follow-up questions. Subagents are useful for tasks that produce large intermediate output such as script drafts, failed runs, and file reads that would crowd the main agent's context.

TodoListMiddleware enables parallel delegation

TodoListMiddleware works with SubAgentMiddleware to enable the main agent to delegate work in parallel instead of blocking on each task. The main agent can use the task tool to delegate chart work and other operations to subagents while continuing with planning and analysis.

SubAgentMiddleware purpose

SubAgentMiddleware allows handing off tasks to subagents, isolating context and keeping the main (supervisor) agent's context window clean while still going deep on a task. Subagents are supplied through a task tool.

SubAgentMiddleware configuration

SubAgentMiddleware accepts: default_model (model for subagents if not specified), default_tools (default tools for subagents), subagents (array of subagent definitions). Each subagent is defined with name, description, system_prompt, tools, and optionally custom model and middleware. Additionally, main agent always has access to a general-purpose subagent with same instructions as main agent and all its tools, primarily for context isolation.

SubAgentMiddleware example - basic setup

from langchain.tools import tool from langchain.agents import create_agent from deepagents.middleware.subagents import SubAgentMiddleware @tool def get_weather(city: str) -> str: """Get the weather in a city.""" return f"The weather in {city} is sunny." agent = create_agent( model="claude-sonnet-4-6", middleware=[ SubAgentMiddleware( default_model="claude-sonnet-4-6", default_tools=[], subagents=[ { "name": "weather", "description": "This subagent can get weather in cities.", "system_prompt": "Use the get_weather tool to get the weather in a city.", "tools": [get_weather], "model": "gpt-5.5", "middleware": [], } ], ) ], ) This shows defining a weather subagent with custom model.

SubAgentMiddleware example - prebuilt LangGraph graph

from langchain.agents import create_agent from deepagents.middleware.subagents import SubAgentMiddleware from deepagents import CompiledSubAgent from langgraph.graph import StateGraph def create_weather_graph(): workflow = StateGraph(...) # Build your custom graph return workflow.compile() weather_graph = create_weather_graph() weather_subagent = CompiledSubAgent( name="weather", description="This subagent can get weather in cities.", runnable=weather_graph ) agent = create_agent( model="claude-sonnet-4-6", middleware=[ SubAgentMiddleware( default_model="claude-sonnet-4-6", default_tools=[], subagents=[weather_subagent], ) ], ) This shows wrapping a custom LangGraph graph as a CompiledSubAgent.

Difference between state machine and subagents patterns

State machine pattern: single agent with dynamic configuration (prompt + tools) that changes based on current_step. Subagents pattern: multiple agents called as tools by a coordinator. State machine works best for sequential linear workflows; subagents work better for parallel or orchestrated workflows with specialization.

Handoffs architecture: state-driven behavior

The handoffs architecture uses tools to update a state variable (e.g., current_step or active_agent) that persists across conversation turns. The system reads this variable to adjust behavior by applying different configuration (system prompt, tools) or routing to a different agent. This pattern supports both handoffs between distinct agents and dynamic configuration changes within a single agent.

Handoffs: when to use this pattern

Use the handoffs pattern when you need to enforce sequential constraints (unlock capabilities only after preconditions are met), the agent needs to converse directly with the user across different states, or you're building multi-stage conversational flows. This pattern is particularly valuable for customer support scenarios where you need to collect information in a specific sequence—for example, collecting a warranty ID before processing a refund.

Command and ToolMessage in handoff tools

Handoff tools return a Command object that updates state and triggers transitions. When a tool updates messages via Command, you must include a ToolMessage with matching tool_call_id to complete the request-response cycle. Without this ToolMessage, the conversation history becomes malformed because LLMs expect tool calls to be paired with responses.

State schema for single-agent handoffs

When implementing single-agent handoffs with middleware, define state with a current_step tracker field (e.g., current_step: str = "triage") and any domain-specific fields needed (e.g., warranty_status: str | None = None). This state persists across conversation turns and is read by middleware to determine which configuration to apply.

Handoff tool basic implementation in Python

from langchain.tools import tool from langchain.messages import ToolMessage from langgraph.types import Command @tool def transfer_to_specialist(runtime) -> Command: """Transfer to the specialist agent.""" return Command( update={ "messages": [ ToolMessage( content="Transferred to specialist", tool_call_id=runtime.tool_call_id ) ], "current_step": "specialist" } )

Handoff tool basic implementation in TypeScript

import { tool, ToolMessage, type ToolRuntime } from "langchain"; import { Command } from "@langchain/langgraph"; import { z } from "zod"; const transferToSpecialist = tool( async (_, config: ToolRuntime<typeof StateSchema>) => { return new Command({ update: { messages: [ new ToolMessage({ content: "Transferred to specialist", tool_call_id: config.toolCallId }) ], currentStep: "specialist" } }); }, { name: "transfer_to_specialist", description: "Transfer to the specialist agent.", schema: z.object({}) } );

Single agent with middleware handoff implementation

Single agent with middleware uses one agent that changes behavior based on state. Middleware intercepts each model call and dynamically adjusts the system prompt and available tools. Tools update the state variable via Command to trigger transitions. This approach is simpler than multiple agent subgraphs and recommended for most handoff use cases.

Multiple agent subgraphs handoff implementation

Multiple distinct agents exist as separate nodes in a graph. Handoff tools navigate between agent nodes using Command.PARENT to specify which node to execute next. This approach requires careful context engineering because unlike single-agent middleware (where message history flows naturally), you must explicitly decide what messages pass between agents.

Subgraph handoff with context engineering in Python

@tool def transfer_to_sales( runtime: ToolRuntime, ) -> Command: """Transfer to the sales agent.""" last_ai_message = next( msg for msg in reversed(runtime.state["messages"]) if isinstance(msg, AIMessage) ) transfer_message = ToolMessage( content="Transferred to sales agent", tool_call_id=runtime.tool_call_id, ) return Command( goto="sales_agent", update={ "active_agent": "sales_agent", "messages": [last_ai_message, transfer_message], }, graph=Command.PARENT )

Subgraph handoff with context engineering in TypeScript

import { tool, ToolMessage, AIMessage, type ToolRuntime, } from "langchain"; import { Command, StateSchema, MessagesValue } from "@langchain/langgraph"; const CustomState = new StateSchema({ messages: MessagesValue, }); const transferToSales = tool( async (_, runtime: ToolRuntime<typeof CustomState.State>) => { const lastAiMessage = runtime.state.messages .reverse() .find(AIMessage.isInstance); const transferMessage = new ToolMessage({ content: "Transferred to sales agent", tool_call_id: runtime.toolCallId, }); return new Command({ goto: "sales_agent", update: { activeAgent: "sales_agent", messages: [lastAiMessage, transferMessage].filter(Boolean), }, graph: Command.PARENT, }); }, { name: "transfer_to_sales", description: "Transfer to the sales agent.", schema: z.object({}), } );

Handoff context engineering: message pairing requirement

When handing off between agents using Command.PARENT, you must include both the AIMessage containing the tool call (the message that triggered the handoff) and a ToolMessage acknowledging the handoff (the artificial response to that tool call). Without this pairing, the receiving agent will see an incomplete conversation and may produce errors or unexpected behavior.

Handoff context engineering: why not pass full history

While you could include the full subagent conversation in a handoff, this often creates problems. The receiving agent may become confused by irrelevant internal reasoning, and token costs increase unnecessarily. By passing only the handoff pair (AIMessage and ToolMessage), you keep the parent graph's context focused on high-level coordination. If the receiving agent needs additional context, consider summarizing the subagent's work in the ToolMessage content instead of passing raw message history.

Handoff final message requirement

When returning control to the user (ending the agent's turn), ensure the final message is an AIMessage. This maintains valid conversation history and signals to the user interface that the agent has finished its work.

Handoff implementation considerations

When designing multi-agent systems with handoffs, consider three key factors: context filtering strategy (will each agent receive full conversation history, filtered portions, or summaries; different agents may need different context depending on their role), tool semantics (clarify whether handoff tools only update routing state or also perform side effects), and token efficiency (balance context completeness against token costs, with summarization and selective context passing becoming more important as conversations grow longer).

Recommendation: single agent middleware over subgraphs

Use single agent with middleware for most handoff use cases—it is simpler. Only use multiple agent subgraphs when you need bespoke agent implementations (for example, a node that is itself a complex graph with reflection or retrieval steps).

Multi-agent routing based on AIMessage tool calls

In multi-agent systems, check the last message to determine routing: if it is an AIMessage without tool_calls, the agent has finished and you should end the conversation. Otherwise, route to the active agent. This logic ensures agents only continue when they have work to do (tool calls), preventing unnecessary routing.

State schema for multi-agent handoffs

When implementing multi-agent handoffs with subgraphs, define state with an active_agent tracker field to specify which agent node should execute. This state persists across conversation turns and is used by conditional routing logic to determine which agent to invoke next.

Give your agent this brain