UAT: InvariantService uses in-memory-only storage — agents invariant add/list/remove state is lost between CLI invocations, no persistence layer exists #4696

Open
opened 2026-04-08 18:02:02 +00:00 by HAL9000 · 0 comments
Owner

Bug Report

Feature Area

InvariantService / agents invariant add/list/remove CLI commands / invariant persistence

What Was Tested

Code-level analysis of src/cleveragents/application/services/invariant_service.py, src/cleveragents/cli/commands/invariant.py, and src/cleveragents/infrastructure/database/repositories.py.

Expected Behavior (from spec)

The spec (docs/specification.md, Layer 3: Invariant Enforcement) states:

Global invariants are system-wide constraints that persist across all plans. They are set via agents invariant add --global "..." and remain active until explicitly removed.

The CLI docstring also states:

"Module-level service instance (in-memory, same lifetime as CLI process)"

The spec implies invariants should persist across CLI invocations — otherwise agents invariant add --global "Never delete production data" followed by a new CLI session would show no invariants.

Actual Behavior (from code)

1. InvariantService uses in-memory dict storage:

class InvariantService:
    def __init__(self, event_bus: EventBus | None = None) -> None:
        self._invariants: dict[str, Invariant] = {}  # <-- in-memory only
        self._enforcement_records: list[InvariantEnforcementRecord] = []

All invariants are stored in a Python dict that exists only for the lifetime of the process.

2. CLI creates a module-level singleton that is also in-memory:

# Module-level service instance (in-memory, same lifetime as CLI process)
_service: InvariantService | None = None

def _get_service() -> InvariantService:
    global _service
    if _service is None:
        _service = InvariantService()
    return _service

Each new CLI invocation creates a fresh InvariantService() with no invariants.

3. No InvariantRepository exists in the database layer:

src/cleveragents/infrastructure/database/repositories.py has:

  • ActionRepository — persists ActionInvariantModel (action YAML invariants)
  • PlanRepository — persists PlanInvariantModel (plan-attached invariants)

But there is no InvariantRepository for the standalone Invariant objects managed by InvariantService. The InvariantService invariants (added via CLI) are never persisted to the database.

Steps to Reproduce

# Session 1: Add a global invariant
agents invariant add --global "Never delete production data"
# Output: Invariant added: 01HXYZ...

# Session 2 (new process): List global invariants
agents invariant list --global
# Output: No invariants found.
# Expected: Should show "Never delete production data"

Impact

  • agents invariant add --global "..." is effectively a no-op — invariants are lost immediately when the CLI exits
  • The entire invariant management CLI (add, list, remove) is non-functional for persistent use
  • The InvariantReconciliationActor would always see an empty invariant set when called from a fresh process

Code Locations

  • src/cleveragents/application/services/invariant_service.pyInvariantService.__init__ (line 51-61)
  • src/cleveragents/cli/commands/invariant.py_get_service() (line 62-67)
  • src/cleveragents/infrastructure/database/repositories.py — no InvariantRepository class

Fix Required

  1. Create InvariantRepository in src/cleveragents/infrastructure/database/repositories.py to persist Invariant objects
  2. Add InvariantModel to src/cleveragents/infrastructure/database/models.py
  3. Update InvariantService to use the repository for persistence (or add a load/save mechanism)
  4. Update the CLI _get_service() to wire in the database-backed service

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: uat-tester

## Bug Report ### Feature Area `InvariantService` / `agents invariant add/list/remove` CLI commands / invariant persistence ### What Was Tested Code-level analysis of `src/cleveragents/application/services/invariant_service.py`, `src/cleveragents/cli/commands/invariant.py`, and `src/cleveragents/infrastructure/database/repositories.py`. ### Expected Behavior (from spec) The spec (docs/specification.md, Layer 3: Invariant Enforcement) states: > Global invariants are system-wide constraints that persist across all plans. They are set via `agents invariant add --global "..."` and remain active until explicitly removed. The CLI docstring also states: > "Module-level service instance (in-memory, same lifetime as CLI process)" The spec implies invariants should persist across CLI invocations — otherwise `agents invariant add --global "Never delete production data"` followed by a new CLI session would show no invariants. ### Actual Behavior (from code) **1. `InvariantService` uses in-memory dict storage:** ```python class InvariantService: def __init__(self, event_bus: EventBus | None = None) -> None: self._invariants: dict[str, Invariant] = {} # <-- in-memory only self._enforcement_records: list[InvariantEnforcementRecord] = [] ``` All invariants are stored in a Python dict that exists only for the lifetime of the process. **2. CLI creates a module-level singleton that is also in-memory:** ```python # Module-level service instance (in-memory, same lifetime as CLI process) _service: InvariantService | None = None def _get_service() -> InvariantService: global _service if _service is None: _service = InvariantService() return _service ``` Each new CLI invocation creates a fresh `InvariantService()` with no invariants. **3. No `InvariantRepository` exists in the database layer:** `src/cleveragents/infrastructure/database/repositories.py` has: - `ActionRepository` — persists `ActionInvariantModel` (action YAML invariants) - `PlanRepository` — persists `PlanInvariantModel` (plan-attached invariants) But there is **no `InvariantRepository`** for the standalone `Invariant` objects managed by `InvariantService`. The `InvariantService` invariants (added via CLI) are never persisted to the database. ### Steps to Reproduce ```bash # Session 1: Add a global invariant agents invariant add --global "Never delete production data" # Output: Invariant added: 01HXYZ... # Session 2 (new process): List global invariants agents invariant list --global # Output: No invariants found. # Expected: Should show "Never delete production data" ``` ### Impact - `agents invariant add --global "..."` is effectively a no-op — invariants are lost immediately when the CLI exits - The entire invariant management CLI (`add`, `list`, `remove`) is non-functional for persistent use - The `InvariantReconciliationActor` would always see an empty invariant set when called from a fresh process ### Code Locations - `src/cleveragents/application/services/invariant_service.py` — `InvariantService.__init__` (line 51-61) - `src/cleveragents/cli/commands/invariant.py` — `_get_service()` (line 62-67) - `src/cleveragents/infrastructure/database/repositories.py` — no `InvariantRepository` class ### Fix Required 1. Create `InvariantRepository` in `src/cleveragents/infrastructure/database/repositories.py` to persist `Invariant` objects 2. Add `InvariantModel` to `src/cleveragents/infrastructure/database/models.py` 3. Update `InvariantService` to use the repository for persistence (or add a `load`/`save` mechanism) 4. Update the CLI `_get_service()` to wire in the database-backed service --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
HAL9000 added this to the v3.5.0 milestone 2026-04-08 18:05:36 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#4696
No description provided.