a2a-rust 0.1.0

Rust SDK for the A2A (Agent-to-Agent) protocol
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# a2a-rust Proto-First Design

Status: implementation contract
Date: 2026-03-12

This document is the repository-local design for `a2a-rust`. It intentionally does not modify the earlier external planning note. For work in this repository, this document supersedes earlier notes when they disagree.

## 1. Purpose

`a2a-rust` is a generic Rust SDK for A2A Protocol v1.0. It provides:

- a protocol-accurate type layer
- a server framework for REST, JSON-RPC, and SSE
- a client for discovery and remote invocation
- a stable foundation for downstream projects without any Clawhive-specific logic

The crate is not a concrete agent implementation and does not include hub-specific behavior.

## 2. Normative Sources

The repository follows these sources in this order:

1. Tagged proto: `v1.0.0`, commit `1736957`
2. Latest v1.0 specification text
3. Repository-local design documents

Normative links:

- Spec: <https://a2a-protocol.org/latest/specification/>
- Proto: <https://raw.githubusercontent.com/a2aproject/A2A/v1.0.0/specification/a2a.proto>

Rules:

- The proto is authoritative for data objects and request/response message shapes.
- The spec is authoritative for behavioral requirements, transport semantics, and interoperability guidance unless it conflicts with the proto.
- If spec prose and proto disagree, `a2a-rust` follows the proto for wire shape and documents the divergence.

Version lock:

- A2A protocol version: `1.0`
- Proto package: `lf.a2a.v1`
- Supported upstream release tag: `v1.0.0`

## 3. Scope

### In scope for `1.0.0`

- Full core type system from the tagged proto
- REST server endpoints from the proto HTTP annotations
- JSON-RPC support using the v1.0 method names
- SSE streaming for `SendStreamingMessage` and `SubscribeToTask`
- Agent discovery from `/.well-known/agent-card.json`
- Client support for discovery, unary calls, and streaming calls
- Push-notification request and response types
- Push-notification server and client API surface with default "not supported" behavior
- Extended agent card API surface

### Out of scope for `1.0.0`

- Agent card signature verification
- Generated proto bindings as the public API
- gRPC transport implementation
- Clawhive-specific storage, config, terminology, or adapters

## 4. Known Spec Drift and Repository Policy

The spec and the tagged proto are not perfectly aligned. This crate uses the following policy:

### 4.1 Method names

Use PascalCase JSON-RPC methods from the latest v1.0 binding tables:

- `SendMessage`
- `SendStreamingMessage`
- `GetTask`
- `ListTasks`
- `CancelTask`
- `SubscribeToTask`
- `CreateTaskPushNotificationConfig`
- `GetTaskPushNotificationConfig`
- `ListTaskPushNotificationConfigs`
- `DeleteTaskPushNotificationConfig`
- `GetExtendedAgentCard`

The old slash-style methods from earlier A2A versions are not the primary design target.

### 4.2 JSON-RPC endpoint path

The agent card declares the actual JSON-RPC URL in `supportedInterfaces`. The crate should not hardcode `/jsonrpc` as the protocol contract.

Repository decision:

- server router defaults the JSON-RPC endpoint to `/rpc`
- router configuration may add compatibility aliases such as `/jsonrpc`
- the advertised `AgentInterface.url` must match the actual deployed endpoint

### 4.3 Subscribe REST method

The spec prose and proto HTTP annotation disagree for `SubscribeToTask`.

- proto canonical binding: `GET /tasks/{id}:subscribe`
- spec prose still contains `POST /tasks/{id}:subscribe`

Repository decision:

- server exposes `GET` as canonical behavior
- server may optionally accept `POST` as a compatibility alias
- client uses `GET`

### 4.4 SendMessageConfiguration field naming

The final tagged proto defines:

- `acceptedOutputModes`
- `taskPushNotificationConfig`
- `historyLength`
- `returnImmediately`

Repository decision:

- canonical Rust and JSON field name is `return_immediately`
- canonical push-config field is `task_push_notification_config`
- no RC compatibility alias is required in `1.0.0`

## 5. Crate Architecture

The crate is layered so that `types` remains transport-agnostic and low-dependency.

```text
types/jsonrpc/error
        ^
        |
   server   client
        ^
        |
      store
```

### 5.1 Modules

```text
src/
  lib.rs
  error.rs
  jsonrpc.rs
  types/
    mod.rs
    agent_card.rs
    task.rs
    message.rs
    security.rs
    push.rs
    requests.rs
    responses.rs
  server/
    mod.rs
    handler.rs
    router.rs
    rest.rs
    jsonrpc.rs
    streaming.rs
  client/
    mod.rs
    discovery.rs
    api.rs
  store.rs
```

