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

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