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

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:
freemo
2026-05-17 00:27:53 +00:00
parent 6915498ff8
commit d2d26f1780
5 changed files with 433 additions and 0 deletions
@@ -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.
+68
View File
@@ -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.
+148
View File
@@ -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
View File
@@ -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