docs: add specification, two ADRs, and MkDocs nav
CI / unit_tests (push) Successful in 1m18s
CI / build (push) Has been skipped
CI / typecheck (push) Failing after 1m20s
CI / lint (push) Failing after 1m20s
CI / dead_code (push) Failing after 43s
CI / security (push) Successful in 1m19s
CI / coverage (push) Successful in 1m20s
CI / unit_tests (push) Successful in 1m18s
CI / build (push) Has been skipped
CI / typecheck (push) Failing after 1m20s
CI / lint (push) Failing after 1m20s
CI / dead_code (push) Failing after 43s
CI / security (push) Successful in 1m19s
CI / coverage (push) Successful in 1m20s
docs/specification.md — the library's architectural commitment:
scope diagram, public surface, extension points (ToolRegistryPort),
explicit list of what's NOT in scope, origin sha, versioning
policy, and concrete integration patterns for cleveragents-core
and cleverrouter.
docs/adr/ADR-001-library-boundary-and-extraction-scope.md —
records the Medium-scope decision; lists the file-by-file
extraction table; documents the rejected alternatives (Narrow,
Wide, and 'no extraction'); names the upstream commit
(20ad9a46) the extraction baseline traces back to.
docs/adr/ADR-002-tool-registry-protocol.md — records the Protocol
decoupling that lets the library accept any object with
`get(name) -> spec | None` as a tool registry without
importing cleveragents-core or cleverrouter.
mkdocs.yml — site config with three-page nav (Home + Specification +
ADRs), Material theme, permalinked TOC.
docs/index.md — symlinked copy of README.md so the rendered site
renders the library README as the home page.
The two ADRs use the same metadata frontmatter shape (adr_number,
status_history, tier, authors, related_adrs) as cleveragents-core's
ADRs so contributors moving between projects see identical structure.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
---
|
||||
adr_number: 1
|
||||
title: Library Boundary and Extraction Scope
|
||||
status_history:
|
||||
- - "2026-05-17"
|
||||
- Accepted
|
||||
- Jeffrey Phillips Freeman
|
||||
tier: 1
|
||||
authors:
|
||||
- Jeffrey Phillips Freeman
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The CleverAgents Core repository (`cleveragents/cleveragents-core`) had
|
||||
grown to 568 Python source files spanning the plan lifecycle, the
|
||||
actor system, the resource and tool registries, the reactive routing
|
||||
runtime, the A2A wire protocol, the CLI / TUI, and the ACMS knowledge
|
||||
ontology. CleverRouter (a separate product) needs only the
|
||||
declarative-actor subset — YAML parsing + Jinja2 preprocessing +
|
||||
LangGraph compilation — but consuming `cleveragents-core` as-is would
|
||||
pull in a database, an A2A runtime, the CLI, and the entire plan
|
||||
lifecycle.
|
||||
|
||||
We needed a library that:
|
||||
|
||||
- Compiles a CleverAgents v3 actor YAML into a runnable LangGraph
|
||||
spec, with no other transitive runtime dependencies.
|
||||
- Stays in lockstep with the canonical schema in cleveragents-core,
|
||||
so customers don't see a divergent actor format depending on which
|
||||
product they're using.
|
||||
- Doesn't force cleveragents-core into a refactor of its application
|
||||
services or its database model.
|
||||
|
||||
## Decision
|
||||
|
||||
Extract `cleveractors-core` containing the **Medium-scope** subset of
|
||||
cleveragents-core, defined precisely as:
|
||||
|
||||
| Subpackage | Source | Notes |
|
||||
|-----------|--------|-------|
|
||||
| `cleveractors.actor` | `cleveragents.actor` (9 of 12 files) | All except `registry.py`, `v3_registry.py`, `reconciliation.py` — those three depend on `cleveragents.application.services` and are kept upstream. |
|
||||
| `cleveractors.langgraph` | `cleveragents.langgraph` (7 of 9 files) | All except `graph.py` and `bridge.py` — those depend on the rx-based reactive stream router. `state.py` is edited to drop the rx-coupled `StateManager`; `GraphState` / `StateSnapshot` / `StateUpdateMode` remain. |
|
||||
| `cleveractors.templates` | `cleveragents.templates` | All 3 files — pure, no cross-package imports. |
|
||||
| `cleveractors.agents` | rewritten | Minimal `Agent` ABC only — no rx. The upstream `cleveragents.agents.base.Agent` is rx-coupled and belongs to the routing runtime. |
|
||||
| `cleveractors.lsp` | `cleveragents.lsp.models` only | Just the `LspBinding` data class; the LSP runtime stays in cleveragents-core. |
|
||||
| `cleveractors.acms` | `cleveragents.acms` + `acms.uko` | Pure UKO vocabulary data; the indexer / storage / strategy runtime stays in cleveragents-core. |
|
||||
| `cleveractors.domain.models.acms.detail_level` | `cleveragents.domain.models.acms.detail_level` | The one Pydantic data class the library needs from upstream's larger `domain.models.acms` surface. |
|
||||
| `cleveractors.core.exceptions` | rewritten | `ValidationError` + `NotFoundError` only. Mirrors the upstream names; subclasses `Exception` directly. |
|
||||
| `cleveractors.ports` | new | Protocols the library uses to talk to host-supplied registries — see [ADR-002](ADR-002-tool-registry-protocol.md). |
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- The library is genuinely pure-domain. Installing it does not pull
|
||||
in a database, the A2A SDK, the rx routing runtime, or the LangChain
|
||||
provider registry. Total runtime deps: `pydantic`, `structlog`,
|
||||
`langchain-core`, `langgraph`, `jinja2`, `pyyaml`, `jsonschema`,
|
||||
`python-ulid` — eight packages.
|
||||
- `cleveragents-core` keeps owning everything stateful. The
|
||||
layering-violator files (`actor/registry.py`,
|
||||
`actor/v3_registry.py`, `actor/reconciliation.py`) which reach into
|
||||
`application.services.*` are unchanged in place; they're rewritten
|
||||
only to import the data shapes from `cleveractors.actor` instead of
|
||||
from `cleveragents.actor`.
|
||||
- `cleverrouter` can pin the library as a single git dependency and
|
||||
immediately have actor parse + compile.
|
||||
|
||||
### Negative
|
||||
|
||||
- The library duplicates a small set of types (`DetailLevelMap`,
|
||||
`LspBinding`, exception names). Drift between the two
|
||||
implementations must be policed; a future cleveragents-core release
|
||||
may want to re-export from the library to eliminate the
|
||||
duplication.
|
||||
- The extraction does not preserve git history (fresh-start
|
||||
bootstrap). Per-file blame for the extracted files is recoverable
|
||||
by following the link to cleveragents-core@`20ad9a46`.
|
||||
|
||||
### Considered alternatives
|
||||
|
||||
- **Narrow (actor + langgraph + templates only)**: rejected as
|
||||
insufficient — `lsp.LspBinding` and the UKO vocabularies are
|
||||
referenced from the actor schema, so omitting them would break the
|
||||
compile path or require Protocol shims for trivial data shapes.
|
||||
- **Wide (also extracting `tool`, `skill`, `resource` data models)**:
|
||||
rejected — would have required a substantial refactor of
|
||||
`application.services.*` in cleveragents-core, and cleverrouter's
|
||||
tool concept differs from cleveragents-core's, so a shared library
|
||||
is the wrong shape.
|
||||
- **No extraction; cleverrouter vendors cleveragents-core directly**:
|
||||
rejected — cleveragents-core's runtime footprint (rx, A2A SDK,
|
||||
database) is too large to install on the gateway's hot path.
|
||||
|
||||
## Status
|
||||
|
||||
Accepted 2026-05-17. The extraction is the only authorised path from
|
||||
cleveragents-core into cleverrouter and any other actor-consuming
|
||||
product.
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
adr_number: 2
|
||||
title: Tool Registry Protocol
|
||||
status_history:
|
||||
- - "2026-05-17"
|
||||
- Accepted
|
||||
- Jeffrey Phillips Freeman
|
||||
tier: 2
|
||||
authors:
|
||||
- Jeffrey Phillips Freeman
|
||||
related_adrs:
|
||||
- number: 1
|
||||
title: Library Boundary and Extraction Scope
|
||||
relationship: This ADR realises the "no-host-imports" invariant for the actor loader's optional tool-reference verification step.
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The cleveragents-core `ActorLoader` accepts an optional
|
||||
`tool_registry: ToolRegistry` parameter. At load time it walks
|
||||
each actor's `tools:` list and calls `tool_registry.get(name)`; any
|
||||
unknown reference becomes a warning on `loader.warnings`. This is a
|
||||
parse-time integrity check, not a runtime dispatch.
|
||||
|
||||
The concrete `ToolRegistry` class lives in
|
||||
`cleveragents.tool.registry`, which imports from
|
||||
`cleveragents.domain` and `cleveragents.infrastructure`. Bringing it
|
||||
into the library would drag along the entire tool-system runtime —
|
||||
exactly the kind of host-side concern this library is supposed to
|
||||
exclude.
|
||||
|
||||
CleverRouter has its own tool registry (`cleverrouter.tool.registry`)
|
||||
with a different shape. A library hardcoded to cleveragents-core's
|
||||
class wouldn't fit cleverrouter's data model.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce a Protocol at `cleveractors.ports.tool_registry.ToolRegistryPort`
|
||||
that captures exactly the surface `ActorLoader` needs:
|
||||
|
||||
```python
|
||||
@runtime_checkable
|
||||
class ToolRegistryPort(Protocol):
|
||||
def get(self, name: str) -> Any | None: ...
|
||||
```
|
||||
|
||||
The `ActorLoader` constructor takes `tool_registry:
|
||||
ToolRegistryPort | None`. Anything with a `.get(name) -> spec | None`
|
||||
method satisfies it structurally — cleveragents-core's existing
|
||||
`ToolRegistry` does, cleverrouter's does, and any third-party tool
|
||||
catalogue can adopt the same name without inheriting from a base
|
||||
class.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- Zero cleveragents-core imports anywhere in the library tree.
|
||||
- Structural typing means existing concrete classes work unchanged —
|
||||
no need to add an `@implements(ToolRegistryPort)` mixin in
|
||||
cleveragents-core.
|
||||
- `Any` return type intentionally underspecifies the spec shape:
|
||||
the loader only checks for `None`-ness. Different host
|
||||
applications will have different spec types and the library
|
||||
doesn't need to know.
|
||||
|
||||
### Negative
|
||||
|
||||
- The Protocol surface is unverified by type-checkers at the
|
||||
cleveragents-core boundary unless the host explicitly asserts
|
||||
`isinstance(my_registry, ToolRegistryPort)` (the
|
||||
`@runtime_checkable` decorator enables that). Practically this is
|
||||
a non-issue because the host has tests around its own registry.
|
||||
|
||||
### Considered alternatives
|
||||
|
||||
- **Abstract base class** that cleveragents-core's `ToolRegistry`
|
||||
inherits from: rejected because it forces upstream to depend on
|
||||
the library (the library is supposed to be a downstream
|
||||
dependency, not an upstream one).
|
||||
- **Generic `Callable[[str], Any | None]`**: rejected because
|
||||
expressing intent as a Protocol with a named method is clearer in
|
||||
function signatures than `tool_registry: Callable[..., ...] | None`.
|
||||
|
||||
## Status
|
||||
|
||||
Accepted 2026-05-17.
|
||||
@@ -0,0 +1,68 @@
|
||||
# CleverActors Core
|
||||
|
||||
**CleverActors** is the declarative-actor library used by
|
||||
[CleverAgents](https://git.cleverthis.com/cleveragents/cleveragents-core)
|
||||
and [CleverRouter](https://git.cleverthis.com/cleverrouter/cleverrouter).
|
||||
|
||||
It is the smallest possible Python module that lets a host application:
|
||||
|
||||
- Parse a CleverAgents v3 actor YAML file (with sandboxed Jinja2 + env-var
|
||||
preprocessing).
|
||||
- Validate it against the Pydantic schema.
|
||||
- Compile it into a LangGraph node + edge graph ready for execution.
|
||||
|
||||
The library carries **no** I/O concerns of its own — no databases, no HTTP,
|
||||
no CLI, no dependency-injection containers. Persistence, event publishing,
|
||||
and lifecycle management belong to whoever consumes it.
|
||||
|
||||
## Origin
|
||||
|
||||
Extracted from `cleveragents/cleveragents-core` at commit `20ad9a46` in 2026.
|
||||
The extraction preserves the customer-facing YAML format exactly; the host
|
||||
application sees the same `ActorConfigSchema` and `compile_actor()` surface
|
||||
as before, just at a different import path.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pip install "cleveractors @ git+https://git.cleverthis.com/cleveragents/cleveractors-core@master"
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```python
|
||||
from cleveractors.actor import compile_actor
|
||||
from cleveractors.actor.schema import ActorConfigSchema
|
||||
from cleveractors.actor.yaml_loader import load_yaml_text
|
||||
|
||||
raw = load_yaml_text(open("my-actor.yaml").read())
|
||||
config = ActorConfigSchema.model_validate(raw)
|
||||
compiled = compile_actor(config)
|
||||
print(compiled.metadata.node_ids)
|
||||
```
|
||||
|
||||
The compiled `GraphConfig` is the LangGraph spec — feed it to a runtime
|
||||
that understands `cleveractors.langgraph.nodes.Node` / `Edge` /
|
||||
`NodeConfig`, which the library also provides.
|
||||
|
||||
## What the library does NOT do
|
||||
|
||||
These belong to the host application (cleveragents-core, cleverrouter,
|
||||
your own integration) — not the library:
|
||||
|
||||
| Concern | Where it lives |
|
||||
|---------|----------------|
|
||||
| Actor persistence (DB) | `cleveragents.application.services.actor_service` |
|
||||
| Decision tree recording | `cleveragents.application.services.decision_service` |
|
||||
| Invariant reconciliation runtime | `cleveragents.actor.reconciliation` |
|
||||
| Reactive stream routing | `cleveragents.reactive` |
|
||||
| LangChain provider lookups | `cleveragents.providers.registry` (or supply via `cleveractors.ports.provider_registry`) |
|
||||
| LSP runtime | `cleveragents.lsp` (only the data models live here) |
|
||||
| Plan lifecycle (Action / Strategize / Execute / Apply) | `cleveragents.application` |
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
|
||||
See also [CleverAgents Operations Code (CONTRIBUTING)](https://git.cleverthis.com/cleveragents/cleveragents-core/src/branch/master/CONTRIBUTING.md)
|
||||
for commit, PR, and testing conventions.
|
||||
@@ -0,0 +1,148 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
## 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 |
|
||||
|
||||
## 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}` |
|
||||
|
||||
## 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)
|
||||
|
||||
## 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),
|
||||
))
|
||||
```
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
site_name: CleverActors
|
||||
site_description: Declarative actor parser, validator, and LangGraph compiler — pure Python library extracted from cleveragents-core.
|
||||
site_url: https://docs.cleverthis.com/cleveractors
|
||||
repo_url: https://git.cleverthis.com/cleveragents/cleveractors-core
|
||||
repo_name: cleveragents/cleveractors-core
|
||||
edit_uri: src/branch/master/docs
|
||||
|
||||
theme:
|
||||
name: material
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.sections
|
||||
- navigation.indexes
|
||||
- toc.follow
|
||||
- content.code.copy
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Specification: specification.md
|
||||
- ADRs:
|
||||
- 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
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences
|
||||
- tables
|
||||
- toc:
|
||||
permalink: true
|
||||
Reference in New Issue
Block a user