feat(plan): enforce invariants during Strategize phase via Invariant Reconciliation Actor #843

Open
opened 2026-03-13 21:51:35 +00:00 by freemo · 3 comments
Owner

Background and Context

M3 (v3.2.0) acceptance criteria state: "Invariants are enforced during strategize." Per the specification (approx. lines 19554–19610), the Invariant Reconciliation Actor must compute effective invariants and record invariant_enforced decisions in the plan's decision tree during the Strategize phase.

Currently, plan_lifecycle_service.py:668-701 merges invariant text during plan creation, but does not invoke the Invariant Reconciliation Actor or produce invariant_enforced decisions in the decision tree during the Strategize phase.

Issue #829 (feat(plan): wire Invariant Reconciliation Actor auto-invocation during lifecycle transitions) exists but is assigned to v3.5.0, not v3.2.0. This issue tracks the v3.2.0 acceptance criterion specifically.

Current Behavior

Invariants can be created (invariant add) and listed (invariant list), but they are not evaluated or enforced when agents plan strategize runs. The Strategize phase proceeds without checking whether invariants are satisfied.

Expected Behavior

  1. When a plan enters the Strategize phase, the Invariant Reconciliation Actor is invoked automatically
  2. Each active invariant is evaluated against the current plan context
  3. invariant_enforced decisions are recorded in the decision tree for each invariant
  4. Violated invariants produce a strategy revision or user prompt (per spec)
  5. The plan's decision tree reflects invariant enforcement as first-class decisions

Acceptance Criteria

  • Invariant Reconciliation Actor is invoked at the start of the Strategize phase
  • Each invariant produces an invariant_enforced decision in the decision tree
  • Violated invariants trigger the appropriate response (revision or prompt)
  • agents plan tree shows invariant enforcement decisions
  • Tests (Behave): Add scenarios for invariant enforcement during strategize
  • Tests (Robot): Add integration test for invariant lifecycle
  • Verify coverage >=97% via nox -s coverage_report

Metadata

  • Type: Feature
  • Priority: High
  • MoSCoW: Must have
  • Points: 5
  • Milestone: v3.2.0
  • Commit Message: feat(plan): enforce invariants during Strategize phase via Invariant Reconciliation Actor
  • Branch: feature/m3-invariant-enforcement-strategize

Relates to

  • #829 (v3.5.0 — lifecycle transition wiring, broader scope)
  • M3 acceptance criterion: "Invariants are enforced during strategize"
## Background and Context M3 (v3.2.0) acceptance criteria state: *"Invariants are enforced during strategize."* Per the specification (approx. lines 19554–19610), the Invariant Reconciliation Actor must compute effective invariants and record `invariant_enforced` decisions in the plan's decision tree during the Strategize phase. Currently, `plan_lifecycle_service.py:668-701` merges invariant text during plan creation, but does **not** invoke the Invariant Reconciliation Actor or produce `invariant_enforced` decisions in the decision tree during the Strategize phase. Issue #829 (`feat(plan): wire Invariant Reconciliation Actor auto-invocation during lifecycle transitions`) exists but is assigned to **v3.5.0**, not v3.2.0. This issue tracks the v3.2.0 acceptance criterion specifically. ## Current Behavior Invariants can be created (`invariant add`) and listed (`invariant list`), but they are not evaluated or enforced when `agents plan strategize` runs. The Strategize phase proceeds without checking whether invariants are satisfied. ## Expected Behavior 1. When a plan enters the Strategize phase, the Invariant Reconciliation Actor is invoked automatically 2. Each active invariant is evaluated against the current plan context 3. `invariant_enforced` decisions are recorded in the decision tree for each invariant 4. Violated invariants produce a strategy revision or user prompt (per spec) 5. The plan's decision tree reflects invariant enforcement as first-class decisions ## Acceptance Criteria - [x] Invariant Reconciliation Actor is invoked at the start of the Strategize phase - [x] Each invariant produces an `invariant_enforced` decision in the decision tree - [x] Violated invariants trigger the appropriate response (revision or prompt) - [x] `agents plan tree` shows invariant enforcement decisions - [x] Tests (Behave): Add scenarios for invariant enforcement during strategize - [x] Tests (Robot): Add integration test for invariant lifecycle - [x] Verify coverage >=97% via `nox -s coverage_report` ## Metadata - **Type**: Feature - **Priority**: High - **MoSCoW**: Must have - **Points**: 5 - **Milestone**: v3.2.0 - **Commit Message**: `feat(plan): enforce invariants during Strategize phase via Invariant Reconciliation Actor` - **Branch**: `feature/m3-invariant-enforcement-strategize` ## Relates to - #829 (v3.5.0 — lifecycle transition wiring, broader scope) - M3 acceptance criterion: "Invariants are enforced during strategize"
freemo added this to the v3.2.0 milestone 2026-03-13 21:51:59 +00:00
Member