### 5.2 Feature flags

- default: `server`, `client`
- `server`: enables `axum`, SSE helpers, router, and server-side error mapping
- `client`: enables `reqwest`, discovery, streaming parsers, and client transport errors

Types, JSON-RPC envelopes, and core errors must compile with `default-features = false`.

## 6. Rust Data Model Decisions

### 6.1 Core mapping rules

Use `serde(rename_all = "camelCase")` on all protocol structs unless a field requires special handling.

Mapping choices:

- `google.protobuf.Struct` -> `JsonObject = serde_json::Map<String, serde_json::Value>`
- `google.protobuf.Value` -> `serde_json::Value`
- `google.protobuf.Timestamp` -> `String` containing RFC 3339 UTC text
- `bytes` -> `Vec<u8>` with custom base64 serde to match ProtoJSON
- proto maps -> `BTreeMap<_, _>` for deterministic serialization in tests and docs

Rationale:

- The public API should match ProtoJSON exactly.
- `types` should not force a time crate on downstream users in `1.0.0`.
- Deterministic map ordering simplifies serde tests and snapshots.

### 6.2 Oneof handling

Use enums for pure wrapper `oneof` messages:

- `SendMessageResponse`
- `StreamResponse`
- `SecurityScheme`
- `OAuthFlows`

Use structs with validation for mixed-content messages where `oneof` fields coexist with shared fields:

- `Part`

Validation helpers will reject invalid states such as:

- `Part` containing zero or more than one of `text`, `raw`, `url`, `data`
- `SendMessageResponse` or `StreamResponse` with impossible payload combinations

### 6.3 Proto-driven corrections to the previous design

These decisions are required by the tagged proto:

- `Task.context_id` is optional
- `TaskStatusUpdateEvent.context_id` is required
- `TaskArtifactUpdateEvent.context_id` is required
- `Role` includes `ROLE_UNSPECIFIED`
- `Artifact` does not have an `index` field
- `SecurityRequirement` is a wrapper object with `schemes`
- `OAuth2SecurityScheme.flows` is a typed object, not raw JSON
- `ListTasksRequest` and `ListTasksResponse` are richer than the earlier planning note
- `SubscribeToTaskRequest` only contains `tenant` and `id`

### 6.4 Compatibility leniency

`a2a-rust` should serialize canonically and deserialize strictly by default. The only planned lenient handling in `1.0.0` is:

- alternate `SecurityScheme` JSON shapes used by known SDKs, if they can be accepted without ambiguity

Any broader compatibility behavior should live behind explicit helper code, not weaken the canonical serializer.

## 7. Protocol Objects to Implement

The type layer must include at least:

- `AgentCard`, `AgentInterface`, `AgentProvider`, `AgentCapabilities`, `AgentExtension`, `AgentSkill`, `AgentCardSignature`
- `Task`, `TaskStatus`, `TaskState`
- `Message`, `Role`, `Part`, `Artifact`
- `TaskStatusUpdateEvent`, `TaskArtifactUpdateEvent`
- `AuthenticationInfo`, `TaskPushNotificationConfig`
- `SecurityRequirement`, `SecurityScheme`, all security scheme variants, `OAuthFlows`, and OAuth flow structs
- all request messages from the tagged proto
- all response messages from the tagged proto

The public type names should match the operation and object names from the tagged proto unless there is a compelling Rust API reason not to.

## 8. Error Model

The core error layer must cover all A2A-specific errors currently defined by the latest spec:

- `TaskNotFoundError` -> `-32001`
- `TaskNotCancelableError` -> `-32002`
- `PushNotificationNotSupportedError` -> `-32003`
- `UnsupportedOperationError` -> `-32004`
- `ContentTypeNotSupportedError` -> `-32005`
- `InvalidAgentResponseError` -> `-32006`
- `ExtendedAgentCardNotConfiguredError` -> `-32007`
- `ExtensionSupportRequiredError` -> `-32008`
- `VersionNotSupportedError` -> `-32009`

Design rules:

- `A2AError` is transport-neutral in the core crate
- JSON-RPC and HTTP mapping helpers live alongside the core error type
- `reqwest` conversions are only compiled under the `client` feature
- server transport errors and parsing failures map to standard JSON-RPC errors or HTTP problem details as appropriate

## 9. Server Design

### 9.1 Handler trait

Expose a user-implemented `A2AHandler` trait with default methods for optional functionality.

Required methods:

- `get_agent_card`
- `send_message`

Defaulted methods:

- `send_streaming_message`
- `get_task`
- `list_tasks`
- `cancel_task`
- `subscribe_to_task`
- push notification config CRUD/list
- `get_extended_agent_card`

