# 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.