Implementation Notes

Design Decisions

  1. Optional dependency pattern: The InvariantService is wired as an optional parameter (invariant_service: InvariantService | None = None) to PlanLifecycleService.__init__(), consistent with the existing pattern for DecisionService, EventBus, ErrorPatternService, etc. When not provided, invariant enforcement is silently skipped.

  2. Enforcement point: Invariant enforcement happens inside start_strategize(), after pre-flight guardrail checks and the initial strategy_choice decision recording, but before returning the plan. This matches the spec (§19554-19610) which states invariants are reconciled "at the start of Strategize."

  3. Plan-level invariant registration: When _enforce_invariants() runs, it first registers any PlanInvariant objects from the plan model into the InvariantService as PLAN-scoped invariants. This bridges the gap between invariants carried forward from actions (stored on the Plan model) and the InvariantService's in-memory store.

  4. Failure resilience: Consistent with all other optional service invocations in the lifecycle service, _enforce_invariants() wraps the entire operation in a try/except, logging warnings but never blocking the strategize transition.

  5. DI container wiring: InvariantService is registered as a providers.Singleton in the DI container and injected into PlanLifecycleService. Singleton is appropriate because InvariantService uses in-memory state that should be shared across callers within a process.

Key Code Locations

  • cleveragents.application.services.plan_lifecycle_service.PlanLifecycleService._enforce_invariants() — new method (commit @HEAD)
  • cleveragents.application.services.plan_lifecycle_service.PlanLifecycleService.start_strategize() — calls _enforce_invariants() (commit @HEAD)
  • cleveragents.application.services.plan_lifecycle_service.PlanLifecycleService.__init__() — new invariant_service parameter (commit @HEAD)
  • cleveragents.application.container.ApplicationContainer — new invariant_service singleton and wiring to plan_lifecycle_service (commit @HEAD)
  • cleveragents.actor.reconciliation.InvariantReconciliationActor — existing actor, now invoked during strategize (commit @HEAD)

Test Coverage

  • Behave BDD: 6 new scenarios in features/invariant_enforcement_strategize.feature covering:

    • Basic enforcement with action + global invariants
    • Zero-invariant case
    • Missing InvariantService (graceful degradation)
    • Multi-scope reconciliation (global + project + action)
    • Duplicate de-duplication
    • Failure resilience
  • Robot Framework: 4 new integration tests in robot/invariant_enforcement_strategize.robot covering:

    • Full enforcement pipeline
    • No-invariant-service case
    • Multi-scope enforcement
    • Failure resilience

Quality Gate Results

  • lint: All checks passed
  • typecheck: 0 errors, 0 warnings
  • unit_tests: 470 features, 12375 scenarios, 47330 steps
  • coverage_report: 98% (threshold: 97%)
  • integration_tests: 1720/1723 passed (3 pre-existing failures unrelated to this change)
  • e2e_tests: 36/37 passed (1 pre-existing M6 failure unrelated to this change)
