# CleverActors Specification ## Overview CleverActors is a pure Python library: it parses a CleverAgents v3 actor YAML file, validates it against a Pydantic schema, optionally preprocesses it through a sandboxed Jinja2 engine, and compiles it into a LangGraph node + edge graph ready for execution by a host application. The library carries **no** runtime concerns of its own — no I/O, no persistence, no event publishing, no CLI, no HTTP, no DI container. Everything that needs the host's state goes through the small set of Protocols in `cleveractors.ports`. ## Scope ```text Customer YAML │ ▼ ┌──────────────────────┐ │ cleveractors.actor │ ← schema, loader, compiler, │ .{loader,schema, │ role validation, Jinja2 │ compiler,config, │ preprocessing │ yaml_loader,…} │ └──────────┬───────────┘ │ ▼ ┌──────────────────────┐ │ cleveractors. │ ← compile target — pure │ langgraph.{nodes, │ data classes describing │ state, …} │ node/edge structure └──────────┬───────────┘ │ ▼ host application (cleveragents-core, cleverrouter, third-party) drives the resulting graph through whatever execution model it chooses. ``` ## Public surface ```python from cleveractors.actor.schema import ActorConfigSchema from cleveractors.actor.compiler import compile_actor, CompiledActor from cleveractors.actor.loader import ActorLoader from cleveractors.actor.config import ActorConfiguration from cleveractors.actor import ( ActorCompilationError, SubgraphCycleError, MissingNodeError, InvalidEntryExitError, ) from cleveractors.langgraph.nodes import Node, Edge, NodeConfig, NodeType from cleveractors.langgraph.state import GraphState, StateSnapshot from cleveractors.templates.secure_renderer import SecureTemplateRenderer from cleveractors.lsp.models import LspBinding from cleveractors.agents.base import Agent # minimal ABC from cleveractors.acms.uko import VocabularyRegistry from cleveractors.core import ValidationError, NotFoundError from cleveractors.ports import ToolRegistryPort, ProviderRegistryPort ``` ## Extension points (Protocols) | Protocol | Where the library calls it | Host concrete | |----------|---------------------------|---------------| | `cleveractors.ports.tool_registry.ToolRegistryPort` | `ActorLoader._resolve_tools()` — looks up tool references at load time and emits a warning when unknown | `cleveragents.tool.registry.ToolRegistry` (cleveragents-core); cleverrouter's own tool registry; anything with a `.get(name) -> spec \| None` method | | `cleveractors.ports.provider_registry.ProviderRegistryPort` | `Node._execute_agent()` — resolves `(provider, model)` to an `Agent` instance at graph execution time | `cleveragents.providers.registry` wrapped in an adapter; cleverrouter's provider registry; any object with a `.get(provider, model) -> Agent \| None` method — see [ADR-005](adr/ADR-005-provider-registry-protocol.md) | ## What is intentionally NOT in the library These belong to the host application: | Concern | Where it lives | |---------|----------------| | Actor persistence (database) | `cleveragents.application.services.actor_service` | | Decision-tree recording | `cleveragents.application.services.decision_service` | | Invariant reconciliation runtime | `cleveragents.actor.reconciliation` | | Actor registry runtime (`ActorRegistry`, `v3_registry`) | `cleveragents.actor.{registry, v3_registry}` | | Reactive stream routing (rx, BehaviorSubject) | `cleveragents.reactive` | | LangChain provider configuration | `cleveragents.providers.registry` | | LSP runtime (registry, lifecycle, transport) | `cleveragents.lsp.{registry, lifecycle, server, transport}` | | Plan lifecycle (Action / Strategize / Execute / Apply) | `cleveragents.application` | | CLI / TUI / A2A wire protocol | `cleveragents.{cli, tui, a2a}` | | ACMS indexer / file traversal engine | `cleveragents.acms.index` | ## Internal modules (not public API) The following modules exist in the source tree but are **not** part of the public API surface. They are implementation details used internally and may change without a semver bump: | Module | Purpose | |--------|---------| | `cleveractors.actor.role_validation` | Validates that an actor config is appropriate for a declared role (strategy, execution, etc.) — logic used by the compiler, not exposed directly | | `cleveractors.actor.utils` | Small shared helpers (name normalisation, namespace parsing) used across the `actor` package | | `cleveractors.actor.yaml_template_engine` | `YAMLTemplateEngine` — the Jinja2 + env-var two-phase pipeline described in ADR-004; consumed by `ActorLoader` and `SecureTemplateRenderer` | | `cleveractors.langgraph.dynamic_router` | `DynamicRouter` — minimal predicate-based edge selector; used internally by graph execution | | `cleveractors.langgraph.message_router` | Helpers that build `MESSAGE_ROUTER` `NodeConfig` and its outgoing `Edge` list from a rule set | | `cleveractors.langgraph.pure_graph` | `PureGraph` — lightweight Pydantic model for a named node/edge graph; used in tests and by the compiler before LangGraph wiring | | `cleveractors.langgraph.routing_adapter` | `build_route_nodes_from_stream` — translates a reactive stream operator list into a `NodeConfig` / `Edge` chain; kept for migration compatibility | ## Origin Extracted from `cleveragents/cleveragents-core` at commit `20ad9a46` in 2026. The extraction preserves the customer-facing actor YAML format exactly; consumers see the same `ActorConfigSchema` and `compile_actor()` surface — only the import path changes. The decision tree that produced the extraction is recorded in: - [ADR-001: Library Boundary and Extraction Scope](adr/ADR-001-library-boundary-and-extraction-scope.md) - [ADR-002: Tool Registry Protocol](adr/ADR-002-tool-registry-protocol.md) - [ADR-003: Actor Abstraction Definition](adr/ADR-003-actor-abstraction-definition.md) - [ADR-004: Jinja2 YAML Template Preprocessing](adr/ADR-004-jinja2-yaml-template-preprocessing.md) - [ADR-005: Provider Registry Protocol](adr/ADR-005-provider-registry-protocol.md) ## Versioning `0.x.y` while the library is following cleveragents-core changes closely. `1.0.0` will be cut once the surface has stabilised through at least one cleveragents-core release and one cleverrouter release that consume it. ## Consumer integration patterns ### cleveragents-core The host already owns the actor lifecycle. It calls the library only for parsing + compile, and keeps its own registry/persistence/runtime. ```python from cleveractors.actor.loader import ActorLoader from cleveractors.actor.compiler import compile_actor # Host supplies its own tool registry (any object with .get(name) works). loader = ActorLoader(search_roots=[...], tool_registry=my_tool_registry) configs = loader.discover() for cfg in configs: compiled = compile_actor(cfg) my_actor_service.persist(cfg, compiled) ``` ### cleverrouter The cleverrouter gateway accepts an actor YAML upload, compiles it once into an immutable IR, and serves it as a model endpoint (`model: actor/{namespace}/{name}@{version}`). The library handles the parse + compile; cleverrouter holds the compiled artefact and runs it through its own LangGraph executor on each request. ```python from cleveractors.actor.compiler import compile_actor from cleveractors.actor.schema import ActorConfigSchema # Inside the data-plane actor.deploy handler: cfg = ActorConfigSchema.model_validate(yaml.safe_load(yaml_text)) compiled = compile_actor(cfg) db.session.add(ActorVersion( actor_id=actor.id, source_hash=hashlib.sha256(yaml_text.encode()).hexdigest(), compiled_ir=compiled.model_dump(), safety_report=cleverrouter_safety_pass(compiled), )) ```