forked from cleveragents/cleveragents-core
4b9df961f0
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
269 lines
12 KiB
Markdown
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 |
|