# `Sagents.Middleware.SubAgent`

Middleware for delegating tasks to specialized SubAgents.

Provides a `task` tool that allows the main agent to delegate complex,
multi-step work to specialized SubAgents. SubAgents run in isolated
contexts with their own conversation history, providing token efficiency
and clean separation of concerns.

## Features

- **Dynamic SubAgents**: Create SubAgents from configuration at runtime
- **Pre-compiled SubAgents**: Use pre-built Agent instances
- **HITL Propagation**: SubAgent interrupts automatically propagate to parent
- **Token Efficiency**: Parent only sees final result, not SubAgent's internal work
- **Process Isolation**: SubAgents run as supervised processes

## Configuration Options

The middleware accepts these options:

  * `:subagents` - List of `SubAgent.Config` or `SubAgent.Compiled` configurations
    for pre-defined subagents. Defaults to `[]`.

  * `:model` - The chat model for dynamic subagents. Required.

  * `:middleware` - Additional middleware to add to subagents. Defaults to `[]`.

  * `:block_middleware` - List of middleware modules to exclude from general-purpose
    subagent inheritance. Defaults to `[]`. See "Middleware Filtering" below.

## Configuration Example

    middleware = [
      {SubAgent, [
        model: model,
        subagents: [
          SubAgent.Config.new!(%{
            name: "researcher",
            description: "Research topics using internet search",
            system_prompt: "You are an expert researcher...",
            tools: [internet_search_tool]
          }),
          SubAgent.Compiled.new!(%{
            name: "coder",
            description: "Write code for specific tasks",
            agent: pre_built_coder_agent
          })
        ],
        block_middleware: [ConversationTitle, Summarization]
      ]}
    ]

## Middleware Filtering

When a general-purpose subagent is created, it inherits the parent agent's middleware
stack with certain exclusions:

1. **SubAgent middleware is ALWAYS excluded** - This prevents recursive subagent
   nesting which could lead to resource exhaustion. You cannot override this.

2. **Blocked middleware is excluded** - Any modules listed in `:block_middleware`
   are filtered out before passing to the subagent.

### Example: Blocking Unnecessary Middleware

Some middleware is inappropriate for short-lived subagents:

    {SubAgent, [
      model: model,
      subagents: [],

      # These middleware modules won't be inherited by general-purpose subagents
      block_middleware: [
        Sagents.Middleware.ConversationTitle,  # Subagents don't need titles
        Sagents.Middleware.Summarization       # Short tasks don't need summarization
      ]
    ]}

### Pre-configured Subagents

The `:block_middleware` option only affects **general-purpose** subagents created
dynamically via the `task` tool. Pre-configured subagents (defined in `:subagents`)
use their own explicitly defined middleware and are NOT affected by this option.

    {SubAgent, [
      subagents: [
        # This subagent defines its own middleware - block_middleware doesn't apply
        SubAgent.Config.new!(%{
          name: "researcher",
          middleware: [ConversationTitle]  # Explicitly included
        })
      ],
      block_middleware: [ConversationTitle]  # Only affects general-purpose
    ]}

## Usage Example

    # Main agent decides to delegate work
    "I need to research renewable energy. I'll use the researcher SubAgent."
    → Calls: task("Research renewable energy impacts", "researcher")

    # SubAgent executes independently
    # If SubAgent hits HITL interrupt (e.g., internet_search needs approval):
    #   1. SubAgent pauses
    #   2. Interrupt propagates to parent
    #   3. User sees: "SubAgent 'researcher' needs approval for 'internet_search'"
    #   4. User approves
    #   5. Parent resumes, which resumes SubAgent
    #   6. SubAgent completes and returns result

## Architecture

    Main Agent
      │
      ├─ task("research task", "researcher")
      │   │
      │   └─ SubAgent (as SubAgentServer process)
      │       ├─ Fresh conversation
      │       ├─ Specialized tools
      │       ├─ LLM executes
      │       └─ Returns final message only
      │
      └─ Receives result, continues

