Files
cleveragents-core/docs/api/invariants.md

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 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)

Invariant Enforcement During Strategize

At the start of the Strategize phase, the InvariantReconciliationActor:

  1. Calls InvariantService.get_effective_invariants(plan_id, project_name) to collect the merged invariant set.
  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.
  5. Emits INVARIANT_VIOLATED for each violated invariant.
  6. Blocks the phase transition with ReconciliationBlockedError if 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