# ACP (Agent Communication Protocol) Reference ## Overview The ACP package provides the boundary layer between the CleverAgents application core and any external orchestrator or UI. It defines a request/response envelope, a set of named operations, and an event streaming interface. **Module:** `cleveragents.acp` **ACP Version:** 1.0 ## Table of Contents - [Modes of Operation](#modes-of-operation) - [Local Facade](#local-facade) - [Service Wiring](#service-wiring) - [Operation Routing Table](#operation-routing-table) - [Error Code Taxonomy](#error-code-taxonomy) - [Server Transport Stub](#server-transport-stub) - [Event Queue](#event-queue) - [Version Negotiation](#version-negotiation) - [Models](#models) - [Error Hierarchy](#error-hierarchy) --- ## Modes of Operation | Mode | Class | Behaviour | |--------|--------------------|--------------------------------------------------| | Local | `AcpLocalFacade` | Routes operations to in-process service calls | | Server | `AcpHttpTransport` | Stub — raises `AcpNotAvailableError` on all ops | In local mode the facade translates each ACP operation into a direct Python method call. No serialization, no network, no authentication overhead. --- ## Local Facade ```python from cleveragents.acp import AcpLocalFacade, AcpRequest facade = AcpLocalFacade() response = facade.dispatch(AcpRequest(operation="session.create")) assert response.status == "ok" ``` ### Constructor | Parameter | Type | Default | Description | |-----------|----------------------------|---------|-------------------------------| | `services` | `dict[str, Any] \| None` | `None` | Named services for routing | ### Methods | Method | Returns | Description | |---------------------|-------------------|--------------------------------------| | `dispatch(request)` | `AcpResponse` | Route request to handler | | `register_service` | `None` | Register a named service | | `list_operations` | `list[str]` | All supported operation names | --- ## Service Wiring Each ACP operation is wired to a concrete application service. Services are injected via the `services` dict at construction time or registered later with `register_service()`. When a service key is absent, the handler falls back to a safe stub response. ### Service Keys | Key | Type | Wired Operations | |------------------------------|-----------------------------|-------------------------------| | `session_service` | `SessionService` | `session.create`, `session.close` | | `plan_lifecycle_service` | `PlanLifecycleService` | `plan.create`, `plan.execute`, `plan.status`, `plan.diff`, `plan.apply` | | `tool_registry` | `ToolRegistry` | `registry.list_tools` | | `resource_registry_service` | `ResourceRegistryService` | `registry.list_resources` | | `event_queue` | `AcpEventQueue` | `event.subscribe` | ### Wired Operation Details | Operation | Service | Service Method | Required Params | |------------------------|---------------------------|-------------------------|------------------------------------| | `session.create` | `SessionService` | `create(actor_name=…)` | `actor_name` (optional) | | `session.close` | `SessionService` | `delete(session_id)` | `session_id` | | `plan.create` | `PlanLifecycleService` | `use_action(…)` | `action_name` | | `plan.execute` | `PlanLifecycleService` | `execute_plan(plan_id)` | `plan_id` | | `plan.status` | `PlanLifecycleService` | `get_plan(plan_id)` | `plan_id` | | `plan.diff` | `PlanLifecycleService` | `get_plan(plan_id)` | `plan_id` | | `plan.apply` | `PlanLifecycleService` | `apply_plan(plan_id)` | `plan_id` | | `registry.list_tools` | `ToolRegistry` | `list_tools(namespace=…)` | `namespace` (optional) | | `registry.list_resources` | `ResourceRegistryService` | `list_resources(type_name=…)` | `type_name` (optional) | | `context.get` | *(stub)* | N/A | — | | `event.subscribe` | `AcpEventQueue` | `subscribe_local(cb)` | — | !!! note "context.get" `context.get` currently returns a stub response (`{"context": {}, "stub": true}`) pending the completion of the ACMS `ContextAssemblyPipeline`. ### Example: Wired Facade ```python from cleveragents.acp.facade import AcpLocalFacade from cleveragents.acp.events import AcpEventQueue from cleveragents.acp.models import AcpRequest facade = AcpLocalFacade(services={ "session_service": my_session_service, "plan_lifecycle_service": my_plan_lifecycle_service, "tool_registry": my_tool_registry, "resource_registry_service": my_resource_registry_service, "event_queue": AcpEventQueue(), }) response = facade.dispatch( AcpRequest(operation="plan.status", params={"plan_id": "01J…"}) ) assert response.status == "ok" assert "phase" in response.data ``` --- ## Operation Routing Table | Operation | Response Keys | |------------------------|--------------------------------------------------| | `session.create` | `session_id`, `status` | | `session.close` | `status` | | `plan.create` | `plan_id`, `status` | | `plan.execute` | `plan_id`, `status` | | `plan.status` | `plan_id`, `phase`, `state` | | `plan.diff` | `plan_id`, `changes`, `phase` | | `plan.apply` | `plan_id`, `status` | | `registry.list_tools` | `tools` (list of `{name, description}`) | | `registry.list_resources` | `resources` (list of `{resource_id, name, type_name}`) | | `context.get` | `context`, `stub`, `message` | | `event.subscribe` | `subscription_id`, `status` | Unknown operations raise `AcpOperationNotFoundError`. --- ## Error Code Taxonomy Domain exceptions are mapped to ACP error codes via `map_domain_error()`: | ACP Error Code | Domain Exception(s) | Meaning | |-----------------------|----------------------------------------|----------------------------------| | `NOT_FOUND` | `ResourceNotFoundError` | Requested entity does not exist | | `VALIDATION_ERROR` | `ValidationError` | Input failed validation | | `PLAN_ERROR` | `PlanError` | Plan lifecycle fault | | `INVALID_STATE` | `BusinessRuleViolation` | State precondition not met | | `AUTH_ERROR` | `AuthenticationError` | Authentication failure | | `FORBIDDEN` | `AuthorizationError` | Insufficient permissions | | `CONFIGURATION_ERROR` | `ConfigurationError` | Configuration missing or invalid | | `INTERNAL_ERROR` | Any other `CleverAgentsError` or `Exception` | Unexpected server error | Error details are returned in the `AcpResponse.error` field as an `AcpErrorDetail` with the `code` and `message` fields populated. --- ## Server Transport Stub All methods on `AcpHttpTransport` raise `AcpNotAvailableError`: | Method | Description | |-----------------|------------------------------------------| | `send(request)` | Would send request over HTTP | | `connect(url)` | Would open HTTP connection | | `disconnect()` | Would close connection | | `is_connected()`| Returns `False` (does not raise) | --- ## Event Queue `AcpEventQueue` provides an in-memory event queue for local mode: | Method | Mode | Description | |-----------------------|--------|------------------------------------| | `publish(event)` | Local | Append event and notify callbacks | | `subscribe_local(cb)` | Local | Register callback, return sub ID | | `unsubscribe(id)` | Local | Remove subscription | | `get_events(limit)` | Local | Return recent events | | `subscribe_remote(ep)`| Server | Raises `AcpNotAvailableError` | --- ## Version Negotiation `AcpVersionNegotiator` validates protocol version compatibility: ```python from cleveragents.acp import AcpVersionNegotiator negotiator = AcpVersionNegotiator() version = negotiator.negotiate("1.0") # returns "1.0" negotiator.negotiate("2.0") # raises AcpVersionMismatchError ``` Supported versions: `["1.0"]` --- ## Models ### AcpRequest | Field | Type | Default | |---------------|----------------------------|-------------------| | `acp_version` | `str` | `"1.0"` | | `request_id` | `str` | Auto-generated ULID | | `operation` | `str` | *required* | | `params` | `dict[str, Any]` | `{}` | | `auth` | `dict[str, Any] \| None` | `None` | ### AcpResponse | Field | Type | Default | |---------------|------------------------------|-------------------| | `acp_version` | `str` | `"1.0"` | | `request_id` | `str` | *required* | | `status` | `str` (`"ok"` or `"error"`) | *required* | | `data` | `dict[str, Any]` | `{}` | | `error` | `AcpErrorDetail \| None` | `None` | | `timing_ms` | `float \| None` | `None` | ### AcpErrorDetail | Field | Type | Default | |-----------|-------------------|---------| | `code` | `str` | *required* | | `message` | `str` | *required* | | `details` | `dict[str, Any]` | `{}` | ### AcpEvent | Field | Type | Default | |--------------|---------------------------|------------------------| | `event_id` | `str` | Auto-generated ULID | | `event_type` | `str` | *required* | | `plan_id` | `str \| None` | `None` | | `data` | `dict[str, Any]` | `{}` | | `timestamp` | `str` | Auto-generated ISO UTC | --- ## Error Hierarchy ``` CleverAgentsError └── AcpError ├── AcpNotAvailableError ├── AcpVersionMismatchError └── AcpOperationNotFoundError ``` | Exception | When Raised | |-----------------------------|-------------------------------------------| | `AcpNotAvailableError` | Server-mode operation in local mode | | `AcpVersionMismatchError` | Unsupported ACP version requested | | `AcpOperationNotFoundError` | Unknown operation dispatched |