Add SubplanService for building child plans from DecisionService spawn entries and SubplanConfig. The service validates resource scopes, merge strategies, and max_parallel bounds before spawning. Key additions: - SubplanService: Orchestrates child plan creation from spawn entries - SpawnMetadata: Persisted metadata (spawn_decision_id, parent/root plan IDs, execution mode) for status output - Spawn validation: Checks resource scopes, merge strategy, and parallelism bounds before spawning - Documentation: subplan_service.md with spawn workflow and lifecycle ISSUES CLOSED: #197
5.6 KiB
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:
- Extract spawn decisions from the
DecisionService - Build
SpawnEntryobjects from those decisions - Validate the spawn request
- Create
SubplanStatusandSpawnMetadatafor each entry - 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:
-
Resource scopes resolved: All
target_resourcesin each entry exist in theavailable_resourcesset. Unresolved resources produce a validation error. -
Merge strategy defined: The
SubplanConfig.merge_strategymust not beNone. Without a merge strategy, child plan results cannot be combined. -
max_parallel bounds: In
PARALLELexecution mode, the number of spawn entries must not exceedconfig.max_parallel. -
Valid action names: Each entry must have a non-empty
action_name. -
Correct decision types: Each entry's decision must be either
subplan_spawnorsubplan_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
SubplanStatusobjects fromSpawnResult. - SubplanMergeService: Merges child plan outputs using the strategy
from
SubplanConfig. - PlanLifecycleService: Manages the parent plan's lifecycle,
persisting the updated
subplan_statusesandsubplan_config.
Example Usage
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.