5.1 KiB
ADR-0032: A2A Error Handling and JSON-RPC 2.0 Compliance
Status: Implemented Date: 2026-02-07 Deciders: VAPORA Team Technical Story: Consistent, specification-compliant error representation across the A2A client/server boundary
Decision
Two-layer error handling strategy for the A2A subsystem:
Layer 1 — Domain errors (Rust thiserror):
// vapora-a2a
pub enum A2aError {
TaskNotFound(String),
InvalidStateTransition { current: String, target: String },
CoordinatorError(String),
UnknownSkill(String),
SerdeError,
IoError,
InternalError(String),
}
// vapora-a2a-client
pub enum A2aClientError {
HttpError,
TaskNotFound(String),
ServerError { code: i32, message: String },
ConnectionRefused(String),
Timeout(String),
InvalidResponse,
InternalError(String),
}
Layer 2 — Protocol serialization (JSON-RPC 2.0):
impl A2aError {
pub fn to_json_rpc_error(&self) -> serde_json::Value {
json!({
"jsonrpc": "2.0",
"error": { "code": <domain-code>, "message": <message> }
})
}
}
Error code mapping:
| Category | JSON-RPC Code | A2aError variants |
|---|---|---|
| Domain / server errors | -32000 | TaskNotFound, UnknownSkill, InvalidStateTransition |
| Internal errors | -32603 | SerdeError, IoError, InternalError |
| Parse errors | -32700 | Handled by JSON parser |
| Invalid request | -32600 | Handled by Axum |
Rationale
Why two layers? Domain layer gives type-safe Result<T, A2aError> propagation throughout the crate. Protocol layer isolates JSON-RPC specifics to conversion methods — domain code has no protocol awareness.
Why JSON-RPC 2.0 standard codes? Code ranges are defined by the specification and understood by compliant clients without custom documentation. Enables generic error handling on the client side.
Why thiserror? Minimal boilerplate. Automatic Display derives. Composes cleanly with ?. Validated pattern throughout the VAPORA codebase (ADR-0022).
Why one-way conversion (domain → protocol)? Protocol details cannot bleed into domain code. Future protocol changes are contained to conversion methods. Each layer is independently testable.
Alternatives Considered
Custom error codes — rejected: non-standard, client libraries can't handle them generically, harder to debug.
Single error type — rejected: collapses domain semantics into protocol representation, loses type safety, makes specific error handling impossible.
No protocol conversion (raw Rust errors as HTTP 500) — rejected: violates JSON-RPC 2.0 compliance, breaks A2A client expectations, prevents interoperability.
Trade-offs
Pros:
- Compile-time exhaustive error handling via
match - Protocol compliance verified: clients receive spec-compliant
{"jsonrpc":"2.0","error":{...}} - Error flow is auditable — each variant maps to exactly one JSON-RPC code
- Contextual tracing: all errors logged with
task_id,operation, error message - Client retry logic (
RetryPolicy) classifies errors from JSON-RPC codes: 5xx retried, 4xx not retried
Cons:
- Some error context is intentionally lost in translation (internal detail not exposed to clients)
- JSON-RPC code documentation must be kept in sync with new variants
- Boundary conversions require explicit calls at each Axum handler
Implementation
Key files:
crates/vapora-a2a/src/error.rs—A2aError+to_json_rpc_error()crates/vapora-a2a-client/src/error.rs—A2aClientErrorcrates/vapora-a2a-client/src/retry.rs— Error classification for retry policy
Error flow:
HTTP request
→ Axum handler
→ TaskManager::get(id) → Err(A2aError::TaskNotFound)
→ to_json_rpc_error() → {"jsonrpc":"2.0","error":{"code":-32000,...}}
→ (StatusCode::NOT_FOUND, Json(error_body))
← vapora-a2a-client parses → A2aClientError::TaskNotFound
← caller matches variant
Verification
cargo test -p vapora-a2a # error conversion tests
cargo test -p vapora-a2a-client # 5/5 pass (includes retry classification)
cargo clippy -p vapora-a2a -- -D warnings
cargo clippy -p vapora-a2a-client -- -D warnings
Consequences
- All new A2A error variants must be added to both
A2aErrorand the JSON-RPC code mapping table A2aClientErrormust mirror any new server-side variants that clients need to handle specifically- Pattern is scoped to the A2A subsystem; general VAPORA error handling follows ADR-0022
References
crates/vapora-a2a/src/error.rscrates/vapora-a2a-client/src/error.rs- thiserror
- JSON-RPC 2.0 Specification
- Axum error responses
Related ADRs: