docs: add API documentation (CLI reference, Python API, protocols) [AUTO-DOCS-2]
CI / push-validation (pull_request) Successful in 16s
CI / build (pull_request) Successful in 24s
CI / helm (pull_request) Successful in 28s
CI / quality (pull_request) Successful in 32s
CI / lint (pull_request) Successful in 34s
CI / typecheck (pull_request) Successful in 48s
CI / security (pull_request) Successful in 59s
CI / e2e_tests (pull_request) Successful in 3m3s
CI / integration_tests (pull_request) Successful in 3m54s
CI / unit_tests (pull_request) Successful in 5m0s
CI / docker (pull_request) Successful in 11s
CI / coverage (pull_request) Successful in 10m50s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 57m42s

This commit is contained in:
2026-04-13 05:09:31 +00:00
parent 2005b8ef82
commit 236d1abd80
5 changed files with 1824 additions and 0 deletions
+525
View File
@@ -0,0 +1,525 @@
# Python API — Application Layer
This page documents the **application layer** of the CleverAgents Python package:
application services, the dependency injection (DI) container, domain models,
and key protocols. For module-level API documentation (exceptions, registries,
adapters) see the [Module Index](index.md).
See [ADR-001](../adr/ADR-001-layered-architecture.md) (layered architecture) and
[ADR-003](../adr/ADR-003-dependency-injection.md) (dependency injection) for
design rationale.
---
## Architecture Overview
CleverAgents follows a strict layered architecture:
```
Entry Points (CLI / TUI / A2A server)
Application Layer ← this page
Domain Layer (models, value objects, domain services)
Infrastructure (database, file system, external services)
Integration (LangChain/LangGraph, MCP, LSP adapters)
Core (exceptions, retry, circuit breaker, async cleanup)
```
All cross-layer dependencies flow **downward only**. The DI container wires
everything together at startup.
---
## Dependency Injection Container
**Module:** `cleveragents.application.container`
CleverAgents uses [dependency-injector](https://python-dependency-injector.ets-labs.org/)
for IoC. The `CleverAgentsContainer` is the root container; all application
services are registered as providers and resolved lazily.
```python
from cleveragents.application.container import CleverAgentsContainer
container = CleverAgentsContainer()
container.config.from_dict({"provider": "openai", ...})
container.wire(modules=[__name__])
# Resolve services
plan_service = container.plan_service()
registry_service = container.registry_service()
session_service = container.session_service()
invariant_service = container.invariant_service()
```
### Key Providers
| Provider | Type | Description |
|----------|------|-------------|
| `container.settings` | `Singleton` | Application `Settings` instance |
| `container.event_bus` | `Singleton` | In-process event bus |
| `container.plan_service` | `Singleton` | `PlanLifecycleService` |
| `container.registry_service` | `Singleton` | `RegistryService` |
| `container.session_service` | `Singleton` | `SessionService` |
| `container.invariant_service` | `Singleton` | `InvariantService` |
| `container.actor_registry` | `Singleton` | `ActorRegistry` |
| `container.tool_registry` | `Singleton` | `ToolRegistry` |
| `container.skill_registry` | `Singleton` | `SkillRegistry` |
| `container.provider_registry` | `Singleton` | `ProviderRegistry` |
| `container.mcp_registry` | `Singleton` | `McpRegistry` |
| `container.lsp_registry` | `Singleton` | `LSPRegistry` |
| `container.resource_registry` | `Singleton` | `ResourceRegistry` |
| `container.a2a_facade` | `Singleton` | `A2aLocalFacade` |
> **Tip:** Always obtain services via the container rather than constructing
> them directly. This ensures correct lifecycle management and dependency
> resolution.
---
## PlanLifecycleService
**Module:** `cleveragents.application.services.plan_service`
The central application service for the plan lifecycle. Orchestrates the
four phases (Action → Strategize → Execute → Apply) and coordinates actors,
tools, resources, and invariants.
```python
from cleveragents.application.services.plan_service import PlanLifecycleService
service: PlanLifecycleService = container.plan_service()
# Create a plan from an action
plan = await service.create_plan(
action_name="local/refactor",
project_names=["my-project"],
args={"target": "src/"},
automation_profile="review",
)
# Execute the plan (Strategize + Execute phases)
result = await service.execute_plan(plan.plan_id)
# Apply the sandbox changeset
await service.apply_plan(plan.plan_id)
```
### Key Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `create_plan(action_name, project_names, args, ...)` | `Plan` | Instantiate a plan from an action |
| `execute_plan(plan_id)` | `PlanExecutionResult` | Run Strategize and Execute phases |
| `apply_plan(plan_id)` | `PlanApplyResult` | Merge sandbox changeset into real resources |
| `get_plan(plan_id)` | `Plan` | Retrieve a plan by ID |
| `list_plans(filters)` | `list[Plan]` | List plans with optional filters |
| `cancel_plan(plan_id, reason)` | `Plan` | Cancel a running plan |
| `rollback_plan(plan_id, checkpoint_id)` | `Plan` | Roll back to a checkpoint |
| `get_plan_diff(plan_id)` | `PlanDiff` | Get the sandbox diff for a plan |
| `correct_decision(decision_id, mode, guidance)` | `CorrectionAttempt` | Correct a decision and recompute |
| `get_decision_tree(plan_id)` | `DecisionTree` | Retrieve the full decision tree |
---
## RegistryService
**Module:** `cleveragents.application.services.registry_service`
Unified CRUD service for all registry entities: actions, actors, skills,
tools, resources, projects, LSP servers, and automation profiles.
```python
from cleveragents.application.services.registry_service import RegistryService
service: RegistryService = container.registry_service()
# Register an action from a YAML file
action = await service.register_action(Path("examples/actions/refactor.yaml"))
# List all tools
tools = await service.list_tools(namespace="local", source="mcp")
# Register a resource
resource = await service.register_resource(
type_name="git-checkout",
name="local/my-repo",
args={"url": "https://github.com/org/repo.git"},
)
```
### Key Methods
| Method | Description |
|--------|-------------|
| `register_action(path, update=False)` | Register or update an action from YAML |
| `list_actions(namespace, state, regex)` | List actions |
| `get_action(name)` | Get an action by name |
| `archive_action(name)` | Archive an action |
| `register_actor(path, update=False)` | Register or update an actor from YAML |
| `list_actors()` | List all actors |
| `get_actor(name)` | Get an actor by name |
| `register_skill(path, update=False)` | Register or update a skill from YAML |
| `list_skills(namespace, source)` | List skills |
| `register_tool(path, update=False)` | Register or update a tool from YAML |
| `list_tools(namespace, source, type)` | List tools |
| `register_resource(type_name, name, args)` | Register a resource |
| `list_resources(type, include_stopped)` | List resources |
| `get_resource(name)` | Get a resource by name |
| `create_project(name, description, resources, invariants)` | Create a project |
| `list_projects(namespace, regex)` | List projects |
| `link_resource_to_project(project, resource, read_only)` | Link a resource to a project |
---
## SessionService
**Module:** `cleveragents.application.services.session_service`
Manages conversation sessions — creation, retrieval, message history,
export, and import.
```python
from cleveragents.application.services.session_service import SessionService
service: SessionService = container.session_service()
# Create a session
session = await service.create_session(actor="openai/gpt-4o")
# Send a message
response = await service.send_message(
session_id=session.session_id,
content="What is the current plan status?",
)
# Export as JSON
export_data = await service.export_session(session.session_id)
# Export as Markdown transcript
md = await service.export_session_markdown(session.session_id)
```
### Key Methods
| Method | Description |
|--------|-------------|
| `create_session(actor)` | Create a new session |
| `get_session(session_id)` | Retrieve a session |
| `list_sessions()` | List all sessions |
| `delete_session(session_id)` | Delete a session |
| `send_message(session_id, content, stream)` | Send a message and get a response |
| `export_session(session_id)` | Export session as a JSON-serializable dict |
| `export_session_markdown(session_id)` | Export session as a Markdown transcript |
| `import_session(data)` | Import a session from a previously exported dict |
---
## InvariantService
**Module:** `cleveragents.application.services.invariant_service`
See [`cleveragents.core` — Invariant Service](core.md#invariant-service) for
the full API reference. The service is registered as a Singleton in the DI
container:
```python
invariant_service = container.invariant_service()
```
---
## Domain Models
**Module:** `cleveragents.domain.models`
All domain models inherit from `DomainBaseModel` (see [Core Utilities](core.md#domain-base-model)).
### Plan
```python
from cleveragents.domain.models.core.plan import Plan, PlanPhase, PlanState
class Plan(DomainBaseModel):
plan_id: str # ULID
name: str | None # namespaced name (top-level plans only)
action_name: str # action this plan was instantiated from
project_names: list[str] # bound projects
phase: PlanPhase # Action | Strategize | Execute | Apply
state: PlanState # running | applied | errored | cancelled | ...
automation_profile: str
created_at: datetime
updated_at: datetime
parent_plan_id: str | None
```
**`PlanPhase`** enum: `ACTION`, `STRATEGIZE`, `EXECUTE`, `APPLY`
**`PlanState`** enum: `RUNNING`, `APPLIED`, `CONSTRAINED`, `ERRORED`, `CANCELLED`
### Action
```python
from cleveragents.domain.models.core.action import Action
class Action(DomainBaseModel):
name: str # namespaced name
description: str
definition_of_done: str
strategy_actor: str | None
execution_actor: str | None
estimation_actor: str | None
invariant_actor: str | None
args: list[ActionArgument]
invariants: list[str]
state: str # "active" | "archived"
```
### Decision
```python
from cleveragents.domain.models.core.decision import Decision, DecisionType
class Decision(DomainBaseModel):
decision_id: str # ULID
plan_id: str
type: DecisionType
question: str
chosen_option: str
alternatives: list[str]
confidence: float # 0.01.0
rationale: str
phase: PlanPhase
created_at: datetime
superseded_by: str | None
```
**`DecisionType`** enum: `PROMPT_DEFINITION`, `INVARIANT_ENFORCED`,
`STRATEGY_CHOICE`, `SUBPLAN_SPAWN`, `SUBPLAN_PARALLEL_SPAWN`, …
### Resource
```python
from cleveragents.domain.models.core.resource import Resource
class Resource(DomainBaseModel):
resource_id: str # ULID
name: str # namespaced name
type_name: str # e.g. "git-checkout", "sqlite"
description: str | None
is_physical: bool
parent_ids: list[str]
child_ids: list[str]
state: str # "active" | "stopped"
created_at: datetime
```
### Project
```python
from cleveragents.domain.models.core.project import Project
class Project(DomainBaseModel):
name: str # namespaced name (sole identifier — no ULID)
description: str | None
resource_links: list[ResourceLink]
invariants: list[str]
context_policy: ContextPolicy | None
created_at: datetime
```
### Session
```python
from cleveragents.domain.models.core.session import Session
class Session(DomainBaseModel):
session_id: str
actor: str
messages: list[Message]
linked_plan_ids: list[str]
created_at: datetime
updated_at: datetime
def as_export_markdown(self) -> str:
"""Render a human-readable Markdown transcript (lossy, not importable)."""
```
### Invariant
```python
from cleveragents.domain.models.core.invariant import Invariant, InvariantScope
class Invariant(DomainBaseModel):
invariant_id: str # ULID
text: str
scope: InvariantScope # GLOBAL | PROJECT | ACTION | PLAN
source_name: str # project/action/plan name, or "global"
active: bool
non_overridable: bool # global invariants only
created_at: datetime
```
**`InvariantScope`** enum: `GLOBAL`, `PROJECT`, `ACTION`, `PLAN`
---
## Key Protocols and Interfaces
### `AIProviderInterface`
**Module:** `cleveragents.providers.interface`
Protocol that all AI provider implementations must satisfy.
```python
class AIProviderInterface(Protocol):
@property
def name(self) -> str: ...
@property
def model_id(self) -> str: ...
async def generate(
self,
messages: list[Message],
tools: list[ToolSpec] | None = None,
stream: bool = False,
) -> GenerationResult: ...
```
### `ResourceHandler`
**Module:** `cleveragents.resource.handlers.base`
Protocol for resource handler implementations.
```python
class ResourceHandler(Protocol):
async def read(self, resource_id: str, context: ...) -> Any: ...
async def write(self, resource_id: str, data: Any, context: ...) -> None: ...
async def delete(self, resource_id: str, context: ...) -> None: ...
async def list_children(self, resource_id: str, context: ...) -> list[str]: ...
async def diff(self, resource_id: str, other_id: str, context: ...) -> str: ...
async def checkpoint(self, resource_id: str) -> str: ...
async def rollback(self, resource_id: str, checkpoint_id: str) -> None: ...
async def create_sandbox(self, resource_id: str) -> "Sandbox": ...
```
### `LLMCaller`
**Module:** `cleveragents.tool`
Protocol for calling an LLM. Implement this to plug in a custom provider
into the tool-calling runtime.
```python
class LLMCaller(Protocol):
async def call(
self,
messages: list[Message],
tools: list[ToolSpec],
) -> LLMResponse: ...
```
---
## End-to-End Example
The following example shows how to use the Python API to create and execute
a plan programmatically:
```python
import asyncio
from cleveragents.application.container import CleverAgentsContainer
async def main():
# Bootstrap the container
container = CleverAgentsContainer()
container.config.from_env("CLEVERAGENTS_")
plan_service = container.plan_service()
registry_service = container.registry_service()
# Register a resource
await registry_service.register_resource(
type_name="git-checkout",
name="local/my-repo",
args={"url": "https://github.com/org/repo.git", "path": "/workspace/repo"},
)
# Create a project
await registry_service.create_project(
name="my-project",
description="Main application project",
resources=["local/my-repo"],
)
# Instantiate a plan
plan = await plan_service.create_plan(
action_name="local/refactor",
project_names=["my-project"],
args={"target": "src/"},
automation_profile="review",
)
print(f"Plan created: {plan.plan_id}")
# Execute the plan
result = await plan_service.execute_plan(plan.plan_id)
print(f"Execution result: {result.state}")
# Review the decision tree
tree = await plan_service.get_decision_tree(plan.plan_id)
for decision in tree.decisions:
print(f" [{decision.type}] {decision.question[:60]}")
# Apply if satisfied
await plan_service.apply_plan(plan.plan_id)
print("Plan applied successfully.")
asyncio.run(main())
```
---
## Event Bus
**Module:** `cleveragents.application.events`
The in-process event bus enables loose coupling between application services.
Services emit events; subscribers react without direct dependencies.
```python
from cleveragents.application.events import EventBus, EventType
bus: EventBus = container.event_bus()
# Subscribe to plan phase transitions
@bus.on(EventType.PLAN_PHASE_CHANGED)
async def on_phase_change(event):
print(f"Plan {event.plan_id} moved to {event.new_phase}")
# Emit an event (done internally by services)
await bus.emit(EventType.PLAN_PHASE_CHANGED, plan_id="01JXYZ...", new_phase="execute")
```
### Key Event Types
| Event | Description |
|-------|-------------|
| `PLAN_CREATED` | A new plan was instantiated |
| `PLAN_PHASE_CHANGED` | A plan advanced to a new phase |
| `PLAN_APPLIED` | A plan was successfully applied |
| `PLAN_CANCELLED` | A plan was cancelled |
| `PLAN_ERRORED` | A plan encountered an unrecoverable error |
| `DECISION_RECORDED` | A new decision was added to the decision tree |
| `INVARIANT_VIOLATED` | An invariant was not satisfied |
| `INVARIANT_ENFORCED` | An invariant enforcement record was created |
| `INVARIANT_RECONCILED` | Invariant reconciliation completed for a phase |
| `TOOL_EXECUTED` | A tool invocation completed |
| `SESSION_CREATED` | A new session was created |
| `SESSION_MESSAGE_ADDED` | A message was added to a session |