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
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
170 lines
8.3 KiB
Markdown
170 lines
8.3 KiB
Markdown
# 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),
|
|
))
|
|
```
|