# `Sagents.SubAgent`

A runnable, pausable, and resumable agent execution context.

## Core Philosophy

**"The SubAgent struct HOLDS the LLMChain."**

This is the key insight that makes pause/resume trivial:
- **The chain persists** in the SubAgent struct between pause and resume
- The chain remembers all messages, tool calls, and state
- Pause = stop executing, save the SubAgent struct
- Resume = continue the SAME chain with decisions
- No reconstruction needed

## The SubAgent Struct

The SubAgent holds:
- **The LLMChain** - THE KEY FIELD - manages the entire conversation
- **Status tracking** (idle, running, interrupted, completed, error)
- **Interrupt data** when paused (which tools need approval)
- **Error** when failed
- **Metadata** (id, parent_agent_id, created_at)

## How It Works

1. **Initialization**: Create LLMChain with initial messages, tools, and model
2. **Execution**: Run LLMChain in a loop until completion or interrupt
3. **Conversation**: All messages live in the chain - it's all there
4. **Results**: Extract from the final message in the chain

## Key Design Principles

1. **Chain Persistence = Simple Resume**
   - Chain is already paused at the right spot
   - Resume just continues the chain with decisions
   - The chain remembers everything

2. **Direct Chain Management**
   - SubAgent.execute runs LLMChain.run directly
   - No delegation to Agent
   - Full control over the execution loop

3. **HITL = Pause/Resume**
   - Interrupt → Chain pauses after LLM returns tool calls
   - Save interrupt data (which tools, what arguments)
   - Resume → Apply decisions, execute tools, continue chain
   - Can have multiple pause/resume cycles naturally

## SubAgent Execution Flow

### Creating a SubAgent

    # From configuration
    subagent = SubAgent.new_from_config(
      parent_agent_id: "main-agent",
      instructions: "Research renewable energy",
      agent_config: agent_from_registry,
      parent_state: parent_state
    )

### Executing

    case SubAgent.execute(subagent) do
      {:ok, completed_subagent} ->
        # Extract result
        result = SubAgent.extract_result(completed_subagent)

      {:interrupt, interrupted_subagent} ->
        # Needs human approval
        # interrupted_subagent.interrupt_data contains action requests

      {:error, error_subagent} ->
        # Execution failed
        # error_subagent.error contains the error
    end

### Resuming After Interrupt

    case SubAgent.resume(interrupted_subagent, decisions) do
      {:ok, completed_subagent} -> # Completed
      {:interrupt, interrupted_subagent} -> # Another interrupt
      {:error, error_subagent} -> # Failed
    end

## Multiple Interrupts

The beauty of this design: multiple interrupts just repeat the pause/resume:

    # First execution
    {:interrupt, subagent1} = SubAgent.execute(subagent0)
    # chain paused at: [user, assistant_with_tool_call_1]

    # First resume
    {:interrupt, subagent2} = SubAgent.resume(subagent1, [decision1])
    # chain paused at: [user, assistant_1, tool_result_1, assistant_with_tool_call_2]

    # Second resume
    {:ok, subagent3} = SubAgent.resume(subagent2, [decision2])
    # chain completed

# `t`

```elixir
@type t() :: %Sagents.SubAgent{
  chain: LangChain.Chains.LLMChain.t() | nil,
  created_at: DateTime.t() | nil,
  error: term() | nil,
  id: String.t() | nil,
  interrupt_data: map() | nil,
  interrupt_on: map() | nil,
  max_runs: term(),
  parent_agent_id: String.t() | nil,
  status: :idle | :running | :interrupted | :completed | :error,
  until_tool: String.t() | [String.t()] | nil
}
```

# `build_agent_map`

Build an agent map of subagents from configurations.

# `build_agent_map!`

Build a registry of subagents, raising on error.

# `build_descriptions`

Build descriptions map for subagents.

# `can_execute?`

Check if SubAgent can be executed.

Only SubAgents with status :idle can be executed.

# `can_resume?`

Check if SubAgent can be resumed.

Only SubAgents with status :interrupted can be resumed.

