526 lines
16 KiB
Markdown
526 lines
16 KiB
Markdown
# 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.0–1.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 |
|