7.0 KiB
Invariant Management API (v3.2.0)
Invariants are natural-language constraints that govern plan execution. They are
evaluated at the start of the Strategize phase by the Invariant Reconciliation Actor
and recorded in the decision tree as invariant_enforced nodes.
Overview
Invariants introduced in v3.2.0 provide a declarative way to constrain what a plan
may do. They are scoped (global, project, action, or plan), merged by precedence, and
de-duplicated before enforcement. Violations block the phase transition and emit
INVARIANT_VIOLATED events.
Key capabilities:
- Four scope levels:
GLOBAL,PROJECT,ACTION,PLAN. - Merge precedence: plan > project > global (action invariants are promoted to plan scope).
- Automatic enforcement at every phase transition via
InvariantReconciliationActor. - CLI commands for adding, listing, and removing invariants.
CLI Reference
agents invariant add
Create a new invariant constraint.
agents invariant add <NAME> --description <DESC> [SCOPE_FLAG]
Scope flags:
| Flag | Scope | Description |
|---|---|---|
--global |
GLOBAL |
Applies to every plan in the system |
--project <NAME> |
PROJECT |
Applies to plans targeting the named project |
--plan <PLAN_ID> |
PLAN |
Attached directly to a specific plan |
--action <ACTION> |
ACTION |
Defined in an action template; promoted on plan use |
Examples:
# 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"
agents invariant list
Display invariants, optionally filtered by scope or project.
agents invariant list [PATTERN] [OPTIONS]
Options:
| Flag | Description |
|---|---|
--global |
Show only global invariants |
--project <NAME> |
Show only invariants for the named project |
--effective --project <NAME> |
Show the merged effective set for a project |
--format, -f |
Output format: json, yaml, plain, table, rich |
Examples:
# 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 pattern
agents invariant list "data.*safe"
# JSON output
agents invariant list --format json
agents invariant remove
Remove (soft-delete) an invariant by its ULID.
agents invariant remove <INVARIANT_ID> [--yes]
Options:
| Flag | Description |
|---|---|
--yes |
Skip the interactive confirmation prompt |
Examples:
# With confirmation prompt
agents invariant remove 01HXYZ...
# Skip confirmation
agents invariant remove --yes 01HXYZ...
Removal sets active=False on the invariant record; it is not hard-deleted. This
preserves the audit trail for enforcement records that reference the invariant.
Scope Hierarchy and Merge Precedence
When computing the effective invariant set 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 useis 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:
- "Use Python 3.13 only" (plan)
- "All API changes need tests" (project)
- "Log all changes" (project — shadows the global duplicate)
- "Never delete production data" (global)
Invariant Enforcement During Strategize
At the start of the Strategize phase, the InvariantReconciliationActor:
- Calls
InvariantService.get_effective_invariants(plan_id, project_name)to collect the merged invariant set. - Evaluates each invariant against the current plan context.
- Creates an
InvariantEnforcementRecordfor each invariant. - Records an
invariant_enforceddecision in the decision tree. - Emits
INVARIANT_VIOLATEDfor each violated invariant. - Blocks the phase transition with
ReconciliationBlockedErrorif any invariant fails.
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 invariant_enforced decision node |
Violation Model
When an invariant is violated, 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 |
Python API
The InvariantService is registered as a Singleton in the DI container. Obtain it via
container.invariant_service() rather than constructing it directly.
from cleveragents.application.services.invariant_service import InvariantService
from cleveragents.domain.models.core.invariant import InvariantScope
service = InvariantService(event_bus=event_bus)
# Add an invariant
inv = service.add_invariant(
text="All output files must be UTF-8 encoded",
scope=InvariantScope.PROJECT,
source_name="my-project",
)
# List effective invariants for a plan
effective = service.get_effective_invariants(
plan_id="01HV...",
project_name="my-project",
)
# Remove an invariant (soft-delete)
service.remove_invariant(invariant_id="01HXYZ...")
Key methods:
| Method | Returns | Description |
|---|---|---|
add_invariant(text, scope, source_name) |
Invariant |
Add a new invariant |
list_invariants(scope, source_name, effective) |
list[Invariant] |
Filter invariants |
remove_invariant(invariant_id) |
Invariant |
Soft-delete (sets active=False) |
get_effective_invariants(plan_id, project_name) |
list[Invariant] |
Merged precedence chain |
enforce_invariants(plan_id, invariants, actor_response, violated_ids) |
list[InvariantEnforcementRecord] |
Create enforcement records |
See Also
docs/reference/invariants.md— Full invariant domain model referencedocs/api/decisions.md— Decision recording and tree CLI reference- ADR-016: Invariant System