# `compose_child_system_prompt`

Compose the child agent's `base_system_prompt` from a Config.

Composition rule:
- Header = `system_prompt_override` (if set) else the built-in boilerplate.
- Body = `instructions` (if set) else `system_prompt` (legacy) else "".

The header and body are joined with a blank line. Middleware-contributed
prompt fragments are appended later by the normal `Sagents.Agent` compile
path — this function does not handle those.

# `execute`

Execute the SubAgent.

Runs the LLMChain until:
- Natural completion (no more tool calls)
- HITL interrupt (tool needs approval)
- Error

Returns updated SubAgent struct with new status.

## Options

- `:callbacks` - Map of LLMChain callbacks (e.g., `%{on_message_processed: fn...}`)

## Examples

    case SubAgent.execute(subagent) do
      {:ok, completed_subagent} ->
        result = SubAgent.extract_result(completed_subagent)

      {:interrupt, interrupted_subagent} ->
        # interrupted_subagent.interrupt_data contains action_requests

      {:error, error_subagent} ->
        # error_subagent.error contains the error
    end

    # With callbacks for real-time message broadcasting
    callbacks = %{
      on_message_processed: fn _chain, message ->
        broadcast_message(message)
      end
    }
    SubAgent.execute(subagent, callbacks: callbacks)

# `extract_middleware_module`

```elixir
@spec extract_middleware_module(any()) :: module() | nil
```

Extract the module from any middleware format.

Returns the middleware module regardless of whether the input is:
- A raw module atom
- A {module, opts} tuple
- A MiddlewareEntry struct

Returns nil for unrecognized formats.

# `extract_result`

Extract result from completed SubAgent.

For completed SubAgents, extracts the final message content as a string.
This is the default extraction - middleware or custom logic can provide
different extraction.

Returns `{:ok, string}` on success or `{:error, reason}` on failure.

## Examples

    {:ok, completed_subagent} = SubAgent.execute(subagent)
    {:ok, result} = SubAgent.extract_result(completed_subagent)
    # => {:ok, "Research complete: Solar energy has shown..."}

# `is_subagent_middleware?`

```elixir
@spec is_subagent_middleware?(any()) :: boolean()
```

Check if a middleware spec refers to the SubAgent middleware.

Handles all middleware formats: raw modules, tuples, and MiddlewareEntry structs.

# `is_terminal?`

Check if SubAgent is in a terminal state.

Terminal states are :completed and :error.

# `new_from_compiled`

Create a SubAgent from compiled agent (pre-built).

A compiled subagent is pre-defined by the application with complete control
over configuration.

## Options

- `:parent_agent_id` - Parent's agent ID (required)
- `:instructions` - Task description (required)
- `:compiled_agent` - Pre-built Agent struct (required)
- `:initial_messages` - Optional initial message sequence (default: [])
- `:parent_tool_context` - Parent's tool_context map to inherit (optional).
  Static, caller-supplied data (e.g., `user_id`, feature flags) that tools
  access as flat top-level keys on context. Defaults to `%{}`.
- `:parent_metadata` - Parent's state metadata map to inherit (optional).
  Dynamic, middleware-managed data (e.g., `conversation_title`) that tools
  access via `context.state.metadata`. Defaults to `%{}`.
- `:scope` - Scope struct to inherit from the parent (optional). Propagated to
  the SubAgent's `custom_context.scope` so sub-agent tools and persistence callbacks
  see the same tenant context as the parent. Defaults to `compiled_agent.scope`.

## Examples

    subagent = SubAgent.new_from_compiled(
      parent_agent_id: "main-agent",
      instructions: "Extract structured data",
      compiled_agent: data_extractor_agent,
      initial_messages: [prep_message],
      parent_tool_context: %{user_id: 42},
      parent_metadata: %{"conversation_title" => "Extraction"}
    )

# `new_from_config`

Create a SubAgent from configuration (dynamic subagent).

The main agent configures a subagent through the task tool by providing:
- Instructions (becomes user message)
- Agent configuration (Agent struct)

