- Add docs/api/a2a-protocol.md with JSON-RPC 2.0 wire format, facade session lifecycle, event queue publish/subscribe, stdio and HTTP transport modes, extension methods (_cleveragents/ namespace), standard A2A operations, Agent Card discovery, and CLI commands - Add docs/api/guards.md with guard enforcement documentation covering denylist, allowlist, budget caps, tool call limits, write/apply approval gates, AutonomyGuardrailService, budget hierarchy, and GuardrailAuditTrail - Add docs/api/automation-profiles.md with profile resolution precedence (plan > action > project > global), eight built-in profiles, confidence threshold semantics, safety profile sub-model, Python API, and CLI commands - Update mkdocs.yml navigation to include new API docs - Update CHANGELOG.md with documentation entries Refs: #9010
19 KiB
A2A Protocol — Agent-to-Agent Communication
Version: v3.5.0 / v3.8.0 (JSON-RPC 2.0 wire format) ADRs: ADR-026 · ADR-047
The Agent-to-Agent (A2A) Protocol is the sole communication boundary between all CleverAgents clients (CLI, TUI, IDE plugin) and the Application-layer services. Every operation — from creating a session to executing a plan — flows through A2A. No Presentation-layer module is permitted to bypass A2A and access Domain or Infrastructure services directly.
CleverAgents adopts the external A2A open standard (Linux
Foundation, Apache 2.0) built on JSON-RPC 2.0, with platform-specific operations
exposed via the _cleveragents/ extension namespace.
Overview
+------------------------------------------------------------------+
| Presentation Layer |
| CLI · TUI · IDE Plugin |
+--------------------------------+---------------------------------+
| A2A (JSON-RPC 2.0)
| stdio (local) or HTTP (server)
+--------------------------------v---------------------------------+
| A2A Boundary |
| A2aLocalFacade (local) · A2A SDK HTTP transport (server) |
+--------------------------------+---------------------------------+
|
+--------------------------------v---------------------------------+
| Application Layer |
| SessionService · PlanLifecycleService · ToolRegistry · ... |
+------------------------------------------------------------------+
JSON-RPC 2.0 Wire Format
All A2A messages use the JSON-RPC 2.0 envelope.
Request
{
"jsonrpc": "2.0",
"id": "01HXRCF1ABCDE12345678901AB",
"method": "_cleveragents/plan/status",
"params": {
"plan_id": "01HXRCF1ABCDE12345678901AB"
}
}
| Field | Type | Description |
|---|---|---|
jsonrpc |
"2.0" |
Always the literal string "2.0" |
id |
string |
Request correlation ID (ULID recommended); auto-generated if omitted |
method |
string |
Operation name — standard A2A or _cleveragents/ extension |
params |
object |
Operation parameters (may be empty {}) |
Successful Response
{
"jsonrpc": "2.0",
"id": "01HXRCF1ABCDE12345678901AB",
"result": {
"plan_id": "01HXRCF1ABCDE12345678901AB",
"phase": "execute",
"state": "running"
}
}
Error Response
{
"jsonrpc": "2.0",
"id": "01HXRCF1ABCDE12345678901AB",
"error": {
"code": -32001,
"message": "Plan not found",
"data": { "plan_id": "01HXRCF1ABCDE12345678901AB" }
}
}
result and error are mutually exclusive — exactly one must be present.
Error Codes
| Code | Meaning | Domain Exception |
|---|---|---|
-32700 |
Parse error (malformed JSON) | — |
-32600 |
Invalid request | — |
-32601 |
Method not found | A2aOperationNotFoundError |
-32602 |
Invalid params | ValidationError |
-32603 |
Internal error | Any unhandled Exception |
-32001 |
Entity not found | ResourceNotFoundError |
-32002 |
Authentication required | AuthenticationError |
-32003 |
Authorization forbidden | AuthorizationError |
-32004 |
Invalid state | BusinessRuleViolation |
-32005 |
Already exists | DuplicateEntityError |
-32006 |
Budget exceeded | BudgetExceededError |
-32007 |
Version mismatch | A2aVersionMismatchError |
-32008 |
Plan error | PlanError |
Application-specific codes use the range -32001 to -32099.
Python Models
from cleveragents.a2a.models import A2aRequest, A2aResponse, A2aErrorDetail, A2aEvent
A2aRequest
class A2aRequest(BaseModel):
jsonrpc: str = "2.0" # Always "2.0"; validated
id: str = "" # Auto-generated ULID if empty
method: str # Required; must not be blank
params: dict[str, Any] = {}
A2aResponse
class A2aResponse(BaseModel):
jsonrpc: str = "2.0"
id: str # Echoes the request id
result: dict[str, Any] | None = None # Success path
error: A2aErrorDetail | None = None # Error path
# Exactly one of result/error must be set (enforced by validator)
A2aErrorDetail
class A2aErrorDetail(BaseModel):
code: int # JSON-RPC 2.0 integer error code
message: str # Human-readable description
data: dict[str, Any] = {} # Optional structured context
A2aEvent
class A2aEvent(BaseModel):
event_id: str # Auto-generated ULID
event_type: str # e.g. "TaskStatusUpdateEvent"
plan_id: str | None = None
data: dict[str, Any] = {}
timestamp: str # ISO-8601 UTC; auto-set
Breaking change (v3.7.0+): Fields were renamed to comply with JSON-RPC 2.0.
a2a_version→jsonrpc,request_id→id,operation→method,data(success) →result. See CHANGELOG.
Transport Modes
A2A is transport-agnostic. CleverAgents supports two transports with identical operational semantics.
Local Mode — JSON-RPC 2.0 over stdio
The client spawns the agent as a subprocess. JSON-RPC messages flow over
stdin/stdout. The A2aLocalFacade routes operations to in-process
Application-layer services. No network, no authentication.
CLI Process --stdin/stdout--> Agent Subprocess
|
+-- A2aLocalFacade --> ServiceFacade
from cleveragents.a2a import A2aLocalFacade, A2aRequest
facade = A2aLocalFacade(services={
"session_service": session_svc,
"plan_lifecycle_service": plan_svc,
"tool_registry": tool_registry,
"event_queue": event_queue,
})
response = facade.dispatch(
A2aRequest(method="session.create", params={"actor_name": "openai/gpt-4o"})
)
session_id = response.result["session_id"]
Server Mode — JSON-RPC 2.0 over HTTP
The client connects to the CleverAgents server via the A2A SDK HTTP transport. Authentication uses the security schemes declared in the Agent Card.
CLI --HTTP--> CleverAgents A2A Server --> ServiceFacade
The A2A-Version HTTP header communicates the protocol version. The Agent Card
at /.well-known/agent.json advertises supported security schemes (OAuth2, API
key, HTTP bearer).
A2aLocalFacade — Session Lifecycle
The A2aLocalFacade is the primary entry point in local mode.
Constructor
from cleveragents.a2a.facade import A2aLocalFacade
facade = A2aLocalFacade(services={
"session_service": session_service, # SessionService
"plan_lifecycle_service": plan_lifecycle_service, # PlanLifecycleService
"tool_registry": tool_registry, # ToolRegistry
"resource_registry_service": resource_registry_svc, # ResourceRegistryService
"event_queue": event_queue, # A2aEventQueue
})
All service keys are optional. Missing services return safe stub responses so the facade never crashes due to incomplete wiring.
Session Create
response = facade.dispatch(A2aRequest(
method="session.create",
params={"actor_name": "openai/gpt-4o"}
))
# response.result == {"session_id": "01HXRCF1...", "status": "created"}
If session_id is already supplied in params, the call is idempotent and
returns the existing ID without creating a duplicate (fixes Forgejo #1141).
Session Close
response = facade.dispatch(A2aRequest(
method="session.close",
params={"session_id": "01HXRCF1..."}
))
# response.result == {"status": "closed"}
Session close automatically stops any active devcontainers associated with the session (best-effort; failures are logged but never propagated).
Plan Lifecycle via Extension Methods
# Create (use) a plan
response = facade.dispatch(A2aRequest(
method="_cleveragents/plan/use",
params={"action_name": "refactor-auth", "arguments": {"target": "src/auth"}}
))
plan_id = response.result["plan_id"]
# Execute the plan
response = facade.dispatch(A2aRequest(
method="_cleveragents/plan/execute",
params={"plan_id": plan_id}
))
# Check status
response = facade.dispatch(A2aRequest(
method="_cleveragents/plan/status",
params={"plan_id": plan_id}
))
# response.result == {"plan_id": "...", "phase": "execute", "state": "running"}
# Apply the plan
response = facade.dispatch(A2aRequest(
method="_cleveragents/plan/apply",
params={"plan_id": plan_id}
))
Extension Methods (_cleveragents/)
CleverAgents platform operations use the _cleveragents/ namespace per the A2A
extensibility mechanism (declared in the Agent Card under
urn:cleveragents:extensions:v1).
Plan Lifecycle
| Method | Required Params | Description |
|---|---|---|
_cleveragents/plan/use |
action_name |
Create a plan from an action template |
_cleveragents/plan/execute |
plan_id |
Transition plan to Execute phase |
_cleveragents/plan/apply |
plan_id |
Transition plan to Apply phase |
_cleveragents/plan/cancel |
plan_id |
Cancel a running plan |
_cleveragents/plan/status |
plan_id |
Query plan phase and state |
_cleveragents/plan/tree |
plan_id |
Retrieve the decision tree |
_cleveragents/plan/explain |
plan_id, decision_id |
Explain a decision |
_cleveragents/plan/correct |
plan_id |
Inject a correction |
_cleveragents/plan/diff |
plan_id |
Retrieve plan diff |
_cleveragents/plan/artifacts |
plan_id |
List plan artifacts |
_cleveragents/plan/prompt |
plan_id, guidance |
Inject guidance into execution |
_cleveragents/plan/rollback |
plan_id |
Roll back plan changes |
_cleveragents/plan/list |
(none) | List all plans |
Registry
| Method | Optional Params | Description |
|---|---|---|
_cleveragents/registry/tool/list |
namespace |
List available tools |
_cleveragents/registry/resource/list |
type_name |
List registered resources |
_cleveragents/registry/actor/list |
— | List registered actors |
_cleveragents/registry/skill/list |
— | List registered skills |
_cleveragents/registry/action/list |
— | List registered actions |
_cleveragents/registry/project/list |
— | List registered projects |
Context
| Method | Description |
|---|---|
_cleveragents/context/show |
Show assembled context |
_cleveragents/context/inspect |
Inspect context tiers |
_cleveragents/context/simulate |
Simulate context assembly |
_cleveragents/context/set |
Override context values |
Health & Diagnostics
| Method | Description |
|---|---|
_cleveragents/health/check |
Health check |
_cleveragents/diagnostics/run |
Run diagnostics |
Sync & Namespace (stubs)
| Method | Description |
|---|---|
_cleveragents/sync/pull |
Pull remote changes |
_cleveragents/sync/push |
Push local changes |
_cleveragents/sync/status |
Sync status |
_cleveragents/namespace/list |
List namespaces |
_cleveragents/namespace/show |
Show namespace detail |
_cleveragents/namespace/members |
List namespace members |
Legacy Operations (Deprecated)
The following proprietary operation names are kept for backward compatibility
but are deprecated in favour of the _cleveragents/ extension methods:
| Legacy Name | Replacement |
|---|---|
session.create |
session.create (unchanged) |
session.close |
session.close (unchanged) |
plan.create |
_cleveragents/plan/use |
plan.execute |
_cleveragents/plan/execute |
plan.status |
_cleveragents/plan/status |
plan.diff |
_cleveragents/plan/diff |
plan.apply |
_cleveragents/plan/apply |
registry.list_tools |
_cleveragents/registry/tool/list |
registry.list_resources |
_cleveragents/registry/resource/list |
context.get |
_cleveragents/context/show |
event.subscribe |
_cleveragents/event/subscribe |
Event Queue — Publish / Subscribe
The A2aEventQueue provides in-memory pub/sub for plan and session events in
local mode. In server mode, events are delivered via Server-Sent Events (SSE).
from cleveragents.a2a.events import A2aEventQueue, TASK_STATUS_UPDATE
from cleveragents.a2a.models import A2aEvent
queue = A2aEventQueue()
# Subscribe with a callback
def on_event(event: A2aEvent) -> None:
print(f"[{event.event_type}] plan={event.plan_id} data={event.data}")
sub_id = queue.subscribe_local(on_event)
# Publish an event
queue.publish(A2aEvent(
event_type=TASK_STATUS_UPDATE,
plan_id="01HXRCF1...",
data={"state": "working", "phase": "execute"},
))
# Retrieve recent events
events = queue.get_events(limit=50)
# Unsubscribe
queue.unsubscribe(sub_id)
# Shutdown
queue.close()
Event Types
| Constant | Value | Emitted When |
|---|---|---|
TASK_STATUS_UPDATE |
"TaskStatusUpdateEvent" |
Plan phase or state changes |
TASK_ARTIFACT_UPDATE |
"TaskArtifactUpdateEvent" |
Agent produces output artifacts |
SSE Wire Format
In server mode, events are formatted as Server-Sent Events with a JSON-RPC 2.0 notification payload:
event: TaskStatusUpdateEvent
id: 01HXRCF1ABCDE12345678901AB
data: {"jsonrpc":"2.0","method":"task/statusUpdate","params":{"taskId":"01HXRCF1...","state":"working"}}
EventBusBridge
The EventBusBridge subscribes to the internal domain EventBus and translates
domain events into A2aEvent instances:
from cleveragents.a2a.events import EventBusBridge
bridge = EventBusBridge(event_bus=event_bus, event_queue=queue)
bridge.start() # Subscribe to domain events
# ... plan execution ...
bridge.stop() # Unsubscribe
Domain events mapped to TaskStatusUpdateEvent:
PLAN_CREATED, PLAN_PHASE_CHANGED, PLAN_STATE_CHANGED, PLAN_APPLIED,
PLAN_CANCELLED, PLAN_ERRORED.
Domain events mapped to TaskArtifactUpdateEvent:
CHECKPOINT_RESTORED.
Standard A2A Operations
In addition to the _cleveragents/ extension methods, CleverAgents supports the
standard A2A operations defined by the external specification:
| Operation | Direction | Purpose |
|---|---|---|
message/send |
Client → Server | Send a message to the agent; receive a Task |
message/stream |
Client → Server | Send a message with SSE streaming of task updates |
tasks/get |
Client → Server | Retrieve current state of a task |
tasks/list |
Client → Server | List tasks with optional filtering |
tasks/cancel |
Client → Server | Cancel a running task |
tasks/subscribe |
Client → Server | Subscribe to task updates via SSE |
pushNotificationConfig/* |
Client → Server | Manage push notification webhooks |
getExtendedAgentCard |
Client → Server | Fetch authenticated Agent Card |
Task Lifecycle States
| State | Description |
|---|---|
submitted |
Task received, not yet started |
working |
Task actively executing |
input-required |
Task paused, awaiting human input (automation profile gate) |
completed |
Task finished successfully |
failed |
Task terminated with an error |
canceled |
Task was explicitly cancelled |
rejected |
Task was rejected before starting |
Multi-Turn Interactions
When a plan's automation profile requires human approval, the Task enters
input-required state. The client resumes execution by sending a
message/send with the task's context ID:
{
"jsonrpc": "2.0",
"id": 2,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{ "text": "Approved — proceed with the apply phase." }],
"contextId": "task_01HXRCF1..."
}
}
}
Agent Card Discovery
In server mode, the Agent Card is served at /.well-known/agent.json and
declares CleverAgents capabilities:
{
"name": "CleverAgents",
"description": "Autonomous software engineering agent",
"skills": ["plan-lifecycle", "registry-crud", "context-management"],
"extensions": [
{
"uri": "urn:cleveragents:extensions:v1",
"methods": ["_cleveragents/plan/*", "_cleveragents/registry/*"]
}
],
"securitySchemes": {
"bearerAuth": { "type": "http", "scheme": "bearer" }
}
}
An authenticated extended Agent Card with additional detail is available via
getExtendedAgentCard.
CLI Commands
Every CLI command maps to an A2A operation. The CLI is a thin rendering layer over A2A.
# Session management
agents a2a session create --actor openai/gpt-4o
agents a2a session list
agents a2a session close <SESSION_ID>
# Plan lifecycle
agents plan use <ACTION_NAME> [--automation-profile supervised]
agents plan execute <PLAN_ID>
agents plan status <PLAN_ID>
agents plan diff <PLAN_ID>
agents plan apply <PLAN_ID>
agents plan cancel <PLAN_ID>
agents plan tree <PLAN_ID>
agents plan correct <PLAN_ID> --guidance "Use dependency injection"
agents plan rollback <PLAN_ID>
agents plan list
# Registry
agents tool list [--namespace <NS>]
agents resource list [--type <TYPE>]
agents actor list
agents skill list
# Health
agents health check
agents diagnostics run
Versioning
| Aspect | Mechanism |
|---|---|
| JSON-RPC version | Always "2.0" in the jsonrpc field |
| A2A protocol version | A2A-Version HTTP header (server mode) |
| Extension version | urn:cleveragents:extensions:v1 in Agent Card |
| Backward compatibility | Servers support current + one prior minor version |
| Breaking changes | Require major version bump of the extension namespace |
Error Types
| Exception | Description |
|---|---|
A2aError |
Base A2A exception |
A2aNotAvailableError |
Operation not available in current mode |
A2aOperationNotFoundError |
Unknown operation name |
A2aVersionMismatchError |
Incompatible protocol versions |
from cleveragents.a2a.errors import A2aOperationNotFoundError
try:
response = facade.dispatch(request)
except A2aOperationNotFoundError as exc:
print(f"Unknown method: {exc.operation}")
See Also
cleveragents.a2a— Python API reference for A2A classes- ADR-026 — A2A protocol design rationale
- ADR-047 — A2A standard adoption
- Guards — Guard enforcement during plan execution
- Automation Profiles — Profile resolution and autonomy control