- docs/api/actor.md: Add InvariantReconciliationActor section documenting the built-in reconciliation actor introduced in v3.8.0, including the reconciliation algorithm, ReconciliationResult/ConflictRecord/ScopeInvariants data classes, standalone reconcile_invariants() function, DI registration, and failure behaviour. - docs/modules/devcontainer-discovery.md: New module guide for the devcontainer auto-discovery system (v3.8.0 fix #2615), covering DevcontainerDiscoveryResult, discover_devcontainers(), is_trigger_type(), monorepo named-config support, and gotchas. - mkdocs.yml: Add 'Devcontainer Auto-Discovery' to the Modules nav section, alongside shell-safety, uko-provenance, invariant-reconciliation, context-hydration, and git-worktree-sandbox module docs. - robot/coverage_threshold.robot: Add tdd_issue and tdd_issue_4305 tags to the Noxfile Contains Coverage Threshold Constant test case. ISSUES CLOSED: #4485
7.8 KiB
cleveragents.actor — Actor System
The actor package provides the actor registry, YAML configuration schema,
loader, and compiler. Actors are the primary execution units in CleverAgents
— they encapsulate an LLM, a set of tools, and a LangGraph execution graph.
See ADR-010 and ADR-031 for design rationale.
!!! note "Lazy imports"
The actor package uses lazy imports to avoid pulling in heavy
transitive dependencies (LangChain, LangSmith, SQLAlchemy) when only
lightweight submodules such as role_validation or schema are needed.
ActorConfiguration
from cleveragents.actor import ActorConfiguration
Pydantic model representing a fully-parsed actor YAML file. Key fields:
| Field | Type | Description |
|---|---|---|
name |
str |
Actor identifier (namespaced, e.g. openai/gpt-4o) |
entry_node |
str |
Graph entry node name |
nodes |
dict[str, NodeConfig] |
Node definitions |
edges |
list[EdgeConfig] |
Graph edges |
lsp_binding |
LSPBinding | None |
Per-node LSP server binding |
tool_sources |
list[str] |
Tool source references |
ActorLoader
from cleveragents.actor import ActorLoader
loader = ActorLoader()
actors = loader.discover(Path("./actors/"))
Discovers and loads actor YAML files from a directory tree. Validates
graph reachability (all nodes reachable from entry_node) and reports
YAML line/column positions on error.
Methods
| Method | Description |
|---|---|
discover(path) |
Scan directory for actor YAML files |
load(path) |
Load a single actor YAML file |
validate(config) |
Validate an ActorConfiguration |
ActorRegistry
from cleveragents.actor import ActorRegistry
registry = ActorRegistry()
registry.register(actor_config)
actor = registry.get("openai/gpt-4o")
In-memory registry for actor configurations. Thread-safe.
compile_actor / CompiledActor
from cleveragents.actor import compile_actor, CompiledActor
compiled: CompiledActor = compile_actor(actor_config, tool_registry)
Compiles an ActorConfiguration into a runnable CompiledActor by
resolving tool bindings, building the LangGraph state machine, and
attaching LSP/MCP adapters.
CompilationMetadata
Carries diagnostics from the compilation step: resolved tools, skipped nodes, and any warnings.
Compilation Errors
| Exception | Description |
|---|---|
ActorCompilationError |
General compilation failure |
MissingNodeError |
Referenced node not defined |
InvalidEntryExitError |
Entry/exit node configuration invalid |
SubgraphCycleError |
Cycle detected in subgraph references |
InvariantReconciliationActor
Introduced: v3.8.0 | Module: cleveragents.actor.reconciliation
The built-in InvariantReconciliationActor (builtin/invariant-reconciliation)
is invoked by PlanLifecycleService when a plan enters the Strategize phase.
This gate must succeed before Strategize work begins; the lifecycle service
persists the reconciled invariant view and reuses it for later Execute or Apply
transitions instead of re-running the actor. The actor collects invariants
from four scopes, resolves conflicts using specificity-based precedence,
records invariant_enforced decisions, and returns a reconciled
InvariantSet.
See ADR-016 — Invariant System for the design rationale behind invariant reconciliation.
from cleveragents.actor.reconciliation import InvariantReconciliationActor
actor = InvariantReconciliationActor(
invariant_service=container.invariant_service(),
decision_service=container.decision_service(),
)
result = actor.run(plan_id="01HXYZ...", project_name="local/my-app")
# result.reconciled_set — effective InvariantSet
# result.conflicts — list[ConflictRecord] with resolution details
# result.enforced_decision_ids — list of ULID strings
Reconciliation Algorithm
- Collect invariants from four scopes: global, project, action, plan.
- Group by normalised text (case-insensitive, stripped).
- Detect conflicts between invariants at different scopes.
- Resolve using specificity:
plan > action > project > global. Exception:non_overridableglobal invariants always win. - Record an
invariant_enforceddecision for each active invariant. - Return a reconciled
InvariantSet.
InvariantReconciliationActor Methods
| Method signature | Description |
|---|---|
collect_invariants(*, plan_id: str | None = None, project_name: str | None = None, action_name: str | None = None) -> ScopeInvariants |
Collect invariants from all four scopes and return them as a ScopeInvariants container |
run(*, plan_id: str, project_name: str | None = None, action_name: str | None = None, parent_decision_id: str | None = None) -> ReconciliationResult |
Execute the reconciliation lifecycle (collect → reconcile → record) and return a ReconciliationResult |
ReconciliationResult
from cleveragents.actor.reconciliation import ReconciliationResult
@dataclass(frozen=True)
class ReconciliationResult:
reconciled_set: InvariantSet # effective invariants
conflicts: list[ConflictRecord] # detected conflicts with resolution details
enforced_decision_ids: list[str] # ULIDs of invariant_enforced decisions
ConflictRecord
from cleveragents.actor.reconciliation import ConflictRecord
@dataclass(frozen=True)
class ConflictRecord:
key: str # normalised invariant text used for grouping
winner: Invariant # invariant that prevailed
losers: list[Invariant] # invariants that were overridden
reason: str # human-readable explanation of the resolution
ScopeInvariants
Convenience container grouping invariants by scope tier:
from cleveragents.actor.reconciliation import ScopeInvariants
scope_invs = ScopeInvariants(
global_invariants=[...],
project_invariants=[...],
action_invariants=[...],
plan_invariants=[...],
)
all_invs = scope_invs.all_invariants() # flat list across all scopes
reconcile_invariants (standalone function)
from cleveragents.actor.reconciliation import reconcile_invariants
reconciled, conflicts = reconcile_invariants(scope_invariants)
# reconciled — list[Invariant] (winners only)
# conflicts — list[ConflictRecord]
Constructing the actor via dependency injection
InvariantReconciliationActor is instantiated directly with application
services resolved from the DI container:
from cleveragents.application.container import Container
from cleveragents.actor.reconciliation import InvariantReconciliationActor
container = Container()
actor = InvariantReconciliationActor(
invariant_service=container.invariant_service(),
decision_service=container.decision_service(),
)
Failure Behaviour
Reconciliation failures block the phase transition with
ReconciliationBlockedError and emit an INVARIANT_VIOLATED event. The
exception is raised by PlanLifecycleService, importable via
from cleveragents.application.services.plan_lifecycle_service import ReconciliationBlockedError.
Post-correction reconciliation runs via CORRECTION_APPLIED event subscription
(best-effort; does not block correction completion).
Example: Loading and Compiling an Actor
from pathlib import Path
from cleveragents.actor import ActorLoader, compile_actor
from cleveragents.tool import ToolRegistry
loader = ActorLoader()
config = loader.load(Path("examples/actors/graph_workflow.yaml"))
tool_registry = ToolRegistry()
compiled = compile_actor(config, tool_registry)