# Subplan Service: Spawn Workflow and Lifecycle

The `SubplanService` coordinates the spawning of child plans from
`DecisionService` spawn entries and `SubplanConfig`. It validates
resource scopes, merge strategies, and parallelism bounds before
creating child plan statuses.

## Overview

When a parent plan decides to decompose work into child plans (via
`subplan_spawn` or `subplan_parallel_spawn` decisions during Strategize),
the `SubplanService` handles the spawn workflow:

1. Extract spawn decisions from the `DecisionService`
2. Build `SpawnEntry` objects from those decisions
3. Validate the spawn request
4. Create `SubplanStatus` and `SpawnMetadata` for each entry
5. Return the result for the caller to persist on the parent plan

## Spawn Workflow

```
DecisionService          SubplanService              Parent Plan
     |                        |                          |
     |  get_spawn_decisions   |                          |
     |<-----------------------|                          |
     |  [Decision, ...]       |                          |
     |----------------------->|                          |
     |                        |  build_spawn_entries     |
     |                        |  validate_spawn          |
     |                        |  spawn                   |
     |                        |------------------------->|
     |                        |  SpawnResult             |
     |                        |  (statuses + metadata)   |
```

## Key Types

### SpawnMetadata

Persisted alongside each child plan for status output and provenance:

| Field               | Type   | Description                              |
|---------------------|--------|------------------------------------------|
| `spawn_decision_id` | `str`  | ULID of the decision that triggered spawn|
| `parent_plan_id`    | `str`  | ULID of the parent plan                  |
| `root_plan_id`      | `str`  | ULID of the root plan in the hierarchy   |
| `execution_mode`    | `str`  | How the subplan should be executed       |

### SpawnEntry

A single spawn request derived from a decision:

| Field              | Type         | Description                          |
|--------------------|--------------|--------------------------------------|
| `decision`         | `Decision`   | The decision that triggered spawn    |
| `action_name`      | `str`        | Namespaced action name for child plan|
| `target_resources` | `list[str]`  | Resource IDs the child plan uses     |
| `description`      | `str`        | Description/prompt for the child plan|

### SpawnResult

Result of spawning child plans:

| Field              | Type                          | Description                    |
|--------------------|-------------------------------|--------------------------------|
| `spawned_statuses` | `list[SubplanStatus]`         | Status for each child plan     |
| `metadata`         | `dict[str, SpawnMetadata]`    | Metadata keyed by subplan_id   |
| `total_spawned`    | `int`                         | Number of child plans created  |
| `execution_mode`   | `str`                         | Execution mode from config     |

## Spawn Validation

Before any child plan is created, `validate_spawn` checks:

1. **Resource scopes resolved**: All `target_resources` in each entry
   exist in the `available_resources` set. Unresolved resources produce
   a validation error.

2. **Merge strategy defined**: The `SubplanConfig.merge_strategy` must
   not be `None`. Without a merge strategy, child plan results cannot
   be combined.

3. **max_parallel bounds**: In `PARALLEL` execution mode, the number of
   spawn entries must not exceed `config.max_parallel`.

4. **Valid action names**: Each entry must have a non-empty `action_name`.

5. **Correct decision types**: Each entry's decision must be either
   `subplan_spawn` or `subplan_parallel_spawn`.

If any check fails, `SpawnValidationError` is raised with all errors.

## Integration with Other Services

- **DecisionService**: Provides spawn-type decisions via
  `list_by_type(plan_id, "subplan_spawn")`.
- **SubplanExecutionService**: Executes the spawned child plans using
  the `SubplanStatus` objects from `SpawnResult`.
- **SubplanMergeService**: Merges child plan outputs using the strategy
  from `SubplanConfig`.
- **PlanLifecycleService**: Manages the parent plan's lifecycle,
  persisting the updated `subplan_statuses` and `subplan_config`.

## Example Usage

```python
from cleveragents.application.services.subplan_service import (
    SubplanService,
)

# Get spawn decisions for the parent plan
decisions = subplan_service.get_spawn_decisions(parent_plan.identity.plan_id)

# Build spawn entries from decisions
entries = subplan_service.build_spawn_entries(decisions)

# Spawn child plans (validates first)
result = subplan_service.spawn(
    parent_plan=parent_plan,
    config=parent_plan.subplan_config,
    spawn_entries=entries,
    available_resources=frozenset(known_resource_ids),
)

# Update the parent plan with spawned statuses
parent_plan.subplan_statuses = result.spawned_statuses
```

## Error Handling

| Error                    | When                                        |
|--------------------------|---------------------------------------------|
| `ValueError`             | `None` or empty required arguments          |
| `SpawnValidationError`   | Validation checks fail (resource scope,     |
|                          | merge strategy, max_parallel, action name,  |
|                          | or decision type)                           |

`SpawnValidationError` inherits from `ValidationError` and includes a
`validation_errors` list with detailed messages for each failure.
