Files
cleveractors-core/docs/specification.md
hurui200320 15e81e58f1
CI / dead_code (pull_request) Successful in 40s
CI / security (pull_request) Successful in 1m10s
CI / coverage (pull_request) Successful in 1m11s
CI / typecheck (pull_request) Successful in 1m19s
CI / unit_tests (pull_request) Successful in 1m18s
CI / lint (pull_request) Successful in 1m20s
CI / build (pull_request) Successful in 23s
CI / lint (push) Successful in 51s
CI / typecheck (push) Successful in 47s
CI / unit_tests (push) Successful in 49s
CI / coverage (push) Successful in 53s
CI / dead_code (push) Successful in 26s
CI / build (push) Successful in 26s
CI / security (push) Successful in 42s
fix: sync doc with extraction scope, remove boundary violation, add ProviderRegistryPort
Boundary fix:
- Delete src/cleveractors/acms/index.py (ACMSIndex, FileTraversalEngine,
  IndexEntry, FileType, TierLevel) — these are CLI/storage concerns that
  belong in cleveragents-core per ADR-001; the file already exists there
  at cleveragents/acms/index.py
- Clean src/cleveractors/acms/__init__.py: remove all index.py imports
  and re-exports; __all__ now derives purely from uko.__all__

ProviderRegistryPort (ADR-005):
- Add src/cleveractors/ports/provider_registry.py: ProviderRegistryPort
  Protocol with get(provider, model) -> Agent | None; structural typing,
  no host imports, mirrors the shape of ToolRegistryPort
- Update src/cleveractors/ports/__init__.py: export ProviderRegistryPort
  alongside ToolRegistryPort; update module docstring

Wire provider resolution into the node executor:
- compiler.py: _map_node now accepts actor_provider/actor_model defaults
  and merges them into AGENT node metadata (setdefault so per-node config
  values still win); compile_actor passes config.provider/config.model
- nodes.py: Node.__init__ gains optional provider_registry parameter;
  _execute_agent resolution order is now: (1) pre-resolved agents dict,
  (2) ProviderRegistryPort.get(provider, model) from node metadata,
  (3) graceful synthetic fallback — the ValueError guard for missing
  config.agent is removed since the registry is a valid alternative path

Documentation:
- Port ADR-003 (actor abstraction definition) from cleveragents-core ADR-031
- Port ADR-004 (Jinja2 YAML template preprocessing) from cleveragents-core
  ADR-032
- Add ADR-005 (Provider Registry Protocol) for the new provider_registry port
- Port five reference docs: actors_schema.md, actor_compiler.md,
  actor_config.md, actor_hierarchy.md, actors_examples.md
- Port API reference: api/actor.md
- Add provider field to all YAML examples in actors_examples.md,
  actor_hierarchy.md, and actor_config.md
- Update error messages in actor_config.md to match actual validator output
- Add graph-level provider/model propagation docs to actor_compiler.md
- Add ADR-005 cross-references to ADR-001, ADR-002, and actors_schema.md
- Fix ADR-005 status section to reflect that implementation is in this PR
- Fix ADR-004 to reference actual test files (smoke.feature, not phantom ones)
- Remove fabricated reserved-namespace constraint from ADR-003 Constraints
- Fix broken LICENSE link in docs/index.md for MkDocs rendering
- Add provider field to ActorConfigSchema table in api/actor.md
- Add internal modules section to docs/specification.md (ticket item 7)
- Fix markdown formatting in actors_schema.md provider field definition
- Add ADR-005 cross-reference to actors_schema.md provider field section

BDD coverage (7 scenarios, 30 steps):
- ProviderRegistryPort happy-path resolution
- Graceful fallback when provider registry returns None
- Graceful fallback when no provider registry is supplied
- Pre-resolved agents dict takes precedence over provider registry
- Compile a minimal graph actor, reject missing provider, render template

Post-review fixes applied in amend:
- actors_examples.md and actor_hierarchy.md: added provider to all examples
- ADR-003: removed fabricated reserved-namespace constraint
- ADR-004: corrected phantom test file references
- ADR-005: updated status to reflect implementation is included
- Added 'no registry' and 'agents dict precedence' BDD scenarios
- Added provider to api/actor.md ActorConfigSchema fields table
- ADR-001 and ADR-002: added ADR-005 cross-references
- actor_config.md: fixed error message to match validator
- actor_compiler.md: documented graph-level provider/model propagation
- index.md: fixed broken LICENSE link
- actors_schema.md: added ADR-005 cross-reference

ISSUES CLOSED: #4
2026-05-21 10:10:37 +00:00

8.3 KiB

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

                      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

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

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:

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.

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.

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),
))