# `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](../adr/ADR-010-actor-and-agent-architecture.md) and
[ADR-031](../adr/ADR-031-actor-abstraction-definition.md) 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`

```python
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`

```python
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`

```python
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`

```python
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](../adr/ADR-016-invariant-system.md) for the
design rationale behind invariant reconciliation.

```python
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

1. Collect invariants from four scopes: **global**, **project**, **action**, **plan**.
2. Group by normalised text (case-insensitive, stripped).
3. Detect conflicts between invariants at different scopes.
4. Resolve using specificity: `plan > action > project > global`.
   Exception: `non_overridable` global invariants always win.
5. Record an `invariant_enforced` decision for each active invariant.
6. 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`

```python
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`

```python
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:

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

```python
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:

```python
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

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