Files
cleveragents-core/docs/modules/invariant-reconciliation.md
T
HAL9000 a3762a4cc2
CI / lint (push) Successful in 25s
CI / security (push) Successful in 59s
CI / quality (push) Successful in 45s
CI / typecheck (push) Successful in 1m9s
CI / push-validation (push) Successful in 26s
CI / build (push) Successful in 49s
CI / helm (push) Successful in 42s
CI / e2e_tests (push) Successful in 4m18s
CI / integration_tests (push) Successful in 4m28s
CI / unit_tests (push) Successful in 5m47s
CI / docker (push) Successful in 11s
CI / coverage (push) Successful in 11m48s
CI / status-check (push) Successful in 1s
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Has been cancelled
docs: add invariant-reconciliation module guide, extend automation tracking docs, update mkdocs nav
docs: add invariant-reconciliation module guide, extend automation tracking docs, update mkdocs nav

- Add docs/modules/invariant-reconciliation.md: module guide for InvariantReconciliationActor (v3.8.0)
- Extend docs/development/automation-tracking.md: all 16 supervisors, centralized tracking manager
- Update mkdocs.yml: add Modules nav section, Custom Sandbox Strategy to Development

ISSUES CLOSED: #5700

Co-authored-by: CleverThis <hal9000@cleverthis.com>
Co-committed-by: CleverThis <hal9000@cleverthis.com>
2026-04-09 09:22:43 +00:00

6.5 KiB

Invariant Reconciliation Module

Package: cleveragents.actor.reconciliation Introduced: v3.8.0

The Invariant Reconciliation Actor is a built-in actor that automatically runs at every plan phase transition to ensure all active invariants are consistent and correctly prioritised before any strategy or execution work begins.

For the invariant data model and scope hierarchy, see docs/reference/invariants.md. For the InvariantService API, see docs/api/core.md.


Purpose

Invariants are natural-language constraints on plan execution that can be defined at four scopes: global, project, action, and plan. When multiple invariants apply to the same plan, conflicts can arise — for example, a project-level invariant may contradict a plan-level one. The reconciliation actor resolves these conflicts deterministically using a precedence chain and records each decision in the audit trail.


Automatic Invocation

PlanLifecycleService automatically invokes the reconciliation actor at the start of three phase transitions:

Transition Method
Strategize start start_strategize()
Execute start execute_plan()
Apply start apply_plan()

If reconciliation fails, the phase transition is blocked with ReconciliationBlockedError and an INVARIANT_VIOLATED event is emitted on the event bus. The transition cannot proceed until the invariants are corrected.

Post-correction reconciliation runs automatically via a CORRECTION_APPLIED event subscription (best-effort; does not block correction completion).


Reconciliation Algorithm

The actor follows a six-step algorithm:

  1. Load — fetch all invariants in scope for the plan (global → project → action → plan)
  2. Deduplicate — remove exact-text duplicates, keeping the highest-scope copy
  3. Detect conflicts — identify pairs where one invariant's constraint contradicts another's
  4. Resolve — apply the precedence chain: plan > action > project > global; when scopes are equal, the invariant with the lower position index wins
  5. Record — write each resolution decision to the audit trail with rationale
  6. Emit — publish INVARIANT_RECONCILED event on the event bus

Key Classes

InvariantReconciliationActor

from cleveragents.actor.reconciliation import InvariantReconciliationActor

actor = InvariantReconciliationActor(invariant_service, event_bus, audit_service)
result = await actor.reconcile(plan_id)
Parameter Type Description
invariant_service InvariantService Provides invariant CRUD and scope queries
event_bus EventBus Receives INVARIANT_RECONCILED / INVARIANT_VIOLATED events
audit_service AuditService Records reconciliation decisions

ReconciliationResult

@dataclass
class ReconciliationResult:
    plan_id: str
    resolved_invariants: list[Invariant]
    conflicts_detected: int
    conflicts_resolved: int
    decisions: list[ReconciliationDecision]
    success: bool

ReconciliationDecision

@dataclass
class ReconciliationDecision:
    winner: Invariant
    loser: Invariant
    rationale: str          # e.g. "plan scope > project scope"
    timestamp: datetime

Error Handling

Exception When raised
ReconciliationBlockedError Unresolvable conflict detected; phase transition blocked
InvariantLoadError Database error loading invariants
ReconciliationTimeoutError Reconciliation exceeded configured timeout

When ReconciliationBlockedError is raised:

  1. The phase transition method re-raises the exception to the caller
  2. PlanLifecycleService emits INVARIANT_VIOLATED on the event bus
  3. The plan remains in its current phase (e.g., PENDING before Strategize)
  4. The operator must correct the conflicting invariants and retry the transition

DI Registration

InvariantService is registered as a Singleton in the DI container:

# src/cleveragents/application/container.py (excerpt)
class Container(containers.DeclarativeContainer):
    invariant_service = providers.Singleton(
        InvariantService,
        uow_factory=uow_factory,
        event_bus=event_bus,
    )
    invariant_reconciliation_actor = providers.Factory(
        InvariantReconciliationActor,
        invariant_service=invariant_service,
        event_bus=event_bus,
        audit_service=audit_service,
    )

Usage Examples

Triggering Reconciliation Manually

from cleveragents.application.container import Container

container = Container()
actor = container.invariant_reconciliation_actor()
result = await actor.reconcile(plan_id="plan-01JXYZ")

if not result.success:
    print(f"Reconciliation failed: {result.conflicts_detected} unresolvable conflicts")
else:
    print(f"Reconciled {result.conflicts_resolved} conflicts")
    for decision in result.decisions:
        print(f"  {decision.winner.text!r} won over {decision.loser.text!r}: {decision.rationale}")

Listening for Reconciliation Events

from cleveragents.domain.events import INVARIANT_RECONCILED, INVARIANT_VIOLATED

@event_bus.subscribe(INVARIANT_RECONCILED)
async def on_reconciled(event):
    logger.info("Invariants reconciled", plan_id=event.plan_id, decisions=event.decision_count)

@event_bus.subscribe(INVARIANT_VIOLATED)
async def on_violated(event):
    logger.error("Invariant violation blocked phase transition", plan_id=event.plan_id)
    # Notify operator, create alert, etc.

Defining Invariants at Different Scopes

# Global invariant (applies to all plans)
agents invariant create --scope global --text "Never delete production data without a backup"

# Project-level invariant
agents invariant create --scope project --project my-project \
  --text "All database migrations must be reversible"

# Plan-level invariant (highest precedence)
agents invariant create --scope plan --plan plan-01JXYZ \
  --text "This plan may drop the staging schema"

In this example, if the plan-level invariant conflicts with the project-level one, the plan-level invariant wins (higher precedence).