## Options

- `:parent_agent_id` - Parent's agent ID (required)
- `:instructions` - Task description (required)
- `:agent_config` - Agent struct with tools, model, middleware (required)
- `:parent_tool_context` - Parent's tool_context map to inherit (optional).
  Static, caller-supplied data (e.g., `user_id`, feature flags) that tools
  access as flat top-level keys on context. Defaults to `%{}`.
- `:parent_metadata` - Parent's state metadata map to inherit (optional).
  Dynamic, middleware-managed data (e.g., `conversation_title`) that tools
  access via `context.state.metadata`. Defaults to `%{}`.
- `:scope` - Scope struct to inherit from the parent (optional). Propagated to
  the SubAgent's `custom_context.scope` so sub-agent tools and persistence callbacks
  see the same tenant context as the parent. Defaults to `agent_config.scope`.

## Examples

    subagent = SubAgent.new_from_config(
      parent_agent_id: "main-agent",
      instructions: "Research renewable energy impacts",
      agent_config: agent_struct,
      parent_tool_context: %{user_id: 42, tenant: "acme"},
      parent_metadata: %{"conversation_title" => "Research"},
      scope: %MyApp.Accounts.Scope{user: user}
    )

# `resume`

Resume the SubAgent after HITL interrupt.

Takes human decisions and continues execution from where it left off.
This is the magic - the agent state is already paused at the right spot.

## Parameters

- `subagent` - SubAgent with status :interrupted
- `decisions` - List of decision maps from human reviewer
- `opts` - Optional keyword list with:
  - `:callbacks` - Map of LLMChain callbacks (e.g., `%{on_message_processed: fn...}`)

## Returns

- `{:ok, completed_subagent}` - Execution completed
- `{:interrupt, interrupted_subagent}` - Another interrupt (multiple HITL tools)
- `{:error, error_subagent}` - Resume failed

## Examples

    decisions = [
      %{type: :approve},
      %{type: :edit, arguments: %{"path" => "safe.txt"}}
    ]

    case SubAgent.resume(interrupted_subagent, decisions) do
      {:ok, completed_subagent} ->
        result = SubAgent.extract_result(completed_subagent)

      {:interrupt, interrupted_again} ->
        # Another interrupt - repeat the process

      {:error, error_subagent} ->
        # Handle error
    end

# `subagent_middleware_stack`

```elixir
@spec subagent_middleware_stack(list(), list(), keyword()) :: list()
```

Build a middleware stack for subagents, filtering out blocked middleware.

The SubAgent middleware itself is ALWAYS filtered out to prevent recursive
subagent nesting, regardless of whether it appears in the block list.

This function handles middleware in all formats:
- Raw module atoms (e.g., `Sagents.Middleware.SubAgent`)
- Raw tuples (e.g., `{Sagents.Middleware.SubAgent, opts}`)
- Initialized MiddlewareEntry structs (from `agent.middleware`)

## Options

  * `:block_middleware` - List of middleware modules to exclude from inheritance.
    These modules will not be passed to general-purpose subagents. Defaults to `[]`.

## Examples

    # Default behavior: only SubAgent middleware is filtered
    subagent_middleware_stack(parent_middleware)

    # Block additional middleware from being inherited
    subagent_middleware_stack(parent_middleware, [],
      block_middleware: [ConversationTitle, Summarization]
    )

    # Add additional middleware while blocking others
    subagent_middleware_stack(parent_middleware, [CustomMiddleware],
      block_middleware: [ConversationTitle]
    )

# `task_subagent_boilerplate`

Returns the universal framing prompt prepended to every task-style sub-agent's
system prompt.

Encodes the "no user, complete-or-fail, no clarifying questions" contract so
that `Sagents.SubAgent.Task` authors can focus their `instructions/0` on the
substantive procedure. Host compilers combine this with the task's
`instructions/0` to form the child agent's system prompt; it can be replaced
per-sub-agent via `Sagents.SubAgent.Config`'s `system_prompt_override`.

---

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