feat(subplans): Subplan System Specification and Invariant Enforcement v3.3.0 (#8725)

Add comprehensive Subplan System specification defining module boundaries,
data models (Subplan, SubplanResult, SubplanTree), PostgreSQL schema with
indexes, the 8-step spawning algorithm, concurrency control via per-plan
semaphores, and error handling.

Implement invariant loading and enforcement in Strategize phase:
- Add InvariantViolationError exception class
- Add load_active_invariants() and check_invariants() methods to InvariantService
- Add _is_violation heuristic for action/invariant matching
- Add BDD tests with @load_invariants, @check_invariants, etc. tags

ISSUES CLOSED: #8725
This commit is contained in:
2026-05-07 20:49:13 +00:00
committed by Forgejo
parent 118cd167ca
commit bc6677b3cd
7 changed files with 969 additions and 1 deletions
+146
View File
@@ -44190,6 +44190,152 @@ Pydantic handles serialization to and from JSON, YAML (via dict intermediary), a
4. **Pyright in strict mode** verifies that Pydantic model field types are consistent across the codebase.
5. **Schema drift tests** verify that JSON Schema generated from Pydantic tool models matches the schemas expected by MCP and LangChain tool-calling protocols.
---
## Subplan System (v3.3.0)
### Overview
The Subplan System enables plans to spawn child plans (subplans) during execution. Subplans execute in parallel with configurable concurrency limits (`max_parallel`). Results are merged back into the parent plan using three-way merge strategies. The parent plan tracks all subplan statuses and waits for completion before proceeding.
### Module Boundaries
- **Module**: `cleveragents.subplans`
- **Layer**: Domain (with Application orchestration)
- **Responsibilities**:
- Spawning subplans from a parent plan during Execute phase
- Managing subplan lifecycle (pending → running → complete/failed)
- Enforcing `max_parallel` concurrency limits
- Tracking parent-child plan relationships
- Providing subplan status to parent plan
- **Public Interfaces**:
- `SubplanSpawner` — creates and registers subplans from a parent plan
- `SubplanRepository` — CRUD for subplan entities (domain repository interface)
- `SubplanExecutor` — orchestrates parallel subplan execution with concurrency control
- `SubplanStatusTracker` — tracks and reports subplan completion status
- **Forbidden Dependencies**: Must not import from ``cleveragents.cli`` or ``cleveragents.tui``
### Data Models
#### Subplan Entity
```python
@dataclass
class Subplan:
id: str # ULID
parent_plan_id: str # ULID
root_plan_id: str # ULID — Top-level ancestor plan
depth: int # 1 = direct child of root, 2 = grandchild, etc.
title: str
description: str
action_id: str # ULID — Action template driving this subplan
status: Literal["pending", "running", "complete", "failed", "cancelled"]
max_parallel: int # Max concurrent sub-subplans this plan may spawn
created_at: datetime
started_at: Optional[datetime]
completed_at: Optional[datetime]
result: Optional[SubplanResult]
metadata: dict
```
#### SubplanResult
```python
@dataclass
class SubplanResult:
subplan_id: str # ULID
status: Literal["success", "partial", "failed"]
output: dict # Structured output from the subplan
artifacts: list[str] # File paths or resource IDs produced
error: Optional[str]
merge_conflicts: list[MergeConflict] # Conflicts detected during merge
```
#### SubplanTree
```python
@dataclass
class SubplanTree:
root_plan_id: str # ULID
def get_children(self, plan_id: str) -> list[Subplan]: ...
def get_all_descendants(self, plan_id: str) -> list[Subplan]: ...
def get_depth(self, plan_id: str) -> int: ...
def get_running_count(self, plan_id: str) -> int: ...
```
### Database Schema
```sql
CREATE TABLE subplans (
id TEXT PRIMARY KEY, -- ULID
parent_plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
root_plan_id TEXT NOT NULL REFERENCES plans(id),
depth INTEGER NOT NULL DEFAULT 1,
title VARCHAR(500) NOT NULL,
description TEXT NOT NULL,
action_id TEXT NOT NULL REFERENCES actions(id), -- ULID
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'running', 'complete', 'failed', 'cancelled')),
max_parallel INTEGER NOT NULL DEFAULT 4,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
result JSONB,
metadata JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_subplans_parent_plan ON subplans(parent_plan_id);
CREATE INDEX idx_subplans_root_plan ON subplans(root_plan_id);
CREATE INDEX idx_subplans_status ON subplans(status);
```
### Spawning Algorithm
During the Execute phase, the LangGraph execute node may spawn subplans:
1. The LLM decides to decompose a task into subtasks
2. `SubplanSpawner.spawn(parent_plan_id, subtasks)` creates subplan records
3. Each subplan is assigned an Action template appropriate for the subtask
4. `SubplanExecutor.execute_parallel(subplans, max_parallel)` begins execution
5. Subplans are dispatched in batches of `max_parallel`
6. Each subplan runs its own Strategize → Execute → Apply lifecycle
7. Subplans may themselves spawn sub-subplans (hierarchical decomposition, 4+ levels)
8. Parent plan waits for all subplans to complete before proceeding
### Concurrency Control
- `max_parallel` is configurable per plan (default: 4, max: 16)
- Concurrency is enforced using a semaphore per parent plan
- If a subplan fails, sibling subplans continue unless `fail_fast=True`
- Cancelled subplans are marked `cancelled` and their results are excluded from merge
### Integration Points
| Integration | Direction | Description |
|---|---|---|
| Plan Executor | Called by | Execute node spawns subplans via `SubplanSpawner` |
| Three-Way Merge | Calls | `SubplanExecutor` calls merge strategy after all subplans complete |
| Decision Recording | Calls | Subplan spawning decisions recorded via `DecisionRecorder` |
| Checkpoint System | Calls | Checkpoints created before and after subplan execution |
### Error Handling
- `SubplanSpawnError(parent_plan_id, reason)` — failed to create subplan
- `SubplanExecutionError(subplan_id, reason)` — subplan execution failed
- `MaxParallelExceededError(plan_id, requested, max)` — concurrency limit exceeded
- `SubplanDepthLimitError(plan_id, depth, max_depth)` — hierarchical depth limit exceeded (max: 8)
### Cross-Cutting Concerns
- **Observability**: Subplan count, depth, and completion rate tracked as metrics
- **Logging**: All subplan lifecycle events logged at INFO level
- **Cancellation**: Parent plan cancellation propagates to all running subplans
- **Timeout**: Each subplan has a configurable timeout (default: 30 minutes)
---
### ACMS (Advanced Context Management System)
!!! adr "Architecture Decision"