Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b0bc485a4 |
@@ -5,6 +5,17 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- **A2A Protocol Guide** (`docs/api/a2a-protocol.md`): Comprehensive documentation for the A2A (Agent-to-Agent) protocol covering 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. Covers v3.5.0 and v3.8.0 features. Refs #9010
|
||||
|
||||
- **Guards Documentation** (`docs/api/guards.md`): Documentation for autonomy guardrail enforcement covering denylist, allowlist, budget caps (`max_total_cost`), tool call limits (`max_tool_calls_per_step`), write/apply approval gates, `AutonomyGuardrailService` enforcement methods, budget hierarchy (plan > session > org), and the `GuardrailAuditTrail` for immutable enforcement records. Refs #9010
|
||||
|
||||
- **Automation Profiles Documentation** (`docs/api/automation-profiles.md`): Documentation for automation profiles covering the eight built-in profiles (`manual`, `review`, `supervised`, `cautious`, `trusted`, `auto`, `ci`, `full-auto`), confidence threshold semantics, safety profile sub-model, profile resolution precedence (plan > action > project > global), `AutomationProfileService` Python API, YAML configuration, and CLI commands. Refs #9010
|
||||
|
||||
- **mkdocs.yml navigation**: Added `A2A Protocol Guide`, `Guards`, and `Automation Profiles` entries to the API Reference section.
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Automation Profile Silent Fallback** (#8232): `_resolve_profile_for_plan` in
|
||||
|
||||
@@ -0,0 +1,607 @@
|
||||
# A2A Protocol — Agent-to-Agent Communication
|
||||
|
||||
> **Version:** v3.5.0 / v3.8.0 (JSON-RPC 2.0 wire format)
|
||||
> **ADRs:** [ADR-026](../adr/ADR-026-agent-client-protocol.md) · [ADR-047](../adr/ADR-047-acp-standard-adoption.md)
|
||||
|
||||
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](https://a2a-protocol.org) (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](https://www.jsonrpc.org/specification) envelope.
|
||||
|
||||
### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "01HXRCF1ABCDE12345678901AB",
|
||||
"result": {
|
||||
"plan_id": "01HXRCF1ABCDE12345678901AB",
|
||||
"phase": "execute",
|
||||
"state": "running"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```python
|
||||
from cleveragents.a2a.models import A2aRequest, A2aResponse, A2aErrorDetail, A2aEvent
|
||||
```
|
||||
|
||||
### `A2aRequest`
|
||||
|
||||
```python
|
||||
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`
|
||||
|
||||
```python
|
||||
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`
|
||||
|
||||
```python
|
||||
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`
|
||||
|
||||
```python
|
||||
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](../../CHANGELOG.md).
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
# 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).
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```bash
|
||||
# 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 |
|
||||
|
||||
```python
|
||||
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`](a2a.md) — Python API reference for A2A classes
|
||||
- [ADR-026](../adr/ADR-026-agent-client-protocol.md) — A2A protocol design rationale
|
||||
- [ADR-047](../adr/ADR-047-acp-standard-adoption.md) — A2A standard adoption
|
||||
- [Guards](guards.md) — Guard enforcement during plan execution
|
||||
- [Automation Profiles](automation-profiles.md) — Profile resolution and autonomy control
|
||||
@@ -0,0 +1,359 @@
|
||||
# Automation Profiles
|
||||
|
||||
> **Version:** v3.2.0+
|
||||
> **ADR:** [ADR-017](../adr/ADR-017-automation-profiles.md)
|
||||
|
||||
**Automation profiles** are named configuration bundles that control the
|
||||
autonomy level of plan execution. They define confidence thresholds for each
|
||||
phase transition and safety constraints that govern sandboxing, checkpointing,
|
||||
and human approval gates.
|
||||
|
||||
Eight built-in profiles span the spectrum from fully manual to fully
|
||||
autonomous. Custom profiles can be created and registered via YAML
|
||||
configuration.
|
||||
|
||||
---
|
||||
|
||||
## What Are Automation Profiles?
|
||||
|
||||
Different tasks require different levels of autonomy:
|
||||
|
||||
- A trusted refactoring in a well-tested codebase can run **fully automatically**
|
||||
- A production infrastructure change requires **human approval at every step**
|
||||
|
||||
Automation profiles provide a single, configurable mechanism to control this
|
||||
spectrum without code changes. The profile is resolved once at `plan use` time
|
||||
and **locked to the plan** — it cannot change during execution.
|
||||
|
||||
---
|
||||
|
||||
## Profile Fields
|
||||
|
||||
Each profile consists of **confidence thresholds** (0.0–1.0) and a composed
|
||||
**safety profile** sub-model.
|
||||
|
||||
### Confidence Thresholds
|
||||
|
||||
A threshold of **0.0** means "always proceed automatically."
|
||||
A threshold of **1.0** means "always require human approval."
|
||||
Intermediate values (e.g., 0.7) mean "auto-proceed only when the system's
|
||||
confidence score meets or exceeds the threshold; otherwise prompt the user."
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `decompose_task` | Auto-enter Strategize after `plan use` |
|
||||
| `create_tool` | Auto-proceed from Strategize to Execute |
|
||||
| `select_tool` | Auto-proceed from Execute to Apply |
|
||||
| `edit_code` | Auto-make decisions during Strategize |
|
||||
| `execute_command` | Auto-make decisions during Execute |
|
||||
| `create_file` | Auto-attempt to fix validation failures |
|
||||
| `delete_content` | Auto-revise strategy when Execute hits constraints |
|
||||
| `access_network` | Auto-revert from Apply (`constrained`) to Strategize |
|
||||
| `install_dependency` | Auto-spawn and execute child plans |
|
||||
| `modify_config` | Auto-retry on transient failures |
|
||||
| `approve_plan` | Auto-restore from checkpoint on failure |
|
||||
|
||||
### Safety Profile (`safety`)
|
||||
|
||||
The `safety` field is a `SafetyProfile` sub-model with hard safety constraints:
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `require_sandbox` | `bool` | `true` | Require sandbox isolation |
|
||||
| `require_checkpoints` | `bool` | `true` | Require checkpointing |
|
||||
| `allow_unsafe_tools` | `bool` | `false` | Allow tools marked as unsafe |
|
||||
| `require_human_approval` | `bool` | `false` | Require approval before each action step |
|
||||
| `allowed_skill_categories` | `list[str]` | `[]` | Permitted skill categories (empty = all) |
|
||||
| `max_cost_per_plan` | `float \| None` | `None` | Maximum cost (USD) per plan execution |
|
||||
| `max_total_cost` | `float \| None` | `None` | Maximum total cost (USD) across all plans |
|
||||
| `max_retries_per_step` | `int` | `3` | Maximum retry attempts per action step |
|
||||
|
||||
---
|
||||
|
||||
## Eight Built-In Profiles
|
||||
|
||||
| Profile | Intent | Key Settings |
|
||||
|---------|--------|-------------|
|
||||
| `manual` | Human controls everything | All thresholds 1.0; sandbox + checkpoints required |
|
||||
| `review` | Auto-strategize, human reviews before execute and apply | `decompose_task: 0.0`; execute/apply thresholds high |
|
||||
| `supervised` | Auto through strategize/execute, human approves apply | `select_tool: 1.0`; others low |
|
||||
| `cautious` | Mostly auto with conservative thresholds | Moderate thresholds (~0.7); sandbox required |
|
||||
| `trusted` | Fully auto for well-tested domains | Low thresholds (~0.3); sandbox required |
|
||||
| `auto` | Fully automatic with safety nets | All thresholds 0.0; sandbox + checkpoints required |
|
||||
| `ci` | CI/CD pipeline automation | All thresholds 0.0; sandbox + checkpoints required |
|
||||
| `full-auto` | Maximum autonomy, minimal guardrails | All thresholds 0.0; no sandbox; unsafe tools allowed |
|
||||
|
||||
!!! warning "full-auto profile"
|
||||
The `full-auto` profile removes key safety nets (`require_sandbox: false`,
|
||||
`allow_unsafe_tools: true`). Use only in fully trusted, isolated environments.
|
||||
|
||||
---
|
||||
|
||||
## Profile Resolution Precedence
|
||||
|
||||
The effective profile for a plan is resolved at `plan use` time using a
|
||||
four-level precedence chain:
|
||||
|
||||
```
|
||||
plan (--automation-profile flag)
|
||||
> action (automation_profile field on the action template)
|
||||
> project (core.automation-profile project config)
|
||||
> global (core.automation-profile config or CLEVERAGENTS_AUTOMATION_PROFILE env var)
|
||||
```
|
||||
|
||||
Once resolved, the profile is **locked to the plan**. Changing upstream
|
||||
configuration after plan creation has no effect on the locked plan.
|
||||
|
||||
### Resolution Examples
|
||||
|
||||
**Example 1 — Plan flag wins:**
|
||||
|
||||
```
|
||||
Plan flag: --automation-profile supervised ← WINS (highest precedence)
|
||||
Action: automation_profile: cautious
|
||||
Project: core.automation-profile: trusted
|
||||
Global: CLEVERAGENTS_AUTOMATION_PROFILE=auto
|
||||
```
|
||||
|
||||
Result: `supervised` profile is used.
|
||||
|
||||
**Example 2 — Action wins when no plan flag:**
|
||||
|
||||
```
|
||||
Plan flag: (not set)
|
||||
Action: automation_profile: cautious ← WINS
|
||||
Project: core.automation-profile: trusted
|
||||
Global: CLEVERAGENTS_AUTOMATION_PROFILE=auto
|
||||
```
|
||||
|
||||
Result: `cautious` profile is used.
|
||||
|
||||
**Example 3 — Global env var as fallback:**
|
||||
|
||||
```
|
||||
Plan flag: (not set)
|
||||
Action: (not set)
|
||||
Project: (not set)
|
||||
Global: CLEVERAGENTS_AUTOMATION_PROFILE=auto ← WINS (lowest precedence)
|
||||
```
|
||||
|
||||
Result: `auto` profile is used.
|
||||
|
||||
If no profile is set at any level, the default is `manual`.
|
||||
|
||||
---
|
||||
|
||||
## Python API
|
||||
|
||||
### `AutomationProfileService`
|
||||
|
||||
```python
|
||||
from cleveragents.application.services.automation_profile_service import (
|
||||
AutomationProfileService,
|
||||
)
|
||||
|
||||
service = AutomationProfileService(
|
||||
repo=profile_repository, # Optional; enables custom profiles
|
||||
global_default="supervised", # Optional; overrides env var / "manual"
|
||||
)
|
||||
```
|
||||
|
||||
### Resolving the Effective Profile
|
||||
|
||||
```python
|
||||
profile = service.resolve_profile(
|
||||
plan_profile="supervised", # From --automation-profile flag
|
||||
action_profile="cautious", # From action template
|
||||
project_profile="trusted", # From project config
|
||||
)
|
||||
# Returns the AutomationProfile for "supervised"
|
||||
```
|
||||
|
||||
### Getting a Profile by Name
|
||||
|
||||
```python
|
||||
profile = service.get_profile("auto")
|
||||
print(profile.decompose_task) # 0.0
|
||||
print(profile.safety.require_sandbox) # True
|
||||
```
|
||||
|
||||
### Listing All Profiles
|
||||
|
||||
```python
|
||||
profiles = service.list_profiles()
|
||||
for p in profiles:
|
||||
print(f"{p.name}: decompose_task={p.decompose_task}")
|
||||
```
|
||||
|
||||
### Creating a Custom Profile
|
||||
|
||||
```python
|
||||
profile = service.create_profile({
|
||||
"name": "my-custom-profile",
|
||||
"decompose_task": 0.0,
|
||||
"create_tool": 0.5,
|
||||
"select_tool": 0.8,
|
||||
"edit_code": 0.6,
|
||||
"execute_command": 0.9,
|
||||
"create_file": 0.4,
|
||||
"delete_content": 0.9,
|
||||
"access_network": 0.7,
|
||||
"install_dependency": 0.5,
|
||||
"modify_config": 0.8,
|
||||
"approve_plan": 0.3,
|
||||
"safety": {
|
||||
"require_sandbox": True,
|
||||
"require_checkpoints": True,
|
||||
"allow_unsafe_tools": False,
|
||||
"max_cost_per_plan": 10.00,
|
||||
"max_retries_per_step": 3,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Loading from YAML
|
||||
|
||||
```python
|
||||
profile = AutomationProfileService.from_yaml("/path/to/profile.yaml")
|
||||
```
|
||||
|
||||
### Evaluating a Guard
|
||||
|
||||
```python
|
||||
from cleveragents.domain.models.core.automation_guard import GuardScope
|
||||
|
||||
result = service.evaluate_guard(
|
||||
profile_name="supervised",
|
||||
tool_name="shell_exec",
|
||||
context={
|
||||
"is_write": True,
|
||||
"cost_so_far": 2.50,
|
||||
"calls_so_far": 5,
|
||||
"scope": GuardScope.PLAN,
|
||||
},
|
||||
)
|
||||
if not result.allowed:
|
||||
print(f"Blocked: {result.reason}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `AutomationProfile` Domain Model
|
||||
|
||||
```python
|
||||
from cleveragents.domain.models.core.automation_profile import (
|
||||
AutomationProfile,
|
||||
BUILTIN_PROFILES,
|
||||
)
|
||||
|
||||
# Access built-in profiles directly
|
||||
auto_profile = BUILTIN_PROFILES["auto"]
|
||||
manual_profile = BUILTIN_PROFILES["manual"]
|
||||
|
||||
# Check a guard
|
||||
from cleveragents.domain.models.core.automation_guard import GuardScope
|
||||
|
||||
result = auto_profile.check_guard(
|
||||
tool_name="shell_exec",
|
||||
is_write=False,
|
||||
cost_so_far=1.00,
|
||||
calls_so_far=3,
|
||||
scope=GuardScope.PLAN,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## YAML Configuration
|
||||
|
||||
Custom profiles are defined in YAML and registered via:
|
||||
|
||||
```bash
|
||||
agents automation-profile add --config my-profile.yaml
|
||||
```
|
||||
|
||||
Example YAML:
|
||||
|
||||
```yaml
|
||||
name: strict-ci
|
||||
decompose_task: 0.0
|
||||
create_tool: 0.0
|
||||
select_tool: 0.0
|
||||
edit_code: 0.0
|
||||
execute_command: 0.0
|
||||
create_file: 0.0
|
||||
delete_content: 0.5
|
||||
access_network: 0.8
|
||||
install_dependency: 0.3
|
||||
modify_config: 0.7
|
||||
approve_plan: 0.0
|
||||
safety:
|
||||
require_sandbox: true
|
||||
require_checkpoints: true
|
||||
allow_unsafe_tools: false
|
||||
require_human_approval: false
|
||||
max_cost_per_plan: 2.00
|
||||
max_retries_per_step: 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
```bash
|
||||
# List all profiles (built-in + custom)
|
||||
agents automation-profile list
|
||||
|
||||
# Show a specific profile
|
||||
agents automation-profile show supervised
|
||||
|
||||
# Add a custom profile from YAML
|
||||
agents automation-profile add --config my-profile.yaml
|
||||
|
||||
# Update a custom profile
|
||||
agents automation-profile update my-profile --config updated.yaml
|
||||
|
||||
# Delete a custom profile
|
||||
agents automation-profile delete my-profile
|
||||
|
||||
# Use a specific profile when creating a plan
|
||||
agents plan use <ACTION_NAME> --automation-profile supervised
|
||||
|
||||
# Show the effective profile for a plan
|
||||
agents plan profile show <PLAN_ID>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variable
|
||||
|
||||
The global default profile can be set via environment variable:
|
||||
|
||||
```bash
|
||||
export CLEVERAGENTS_AUTOMATION_PROFILE=supervised
|
||||
agents plan use my-action # Uses "supervised" profile
|
||||
```
|
||||
|
||||
Precedence: `--automation-profile` flag > action template > project config >
|
||||
`CLEVERAGENTS_AUTOMATION_PROFILE` env var > `"manual"` (hardcoded default).
|
||||
|
||||
---
|
||||
|
||||
## Constraints
|
||||
|
||||
- Threshold values must be in the range `[0.0, 1.0]`.
|
||||
- `safety.require_sandbox: false` is only valid if the sandbox strategy is `none`.
|
||||
- `safety.require_checkpoints: false` is only valid if `sandbox.checkpoint.enabled` is `false`.
|
||||
- Built-in profile names (`manual`, `review`, `supervised`, `cautious`, `trusted`,
|
||||
`auto`, `ci`, `full-auto`) are reserved and cannot be overridden by custom profiles.
|
||||
- The effective profile is resolved once at `plan use` time and locked to the plan.
|
||||
To change the profile, create a new plan.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Guards](guards.md) — Runtime guard enforcement hooks
|
||||
- [A2A Protocol](a2a-protocol.md) — Protocol layer for plan execution
|
||||
- [ADR-017](../adr/ADR-017-automation-profiles.md) — Automation profiles design rationale
|
||||
- [ADR-041](../adr/ADR-041-safety-profile-extraction.md) — Safety profile extraction
|
||||
@@ -0,0 +1,418 @@
|
||||
# Guards — Autonomy Guardrail Enforcement
|
||||
|
||||
> **Version:** v3.5.0 (autonomy hardening milestone)
|
||||
> **ADR:** [ADR-017](../adr/ADR-017-automation-profiles.md)
|
||||
|
||||
**Guards** are runtime enforcement hooks that gate tool invocations and plan
|
||||
execution steps beyond the phase-transition thresholds defined by an
|
||||
[Automation Profile](automation-profiles.md). They provide a second layer of
|
||||
safety — even when a profile permits automatic execution, guards can block
|
||||
individual tool calls that exceed budget, call count, or denylist constraints.
|
||||
|
||||
Guards are evaluated by the `AutonomyGuardrailService` and the
|
||||
`AutomationGuard` model. Every enforcement decision is recorded in an
|
||||
immutable `GuardrailAuditTrail` persisted to plan metadata.
|
||||
|
||||
---
|
||||
|
||||
## Guard Types
|
||||
|
||||
### 1. Denylist
|
||||
|
||||
The **tool denylist** prevents specific tools from being called automatically.
|
||||
Any tool on the denylist always requires human approval, regardless of the
|
||||
automation profile's confidence thresholds.
|
||||
|
||||
```python
|
||||
from cleveragents.domain.models.core.automation_guard import AutomationGuard
|
||||
|
||||
guard = AutomationGuard(
|
||||
tool_denylist=["shell_exec", "rm_file", "network_request"],
|
||||
)
|
||||
```
|
||||
|
||||
When a denylisted tool is invoked, the guard returns:
|
||||
|
||||
```python
|
||||
GuardResult(
|
||||
allowed=False,
|
||||
reason="Tool 'shell_exec' is on the denylist. "
|
||||
"Remediation: remove it from the denylist or request human approval.",
|
||||
requires_approval=True,
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Allowlist
|
||||
|
||||
The **tool allowlist** restricts automatic execution to a specific set of
|
||||
tools. Any tool not on the allowlist requires human approval.
|
||||
|
||||
```python
|
||||
guard = AutomationGuard(
|
||||
tool_allowlist=["read_file", "search_code", "list_directory"],
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Budget Cap (`max_total_cost`)
|
||||
|
||||
The **budget cap** halts automatic execution when the cumulative cost of tool
|
||||
invocations exceeds a threshold (in USD).
|
||||
|
||||
```python
|
||||
guard = AutomationGuard(
|
||||
max_total_cost=5.00, # Stop after $5.00 of tool costs
|
||||
)
|
||||
```
|
||||
|
||||
When the budget is exceeded:
|
||||
|
||||
```python
|
||||
GuardResult(
|
||||
allowed=False,
|
||||
reason="Budget cap exceeded: $5.23 > $5.00. "
|
||||
"Remediation: raise max_total_cost, lower cost, or request human approval.",
|
||||
requires_approval=True,
|
||||
)
|
||||
```
|
||||
|
||||
### 4. Tool Call Limit (`max_tool_calls_per_step`)
|
||||
|
||||
The **tool call limit** caps the number of tool invocations per execution step.
|
||||
This prevents runaway loops and unbounded tool usage.
|
||||
|
||||
```python
|
||||
guard = AutomationGuard(
|
||||
max_tool_calls_per_step=10, # Max 10 tool calls per step
|
||||
)
|
||||
```
|
||||
|
||||
When the limit is reached:
|
||||
|
||||
```python
|
||||
GuardResult(
|
||||
allowed=False,
|
||||
reason="Tool call limit reached: 10/10. "
|
||||
"Remediation: increase max_tool_calls_per_step, reduce tool usage, "
|
||||
"or request human approval.",
|
||||
requires_approval=True,
|
||||
)
|
||||
```
|
||||
|
||||
### 5. Write Approval
|
||||
|
||||
The **write approval** flag requires human approval for any tool invocation
|
||||
that performs a write operation (file modification, database write, etc.).
|
||||
|
||||
```python
|
||||
guard = AutomationGuard(
|
||||
require_approval_for_writes=True,
|
||||
)
|
||||
```
|
||||
|
||||
### 6. Apply Approval
|
||||
|
||||
The **apply approval** flag requires human approval before the plan transitions
|
||||
to the Apply phase.
|
||||
|
||||
```python
|
||||
guard = AutomationGuard(
|
||||
require_approval_for_apply=True,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `AutomationGuard` Model
|
||||
|
||||
```python
|
||||
from cleveragents.domain.models.core.automation_guard import AutomationGuard, GuardResult
|
||||
|
||||
class AutomationGuard(BaseModel):
|
||||
max_tool_calls_per_step: int | None = None
|
||||
max_total_cost: float | None = None
|
||||
tool_allowlist: list[str] | None = None
|
||||
tool_denylist: list[str] | None = None
|
||||
require_approval_for_writes: bool = False
|
||||
require_approval_for_apply: bool = False
|
||||
```
|
||||
|
||||
`None` values mean "no limit" / "no restriction".
|
||||
|
||||
### `GuardResult`
|
||||
|
||||
```python
|
||||
class GuardResult(BaseModel):
|
||||
allowed: bool
|
||||
reason: str | None = None
|
||||
requires_approval: bool = False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Autonomy Guardrails (`AutonomyGuardrails`)
|
||||
|
||||
The `AutonomyGuardrails` model extends the guard framework with runtime
|
||||
execution constraints tracked per plan:
|
||||
|
||||
```python
|
||||
from cleveragents.domain.models.core.autonomy_guardrails import (
|
||||
AutonomyGuardrails,
|
||||
ActorLimits,
|
||||
)
|
||||
|
||||
guardrails = AutonomyGuardrails(
|
||||
max_steps=100, # Max execution steps
|
||||
tool_budget=10.00, # Max $10.00 in tool costs
|
||||
max_wall_clock_seconds=3600.0, # Max 1 hour wall-clock time
|
||||
actor_limits=ActorLimits(
|
||||
max_tool_calls_per_invocation=20, # Max 20 tool calls per actor invocation
|
||||
max_retries_per_failure=3, # Max 3 retries per tool failure
|
||||
),
|
||||
required_confirmations=["delete_database", "deploy_to_production"],
|
||||
)
|
||||
```
|
||||
|
||||
### Guardrail Fields
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `max_steps` | `int \| None` | `None` | Maximum execution steps; `None` = unlimited |
|
||||
| `tool_budget` | `float \| None` | `None` | Maximum cumulative tool cost (USD); `None` = unlimited |
|
||||
| `max_wall_clock_seconds` | `float \| None` | `None` | Maximum wall-clock time in seconds; `None` = unlimited |
|
||||
| `actor_limits` | `ActorLimits` | `ActorLimits()` | Per-actor runtime limits |
|
||||
| `required_confirmations` | `list[str]` | `[]` | Operations requiring human confirmation |
|
||||
| `step_count` | `int` | `0` | Current step counter (tracked at runtime) |
|
||||
| `budget_spent` | `float` | `0.0` | Cumulative cost spent (tracked at runtime) |
|
||||
| `start_time` | `str \| None` | `None` | ISO-8601 execution start time |
|
||||
|
||||
### `ActorLimits`
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `max_tool_calls_per_invocation` | `int \| None` | `None` | Max tool calls per actor invocation |
|
||||
| `max_retries_per_failure` | `int \| None` | `None` | Max retries per tool failure |
|
||||
|
||||
---
|
||||
|
||||
## `AutonomyGuardrailService`
|
||||
|
||||
The `AutonomyGuardrailService` is the Application-layer service for enforcing
|
||||
guardrails during plan execution. It is thread-safe (all state mutations are
|
||||
protected by a reentrant lock).
|
||||
|
||||
```python
|
||||
from cleveragents.application.services.autonomy_guardrail_service import (
|
||||
AutonomyGuardrailService,
|
||||
)
|
||||
from cleveragents.domain.models.core.autonomy_guardrails import AutonomyGuardrails
|
||||
|
||||
service = AutonomyGuardrailService()
|
||||
|
||||
# Configure guardrails for a plan
|
||||
service.configure_guardrails(
|
||||
plan_id="01HXRCF1...",
|
||||
guardrails=AutonomyGuardrails(
|
||||
max_steps=50,
|
||||
tool_budget=5.00,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Enforcement Methods
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `check_step_limit(plan_id, current_step)` | `bool` | `True` if step is within limit |
|
||||
| `check_tool_budget(plan_id, tool_cost)` | `bool` | `True` if cost is within budget; records cost if allowed |
|
||||
| `check_wall_clock(plan_id)` | `bool` | `True` if within time limit |
|
||||
| `check_actor_tool_calls(plan_id, current_calls)` | `bool` | `True` if within per-invocation call limit |
|
||||
| `check_retries_per_failure(plan_id, current_retries)` | `bool` | `True` if within retry limit |
|
||||
| `check_confirmation_required(plan_id, operation)` | `bool` | `True` if operation needs human confirmation |
|
||||
|
||||
### Budget Hierarchy
|
||||
|
||||
The service supports a multi-level budget hierarchy:
|
||||
|
||||
1. **Plan-level** — `AutonomyGuardrails.tool_budget` (checked first)
|
||||
2. **Session-level** — `CostBudgetService` session budget
|
||||
3. **Org-level** — `CostBudgetService` org budget
|
||||
|
||||
```python
|
||||
from cleveragents.application.services.cost_budget_service import CostBudgetService
|
||||
|
||||
budget_service = CostBudgetService(...)
|
||||
guardrail_service = AutonomyGuardrailService(cost_budget_service=budget_service)
|
||||
|
||||
# Associate plan with session for hierarchy checks
|
||||
guardrail_service.associate_plan_with_session(
|
||||
plan_id="01HXRCF1...",
|
||||
session_id="session-abc",
|
||||
)
|
||||
|
||||
# Check full budget hierarchy
|
||||
result = guardrail_service.check_budget_hierarchy(
|
||||
plan_id="01HXRCF1...",
|
||||
tool_cost=0.05,
|
||||
)
|
||||
if not result.allowed:
|
||||
print(f"Budget exceeded at {result.exceeded_level}: {result.reason}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Guard Evaluation via `AutomationProfileService`
|
||||
|
||||
Guards can also be evaluated through the `AutomationProfileService`, which
|
||||
resolves the active automation profile and delegates to
|
||||
`AutomationProfile.check_guard`:
|
||||
|
||||
```python
|
||||
from cleveragents.application.services.automation_profile_service import (
|
||||
AutomationProfileService,
|
||||
)
|
||||
from cleveragents.domain.models.core.automation_guard import GuardScope
|
||||
|
||||
service = AutomationProfileService()
|
||||
|
||||
result = service.evaluate_guard(
|
||||
profile_name="supervised",
|
||||
tool_name="shell_exec",
|
||||
context={
|
||||
"is_write": True,
|
||||
"cost_so_far": 2.50,
|
||||
"calls_so_far": 5,
|
||||
"scope": GuardScope.PLAN,
|
||||
},
|
||||
)
|
||||
|
||||
if not result.allowed:
|
||||
print(f"Guard blocked: {result.reason}")
|
||||
if result.requires_approval:
|
||||
# Prompt user for approval
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audit Trail
|
||||
|
||||
Every guardrail enforcement decision is recorded in a `GuardrailAuditTrail`
|
||||
persisted to plan metadata. The trail is bounded (default 10,000 entries) with
|
||||
oldest-first eviction.
|
||||
|
||||
```python
|
||||
from cleveragents.domain.models.core.autonomy_guardrails import (
|
||||
GuardrailAuditEntry,
|
||||
GuardrailAuditTrail,
|
||||
GuardrailEventType,
|
||||
GuardrailResult,
|
||||
)
|
||||
|
||||
# Retrieve the audit trail for a plan
|
||||
trail = service.get_audit_trail(plan_id="01HXRCF1...")
|
||||
print(f"Allowed: {trail.allowed_count}, Denied: {trail.denied_count}")
|
||||
|
||||
for entry in trail.entries[-10:]: # Last 10 entries
|
||||
print(f"[{entry.timestamp}] {entry.guard_name}: {entry.result.value} — {entry.reason}")
|
||||
```
|
||||
|
||||
### Audit Entry Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `timestamp` | `str` | ISO-8601 UTC timestamp |
|
||||
| `event_type` | `GuardrailEventType` | Type of enforcement event |
|
||||
| `guard_name` | `str` | Name of the guard evaluated |
|
||||
| `result` | `GuardrailResult` | `"allowed"` or `"denied"` |
|
||||
| `reason` | `str \| None` | Human-readable decision reason |
|
||||
| `context` | `dict` | Additional context (cost, step count, etc.) |
|
||||
|
||||
### Event Types
|
||||
|
||||
| Event Type | Description |
|
||||
|------------|-------------|
|
||||
| `step_allowed` / `step_blocked` | Step limit check |
|
||||
| `budget_allowed` / `budget_blocked` | Budget cap check |
|
||||
| `time_allowed` / `time_blocked` | Wall-clock limit check |
|
||||
| `actor_limit_allowed` / `actor_limit_blocked` | Per-actor tool call limit |
|
||||
| `retry_allowed` / `retry_blocked` | Retry limit check |
|
||||
| `confirmation_granted` / `confirmation_required` | Human confirmation gate |
|
||||
|
||||
---
|
||||
|
||||
## Persisting Guardrail State
|
||||
|
||||
Guardrail state can be serialized to and restored from plan metadata:
|
||||
|
||||
```python
|
||||
# Serialize to metadata
|
||||
metadata = service.get_audit_trail_as_metadata(plan_id="01HXRCF1...")
|
||||
# metadata contains "guardrail_audit_trail" and "autonomy_guardrails" keys
|
||||
|
||||
# Restore from metadata
|
||||
service.load_from_metadata(plan_id="01HXRCF1...", metadata=metadata)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
```bash
|
||||
# View guard configuration for a plan
|
||||
agents plan guard show <PLAN_ID>
|
||||
|
||||
# View guardrail audit trail
|
||||
agents plan guard audit <PLAN_ID>
|
||||
|
||||
# Set plan-level budget cap
|
||||
agents plan guard set <PLAN_ID> --max-cost 10.00
|
||||
|
||||
# Set tool call limit
|
||||
agents plan guard set <PLAN_ID> --max-tool-calls 20
|
||||
|
||||
# Add tool to denylist
|
||||
agents plan guard denylist add <PLAN_ID> shell_exec
|
||||
|
||||
# Remove tool from denylist
|
||||
agents plan guard denylist remove <PLAN_ID> shell_exec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Example
|
||||
|
||||
Guards are typically configured as part of an automation profile's safety
|
||||
settings. The following YAML shows a custom profile with guards:
|
||||
|
||||
```yaml
|
||||
name: strict-review
|
||||
decompose_task: 0.0
|
||||
create_tool: 0.8
|
||||
select_tool: 1.0
|
||||
edit_code: 0.9
|
||||
execute_command: 1.0
|
||||
safety:
|
||||
require_sandbox: true
|
||||
require_checkpoints: true
|
||||
allow_unsafe_tools: false
|
||||
require_human_approval: false
|
||||
max_cost_per_plan: 5.00
|
||||
max_retries_per_step: 2
|
||||
guard:
|
||||
max_tool_calls_per_step: 15
|
||||
max_total_cost: 5.00
|
||||
tool_denylist:
|
||||
- shell_exec
|
||||
- network_request
|
||||
require_approval_for_writes: true
|
||||
require_approval_for_apply: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Automation Profiles](automation-profiles.md) — Profile resolution and autonomy control
|
||||
- [A2A Protocol](a2a-protocol.md) — Protocol layer for plan execution
|
||||
- [ADR-017](../adr/ADR-017-automation-profiles.md) — Automation profiles design
|
||||
- [ADR-041](../adr/ADR-041-safety-profile-extraction.md) — Safety profile extraction
|
||||
@@ -15,6 +15,9 @@ nav:
|
||||
- Overview: api/index.md
|
||||
- Core Utilities: api/core.md
|
||||
- A2A Protocol: api/a2a.md
|
||||
- A2A Protocol Guide: api/a2a-protocol.md
|
||||
- Guards: api/guards.md
|
||||
- Automation Profiles: api/automation-profiles.md
|
||||
- Actor System: api/actor.md
|
||||
- Skills: api/skills.md
|
||||
- Tools: api/tool.md
|
||||
|
||||
Reference in New Issue
Block a user