## HITL Interrupt Flow

    1. SubAgent hits HITL interrupt
    2. SubAgentServer.execute() returns {:interrupt, interrupt_data}
    3. Task tool receives interrupt
    4. Task tool returns {:interrupt, enhanced_data} to parent
    5. Parent agent propagates to AgentServer
    6. User approves
    7. Parent agent resumes
    8. Task tool calls SubAgentServer.resume(decisions)
    9. SubAgent continues and completes

# `handle_resume`

Handle resume for SubAgent interrupts.

Claims interrupts where `state.interrupt_data` has `type: :subagent_hitl`.
Delegates to SubAgentServer.resume and handles completion, re-interrupt,
and error cases. Also handles `type: :multiple_interrupts` by processing
the first interrupt and queuing the rest.

# `start_subagent`

```elixir
@spec start_subagent(String.t(), String.t(), map(), map(), map()) ::
  {:ok, String.t()}
  | {:ok, String.t(), term()}
  | {:interrupt, map()}
  | {:error, String.t()}
```

Starts and executes a new SubAgent to delegate work.

This function allows custom tools and middleware to spawn SubAgents for
delegating complex, multi-step tasks, similar to how the built-in `task` tool
works. The SubAgent runs as an isolated, supervised process with its own
conversation context.

## Parameters

- `instructions` - Detailed instructions for what the SubAgent should
  accomplish. Be specific about the task, expected output, and any context
  needed.

- `subagent_type` - The name/type of SubAgent to use. Must match a configured
  SubAgent name (from middleware init) or "general-purpose" for dynamic
  SubAgents.

- `args` - Full arguments map containing:
  - `"instructions"` (required) - Same as instructions parameter
  - `"subagent_type"` (required) - Same as subagent_type parameter
  - `"system_prompt"` (optional) - Custom system prompt for general-purpose
    SubAgents

- `context` - Tool execution context map containing:
  - `:agent_id` - Parent agent ID
  - `:state` - Parent agent state
  - `:parent_middleware` - Parent middleware list (for general-purpose
    SubAgents)
  - `:resume_info` - Resume information if continuing interrupted SubAgent

- `config` - Middleware configuration map containing:
  - `:agent_map` - Map of subagent_type -> Agent struct
  - `:descriptions` - Map of subagent_type -> description string
  - `:agent_id` - Parent agent ID
  - `:model` - Model configuration

## Returns

- `{:ok, result}` - SubAgent completed successfully, returns final message
  content
- `{:interrupt, interrupt_data}` - SubAgent hit HITL interrupt, needs approval
- `{:error, reason}` - Failed to start or execute SubAgent

## Example

Using from a custom tool function:

    def my_research_tool_function(args, context) do
      # Build config from middleware state
      subagent_config = %{
        agent_map: context.subagent_map,
        descriptions: context.subagent_descriptions,
        agent_id: context.agent_id,
        model: context.model
      }

      # Prepare arguments
      task_args = %{
        "instructions" => "Research quantum computing developments",
        "subagent_type" => "researcher"
      }

      # Start SubAgent
      case SubAgent.start_subagent(
        "Research quantum computing developments",
        "researcher",
        task_args,
        context,
        subagent_config
      ) do
        {:ok, result} ->
          {:ok, "Research complete: " <> result}

        {:interrupt, interrupt_data} ->
          # Propagate interrupt to parent
          {:interrupt, interrupt_data}

        {:error, reason} ->
          {:error, "Failed to research: " <> reason}
      end
    end

## Notes

- SubAgents run in isolated process contexts with their own conversation
  history
- Parent only sees final result, not intermediate reasoning (token efficient)
- HITL interrupts from SubAgents automatically propagate to parent
- For "general-purpose" type, tools and middleware are inherited from parent
- SubAgents are supervised and cleaned up automatically

---

*Consult [api-reference.md](api-reference.md) for complete listing*
