988a169831
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Failing after 41s
CI / quality (push) Failing after 1m4s
CI / security (push) Failing after 1m4s
CI / unit_tests (push) Failing after 1m3s
CI / build (push) Failing after 45s
CI / push-validation (push) Successful in 47s
CI / helm (push) Successful in 42s
CI / lint (push) Successful in 1m33s
CI / e2e_tests (push) Failing after 45s
CI / integration_tests (push) Failing after 47s
CI / typecheck (push) Successful in 1m43s
CI / docker (push) Has been skipped
CI / coverage (push) Has been skipped
CI / status-check (push) Failing after 6s
CI / benchmark-publish (pull_request) Has been skipped
CI / helm (pull_request) Successful in 50s
CI / build (pull_request) Successful in 57s
CI / lint (pull_request) Successful in 1m9s
CI / quality (pull_request) Successful in 1m18s
CI / benchmark-regression (pull_request) Failing after 1m16s
CI / typecheck (pull_request) Successful in 1m30s
CI / security (pull_request) Successful in 1m36s
CI / e2e_tests (pull_request) Successful in 3m52s
CI / integration_tests (pull_request) Successful in 4m30s
CI / push-validation (pull_request) Successful in 20s
CI / unit_tests (pull_request) Successful in 6m31s
CI / docker (pull_request) Successful in 1m36s
CI / coverage (pull_request) Successful in 12m19s
CI / status-check (pull_request) Successful in 4s
- 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
240 lines
7.8 KiB
Markdown
240 lines
7.8 KiB
Markdown
# `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)
|
|
```
|