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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user