sherma

Multi-Agent Orchestration

sherma supports multi-agent systems where a supervisor agent delegates work to sub-agents. Sub-agents are automatically wrapped as LangGraph tools, so the supervisor’s LLM decides when and how to invoke them through standard tool calling.

How It Works

  1. You declare sub-agents in the YAML config under sub_agents
  2. Each sub-agent is registered in the agent registry and wrapped as a LangGraph tool
  3. A call_llm node with use_sub_agents_as_tools binds these tools to the LLM (use true/all for all sub-agents, or a list to select specific ones)
  4. The LLM invokes sub-agents through tool calls; sherma handles the A2A message plumbing
  5. Tool nodes and conditional edges are auto-injected, just like regular tools

Declaring Sub-Agents

Sub-agents can be sourced in four ways:

From a YAML file

The simplest approach – point directly to another declarative agent’s YAML. The parent’s http_async_client (and API keys) are automatically forwarded.

sub_agents:
  - id: weather-agent
    version: "1.0.0"
    yaml_path: weather-agent.yaml  # Relative to the parent YAML file

Relative yaml_path values are resolved against the parent YAML file’s directory (i.e., base_path). The sub-agent automatically derives its own base_path from its resolved YAML path, so nested sub-agents chain correctly. See Path Resolution.

From a Python module

Import a pre-constructed Agent instance from a Python module:

sub_agents:
  - id: weather-agent
    version: "1.0.0"
    import_path: my_agents.weather_agent

The import path should resolve to an Agent instance (not a class).

From a remote URL

Connect to an A2A-compatible agent running elsewhere:

sub_agents:
  - id: remote-agent
    version: "1.0.0"
    url: https://remote-agent.example.com

Pre-registered in the registry

If neither yaml_path, import_path, nor url is provided, the agent is expected to already exist in the agent registry:

sub_agents:
  - id: my-agent
    version: "1.0.0"

This is useful when you register agents programmatically before building the supervisor.

Supervisor YAML

A supervisor agent uses use_sub_agents_as_tools on its call_llm node. Use true (or all) to bind all declared sub-agents, or provide a list of specific id/version refs:

prompts:
  - id: supervisor-prompt
    version: "1.0.0"
    instructions: >
      You are a travel planner. Use the weather agent to check conditions
      at the destination before making recommendations.

llms:
  - id: openai-gpt-4o-mini
    version: "1.0.0"
    provider: openai
    model_name: gpt-4o-mini

sub_agents:
  - id: weather-agent
    version: "1.0.0"
    yaml_path: weather-agent.yaml  # Resolved relative to this YAML file

agents:
  travel-planner:
    state:
      fields:
        - name: messages
          type: list
          default: []

    graph:
      entry_point: planner
      nodes:
        - name: planner
          type: call_llm
          args:
            llm:
              id: openai-gpt-4o-mini
              version: "1.0.0"
            prompt: 'prompts["supervisor-prompt"]["instructions"]'
            use_sub_agents_as_tools: true

      edges:
        - source: planner
          target: __end__

The tool_node and conditional edges are auto-injected, so you only need to define the call_llm node.

Selecting specific sub-agents

Instead of binding all sub-agents, you can select a subset by providing a list of id/version refs:

use_sub_agents_as_tools:
  - id: weather-agent
    version: "1.0.0"

This is useful when you have many sub-agents declared but a particular node should only access some of them.

Agent Input Schemas

If a sub-agent defines an input_schema (a Pydantic model), the generated tool accepts two arguments:

If no input_schema is defined, the tool accepts only request: str.

Using Building Blocks Without Declarative Agents

The multi-agent utilities described here are not exclusive to DeclarativeAgent. If you are building agents directly with LangGraphAgent or plain LangGraph, you can use these building blocks independently:

agent_to_langgraph_tool

Wrap any Agent instance as a LangGraph BaseTool. This is the core primitive that the declarative sub_agents config builds on, and you can use it directly in your own graph construction code:

from sherma.langgraph.tools import agent_to_langgraph_tool

# Wrap any sherma Agent (local, remote, declarative) as a tool
weather_tool = agent_to_langgraph_tool(weather_agent)

# Use it in your own LangGraph graph
llm_with_tools = chat_model.bind_tools([weather_tool, other_tool])

Registries and tool conversion

The registry system, tool wrapping helpers (from_langgraph_tool, to_langgraph_tool), and A2A message conversion are all standalone utilities. You can register agents in an AgentRegistry, resolve them by ID/version, and wrap them as tools – all without writing any YAML:

from sherma.registry.base import RegistryEntry
from sherma.registry.bundle import RegistryBundle
from sherma.langgraph.tools import agent_to_langgraph_tool, from_langgraph_tool

bundle = RegistryBundle()

# Register an agent
await bundle.agent_registry.add(
    RegistryEntry(id="weather-agent", version="1.0.0", instance=my_agent)
)

# Resolve and wrap as a tool
agent = await bundle.agent_registry.get("weather-agent")
tool = agent_to_langgraph_tool(agent)

# Register the tool for use elsewhere
sherma_tool = from_langgraph_tool(tool)
await bundle.tool_registry.add(
    RegistryEntry(id=sherma_tool.id, version="1.0.0", instance=sherma_tool)
)

This applies broadly across sherma – hooks, skills, schema helpers, and message converters are all usable as composable building blocks in your own LangGraphAgent subclass or raw LangGraph code.

Running the Example

The examples/multi_agent/ directory contains a complete working example:

examples/multi_agent/
├── weather_agent.yaml       # Sub-agent: looks up weather
├── supervisor_agent.yaml    # Supervisor: travel planner
└── main.py                  # Entry point
uv run python examples/multi_agent/main.py "Plan a trip to Tokyo"

Tenant Propagation

When a DeclarativeAgent has a tenant_id, it propagates to all entities it creates – including sub-agents declared via yaml_path. This means sub-agents inherit the parent’s tenant scope automatically.

agent = DeclarativeAgent(
    id="travel-planner",
    version="1.0.0",
    yaml_path="supervisor.yaml",
    tenant_id="acme-corp",  # Propagated to sub-agents and all registry entries
)