From 1f197646a2be958a240df2d17a100e89c15edbad Mon Sep 17 00:00:00 2001 From: CleverThis Date: Mon, 13 Apr 2026 21:54:29 +0000 Subject: [PATCH] spec: add Invariant Management System module specification (v3.2.0) [AUTO-ARCH-2B] --- docs/specification.md | 128 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/docs/specification.md b/docs/specification.md index f0803345d..b59695988 100644 --- a/docs/specification.md +++ b/docs/specification.md @@ -47095,3 +47095,131 @@ These architectural invariants must be maintained across all milestones: 8. **BDD tests**: All unit-level tests expressed as Behave/Gherkin scenarios. No xUnit-style tests. 9. **File size limit**: No source file exceeds 500 lines. Split into modules if approaching limit. 10. **Atomic commits**: One logical change per commit. No mixed concerns. + +--- + +## Invariant Management System (v3.2.0) + +### Overview + +Invariants are user-defined constraints that must hold true throughout plan execution. They are enforced during the Strategize phase to constrain LLM decision-making. Users manage invariants via the CLI (`agents invariant add/list/remove`). Invariants are scoped to a project and optionally to specific plans. + +### Module Boundaries + +- **Module**: `cleveragents.invariants` +- **Layer**: Domain +- **Responsibilities**: + - CRUD operations for invariant definitions + - Enforcing invariants during Strategize phase + - Providing invariant context to the LLM prompt builder + - Tracking which invariants were applied to each decision +- **Public Interfaces**: + - `InvariantRepository` — CRUD for invariant entities (domain repository interface) + - `InvariantEnforcer` — validates proposed decisions against active invariants + - `InvariantContextBuilder` — builds invariant context strings for LLM prompts +- **Forbidden Dependencies**: Must not import from `cleveragents.cli` or `cleveragents.tui` + +### Data Models + +#### Invariant Entity + +```python +@dataclass +class Invariant: + id: UUID + project_id: UUID + plan_id: Optional[UUID] # None = applies to all plans in project + name: str # Short human-readable name + description: str # Full invariant statement (natural language) + scope: Literal["global", "plan"] + enforcement_mode: Literal["hard", "soft"] + # hard = LLM must not violate; soft = LLM should prefer not to violate + created_at: datetime + created_by: str # User identifier + is_active: bool + metadata: dict +``` + +### Database Schema + +```sql +CREATE TABLE invariants ( + id UUID PRIMARY KEY, + project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + plan_id UUID REFERENCES plans(id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + description TEXT NOT NULL, + scope VARCHAR(20) NOT NULL CHECK (scope IN ('global', 'plan')), + enforcement_mode VARCHAR(10) NOT NULL CHECK (enforcement_mode IN ('hard', 'soft')), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by VARCHAR(255) NOT NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + metadata JSONB NOT NULL DEFAULT '{}' +); + +CREATE INDEX idx_invariants_project_id ON invariants(project_id); +CREATE INDEX idx_invariants_plan_id ON invariants(plan_id); +CREATE INDEX idx_invariants_active ON invariants(is_active) WHERE is_active = TRUE; +``` + +### CLI Interface Specification + +#### `agents invariant add` + +- Interactive or flag-based invariant creation +- Required flags: `--name `, `--description ` +- Optional flags: `--plan ` (scope to plan), `--mode hard|soft` (default: hard), `--project ` (default: current project) +- Output: Confirmation with assigned invariant ID + +#### `agents invariant list` + +- Lists all active invariants for the current project +- Optional flags: `--plan ` (filter by plan), `--all` (include inactive), `--format table|json` +- Output: Table with columns: ID (short), Name, Scope, Mode, Created + +#### `agents invariant remove ` + +- Deactivates (soft-deletes) an invariant +- Requires confirmation unless `--yes` flag provided +- Output: Confirmation message + +#### `agents invariant show ` + +- Shows full details of a single invariant +- Output: Rich panel with all fields + +### Enforcement Mechanism + +During the Strategize phase, `InvariantEnforcer` is called before each LLM decision: + +1. `InvariantContextBuilder.build(project_id, plan_id)` retrieves all active invariants and formats them as a structured prompt section +2. The invariant context is injected into the LLM system prompt +3. Hard invariants are prefixed with `MUST NOT:` in the prompt +4. Soft invariants are prefixed with `SHOULD PREFER NOT:` in the prompt +5. After the LLM responds, `InvariantEnforcer.validate(decision, invariants)` checks for obvious violations +6. If a hard invariant is violated, the decision is rejected and the LLM is re-prompted with explicit violation feedback + +### Integration Points + +| Integration | Direction | Description | +|---|---|---| +| Strategize Phase | Called by | `InvariantContextBuilder` called by prompt builder in strategize LangGraph node | +| Decision Recording | Calls | `InvariantEnforcer` passes applied invariant IDs to `DecisionRecorder` | +| Plan Correction Engine | Called by | When correcting a decision, active invariants at correction time are re-applied | + +### Error Handling + +- `InvariantNotFoundError(invariant_id)` — raised when invariant ID does not exist +- `InvariantViolationError(invariant_id, decision, reason)` — raised when a hard invariant is violated +- `InvariantConflictError(invariant_ids)` — raised when two invariants contradict each other + +### Cross-Cutting Concerns + +- **Logging**: Invariant enforcement logged at DEBUG level; violations logged at WARNING +- **Performance**: Invariant context building must complete within 10ms +- **Caching**: Active invariants for a project are cached per request (invalidated on add/remove) + +--- +**Automated by CleverAgents Bot** +Supervisor: Architecture | Agent: architecture-pool-supervisor +Worker: [AUTO-ARCH-2B] -- 2.52.0