## Implementation Notes ### Design Decisions 1. **Optional dependency pattern**: The `InvariantService` is wired as an optional parameter (`invariant_service: InvariantService | None = None`) to `PlanLifecycleService.__init__()`, consistent with the existing pattern for `DecisionService`, `EventBus`, `ErrorPatternService`, etc. When not provided, invariant enforcement is silently skipped. 2. **Enforcement point**: Invariant enforcement happens inside `start_strategize()`, after pre-flight guardrail checks and the initial `strategy_choice` decision recording, but before returning the plan. This matches the spec (§19554-19610) which states invariants are reconciled "at the start of Strategize." 3. **Plan-level invariant registration**: When `_enforce_invariants()` runs, it first registers any `PlanInvariant` objects from the plan model into the `InvariantService` as PLAN-scoped invariants. This bridges the gap between invariants carried forward from actions (stored on the Plan model) and the InvariantService's in-memory store. 4. **Failure resilience**: Consistent with all other optional service invocations in the lifecycle service, `_enforce_invariants()` wraps the entire operation in a try/except, logging warnings but never blocking the strategize transition. 5. **DI container wiring**: `InvariantService` is registered as a `providers.Singleton` in the DI container and injected into `PlanLifecycleService`. Singleton is appropriate because InvariantService uses in-memory state that should be shared across callers within a process. ### Key Code Locations - `cleveragents.application.services.plan_lifecycle_service.PlanLifecycleService._enforce_invariants()` — new method (commit @HEAD) - `cleveragents.application.services.plan_lifecycle_service.PlanLifecycleService.start_strategize()` — calls `_enforce_invariants()` (commit @HEAD) - `cleveragents.application.services.plan_lifecycle_service.PlanLifecycleService.__init__()` — new `invariant_service` parameter (commit @HEAD) - `cleveragents.application.container.ApplicationContainer` — new `invariant_service` singleton and wiring to `plan_lifecycle_service` (commit @HEAD) - `cleveragents.actor.reconciliation.InvariantReconciliationActor` — existing actor, now invoked during strategize (commit @HEAD) ### Test Coverage - **Behave BDD**: 6 new scenarios in `features/invariant_enforcement_strategize.feature` covering: - Basic enforcement with action + global invariants - Zero-invariant case - Missing InvariantService (graceful degradation) - Multi-scope reconciliation (global + project + action) - Duplicate de-duplication - Failure resilience - **Robot Framework**: 4 new integration tests in `robot/invariant_enforcement_strategize.robot` covering: - Full enforcement pipeline - No-invariant-service case - Multi-scope enforcement - Failure resilience ### Quality Gate Results - **lint**: ✅ All checks passed - **typecheck**: ✅ 0 errors, 0 warnings - **unit_tests**: ✅ 470 features, 12375 scenarios, 47330 steps - **coverage_report**: ✅ 98% (threshold: 97%) - **integration_tests**: 1720/1723 passed (3 pre-existing failures unrelated to this change) - **e2e_tests**: 36/37 passed (1 pre-existing M6 failure unrelated to this change)
Member

Label cleanup needed: The State/Verified and Type/Bug labels should be removed. State/In Review was added correctly. The Type/Bug label (ID 849) was accidentally added while searching for state label IDs — the MCP tooling doesn't support label removal. The correct type is Type/Feature (already present).

**Label cleanup needed:** The `State/Verified` and `Type/Bug` labels should be removed. `State/In Review` was added correctly. The `Type/Bug` label (ID 849) was accidentally added while searching for state label IDs — the MCP tooling doesn't support label removal. The correct type is `Type/Feature` (already present).
Author
Owner

Day 48 Planning — Reclassification: Type/Bug → Type/Feature

This issue was labeled Type/Bug but is clearly a feature request:

  • Title: feat(plan): enforce invariants during Strategize phase
  • Body Metadata explicitly says Type: Feature
  • The issue describes implementing new functionality (invariant enforcement during Strategize), not fixing broken existing functionality

Since this is a feature (not a bug), it does not require a TDD counterpart per the Bug Fix Workflow. The TDD workflow applies only to Type/Bug issues. Label updated to Type/Feature.

The issue is currently State/In Review with all acceptance criteria checked. PR #1154 (feature/m3-invariant-enforcement-strategize by @brent.edwards) appears to be the implementing PR.

**Day 48 Planning — Reclassification: Type/Bug → Type/Feature** This issue was labeled `Type/Bug` but is clearly a feature request: - Title: `feat(plan): enforce invariants during Strategize phase` - Body Metadata explicitly says `Type: Feature` - The issue describes implementing new functionality (invariant enforcement during Strategize), not fixing broken existing functionality Since this is a feature (not a bug), it does **not** require a TDD counterpart per the Bug Fix Workflow. The TDD workflow applies only to `Type/Bug` issues. Label updated to `Type/Feature`. The issue is currently `State/In Review` with all acceptance criteria checked. PR #1154 (`feature/m3-invariant-enforcement-strategize` by @brent.edwards) appears to be the implementing PR.
freemo self-assigned this 2026-04-02 06:13:49 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#843
No description provided.