# 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 |