Changelog
All notable changes to a2a-rust are documented in the project's CHANGELOG.md.
Versioning
a2a-rust follows Semantic Versioning:
- Major (1.0.0) — Breaking API changes
- Minor (0.2.0) — New features; before
1.0a minor may also carry breaking changes, batched and labelled as STABILITY.md requires — see Upgrading Between Minor Versions - Patch (0.2.1) — Bug fixes, backward compatible
All four workspace crates share the same version number and are released together.
Release Process
Releases are triggered by pushing a version tag:
git tag -a v0.3.0 -m "Release v0.3.0"
git push origin v0.3.0
The tag must be annotated (-a). A lightweight tag records no tagger and
no date, so the tag alone attests nothing about who cut the release; the
release workflow runs git cat-file -t and stops if it does not print tag.
Creating a release through the GitHub web UI produces a lightweight tag.
The release workflow automatically:
- Validates the tag — annotated; on the release-preparation commit; nothing
left under
[Unreleased]; breaking releases within STABILITY.md's cadence — against crate versions, CHANGELOG.md, CITATION.cff, SECURITY.md and the provenance manifest - Runs the full CI suite and security audit
- Packages all crates with SLSA build provenance, and checks each
.cratewas built from the tagged commit in a clean tree - Creates a GitHub release with notes extracted from CHANGELOG.md
- Publishes crates to crates.io in dependency order (behind a manually
approved
crates-ioenvironment)
Publish Order
a2a-protocol-types → a2a-protocol-server → a2a-protocol-client → a2a-protocol-sdk
Topological order over all dependency edges: server precedes client
because the client has a versioned dev-dependency on the server, which
cargo publish resolves against the crates.io index.
v0.14.0 (2026-09-26)
A minor release of fixes, most of them found by an audit of drop and cancellation paths and by the A2A project's two official conformance suites. Carries seven breaking changes and ten behaviour changes. It ships in the same calendar month as 0.13.0 under a declared cadence exception (STABILITY.md §3), because the fixes can't wait. Full detail in CHANGELOG.md.
- Tasks no longer wedge when a client goes away. A send dropped mid-commit
used to leave its task stuck; a blocking send whose client left left it
working; a failingafterhook orphaned it. Each now reaches a recorded state. - A refused credential answers the status a client acts on: HTTP
401withWWW-Authenticate, or403, and gRPC and SLIMRPCUNAUTHENTICATEDorPERMISSION_DENIED, instead of400everywhere (ADR 0014). This SDK's client drops a revoked token on it. - Conformance fixes from the official suites: message parts in media types
the card does not declare are refused (opt out with
allow_undeclared_input_modes()), JSON that is not a JSON-RPC request is-32600, and every task status carries a timestamp. Against ACTS every MUST passes on JSON-RPC, gRPC and HTTP+JSON; its one failure is a SHOULD this SDK deliberately does not follow (see Conformance History). - WebSocket is as robust as the HTTP bindings: the client reconnects after a dropped socket, a slow reader no longer stalls other calls on the connection, and the server closes a peer that stops reading.
ServerInterceptor::on_completeruns once per call with its outcome, so cleanup survives errors and disconnects; spans for every inbound call on every binding, andtracingon by default.- The published manifests no longer admit dependency versions with a RustSec advisory, and a CI gate keeps it that way.
- Breaking: seven items.
tracingis a default feature;default-features = falseon the SDK removes rustls; a dropped peer reads the same on every binding; gRPCUNAUTHENTICATED/PERMISSION_DENIEDareUnexpectedStatus401/403 and a peer'sCANCELLEDis not retried;ClientBuilder::from_cardrefuses a card with no A2A 1.x interface; graceful shutdown ends in-flight tasks before it drains; and the shippedTaskStores refuse to move a terminal task to another state. See Upgrading Between Minor Versions, which has a migration for each, and for the ten behaviour changes.
v0.13.0 (2026-09-20)
A minor release that makes the event log the record, and spends it on stream
resumption. Carries nine deliberate breaking changes, batched as
STABILITY.md
requires. This page said three until 2026-09-23; eight were labelled in the
changelog at release, and the ninth, the #[non_exhaustive] marking of
RetentionPolicy and PurgeReport, shipped under [Unreleased] — see the
correction at the top of CHANGELOG.md's 0.13.0 section. Full detail in
CHANGELOG.md.
- An append-only event log, beside the snapshot. A task's state is a fold,
and until now the fold was the only thing kept — so a wrong fold had nothing
to be checked against, which is how
#130 went unobserved.
TaskStoregainssupports_event_log,append_event,last_event_seqandread_events, implemented by every store this crate ships. The snapshot stays authoritative for reads; this is not event sourcing. - SSE frames carry an
id:, andLast-Event-IDresumes from it. A client that was disconnected sends back the position of the last frame it saw and receives exactly what it missed, rather than a snapshot it has to diff. The position is assigned once, where the event fans out to both the log and the stream, so theid:on the wire and theseqin the store are the same number by construction. - An executor can see who called it.
RequestContextgains acall_context, carrying caller identity, tenant, activated extensions and inbound headers across thetokio::spawnthat task-locals do not survive. - Idempotency keys on
message/send, as an SDK extension. A send whose connection drops is ambiguous — the task may exist or it may not — andmessage/sendcreates server-side state, so a client is right to refuse the retry. A key the server deduplicates on makes that retry safe: the same message replays to the first task in whatever state it reached, and a different message on the same key is refused rather than silently answered. Keys are tenant-scoped, every bundled store honours them, and the card advertises support only when the store actually has it. This is not part of A2A v1.0 — it ships ashttps://a2a-rust.com/extensions/idempotency/v1, so a server that enables it stays conformant. See Idempotent Sends. - W3C trace context crosses an A2A hop, so a delegation chain is one trace rather than several unrelated span trees.
- A failed task says why, as a
FailureClassa caller canmatchon instead of parsing English out of a message. - A conformance harness for
AgentExecutor, behind theconformancefeature: thirteen checks an implementation can run against itself. - Breaking: nine items.
RequestContext,IdempotencyClaim,KeyError,RetentionPolicyandPurgeReportare#[non_exhaustive];EventQueueReader::readyields aStreamEventcarrying the log position;FailureClass::ALLis a slice rather than a fixed-size array;PurgeReport::journal_orphans_deletedis noworphan_rows_deleted;build()refuses a signed agent card it would otherwise have to edit; a keyedmessage/sendis retried only against a peer that advertises the extension; andmessage.idis validated at ingress. See Upgrading Between Minor Versions, which has a migration for each.
v0.12.1 (2026-09-17)
A patch release: one specification correction, one security advisory, and documentation fixes. No API changes. Full detail in CHANGELOG.md.
- A new task on an existing context no longer inherits the previous task's
artifacts, history and metadata
(#130). When a client
sent a second message on a context without a
taskId, the server correctly minted a new task id and then built that task from the previous task's state, so every round returned the whole context's accumulated artifacts, growing by one each turn.a2a.protoscopes all three fields to the task. State now carries forward only when the ids match — theinput-requiredcontinuation (spec §3.4.3) the carry-forward was written for in v0.6.0, where wiping accumulated state would be the bug. Affects 0.11.0 and 0.12.0. rustlsadvanced to 0.23.45 (RUSTSEC-2026-0285): rustls accepted TLS 1.3 handshake messages sent at the wrong encryption level when they followed a key-changing message in the same record, contrary to RFC 8446 §5.1. The handshake transcript stays authenticated, so this is a conformance failure rather than a forgery or confidentiality break. The SLIMRPC binding cannot take the upgrade — an upstreamaws-lc-rspin forecloses it — and carries a documented, dated waiver instead; the four published crates use theringbackend and are on 0.23.45.- Documentation corrections: three wrong claims about the
rig-agentexample, a TCK figure carried in seven places that measurement contradicted, and six table rows rendering short of their declared columns.
v0.12.0 (2026-09-10)
The deliberate breaking minor before the two clean minors STABILITY.md requires; the migration for every item is in Upgrading Between Minor Versions. Highlights — see CHANGELOG.md for the full list:
- Fifteen public configuration structs are
#[non_exhaustive]with awith_*setter for every field, so a field can be added later without a break;HandlerLimitsgainspush_delivery_budgetandexecutor_drain_timeout, the client'sWebSocketTransportConfiggainsmax_pending_requests. - Bounds that were missing — a drain that never closes, the push
delivery budget, DNS lookups in the push sender, in-flight WebSocket
requests, served gRPC connections (
with_max_connections), and the SLIMRPC binding's slow-consumer stall. - Every dependency refreshed, and the weekly mutation sweep's survivors
addressed: the three proved equivalent carry
#[mutants::skip]. - An executor's error text reaches a blocking caller on the Failed task's status message; the blocking path reports every push outcome; one deadline per token request and per stream start in the client.
- Adoption:
tools/a2a-cli(unpublished), a Rust worker inmulti-lang-team,examples/resilient-agent, the upgrade guide, andAgentCard::with_streaming,CardFetchOptions,ClientBuilder::chosen_interface. - Gates: reachability, timeout nesting, inert knobs, the reverse
API-reference check, the weekly codecov-ignore check; DCO on every push
to
main; cargo-semver-checks over all four crates with all features; the toolchain action pinned by SHA.
v0.8.0 to v0.11.0
Not summarised here; each release's section in CHANGELOG.md is the record, and the breaking items are in Upgrading Between Minor Versions. (This page carried a "v0.7.0 upcoming" section until 2026-09-10, four releases after 0.7.0 shipped — the decay the sitemap and API-reference gates exist to catch, and which nothing checks for this page.)
v0.6.0 (2026-06-10)
Fixed
- SSE disconnects no longer fail running tasks — clients reattach via
SubscribeToTask; the persistence channel already had every event. Working → Workingis a valid transition — repeated Working status updates (progress narration) no longer mark the task Failed in the store while the stream shows success.Task.historyis now populated — user messages at send time, agentMessageevents from both processors, capped at 1,024 (oldest dropped);historyLengthtruncation is now observable.- Continuations preserve accumulated state — follow-up messages no longer wipe a task's artifacts, metadata, and history.
- Client surfaces streaming errors — JSON-RPC error envelopes on
message/streammap toClientError::Protocolinstead of an empty stream. - Legacy
tasks/resubscribealias accepted alongsideSubscribeToTaskandtasks/subscribe.
Added
incident-responseexample — three-agent team demonstrating multi-turnINPUT_REQUIRED, delegation, streaming progress, artifacts, and cooperative cancellation; runs fully local.- TCK test 20:
a2a_media_type_accepted— servers must accept the registeredapplication/a2a+jsonmedia type that real clients send. - PostgreSQL integration suite — live-database tests for all five
Postgres store files, run in CI against a
postgres:16service.
Security
rustls-webpkiupgraded to 0.103.12 — Fixes RUSTSEC-2026-0098 (GHSA-965h-392x-2mh5): URI name constraints were incorrectly accepted during X.509 path validation. Reachesa2a-protocol-clienttransitively viarustlswhen thetls-rustlsfeature is enabled. No API changes — drop-in update.
v0.5.0 (2026-04-02)
Breaking Changes
TaskStore::save()andinsert_if_absent()now take&Taskinstead of ownedTask— Eliminates forced clones at every save call site. CustomTaskStoreimplementations must update their method signatures. See CHANGELOG.md for migration guide.- Version bump: 0.4.1 → 0.5.0 across all four crates.
Performance
- Broadcast channel capacity 64 → 256 — Pushes per-event cost inflection from ~52 to ~252 events.
serde_helpersmodule —SerBuffer(thread-local buffer reuse, 2.3× less small-payload overhead) anddeser_from_str/deser_from_slice(borrowed deserialization, ~15-25% fewer allocs).- SSE frame building: thread-local reusable buffer — Amortized 0 allocations per event vs previous 1.
InMemoryTaskStore::list()— O(n log n) → O(log n + page_size) — AddedBTreeSet<TaskId>sorted index andHashMap<String, BTreeSet<TaskId>>context index. Eliminates the per-call sort that caused 20-70× regressions at 10K+ tasks.InMemoryTaskStore::insert()— Update fast path — Skips BTreeSet and context index operations when updating an existing task with unchanged context_id. Reduces save() from ~2.5µs to ~700ns for the common update case.- SSE per-event serialization — 2 allocations → 1 —
build_sse_message_frame()serializes JSON directly into the SSE frame buffer viaserde_json::to_writer, skipping the intermediateserde_json::to_string()allocation. Partdeserialization — ~80 fewer allocations per Task — Replaced#[serde(flatten)]with a hand-rolledDeserializeimplementation that reads all fields in a single pass without intermediateserde_json::Valuebuffering.
Benchmarks
- Transport payload scaling extended to 1MB — 100KB and 1MB payloads in
transport_throughput.rs. - New:
protocol/payload_scalingisolation benchmarks — Pure serde cost from 64B to 1MB;to_vecvsSerBuffer,from_slicevsfrom_str. - Cache-busting for
data_volume/getat 100K — 4MB allocation to flush CPU caches between populate and measure. - Documentation comments — Connection reuse, cold start vs steady state, concurrent store anomaly.
- New:
advanced_scenariossuite — Tenant resolver overhead (header, bearer, path), agent card hot-reload and discovery endpoint, subscribe fan-out (1-10 concurrent subscribers), streaming artifact accumulation cost (task.clone() at 0-500 depth), pagination full walk (100-1K tasks), extended agent card round-trip. - New:
production_scenariossuite — SubscribeToTask reconnection, cold start vs steady-state, concurrent cancel+subscribe race, 7-step E2E orchestration, push config CRUD round-trip, parallel agent burst (10-100 agents), dispatch routing isolation. - Fixed:
MultiEventExecutor— Was emitting invalidWorking → Workingstate transitions; now emitsWorkingonce, then N artifacts, thenCompleted. - Fixed:
InMemoryTaskStore::insert()— Optimized update path skips redundant BTreeSet/context index operations, reducing save() variance from [1.5µs, 4.2µs] to ~700ns. - Fixed: Criterion measurement_time warnings — Added measurement_time to 23+ groups across 8 files.
- Improved:
data_volumeget benchmark — Uses 64 pseudo-random keys instead of single midpoint to avoid HashMap bucket anomalies. - Improved:
backpressurestream volume — Added 252 and 502 event counts to push per-event signal above CI noise floor; added timer calibration benchmarks.
v0.4.1 (2026-03-31)
Bug Fixes
- REST streaming deserialization failure —
EventStreamnow correctly parses bareStreamResponseJSON for REST SSE streams instead of expecting a JSON-RPC envelope wrapper. Fixes"data did not match any variant of untagged enum JsonRpcResponse"errors when streaming via the REST binding.
v0.4.0 (2026-03-31)
Breaking Changes
- Part wire format → v1.0 flat oneof —
{"type":"text","text":"..."}→{"text":"..."}. File parts:{"raw":"base64","filename":"f.png","mediaType":"image/png"}or{"url":"https://..."}.PartContentenum variants:Text,Raw,Url,Data. - Enum serialization → SCREAMING_SNAKE_CASE —
TaskState:"completed"→"TASK_STATE_COMPLETED".MessageRole:"user"→"ROLE_USER". Legacy lowercase accepted on deserialization. SendMessageResponse→ externally tagged —{"task":{...}}or{"message":{...}}(was untagged).- Agent Card path —
/.well-known/agent-card.json(previously/.well-known/agent.json). OAuthFlows→ enum (oneof) — One flow per scheme, matching proto.- Error responses → AIP-193 format —
{"error":{"code":N,"status":"...","message":"...","details":[...]}}withgoogle.rpc.ErrorInfo.
Bug Fixes
- HTTP/gRPC error status code mappings corrected for all 9 A2A error types
- Blocking SendMessage returns on interrupted states (
INPUT_REQUIRED/AUTH_REQUIRED) ListTasksincludeArtifactsparameter now applied (default false omits artifacts)
Added
ErrorCode::a2a_reason(),http_status(),grpc_status(),error_info_data()TaskState::is_interrupted()- Error constructors:
push_not_supported(),content_type_not_supported(),extension_support_required(),version_not_supported()
v0.3.4 (unpublished)
A standalone 0.3.4 release was never tagged or published; these changes first shipped as part of v0.4.0 (2026-03-31).
Bug Fixes
- SendMessage rejects terminal tasks — Messages to Completed/Failed/Canceled/Rejected tasks now return
UnsupportedOperationper spec CORE-SEND-002 - SendMessage validates unknown taskId — Client-provided
taskIdthat doesn't reference an existing task now returnsTaskNotFoundper spec section 3.4.2 historyLengthparameter applied —GetTaskandListTasksnow truncate message history to the requested length;historyLength=0returns no history- SubscribeToTask terminal task error — Subscribing to a terminal task now returns
UnsupportedOperationinstead of a generic internal error
Added
Artifact::validate()method — Validates non-emptypartsper A2A specPart::text_content()accessor — Extracts text from a text partServerError::UnsupportedOperationvariant — Maps toErrorCode::UnsupportedOperation(-32004)- SubscribeToTask emits Task snapshot as first event — Prevents clients from missing state on reconnection
ClientBuilder::from_card()preserves tenant — Tenant fromAgentInterfaceis preserved inClientConfig::tenantClientBuilder::with_tenant()method — Explicit tenant configuration for multi-tenancyClientConfig::tenantfield — Default tenant for all requestsTaskListResponserequired fields —next_page_token,page_size,total_sizealways present per proto specSendStreamingMessagefirst event — Task snapshot emitted as first SSE event (like SubscribeToTask)GetExtendedAgentCardcapability check — Returns correct errors per spec section 3.1.11
v0.3.3 (2026-03-30)
Bug Fixes
find_task_by_contextprefers non-terminal tasks — Stale terminal tasks no longer shadow active tasks for the samecontext_idcontext_locksmemory leak — Stale per-context mutexes are now pruned when the map exceedsmax_context_locksPayloadTooLargeerror code — ReturnsInvalidRequest(-32600) instead ofInternalError(-32603)- Params-level
context_idvalidation — Now validated viavalidate_id()like message-levelcontext_id eviction_interval=0panic — No longer panics; treated as "disable periodic eviction"- Push config deterministic ordering —
list()results sorted by(task_id, config_id) - Cancel task TOCTOU race narrowed — Re-reads task before saving
Canceledto avoid overwriting concurrent completion page_sizeclamped at handler — Prevents oversized allocations from untrusted input- Tenant store read-path DoS — Read operations no longer allocate tenant partitions
from_pool()schema parity — Now matcheswith_migrations()schema (addscreated_at, composite index)- JSON-RPC serialization error handling — Returns proper errors instead of
nullresults; uses HTTP 200 per spec MessageRolewire format — Serializes as lowercase"user"/"agent"per A2A spec- Unused example deps removed —
rig-core,bytes
v0.3.2 (2026-03-30)
Bug Fixes
- Task ID not reused for non-terminal continuations —
on_send_messagenow reuses the client-providedtask_idwhen it matches a stored non-terminal task (#66)
v0.3.0 (2026-03-19)
Performance
-
TCP_NODELAYon all sockets — Eliminates ~40ms Nagle/delayed-ACK latency on SSE streaming and JSON-RPC responses -
InMemoryTaskStoreBTreeMap migration — List queries now O(page_size) instead of O(n): 17–164× faster at 1K–100K tasks -
Batch clone removal — JSON-RPC batch dispatch no longer clones each request item
-
memory_overheadbenchmark fix — CI no longer crashes on zero-variance allocation counts -
Benchmark server
TCP_NODELAY— Streaming benchmarks now report actual SDK latency (~1.5ms) instead of Nagle-inflated ~44ms -
Axum framework integration (
axumfeature) —A2aRouterfor idiomatic Axum servers. All 11 REST methods, composable with other Axum routes/middleware. -
TCK wire format conformance tests — 44 tests validating wire format compatibility against the A2A v1.0 specification.
-
Mutation testing — zero surviving mutants across all library crates.
-
Comprehensive inline unit tests — 1,769 tests across all crates covering the full request pipeline, dispatchers, push delivery, streaming, and more.
Beyond-Spec Enhancements
- OpenTelemetry metrics (
otelfeature) —OtelMetricswith native OTLP export - Connection pool metrics —
ConnectionPoolStatsandon_connection_pool_statscallback - Hot-reload agent cards —
HotReloadAgentCardHandlerwith file polling and SIGHUP - Store migration tooling (
sqlitefeature) —MigrationRunnerwith V1–V3 built-in migrations - Per-tenant configuration —
PerTenantConfigandTenantLimitsfor differentiated service levels TenantResolvertrait —HeaderTenantResolver,BearerTokenTenantResolver,PathSegmentTenantResolver- Agent card signing E2E — test 79 in agent-team suite (
signingfeature)
Bug Fixes (Passes 7–10)
- Event queue serialization error swallowing fixed with proper error propagation
- Capacity eviction now falls back to non-terminal tasks when terminal tasks are insufficient
- Lagged event count now exposed in reader warnings for observability
- Timeout errors now correctly classified as retryable (
ClientError::Timeout) - SSE parser O(n) dequeue replaced with
VecDequefor O(1)pop_front - Double-encoded path traversal bypass fixed with two-pass percent-decoding
- gRPC stream errors now preserve protocol error codes
- Rate limiter TOCTOU race fixed with CAS loop
- Push config store now enforces global limits (DoS prevention)
Concurrency, Security & Robustness Fixes (Session 2026-03-19, Pass 15)
Streaming reliability:
- H5: Separate persistence channel — The background event processor now uses a dedicated mpsc persistence channel instead of subscribing to the broadcast channel after executor start. This eliminates the race where fast executors could emit events before the subscription was active, causing the task store to miss updates.
Transport concurrency fixes:
- C1: gRPC Mutex removed — gRPC transport no longer serializes concurrent requests through a Mutex. The tonic
Channel(internally multiplexed, cheap to clone) is now cloned per request, enabling full concurrent throughput. - C2: WebSocket transport redesigned — Replaced reader Mutex with a dedicated background reader task and
HashMap<RequestId, PendingRequest>message routing, eliminating the deadlock where holding the reader lock blocked all other requests. - C3: WebSocket auth headers — Extra headers (including auth interceptor headers) are now applied to the WebSocket upgrade HTTP request via the tungstenite
IntoClientRequesttrait.
Security hardening:
- H6: SSRF DNS rebinding prevention — Added
validate_webhook_url_with_dns()that resolves DNS before IP validation, preventing DNS rebinding attacks where a hostname resolves to a public IP during validation but a private IP during the actual request. - H8: Agent card body size limit — Added a 2 MiB body size limit on agent card fetch responses to prevent OOM from malicious or misconfigured card endpoints.
Performance:
- H7: Retry transport optimization — Params are now serialized to bytes once before the retry loop, then deserialized for each attempt, avoiding deep-clone of the
serde_json::Valuetree on every retry. - L4: Shutdown polling improvement — Reduced shutdown polling interval from 50ms to 10ms with deadline-aware sleep for faster graceful shutdown.
Error handling & validation:
- M3:
find_task_by_contexterror propagation — Changed from silently swallowing errors to propagating them via?. - M13: TaskId/ContextId TryFrom — Added
TryFrom<String>andTryFrom<&str>impls that reject empty and whitespace-only strings. - M14: CachingCardResolver error handling —
new()andwith_path()now returnClientResult<Self>instead of silently producing empty URLs on invalid input. - M16: REST tenant passthrough — Fixed REST dispatch handlers to pass the extracted tenant from the URL path through to all handler methods.
- M17: FileContent validation — Added
validate()method that checks at least one ofbytes/uriis set. - M19: Push URL validation — Added
validate()method onTaskPushNotificationConfigfor URL format validation. - L14: Timestamp validation — Added
has_valid_timestamp()method toTaskStatusfor RFC 3339 timestamp validation.
Resource limits:
- M9: WebSocket concurrency limit — Added per-connection
Semaphore(64)to limit concurrent spawned tasks per WebSocket connection, preventing resource exhaustion from a single client. - M10: WebSocket message size limit — Added 4 MiB message size check for incoming WebSocket frames.
- M8: JSON-RPC batch size limit — Confirmed existing
max_batch_sizeinDispatchConfig.
Database safety:
- L7: SQLite parameterized queries — Changed
LIMITfromformat!string interpolation to parameterized queries, preventing potential SQL injection.
v0.2.0 (2026-03-15)
Initial implementation of A2A v1.0.0 with all 11 protocol methods, dual transport (JSON-RPC + REST), SSE streaming, push notifications, agent card discovery, HTTP caching, enterprise hardening, and 600+ tests.
For the complete version history, see CHANGELOG.md.