Files
cleveragents-core/docs/reference/acp.md
T
freemo 4b9df961f0 feat(acp): wire ACP local facade handlers to live services
Wire all AcpLocalFacade operation handlers to their corresponding
application services via constructor-injected service dependencies:

- session.create/close delegate to SessionService
- plan.create/execute/status/diff/apply delegate to PlanLifecycleService
- registry.list_tools delegates to ToolRegistry
- registry.list_resources delegates to ResourceRegistryService
- event.subscribe delegates to AcpEventQueue
- context.get returns stub pending ACMS ContextAssemblyPipeline

Add domain-to-ACP error code mapping via map_domain_error() translating
ResourceNotFoundError to NOT_FOUND, ValidationError to VALIDATION_ERROR,
PlanError to PLAN_ERROR, BusinessRuleViolation to INVALID_STATE, and
other domain exceptions to their corresponding ACP error codes.

Handlers gracefully fall back to stub responses when services are absent.

Includes 21 Behave scenarios (features/acp_facade_wiring.feature) and
9 Robot Framework integration tests (robot/acp_facade_wiring.robot).
Updated docs/reference/acp.md with wired operation details, service
key table, and error code taxonomy.

ISSUES CLOSED: #501
2026-03-02 22:45:55 -05:00

269 lines
12 KiB
Markdown

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