# Invariants Invariants are natural-language constraints on plan execution. They flow into the decision tree during Strategize and are reconciled by the Invariant Reconciliation Actor before any strategy work begins. ## Scope Hierarchy | Scope | Description | |-----------|----------------------------------------------------------| | `GLOBAL` | Applies to every plan in the system | | `PROJECT` | Applies to plans targeting a specific project | | `ACTION` | Defined in an action template; promoted on `plan use` | | `PLAN` | Attached directly to a specific plan | ## Merge Precedence When computing the effective set of invariants for a plan, the precedence chain is: ``` plan > project > global ``` - **Plan-level** invariants take highest precedence. - **Project-level** invariants apply next. - **Global-level** invariants are lowest precedence. - **Action-level** invariants are promoted to plan scope when `plan use` is called. ### De-duplication Invariants are de-duplicated by text (case-insensitive). When the same constraint text appears at multiple scopes, only the highest-precedence copy is kept. ### Merge Example Given: - Global: `"Never delete production data"`, `"Log all changes"` - Project (`myapp`): `"All API changes need tests"`, `"Log all changes"` - Plan: `"Use Python 3.13 only"` The effective set for a plan targeting `myapp` would be: 1. `"Use Python 3.13 only"` (plan) 2. `"All API changes need tests"` (project) 3. `"Log all changes"` (project — shadows the global duplicate) 4. `"Never delete production data"` (global) ## Enforcement At the start of the Strategize phase, the Invariant Reconciliation Actor: 1. Collects the effective invariant set for the plan. 2. Evaluates each invariant against the current plan context. 3. Creates an `InvariantEnforcementRecord` for each invariant. 4. Records an `invariant_enforced` decision in the decision tree. ### Enforcement Record Each record contains: | Field | Description | |------------------|--------------------------------------------------| | `invariant_id` | ULID of the invariant | | `enforced` | Whether the invariant was successfully enforced | | `actor_response` | Response text from the reconciliation actor | | `decision_id` | ULID of the associated decision node | ## Violation Model When an invariant is violated during execution, an `InvariantViolation` is created: | Field | Description | |-----------------|---------------------------------------------------| | `invariant_id` | ULID of the violated invariant | | `violated_text` | The invariant text that was violated | | `severity` | `error`, `warning`, or `info` | | `details` | Additional violation context | ## CLI Usage ### Add an invariant ```bash # Global invariant agents invariant add --global "Never delete production data" # Project-scoped invariant agents invariant add --project myapp "All API changes need tests" # Plan-specific invariant agents invariant add --plan 01HXYZ... "Use Python 3.13 only" # Action-scoped invariant agents invariant add --action local/code-coverage "Minimum 80% coverage" ``` ### List invariants ```bash # List all active invariants agents invariant list # Filter by scope agents invariant list --global agents invariant list --project myapp # Show merged effective set for a project agents invariant list --effective --project myapp # Filter by regex agents invariant list "data.*safe" # JSON output agents invariant list --format json ``` ### Remove an invariant ```bash # With confirmation prompt agents invariant remove 01HXYZ... # Skip confirmation agents invariant remove --yes 01HXYZ... ``` ## Relationship with PlanInvariant The existing `PlanInvariant` model in `plan.py` represents invariants already attached to a plan with an `InvariantSource` provenance tag. The new `Invariant` model in `invariant.py` is the standalone storage model used by `InvariantService` for cross-scope management. When a plan is created via `plan use`, action invariants are converted to `PlanInvariant` entries on the plan itself.