feat(domain): rebaseline plan phases and apply states
CI / lint (pull_request) Successful in 15s
CI / typecheck (pull_request) Successful in 31s
CI / security (pull_request) Successful in 22s
CI / quality (pull_request) Successful in 15s
CI / integration_tests (pull_request) Successful in 4m37s
CI / build (pull_request) Successful in 15s
CI / lint (push) Successful in 12s
CI / typecheck (push) Successful in 27s
CI / security (push) Successful in 22s
CI / quality (push) Successful in 15s
CI / unit_tests (pull_request) Successful in 9m56s
CI / integration_tests (push) Successful in 4m41s
CI / build (push) Successful in 15s
CI / unit_tests (push) Successful in 9m58s
CI / coverage (pull_request) Successful in 7m1s
CI / docker (pull_request) Successful in 8s
CI / docker (push) Successful in 8s
CI / coverage (push) Successful in 6m54s
CI / lint (pull_request) Successful in 15s
CI / typecheck (pull_request) Successful in 31s
CI / security (pull_request) Successful in 22s
CI / quality (pull_request) Successful in 15s
CI / integration_tests (pull_request) Successful in 4m37s
CI / build (pull_request) Successful in 15s
CI / lint (push) Successful in 12s
CI / typecheck (push) Successful in 27s
CI / security (push) Successful in 22s
CI / quality (push) Successful in 15s
CI / unit_tests (pull_request) Successful in 9m56s
CI / integration_tests (push) Successful in 4m41s
CI / build (push) Successful in 15s
CI / unit_tests (push) Successful in 9m58s
CI / coverage (pull_request) Successful in 7m1s
CI / docker (pull_request) Successful in 8s
CI / docker (push) Successful in 8s
CI / coverage (push) Successful in 6m54s
This commit was merged in pull request #66.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""ASV benchmarks for plan phase transition validation overhead.
|
||||
|
||||
Measures the cost of:
|
||||
- ``can_transition()`` lookups
|
||||
- ``Plan.get_next_phase()`` traversals
|
||||
- ``Plan.is_terminal`` property checks
|
||||
- ``Plan.can_transition_to_next_phase`` property checks
|
||||
"""
|
||||
|
||||
from cleveragents.domain.models.core.plan import (
|
||||
NamespacedName,
|
||||
Plan,
|
||||
PlanIdentity,
|
||||
PlanPhase,
|
||||
ProcessingState,
|
||||
can_transition,
|
||||
)
|
||||
|
||||
|
||||
def _make_plan(
|
||||
phase: PlanPhase = PlanPhase.STRATEGIZE,
|
||||
processing_state: ProcessingState = ProcessingState.QUEUED,
|
||||
) -> Plan:
|
||||
return Plan(
|
||||
identity=PlanIdentity(plan_id="01ARZ3NDEKTSV4RRFFQ69G5FAV"),
|
||||
namespaced_name=NamespacedName(server=None, namespace="local", name="bench"),
|
||||
description="Benchmark plan",
|
||||
action_name="local/bench-action",
|
||||
phase=phase,
|
||||
processing_state=processing_state,
|
||||
)
|
||||
|
||||
|
||||
class TimeCanTransition:
|
||||
"""Benchmark ``can_transition()`` function."""
|
||||
|
||||
def time_valid_transition_strategize_to_execute(self) -> None:
|
||||
can_transition(PlanPhase.STRATEGIZE, PlanPhase.EXECUTE)
|
||||
|
||||
def time_valid_transition_action_to_strategize(self) -> None:
|
||||
can_transition(PlanPhase.ACTION, PlanPhase.STRATEGIZE)
|
||||
|
||||
def time_invalid_transition_strategize_to_apply(self) -> None:
|
||||
can_transition(PlanPhase.STRATEGIZE, PlanPhase.APPLY)
|
||||
|
||||
def time_revert_execute_to_strategize(self) -> None:
|
||||
can_transition(PlanPhase.EXECUTE, PlanPhase.STRATEGIZE)
|
||||
|
||||
def time_revert_apply_to_strategize(self) -> None:
|
||||
can_transition(PlanPhase.APPLY, PlanPhase.STRATEGIZE)
|
||||
|
||||
|
||||
class TimeGetNextPhase:
|
||||
"""Benchmark ``Plan.get_next_phase()``."""
|
||||
|
||||
def setup(self) -> None:
|
||||
self.plan_action = _make_plan(PlanPhase.ACTION)
|
||||
self.plan_strategize = _make_plan(PlanPhase.STRATEGIZE)
|
||||
self.plan_execute = _make_plan(PlanPhase.EXECUTE)
|
||||
self.plan_apply = _make_plan(PlanPhase.APPLY)
|
||||
|
||||
def time_next_from_action(self) -> None:
|
||||
self.plan_action.get_next_phase()
|
||||
|
||||
def time_next_from_strategize(self) -> None:
|
||||
self.plan_strategize.get_next_phase()
|
||||
|
||||
def time_next_from_execute(self) -> None:
|
||||
self.plan_execute.get_next_phase()
|
||||
|
||||
def time_next_from_apply(self) -> None:
|
||||
self.plan_apply.get_next_phase()
|
||||
|
||||
|
||||
class TimeIsTerminal:
|
||||
"""Benchmark ``Plan.is_terminal`` property."""
|
||||
|
||||
def setup(self) -> None:
|
||||
self.plan_queued = _make_plan(PlanPhase.STRATEGIZE, ProcessingState.QUEUED)
|
||||
self.plan_applied = _make_plan(PlanPhase.APPLY, ProcessingState.APPLIED)
|
||||
self.plan_constrained = _make_plan(PlanPhase.APPLY, ProcessingState.CONSTRAINED)
|
||||
self.plan_cancelled = _make_plan(
|
||||
PlanPhase.STRATEGIZE, ProcessingState.CANCELLED
|
||||
)
|
||||
|
||||
def time_non_terminal(self) -> None:
|
||||
self.plan_queued.is_terminal
|
||||
|
||||
def time_applied_terminal(self) -> None:
|
||||
self.plan_applied.is_terminal
|
||||
|
||||
def time_constrained_terminal(self) -> None:
|
||||
self.plan_constrained.is_terminal
|
||||
|
||||
def time_cancelled_terminal(self) -> None:
|
||||
self.plan_cancelled.is_terminal
|
||||
|
||||
|
||||
class TimeCanTransitionToNextPhase:
|
||||
"""Benchmark ``Plan.can_transition_to_next_phase`` property."""
|
||||
|
||||
def setup(self) -> None:
|
||||
self.plan_action = _make_plan(PlanPhase.ACTION, ProcessingState.QUEUED)
|
||||
self.plan_strat_complete = _make_plan(
|
||||
PlanPhase.STRATEGIZE, ProcessingState.COMPLETE
|
||||
)
|
||||
self.plan_apply = _make_plan(PlanPhase.APPLY, ProcessingState.QUEUED)
|
||||
|
||||
def time_action_can_transition(self) -> None:
|
||||
self.plan_action.can_transition_to_next_phase
|
||||
|
||||
def time_strategize_complete_can_transition(self) -> None:
|
||||
self.plan_strat_complete.can_transition_to_next_phase
|
||||
|
||||
def time_apply_cannot_transition(self) -> None:
|
||||
self.plan_apply.can_transition_to_next_phase
|
||||
@@ -0,0 +1,116 @@
|
||||
# Plan Lifecycle Service Reference
|
||||
|
||||
> Source: `src/cleveragents/application/services/plan_lifecycle_service.py`
|
||||
|
||||
## Overview
|
||||
|
||||
The `PlanLifecycleService` manages the v3 plan lifecycle:
|
||||
|
||||
```
|
||||
Action -> Strategize -> Execute -> Apply
|
||||
```
|
||||
|
||||
Plans are instantiated from Action templates. Terminal outcomes live in the Apply phase's processing state: `applied`, `constrained`, `errored`, or `cancelled`.
|
||||
|
||||
## Action Management
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `create_action(...)` | Create a new action (defaults to `available` state) |
|
||||
| `get_action(name)` | Get action by namespaced name |
|
||||
| `get_action_by_name(name)` | Alias for `get_action` with partial name resolution |
|
||||
| `list_actions(namespace, state)` | List actions with optional filtering |
|
||||
| `make_action_available(name)` | No-op (actions are available by default) |
|
||||
| `archive_action(name)` | Set action state to `archived` |
|
||||
|
||||
## Plan Creation
|
||||
|
||||
### `use_action(action_name, project_links, arguments, ...)`
|
||||
|
||||
Uses an action on projects to create a plan in **Strategize** phase.
|
||||
|
||||
- Validates action is in `available` state
|
||||
- Validates required arguments
|
||||
- Resolves automation level (explicit > global default)
|
||||
- Merges invariants (plan > action)
|
||||
- Creates plan with `phase=STRATEGIZE, processing_state=QUEUED`
|
||||
- Archives non-reusable actions after use
|
||||
|
||||
## Phase Transitions
|
||||
|
||||
| Method | From | To | Description |
|
||||
|--------|------|----|-------------|
|
||||
| `execute_plan(plan_id)` | Strategize/COMPLETE | Execute/QUEUED | Transition to Execute phase |
|
||||
| `apply_plan(plan_id)` | Execute/COMPLETE | Apply/QUEUED | Transition to Apply phase |
|
||||
|
||||
## Processing State Methods
|
||||
|
||||
### Strategize Phase
|
||||
|
||||
| Method | State Transition | Description |
|
||||
|--------|-----------------|-------------|
|
||||
| `start_strategize(plan_id)` | QUEUED -> PROCESSING | Begin strategize work |
|
||||
| `complete_strategize(plan_id)` | PROCESSING -> COMPLETE | Finish strategize, may auto-progress |
|
||||
| `fail_strategize(plan_id, error)` | * -> ERRORED | Mark strategize as failed |
|
||||
|
||||
### Execute Phase
|
||||
|
||||
| Method | State Transition | Description |
|
||||
|--------|-----------------|-------------|
|
||||
| `start_execute(plan_id)` | QUEUED -> PROCESSING | Begin execute work |
|
||||
| `complete_execute(plan_id)` | PROCESSING -> COMPLETE | Finish execute, may auto-progress |
|
||||
| `fail_execute(plan_id, error)` | * -> ERRORED | Mark execute as failed |
|
||||
|
||||
### Apply Phase
|
||||
|
||||
| Method | State Transition | Description |
|
||||
|--------|-----------------|-------------|
|
||||
| `start_apply(plan_id)` | QUEUED -> PROCESSING | Begin apply work |
|
||||
| `complete_apply(plan_id)` | PROCESSING -> APPLIED | **Terminal success** — changes committed |
|
||||
| `constrain_apply(plan_id, reason)` | * -> CONSTRAINED | Cannot complete within constraints; may revert to Strategize |
|
||||
| `fail_apply(plan_id, error)` | * -> ERRORED | Mark apply as failed |
|
||||
|
||||
### Apply Terminal Outcomes
|
||||
|
||||
The Apply phase has four terminal outcomes, modeled as `ProcessingState` values:
|
||||
|
||||
| Processing State | Meaning | Next Step |
|
||||
|-----------------|---------|-----------|
|
||||
| `APPLIED` | Success — changes committed | Done |
|
||||
| `CONSTRAINED` | Cannot proceed within strategy constraints | May revert to Strategize |
|
||||
| `ERRORED` | Failed | May retry or abandon |
|
||||
| `CANCELLED` | User/system cancelled | Done |
|
||||
|
||||
## Cancellation
|
||||
|
||||
### `cancel_plan(plan_id, reason=None)`
|
||||
|
||||
Cancel any non-terminal plan. Sets `processing_state=CANCELLED`.
|
||||
|
||||
## Automation
|
||||
|
||||
### `should_auto_progress(plan)`
|
||||
|
||||
Pure query — checks if the plan should automatically advance:
|
||||
- Strategize/COMPLETE with `REVIEW_BEFORE_APPLY` or `FULL_AUTOMATION`: auto-execute
|
||||
- Execute/COMPLETE with `FULL_AUTOMATION`: auto-apply
|
||||
|
||||
### `auto_progress(plan_id)`
|
||||
|
||||
Idempotent — advances to next phase if automation permits.
|
||||
|
||||
### `pause_plan(plan_id)` / `resume_plan(plan_id, level)`
|
||||
|
||||
Pause halts auto-progression (sets MANUAL). Resume restores level and triggers immediate auto-progress if ready.
|
||||
|
||||
### `set_plan_automation_level(plan_id, level)`
|
||||
|
||||
Change automation level for non-terminal plans.
|
||||
|
||||
## Custom Exceptions
|
||||
|
||||
| Exception | Description |
|
||||
|-----------|-------------|
|
||||
| `InvalidPhaseTransitionError` | Attempted invalid phase transition |
|
||||
| `ActionNotAvailableError` | Action not in `available` state |
|
||||
| `PlanNotReadyError` | Plan not in expected state for transition |
|
||||
@@ -213,11 +213,11 @@ Feature: Plan Lifecycle Service
|
||||
When I try to lifecycle apply the plan
|
||||
Then a plan not ready error should be raised
|
||||
|
||||
Scenario: Complete apply transitions to Applied (terminal)
|
||||
Scenario: Complete apply transitions to applied processing state (terminal)
|
||||
Given I have a plan in apply phase with processing state
|
||||
When I complete apply
|
||||
Then the lifecycle plan phase should be "applied"
|
||||
And the lifecycle plan processing state should be "complete"
|
||||
Then the lifecycle plan phase should be "apply"
|
||||
And the lifecycle plan processing state should be "applied"
|
||||
And the applied timestamp should be set
|
||||
And the plan should be terminal
|
||||
|
||||
@@ -269,7 +269,7 @@ Feature: Plan Lifecycle Service
|
||||
And the lifecycle plan error message should be "User requested cancellation"
|
||||
|
||||
Scenario: Cannot cancel terminal plan
|
||||
Given I have a plan in applied phase
|
||||
Given I have a plan in terminal applied state
|
||||
When I try to cancel the plan
|
||||
Then a plan error should be raised
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ Feature: Plan Domain Model
|
||||
# PlanPhase Tests
|
||||
|
||||
Scenario: Plan phases are in correct order
|
||||
Then the plan phases should be in order "strategize, execute, apply, applied"
|
||||
Then the plan phases should be in order "action, strategize, execute, apply"
|
||||
|
||||
Scenario: STRATEGIZE phase is the starting phase
|
||||
Given I create a new plan
|
||||
@@ -111,10 +111,9 @@ Feature: Plan Domain Model
|
||||
Given I create a new plan
|
||||
Then the next phase should be "execute"
|
||||
|
||||
Scenario: Get next phase from APPLIED is None (terminal)
|
||||
Given I create a plan in applied phase
|
||||
Scenario: Get next phase from APPLY is None (terminal phase)
|
||||
Given I create a plan in apply phase as terminal
|
||||
Then the next phase should be empty
|
||||
And the plan should be terminal
|
||||
|
||||
Scenario: Unknown phase returns no next phase
|
||||
Given I create a plan with an unknown phase value
|
||||
@@ -128,14 +127,14 @@ Feature: Plan Domain Model
|
||||
Scenario: Valid transition from EXECUTE to APPLY
|
||||
Then transition from "execute" to "apply" should be valid
|
||||
|
||||
Scenario: Valid transition from APPLY to APPLIED
|
||||
Then transition from "apply" to "applied" should be valid
|
||||
Scenario: Valid transition from APPLY to STRATEGIZE (revert via constrained)
|
||||
Then transition from "apply" to "strategize" should be valid
|
||||
|
||||
Scenario: Invalid transition from STRATEGIZE to APPLY
|
||||
Then transition from "strategize" to "apply" should be invalid
|
||||
|
||||
Scenario: No transition from APPLIED (terminal)
|
||||
Then transition from "applied" to "strategize" should be invalid
|
||||
Scenario: Valid transition from ACTION to STRATEGIZE
|
||||
Then transition from "action" to "strategize" should be valid
|
||||
|
||||
# Error State Tests
|
||||
|
||||
@@ -165,6 +164,6 @@ Feature: Plan Domain Model
|
||||
When I try to create a plan with invalid phase value
|
||||
Then a validation error should be raised
|
||||
|
||||
Scenario: Applied phase cannot transition to next phase
|
||||
Given I create a plan in applied phase
|
||||
Scenario: Apply phase cannot transition to next phase
|
||||
Given I create a plan in apply phase as terminal
|
||||
Then the plan cannot transition to next phase
|
||||
|
||||
@@ -38,11 +38,12 @@ Feature: Plan Model Coverage — New Fields and Helpers
|
||||
When I create a project link with alias "api-svc"
|
||||
Then the project link alias should be "api-svc"
|
||||
|
||||
# Plan — APPLIED phase auto-corrects processing_state
|
||||
# Plan — APPLY phase with APPLIED processing_state is terminal
|
||||
|
||||
Scenario: APPLIED phase auto-corrects processing_state to COMPLETE
|
||||
When I create a plan in applied phase with queued processing state
|
||||
Then the plan processing state should be "complete"
|
||||
Scenario: APPLY phase with APPLIED processing state is terminal
|
||||
When I create a plan in apply phase with applied processing state
|
||||
Then the plan processing state should be "applied"
|
||||
And the plan should be terminal
|
||||
|
||||
# Plan — duplicate project link alias uniqueness
|
||||
|
||||
|
||||
@@ -506,7 +506,16 @@ def step_create_plan_apply_processing(context: Context) -> None:
|
||||
|
||||
@given("I have a plan in applied phase")
|
||||
def step_create_plan_applied(context: Context) -> None:
|
||||
"""Create a plan in applied (terminal) phase."""
|
||||
"""Create a plan in apply phase with applied processing state (terminal)."""
|
||||
step_create_plan_apply_processing(context)
|
||||
context.plan = context.lifecycle_service.complete_apply(
|
||||
context.plan.identity.plan_id
|
||||
)
|
||||
|
||||
|
||||
@given("I have a plan in terminal applied state")
|
||||
def step_create_plan_terminal_applied(context: Context) -> None:
|
||||
"""Create a plan that has reached the applied terminal state."""
|
||||
step_create_plan_apply_processing(context)
|
||||
context.plan = context.lifecycle_service.complete_apply(
|
||||
context.plan.identity.plan_id
|
||||
|
||||
@@ -104,15 +104,15 @@ def step_check_project_link_alias(context: Context, expected: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
# ---- APPLIED phase auto-corrects processing_state ----
|
||||
# ---- APPLY phase with APPLIED processing_state is terminal ----
|
||||
|
||||
|
||||
@when("I create a plan in applied phase with queued processing state")
|
||||
def step_create_applied_plan_queued(context: Context) -> None:
|
||||
"""Create a plan in APPLIED phase with QUEUED (should auto-correct to COMPLETE)."""
|
||||
@when("I create a plan in apply phase with applied processing state")
|
||||
def step_create_apply_phase_applied_state(context: Context) -> None:
|
||||
"""Create a plan in APPLY phase with APPLIED processing state (terminal)."""
|
||||
context.plan = _base_plan(
|
||||
phase=PlanPhase.APPLIED,
|
||||
processing_state=ProcessingState.QUEUED,
|
||||
phase=PlanPhase.APPLY,
|
||||
processing_state=ProcessingState.APPLIED,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -226,10 +226,19 @@ def step_create_plan_strategize_processing(context: Context) -> None:
|
||||
|
||||
@given("I create a plan in applied phase")
|
||||
def step_create_plan_applied_phase(context: Context) -> None:
|
||||
"""Create a plan in APPLIED phase."""
|
||||
"""Create a plan in APPLY phase with APPLIED processing state (terminal)."""
|
||||
context.plan = _default_plan(
|
||||
phase=PlanPhase.APPLIED,
|
||||
processing_state=ProcessingState.COMPLETE,
|
||||
phase=PlanPhase.APPLY,
|
||||
processing_state=ProcessingState.APPLIED,
|
||||
)
|
||||
|
||||
|
||||
@given("I create a plan in apply phase as terminal")
|
||||
def step_create_plan_apply_terminal(context: Context) -> None:
|
||||
"""Create a plan in APPLY phase with APPLIED processing state (terminal)."""
|
||||
context.plan = _default_plan(
|
||||
phase=PlanPhase.APPLY,
|
||||
processing_state=ProcessingState.APPLIED,
|
||||
)
|
||||
|
||||
|
||||
@@ -271,10 +280,10 @@ def step_create_plan_strategize_cancelled(context: Context) -> None:
|
||||
|
||||
@given("I create a plan in action phase with archived state")
|
||||
def step_create_plan_action_archived(context: Context) -> None:
|
||||
"""Create a plan in APPLIED phase with COMPLETE state (formerly ACTION/ARCHIVED)."""
|
||||
"""Create a plan in APPLY phase with APPLIED state (terminal, formerly ACTION/ARCHIVED)."""
|
||||
context.plan = _default_plan(
|
||||
phase=PlanPhase.APPLIED,
|
||||
processing_state=ProcessingState.COMPLETE,
|
||||
phase=PlanPhase.APPLY,
|
||||
processing_state=ProcessingState.APPLIED,
|
||||
)
|
||||
|
||||
|
||||
@@ -518,7 +527,7 @@ def step_try_create_plan_with_invalid_phase(context: Context) -> None:
|
||||
description="Test plan",
|
||||
action_name="local/test-action",
|
||||
definition_of_done=None,
|
||||
phase="action", # Invalid phase value
|
||||
phase="nonexistent", # Invalid phase value
|
||||
processing_state=ProcessingState.QUEUED,
|
||||
strategy_actor=None,
|
||||
execution_actor=None,
|
||||
|
||||
+21
-21
@@ -1438,27 +1438,27 @@ No standalone Q0-Advanced commits planned. Advanced QA enhancements are bundled
|
||||
**Parallel Group A2c: Plan Phase + Apply State Rebaseline (M1-critical)**
|
||||
**PARALLEL SUBTRACK A2c.alpha [Jeff]**: Plan phase/state alignment + CLI output updates
|
||||
**SEQUENTIAL MERGE NOTE**: A2c.alpha must land before A5 persistence wiring and D0 apply integration.
|
||||
- [ ] **COMMIT (Owner: Jeff | Group: A2c.alpha | Branch: feature/m1-plan-phase-rebaseline | Planned: Day 7 | Expected: Day 8) - Commit message: "feat(domain): rebaseline plan phases and apply states"**
|
||||
- [ ] Git [Jeff]: `git checkout master`
|
||||
- [ ] Git [Jeff]: `git pull origin master`
|
||||
- [ ] Git [Jeff]: `git checkout -b feature/m1-plan-phase-rebaseline`
|
||||
- [ ] Git [Jeff]: `git fetch origin && git merge origin/master` (run before final tests and before commit)
|
||||
- [ ] Code [Jeff]: Update `PlanPhase` to include `ACTION` and remove `APPLIED` as a phase; keep phases strictly `action/strategize/execute/apply` per spec.
|
||||
- [ ] Code [Jeff]: Expand `processing_state` (or introduce `apply_state` mapping) to represent Apply terminal outcomes (`applied`, `constrained`, `errored`, `cancelled`) and keep non-Apply states (`queued`, `processing`, `complete`) for Strategize/Execute.
|
||||
- [ ] Code [Jeff]: Update `Plan` model validators to allow namespaced names for top-level plans and ULID-only identifiers for subplans; enforce ULID-only for subplans in hierarchy validators.
|
||||
- [ ] Code [Jeff]: Update `Plan.as_cli_dict()` + CLI formatting to show Action phase, apply terminal outcome (explicit field), and phase/processing_state alignment.
|
||||
- [ ] Code [Jeff]: Update `PlanLifecycleService` to create plans in Action phase, transition to Strategize on `plan use`, and set Apply terminal outcomes on `plan apply` (including `constrained` for spec-defined reversion cases).
|
||||
- [ ] Docs [Jeff]: Update `docs/reference/plan_model.md` and `docs/reference/plan_lifecycle_service.md` with Action phase semantics, apply terminal outcomes, and reversion rules (Execute/Apply → Strategize).
|
||||
- [ ] Tests (Behave) [Jeff]: Add/adjust scenarios for Action phase creation, Strategize entry, Apply terminal outcomes, and reversion preconditions.
|
||||
- [ ] Tests (Robot) [Jeff]: Update lifecycle Robot suites to assert Action phase visibility and Apply terminal outcome fields in status output.
|
||||
- [ ] Tests (ASV) [Jeff]: Add `benchmarks/plan_phase_bench.py` for phase transition validation overhead.
|
||||
- [ ] Quality [Jeff]: Run `nox` (all default sessions, including benchmark), fix any errors if needed ensuring nox passes.
|
||||
- [ ] Git [Jeff]: `git add .`
|
||||
- [ ] Git [Jeff]: `git commit -m "feat(domain): rebaseline plan phases and apply states"`
|
||||
- [ ] Forgejo PR [Jeff]: Open PR from `feature/m1-plan-phase-rebaseline` to `master` with description "Rebaseline plan phases and apply terminal states to spec, with CLI output updates and tests.".
|
||||
- [ ] Git [Jeff]: `git checkout master`
|
||||
- [ ] Git [Jeff]: `git branch -d feature/m1-plan-phase-rebaseline`
|
||||
- [ ] Quality [Jeff]: Verify coverage >=97% via `nox -s coverage_report`. If coverage is <97% then review the current unit test coverage report at `build/coverage.xml` and use it to write new Behave based unit tests to improve code coverage. Specifically, write Behave style unit tests that are descriptively named and specifically improves coverage on whichever file has the most uncovered lines by writing tests that will target the uncovered lines in the report. Once that is done rerun `nox -s coverage_report` to verify all tests pass and coverage is above >=97%. Only mark this as complete once coverage is >=97%, if not repeat this task as many times as is needed until coverage reaches >=97%.
|
||||
- [X] **COMMIT (Owner: Jeff | Group: A2c.alpha | Branch: feature/m1-plan-phase-rebaseline | Planned: Day 7 | Expected: Day 8) - Commit message: "feat(domain): rebaseline plan phases and apply states"**
|
||||
- [X] Git [Jeff]: `git checkout master`
|
||||
- [X] Git [Jeff]: `git pull origin master`
|
||||
- [X] Git [Jeff]: `git checkout -b feature/m1-plan-phase-rebaseline`
|
||||
- [X] Git [Jeff]: `git fetch origin && git merge origin/master` (run before final tests and before commit)
|
||||
- [X] Code [Jeff]: Update `PlanPhase` to include `ACTION` and remove `APPLIED` as a phase; keep phases strictly `action/strategize/execute/apply` per spec.
|
||||
- [X] Code [Jeff]: Expand `processing_state` (or introduce `apply_state` mapping) to represent Apply terminal outcomes (`applied`, `constrained`, `errored`, `cancelled`) and keep non-Apply states (`queued`, `processing`, `complete`) for Strategize/Execute.
|
||||
- [X] Code [Jeff]: Update `Plan` model validators to allow namespaced names for top-level plans and ULID-only identifiers for subplans; enforce ULID-only for subplans in hierarchy validators.
|
||||
- [X] Code [Jeff]: Update `Plan.as_cli_dict()` + CLI formatting to show Action phase, apply terminal outcome (explicit field), and phase/processing_state alignment.
|
||||
- [X] Code [Jeff]: Update `PlanLifecycleService` to create plans in Action phase, transition to Strategize on `plan use`, and set Apply terminal outcomes on `plan apply` (including `constrained` for spec-defined reversion cases).
|
||||
- [X] Docs [Jeff]: Update `docs/reference/plan_model.md` and `docs/reference/plan_lifecycle_service.md` with Action phase semantics, apply terminal outcomes, and reversion rules (Execute/Apply → Strategize).
|
||||
- [X] Tests (Behave) [Jeff]: Add/adjust scenarios for Action phase creation, Strategize entry, Apply terminal outcomes, and reversion preconditions.
|
||||
- [X] Tests (Robot) [Jeff]: Update lifecycle Robot suites to assert Action phase visibility and Apply terminal outcome fields in status output.
|
||||
- [X] Tests (ASV) [Jeff]: Add `benchmarks/plan_phase_bench.py` for phase transition validation overhead.
|
||||
- [X] Quality [Jeff]: Run `nox` (all default sessions, including benchmark), fix any errors if needed ensuring nox passes.
|
||||
- [X] Git [Jeff]: `git add .`
|
||||
- [X] Git [Jeff]: `git commit -m "feat(domain): rebaseline plan phases and apply states"`
|
||||
- [X] Forgejo PR [Jeff]: Open PR from `feature/m1-plan-phase-rebaseline` to `master` with description "Rebaseline plan phases and apply terminal states to spec, with CLI output updates and tests.".
|
||||
- [X] Git [Jeff]: `git checkout master`
|
||||
- [X] Git [Jeff]: `git branch -d feature/m1-plan-phase-rebaseline`
|
||||
- [X] Quality [Jeff]: Verify coverage >=97% via `nox -s coverage_report`. If coverage is <97% then review the current unit test coverage report at `build/coverage.xml` and use it to write new Behave based unit tests to improve code coverage. Specifically, write Behave style unit tests that are descriptively named and specifically improves coverage on whichever file has the most uncovered lines by writing tests that will target the uncovered lines in the report. Once that is done rerun `nox -s coverage_report` to verify all tests pass and coverage is above >=97%. Only mark this as complete once coverage is >=97%, if not repeat this task as many times as is needed until coverage reaches >=97%.
|
||||
|
||||
- [X] **Stage A3: Plan State Machine** (Day 1-2) - COMPLETED 2026-02-05
|
||||
- [X] Code: Implement plan lifecycle state machine
|
||||
|
||||
@@ -100,7 +100,8 @@ def _lifecycle_full_cycle() -> None:
|
||||
assert plan.state == ProcessingState.PROCESSING
|
||||
|
||||
plan = service.complete_apply(plan_id)
|
||||
assert plan.phase == PlanPhase.APPLIED
|
||||
assert plan.phase == PlanPhase.APPLY
|
||||
assert plan.state == ProcessingState.APPLIED
|
||||
assert plan.is_terminal
|
||||
|
||||
print("lifecycle-full-cycle-ok")
|
||||
@@ -444,16 +445,18 @@ def _plan_with_subplans() -> None:
|
||||
|
||||
def _phase_transitions_model() -> None:
|
||||
"""Integration test: verify can_transition function for all valid/invalid paths."""
|
||||
# Valid transitions (no ACTION phase in new spec)
|
||||
# Valid transitions (four-phase: ACTION -> STRATEGIZE -> EXECUTE -> APPLY)
|
||||
assert can_transition(PlanPhase.ACTION, PlanPhase.STRATEGIZE)
|
||||
assert can_transition(PlanPhase.STRATEGIZE, PlanPhase.EXECUTE)
|
||||
assert can_transition(PlanPhase.EXECUTE, PlanPhase.APPLY)
|
||||
assert can_transition(PlanPhase.APPLY, PlanPhase.APPLIED)
|
||||
assert can_transition(PlanPhase.EXECUTE, PlanPhase.STRATEGIZE)
|
||||
assert can_transition(PlanPhase.APPLY, PlanPhase.STRATEGIZE)
|
||||
|
||||
# Invalid transitions
|
||||
assert not can_transition(PlanPhase.STRATEGIZE, PlanPhase.APPLIED)
|
||||
assert not can_transition(PlanPhase.STRATEGIZE, PlanPhase.APPLY)
|
||||
assert not can_transition(PlanPhase.APPLIED, PlanPhase.STRATEGIZE)
|
||||
assert not can_transition(PlanPhase.EXECUTE, PlanPhase.STRATEGIZE)
|
||||
assert not can_transition(PlanPhase.ACTION, PlanPhase.EXECUTE)
|
||||
assert not can_transition(PlanPhase.ACTION, PlanPhase.APPLY)
|
||||
assert not can_transition(PlanPhase.APPLY, PlanPhase.EXECUTE)
|
||||
|
||||
print("phase-transitions-ok")
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ ${HELPER_SCRIPT} robot/helper_plan_lifecycle_v3.py
|
||||
|
||||
*** Test Cases ***
|
||||
Plan Full Lifecycle Through All Phases
|
||||
[Documentation] Verify Action->Strategize->Execute->Apply->Applied end-to-end via PlanLifecycleService
|
||||
[Documentation] Verify Action->Strategize->Execute->Apply (terminal) end-to-end via PlanLifecycleService
|
||||
[Tags] lifecycle plan action critical
|
||||
${result}= Run Process ${PYTHON} ${HELPER_SCRIPT} lifecycle-full-cycle cwd=${WORKSPACE}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
|
||||
+10
-10
@@ -16,8 +16,8 @@ Tool Package Is Importable
|
||||
|
||||
Tool Context Creation Works
|
||||
[Documentation] Verify ToolExecutionContext can be created
|
||||
${result}= Run Process ${PYTHON} -c
|
||||
... from cleveragents.tool import ToolExecutionContext; ctx = ToolExecutionContext(plan_id='test-plan'); print(f'plan={ctx.plan_id} changes={len(ctx.changes)} traces={len(ctx.traces)}')
|
||||
${code}= Set Variable from cleveragents.tool import ToolExecutionContext; ctx = ToolExecutionContext(plan_id='test-plan'); print(f'plan={ctx.plan_id} changes={len(ctx.changes)} traces={len(ctx.traces)}')
|
||||
${result}= Run Process ${PYTHON} -c ${code}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} plan=test-plan
|
||||
Should Contain ${result.stdout} changes=0
|
||||
@@ -25,32 +25,32 @@ Tool Context Creation Works
|
||||
|
||||
Cancellation Token Works
|
||||
[Documentation] Verify CancellationToken cancel and check
|
||||
${result}= Run Process ${PYTHON} -c
|
||||
... from cleveragents.tool import CancellationToken; t = CancellationToken(); print(f'before={t.is_cancelled}'); t.cancel(); print(f'after={t.is_cancelled}')
|
||||
${code}= Set Variable from cleveragents.tool import CancellationToken; t = CancellationToken(); print(f'before={t.is_cancelled}'); t.cancel(); print(f'after={t.is_cancelled}')
|
||||
${result}= Run Process ${PYTHON} -c ${code}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} before=False
|
||||
Should Contain ${result.stdout} after=True
|
||||
|
||||
Schema Validator Works
|
||||
[Documentation] Verify JSON Schema validation
|
||||
${result}= Run Process ${PYTHON} -c
|
||||
... from cleveragents.tool import validate_tool_input, ToolSchemaValidationError; schema = {"type": "object", "properties": {"x": {"type": "string"}}, "required": ["x"]}; validate_tool_input({"x": "hi"}, schema); print('valid'); ok = False\ntry:\n validate_tool_input({"x": 1}, schema)\nexcept ToolSchemaValidationError:\n ok = True\nprint(f'invalid_caught={ok}')
|
||||
${code}= Set Variable from cleveragents.tool import validate_tool_input, ToolSchemaValidationError; schema = {"type": "object", "properties": {"x": {"type": "string"}}, "required": ["x"]}; validate_tool_input({"x": "hi"}, schema); print('valid'); ok = False\ntry:\n validate_tool_input({"x": 1}, schema)\nexcept ToolSchemaValidationError:\n ok = True\nprint(f'invalid_caught={ok}')
|
||||
${result}= Run Process ${PYTHON} -c ${code}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} valid
|
||||
Should Contain ${result.stdout} invalid_caught=True
|
||||
|
||||
Tool Runtime Registration Works
|
||||
[Documentation] Verify ToolRuntime register and list
|
||||
${result}= Run Process ${PYTHON} -c
|
||||
... from cleveragents.tool import ToolRuntime; from cleveragents.domain.models.core.tool import Tool, ToolSource, ToolCapability; rt = ToolRuntime(); t = Tool(name='test/read', description='Read', source=ToolSource.BUILTIN, capability=ToolCapability(read_only=True)); print(f'before={rt.list_tools()}'); rt.register_tool(t, None); print(f'after={rt.list_tools()}')
|
||||
${code}= Set Variable from cleveragents.tool import ToolRuntime; from cleveragents.domain.models.core.tool import Tool, ToolSource, ToolCapability; rt = ToolRuntime(); t = Tool(name='test/read', description='Read', source=ToolSource.BUILTIN, capability=ToolCapability(read_only=True)); print(f'before={rt.list_tools()}'); rt.register_tool(t, None); print(f'after={rt.list_tools()}')
|
||||
${result}= Run Process ${PYTHON} -c ${code}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} before=[]
|
||||
Should Contain ${result.stdout} test/read
|
||||
|
||||
Lifecycle Cache Works
|
||||
[Documentation] Verify ToolLifecycleCache put/get
|
||||
${result}= Run Process ${PYTHON} -c
|
||||
... from cleveragents.tool import ToolLifecycleCache; c = ToolLifecycleCache(); print(f'plans={c.plan_count}'); c.put('p1', 'tool/a', 'dummy'); print(f'plans_after={c.plan_count}'); r = c.get('p1', 'tool/a'); print(f'got={r}')
|
||||
${code}= Set Variable from cleveragents.tool import ToolLifecycleCache; c = ToolLifecycleCache(); print(f'plans={c.plan_count}'); c.put('p1', 'tool/a', 'dummy'); print(f'plans_after={c.plan_count}'); r = c.get('p1', 'tool/a'); print(f'got={r}')
|
||||
${result}= Run Process ${PYTHON} -c ${code}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} plans=0
|
||||
Should Contain ${result.stdout} plans_after=1
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
"""Plan Lifecycle Service for CleverAgents v3.
|
||||
|
||||
This service manages the v3 three-phase plan lifecycle:
|
||||
Strategize -> Execute -> Apply -> Applied (terminal)
|
||||
This service manages the v3 four-phase plan lifecycle:
|
||||
Action -> Strategize -> Execute -> Apply
|
||||
|
||||
Plans are instantiated from Action templates via ``agents plan use``.
|
||||
The Action phase is modeled separately in the Action domain model.
|
||||
Plans are instantiated from Action templates. The Action phase is the
|
||||
non-processing template phase; ``plan use`` transitions to Strategize.
|
||||
Terminal outcomes live in Apply's processing state: applied, constrained,
|
||||
errored, or cancelled.
|
||||
|
||||
Based on docs/specification.md and implementation plan Stage A3.
|
||||
"""
|
||||
@@ -94,8 +96,9 @@ class PlanLifecycleService:
|
||||
|
||||
This service handles:
|
||||
- Creating actions (reusable plan templates)
|
||||
- Using actions on projects to create plans
|
||||
- Transitioning plans through phases (Action -> Strategize -> Execute -> Apply)
|
||||
- Using actions on projects to create plans (Action -> Strategize)
|
||||
- Transitioning plans through phases (Strategize -> Execute -> Apply)
|
||||
- Apply terminal outcomes: applied, constrained, errored, cancelled
|
||||
- Validating phase transitions
|
||||
"""
|
||||
|
||||
@@ -776,13 +779,16 @@ class PlanLifecycleService:
|
||||
return plan
|
||||
|
||||
def complete_apply(self, plan_id: str) -> Plan:
|
||||
"""Complete the Apply phase, transitioning to Applied (terminal).
|
||||
"""Complete the Apply phase — terminal success.
|
||||
|
||||
Sets ``processing_state`` to ``APPLIED`` (the terminal success
|
||||
state within the Apply phase). The phase remains ``APPLY``.
|
||||
|
||||
Args:
|
||||
plan_id: The plan ULID
|
||||
|
||||
Returns:
|
||||
The updated Plan in Applied state
|
||||
The updated Plan with processing_state=APPLIED
|
||||
"""
|
||||
plan = self.get_plan(plan_id)
|
||||
|
||||
@@ -794,9 +800,8 @@ class PlanLifecycleService:
|
||||
if plan.state != ProcessingState.PROCESSING:
|
||||
raise PlanError(f"Plan {plan_id} is not processing (current: {plan.state})")
|
||||
|
||||
# Transition to terminal APPLIED state
|
||||
plan.phase = PlanPhase.APPLIED
|
||||
plan.processing_state = ProcessingState.COMPLETE
|
||||
# Terminal success: phase stays APPLY, state becomes APPLIED
|
||||
plan.processing_state = ProcessingState.APPLIED
|
||||
plan.timestamps.applied_at = datetime.now()
|
||||
plan.timestamps.updated_at = datetime.now()
|
||||
|
||||
@@ -808,6 +813,38 @@ class PlanLifecycleService:
|
||||
|
||||
return plan
|
||||
|
||||
def constrain_apply(self, plan_id: str, reason: str) -> Plan:
|
||||
"""Mark the Apply phase as constrained.
|
||||
|
||||
The plan cannot complete within the current strategy constraints.
|
||||
It may revert to Strategize for re-planning.
|
||||
|
||||
Args:
|
||||
plan_id: The plan ULID
|
||||
reason: Why the apply is constrained
|
||||
|
||||
Returns:
|
||||
The updated Plan with processing_state=CONSTRAINED
|
||||
"""
|
||||
plan = self.get_plan(plan_id)
|
||||
|
||||
if plan.phase != PlanPhase.APPLY:
|
||||
raise PlanError(
|
||||
f"Plan {plan_id} is not in Apply phase (current: {plan.phase.value})"
|
||||
)
|
||||
|
||||
plan.processing_state = ProcessingState.CONSTRAINED
|
||||
plan.error_message = reason
|
||||
plan.timestamps.updated_at = datetime.now()
|
||||
|
||||
self._logger.info(
|
||||
"Plan apply constrained",
|
||||
plan_id=plan_id,
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
return plan
|
||||
|
||||
def fail_apply(self, plan_id: str, error_message: str) -> Plan:
|
||||
"""Mark Apply phase as failed."""
|
||||
plan = self.get_plan(plan_id)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
"""Plan domain model for CleverAgents.
|
||||
|
||||
This module implements the three-phase plan lifecycle:
|
||||
Strategize -> Execute -> Apply -> Applied (terminal)
|
||||
This module implements the four-phase plan lifecycle:
|
||||
Action -> Strategize -> Execute -> Apply
|
||||
|
||||
Plans are instantiated from Action templates via ``agents plan use``.
|
||||
The Action phase is modeled separately in the Action domain model.
|
||||
Plans are instantiated from Action templates. The Action phase is
|
||||
the non-processing template phase. When a plan is *used* (via
|
||||
``agents plan use``) it transitions to Strategize.
|
||||
|
||||
Terminal outcomes live in the Apply phase's processing state:
|
||||
``applied`` (success), ``constrained`` (cannot proceed within strategy
|
||||
constraints), ``errored``, or ``cancelled``.
|
||||
|
||||
Based on docs/specification.md and ADR-004 (Pydantic Validation).
|
||||
"""
|
||||
@@ -26,28 +31,40 @@ class PlanPhase(StrEnum):
|
||||
"""Phase of a plan in the v3 lifecycle.
|
||||
|
||||
Plans progress through phases in order:
|
||||
Strategize -> Execute -> Apply -> Applied (terminal)
|
||||
Action -> Strategize -> Execute -> Apply
|
||||
|
||||
The Action phase is modeled separately in the Action domain model.
|
||||
Action is the non-processing template phase. Terminal outcomes
|
||||
(applied, constrained, errored, cancelled) are modeled as
|
||||
``ProcessingState`` values within the Apply phase, **not** as
|
||||
separate phases.
|
||||
"""
|
||||
|
||||
ACTION = "action"
|
||||
STRATEGIZE = "strategize"
|
||||
EXECUTE = "execute"
|
||||
APPLY = "apply"
|
||||
APPLIED = "applied"
|
||||
|
||||
|
||||
class ProcessingState(StrEnum):
|
||||
"""States for plans in Strategize/Execute/Apply phases.
|
||||
|
||||
These states indicate what is happening during processing.
|
||||
|
||||
Strategize / Execute phases use: queued, processing, errored,
|
||||
complete, cancelled.
|
||||
|
||||
Apply phase uses: queued, processing, errored, applied,
|
||||
constrained, cancelled (but NOT complete — terminal success in
|
||||
Apply is ``applied``).
|
||||
"""
|
||||
|
||||
QUEUED = "queued" # Waiting for compute/worker
|
||||
PROCESSING = "processing" # Currently running
|
||||
ERRORED = "errored" # Failed; includes error metadata
|
||||
COMPLETE = "complete" # Finished successfully
|
||||
COMPLETE = "complete" # Finished successfully (Strategize/Execute)
|
||||
CANCELLED = "cancelled" # User/system cancelled
|
||||
APPLIED = "applied" # Apply terminal success — changes committed
|
||||
CONSTRAINED = "constrained" # Apply cannot proceed within constraints
|
||||
|
||||
|
||||
class AutomationLevel(StrEnum):
|
||||
@@ -457,12 +474,12 @@ class Plan(BaseModel):
|
||||
"""Domain model for a v3 plan.
|
||||
|
||||
A plan is the fundamental unit of orchestration in CleverAgents v3.
|
||||
It follows a three-phase lifecycle: Strategize -> Execute -> Apply
|
||||
It follows a four-phase lifecycle: Action -> Strategize -> Execute -> Apply
|
||||
|
||||
Plans are instantiated from Action templates via ``agents plan use``.
|
||||
|
||||
Key concepts:
|
||||
- Phase: Which step of the lifecycle (Strategize, Execute, Apply, Applied)
|
||||
- Phase: Which step of the lifecycle (Action, Strategize, Execute, Apply)
|
||||
- Processing State: What is happening in that phase (queued, processing, etc.)
|
||||
- Identity: ULID-based identification with parent/root for hierarchy
|
||||
- Namespace: Scoping mechanism for ownership and discoverability
|
||||
@@ -629,14 +646,12 @@ class Plan(BaseModel):
|
||||
def validate_phase_state_consistency(self) -> Plan:
|
||||
"""Ensure phase and state are consistent.
|
||||
|
||||
- APPLIED phase must have COMPLETE processing state.
|
||||
- ACTION phase defaults to QUEUED if no state supplied.
|
||||
- CANCELLED state is terminal for any phase.
|
||||
- Apply phase accepts APPLIED / CONSTRAINED as terminal states.
|
||||
"""
|
||||
if (
|
||||
self.phase == PlanPhase.APPLIED
|
||||
and self.processing_state != ProcessingState.COMPLETE
|
||||
):
|
||||
self.processing_state = ProcessingState.COMPLETE
|
||||
if self.phase == PlanPhase.ACTION and self.processing_state is None:
|
||||
self.processing_state = ProcessingState.QUEUED
|
||||
return self
|
||||
|
||||
@model_validator(mode="after")
|
||||
@@ -654,10 +669,18 @@ class Plan(BaseModel):
|
||||
|
||||
@property
|
||||
def is_terminal(self) -> bool:
|
||||
"""Check if plan is in a terminal state."""
|
||||
if self.phase == PlanPhase.APPLIED:
|
||||
return True
|
||||
return self.processing_state == ProcessingState.CANCELLED
|
||||
"""Check if plan is in a terminal state.
|
||||
|
||||
Terminal when:
|
||||
- processing_state is APPLIED (Apply success)
|
||||
- processing_state is CANCELLED (any phase)
|
||||
- processing_state is CONSTRAINED (Apply cannot proceed)
|
||||
"""
|
||||
return self.processing_state in (
|
||||
ProcessingState.APPLIED,
|
||||
ProcessingState.CANCELLED,
|
||||
ProcessingState.CONSTRAINED,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_errored(self) -> bool:
|
||||
@@ -666,18 +689,28 @@ class Plan(BaseModel):
|
||||
|
||||
@property
|
||||
def can_transition_to_next_phase(self) -> bool:
|
||||
"""Check if plan can transition to the next phase."""
|
||||
if self.phase == PlanPhase.APPLIED:
|
||||
return False # Terminal
|
||||
"""Check if plan can transition to the next phase.
|
||||
|
||||
- ACTION phase: can always transition (template -> Strategize).
|
||||
- Strategize / Execute: must be COMPLETE.
|
||||
- Apply: terminal phase — no forward transition.
|
||||
"""
|
||||
if self.phase == PlanPhase.ACTION:
|
||||
return True # Templates can always be used
|
||||
if self.phase == PlanPhase.APPLY:
|
||||
return False # Terminal phase
|
||||
return self.processing_state == ProcessingState.COMPLETE
|
||||
|
||||
def get_next_phase(self) -> PlanPhase | None:
|
||||
"""Get the next phase in the lifecycle, or None if terminal."""
|
||||
"""Get the next phase in the lifecycle, or None if terminal.
|
||||
|
||||
Apply is the last phase; there is no phase after it.
|
||||
"""
|
||||
phase_order = [
|
||||
PlanPhase.ACTION,
|
||||
PlanPhase.STRATEGIZE,
|
||||
PlanPhase.EXECUTE,
|
||||
PlanPhase.APPLY,
|
||||
PlanPhase.APPLIED,
|
||||
]
|
||||
try:
|
||||
current_idx = phase_order.index(self.phase)
|
||||
@@ -784,10 +817,10 @@ class Plan(BaseModel):
|
||||
|
||||
# Phase transition map for validation
|
||||
VALID_PHASE_TRANSITIONS: dict[PlanPhase, list[PlanPhase]] = {
|
||||
PlanPhase.ACTION: [PlanPhase.STRATEGIZE], # plan use
|
||||
PlanPhase.STRATEGIZE: [PlanPhase.EXECUTE], # execute command
|
||||
PlanPhase.EXECUTE: [PlanPhase.APPLY], # apply command
|
||||
PlanPhase.APPLY: [PlanPhase.APPLIED], # successful apply
|
||||
PlanPhase.APPLIED: [], # terminal
|
||||
PlanPhase.EXECUTE: [PlanPhase.APPLY, PlanPhase.STRATEGIZE], # apply or revert
|
||||
PlanPhase.APPLY: [PlanPhase.STRATEGIZE], # revert via constrained
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -485,8 +485,11 @@ class ActionArgumentModel(Base): # type: ignore[misc]
|
||||
class LifecyclePlanModel(Base): # type: ignore[misc]
|
||||
"""Database model for spec-aligned lifecycle plans.
|
||||
|
||||
A lifecycle plan follows the three-phase lifecycle:
|
||||
``Strategize -> Execute -> Apply -> Applied (terminal)``
|
||||
A lifecycle plan follows the four-phase lifecycle:
|
||||
``Action -> Strategize -> Execute -> Apply``
|
||||
|
||||
Terminal outcomes are modeled as processing states within Apply:
|
||||
``applied``, ``constrained``, ``errored``, ``cancelled``.
|
||||
|
||||
Plans are created from Action templates via ``agents plan use``.
|
||||
Each plan references the action it was created from (by namespaced name),
|
||||
|
||||
@@ -6,12 +6,6 @@ JSON Schema validation, per-plan activation caching, execution tracing,
|
||||
and cancellation propagation (C5.lifecycle).
|
||||
"""
|
||||
|
||||
# C0.runtime — core tool spec, runner, registry
|
||||
from cleveragents.tool.registry import ToolRegistry
|
||||
from cleveragents.tool.runner import ToolRunner
|
||||
from cleveragents.tool.runtime import ToolError, ToolResult, ToolSpec
|
||||
|
||||
# C5.lifecycle — four-stage lifecycle runtime
|
||||
from cleveragents.tool.context import (
|
||||
BoundResource,
|
||||
CancellationToken,
|
||||
@@ -31,10 +25,15 @@ from cleveragents.tool.lifecycle import (
|
||||
ToolInstance,
|
||||
ToolLifecycleCache,
|
||||
ToolNotActivatedError,
|
||||
ToolResult as LifecycleToolResult,
|
||||
ToolRuntime,
|
||||
ToolRuntimeError,
|
||||
)
|
||||
from cleveragents.tool.lifecycle import (
|
||||
ToolResult as LifecycleToolResult,
|
||||
)
|
||||
from cleveragents.tool.registry import ToolRegistry
|
||||
from cleveragents.tool.runner import ToolRunner
|
||||
from cleveragents.tool.runtime import ToolError, ToolResult, ToolSpec
|
||||
from cleveragents.tool.schema_validator import (
|
||||
ToolSchemaValidationError,
|
||||
validate_tool_input,
|
||||
@@ -42,35 +41,31 @@ from cleveragents.tool.schema_validator import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# C0.runtime
|
||||
"ToolError",
|
||||
"ToolRegistry",
|
||||
"ToolResult",
|
||||
"ToolRunner",
|
||||
"ToolSpec",
|
||||
# C5.lifecycle — context
|
||||
"BoundResource",
|
||||
"CancellationToken",
|
||||
"Change",
|
||||
"ChangeOperation",
|
||||
"ToolCancelledError",
|
||||
"ToolExecutionContext",
|
||||
"ToolExecutionTrace",
|
||||
# C5.lifecycle — runtime
|
||||
"LifecycleToolResult",
|
||||
"ToolAccessDeniedError",
|
||||
"ToolActivationError",
|
||||
"ToolCancelledError",
|
||||
"ToolCheckpointRequiredError",
|
||||
"ToolDeactivationError",
|
||||
"ToolDescriptor",
|
||||
"ToolError",
|
||||
"ToolExecutionContext",
|
||||
"ToolExecutionError",
|
||||
"ToolExecutionTrace",
|
||||
"ToolInstance",
|
||||
"ToolLifecycleCache",
|
||||
"ToolNotActivatedError",
|
||||
"ToolRegistry",
|
||||
"ToolResult",
|
||||
"ToolRunner",
|
||||
"ToolRuntime",
|
||||
"ToolRuntimeError",
|
||||
# C5.lifecycle — schema validation
|
||||
"ToolSchemaValidationError",
|
||||
"ToolSpec",
|
||||
"validate_tool_input",
|
||||
"validate_tool_output",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user