Defaults should return the appropriate A2A error instead of panicking.

### 9.2 Router behavior

The server router should:

- expose the well-known discovery document at `/.well-known/agent-card.json`
- expose canonical REST endpoints from the proto annotations
- support tenant-prefixed additional bindings such as `/{tenant}/tasks`
- expose a configurable JSON-RPC endpoint, default `/rpc`
- return SSE with `data: <json>\n\n` framing for streaming endpoints

### 9.3 Streaming behavior

`SendStreamingMessage`:

- message-only flow: emit exactly one `StreamResponse::Message` and close
- task flow: emit initial `StreamResponse::Task`, then zero or more status or artifact updates, then close when interrupted or terminal

`SubscribeToTask`:

- emit current `Task` first
- then stream updates
- reject terminal tasks with `UnsupportedOperationError`

### 9.4 Capability gates

The server must enforce card-declared capabilities:

- if `capabilities.streaming != Some(true)`, streaming operations return `UnsupportedOperationError`
- if `capabilities.push_notifications != Some(true)`, push-config operations return `PushNotificationNotSupportedError`
- if `capabilities.extended_agent_card != Some(true)`, extended card requests return `ExtendedAgentCardNotConfiguredError`

## 10. Client Design

### 10.1 Discovery

`AgentCardDiscovery` should:

- fetch `/.well-known/agent-card.json`
- cache by base URL with TTL
- return the cached result unless refresh is requested

### 10.2 Transport selection

The client should inspect `supportedInterfaces` and prefer:

1. `JSONRPC`
2. `HTTP+JSON`

`GRPC` is out of scope for `1.0.0`.

The client always sends `A2A-Version: 1.0` and optionally `A2A-Extensions`.

### 10.3 JSON-RPC client behavior

The client must:

- use the PascalCase v1.0 method names
- preserve the request `id` on response matching
- distinguish JSON-RPC transport errors from A2A application errors
- parse SSE streams as sequences of `StreamResponse` objects

### 10.4 REST behavior

The client should support REST at least for:

- discovery
- `SendMessage`
- `SendStreamingMessage`
- `GetTask`
- `ListTasks`
- `CancelTask`
- `SubscribeToTask`
- `GetExtendedAgentCard`

Push-notification REST methods should exist in the API surface even if many servers return "not supported".

## 11. TaskStore

`TaskStore` is server-side infrastructure and stays outside `types`.

Required behavior:

- get by task id
- upsert task
- list tasks with cursor pagination semantics
- delete task

The built-in `InMemoryTaskStore` should support:

- TTL-based expiration
- bounded capacity
- deterministic ordering for `ListTasks`

`ListTasks` ordering requirement:

- descending by task status timestamp

## 12. Validation Strategy

Serde alone is not sufficient. The crate should provide explicit validation for:

- required proto-oneof invariants
- object-only metadata and params fields
- page size bounds
- required first-event behavior in subscription streams
- capability and extension checks

Validation should happen in three places:

1. deserialization-time when it is cheap and unambiguous
2. explicit `validate()` helpers on public request and response types
3. server/client operation boundaries before wire transmission

## 13. Testing Strategy

### 13.1 Type tests

Most important tests:

- serde round trips for every protocol object
- canonical examples from spec and proto
- rejection tests for invalid `oneof` states
- JSON base64 handling for `Part.raw`
- security scheme deserialization

### 13.2 Server integration

Use `tower::ServiceExt` against the axum router to test:

- well-known discovery
- tenant and non-tenant REST routes
- JSON-RPC dispatch
- SSE framing and ordering
- push and extended-card default errors

### 13.3 Client integration

Use `wiremock` to test:

- discovery caching
- JSON-RPC unary calls
- REST fallback behavior
- SSE parsing
- error mapping

## 14. Implementation Order

Phase 1:

- `Cargo.toml` feature wiring
- `lib.rs`
- `error.rs`
- `jsonrpc.rs`
- `types/*`

Phase 2:

- `server/*`
- `store.rs`
- server integration tests

Phase 3:

- `client/*`
- client integration tests
- examples and docs cleanup

## 15. Non-Negotiable Constraints

- no Clawhive-specific logic
- no `unsafe`
- no `unwrap()` outside tests
- no invented A2A error codes
- no divergence from tagged proto field names or shapes without an explicitly documented compatibility reason

## 16. Open Issues to Revisit Later

- whether to add optional compatibility aliases for older RC-era field names if real interop requires them
- whether to expose typed timestamp wrappers in a future opt-in feature
- whether to accept additional JSON-RPC endpoint aliases beyond `/rpc`
- whether a separate `compat` module is needed for cross-SDK interoperability quirks beyond `SecurityScheme`