Add A2A (Agent-to-Agent) protocol support#217
Conversation
- Add BedrockAgentCoreA2AApp class for hosting A2A agents - Add A2A models (AgentCard, AgentSkill, JsonRpcRequest/Response, etc.) - Add @entrypoint decorator for message handling - Support JSON-RPC 2.0 protocol on port 9000 - Export A2A classes from runtime module - Add comprehensive unit tests (54 tests)
|
@sundargthb, |
| from urllib.parse import quote | ||
|
|
||
|
|
||
| class JsonRpcErrorCode(int, Enum): |
There was a problem hiding this comment.
the codes here match this page but contradicts with the A2A Protocol contract page.. need to check the valid range
| return JSONResponse(card_dict) | ||
| except Exception as e: | ||
| self.logger.exception("Failed to serve Agent Card") | ||
| return JSONResponse({"error": str(e)}, status_code=500) |
There was a problem hiding this comment.
str(e) is passed directly into the JSON-RPC error response. ... internal exception details (paths, library errors, connection strings) can reach external agents ? the HTTP app.py has the same str(e) pattern — so this isn't new to the PR, it's consistent with existing code. But A2A is explicitly cross-organizational (A2A Enterprise Features)
| self.logger.exception("Request failed (%.3fs)", duration) | ||
| return self._jsonrpc_error_response( | ||
| body.get("id") if "body" in dir() else None, | ||
| JsonRpcErrorCode.INTERNAL_ERROR, |
There was a problem hiding this comment.
same comment as _handle_agent_card..
| ) | ||
|
|
||
|
|
||
| class A2ARequestContextFormatter(logging.Formatter): |
There was a problem hiding this comment.
The HTTP app has @async_task, add_async_task()/complete_async_task(), and force_ping_status()/clear_forced_ping_status(). Without these, developers can't signal HealthyBusy during long-running agent workflows, which means the runtime may incorrectly scale down or over-route. Can either finish the port from app.py or remove the unused attributes/logic
Add missing A2A protocol standard error codes (-32001 to -32007) alongside existing AgentCore Runtime error codes (-32501 to -32505). The two sets serve different layers: protocol-level (task management) vs infrastructure-level (throttling, resource management). Added corresponding tests. Addresses review feedback on PR aws#217 regarding error code alignment between A2A protocol spec and AWS AgentCore documentation. https://claude.ai/code/session_01Fnc4LHzBPBej7FK7ooT2La
- Remove unused imports (JsonRpcRequest, pytest) in tests - Initialize body=None before try block; replace `"body" in dir()` with `body is not None` - Add `isinstance(body, dict)` guard so non-object JSON payloads return INVALID_REQUEST (-32600) instead of INTERNAL_ERROR (-32603) - Use `_convert_to_serializable()` / `_safe_serialize_to_json_string()` for both streaming and non-streaming paths so dataclass helpers (A2AArtifact, etc.) serialize correctly - Fall back to `request.base_url` when AGENTCORE_RUNTIME_URL is unset so the agent card always contains a reachable URL - Add tests for dataclass serialization (sync + streaming) and non-object payload rejection
0a85319 to
ee117a9
Compare
|
@sundargthb Thanks for your review! Fixed it. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #217 +/- ##
=======================================
Coverage ? 90.38%
=======================================
Files ? 44
Lines ? 4420
Branches ? 664
=======================================
Hits ? 3995
Misses ? 240
Partials ? 185
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
679bab9 to
8c9cd91
Compare
| VALIDATION_ERROR = -32502 | ||
| THROTTLING = -32503 | ||
| RESOURCE_CONFLICT = -32504 | ||
| RUNTIME_CLIENT_ERROR = -32505 |
There was a problem hiding this comment.
| """ | ||
| # URL encode the ARN (safe='' means encode all special characters) | ||
| escaped_arn = quote(agent_arn, safe="") | ||
| return f"https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{escaped_arn}/invocations/" |
There was a problem hiding this comment.
Region can be parsed from the ARN
| if asyncio.iscoroutinefunction(handler): | ||
| return await handler(*args) | ||
| else: | ||
| loop = asyncio.get_event_loop() |
There was a problem hiding this comment.
get_event_loop is deprecated for Python 3.10+
Use run_in_threadpool similar to app.py
| return json.dumps(log_entry, ensure_ascii=False) | ||
|
|
||
|
|
||
| class BedrockAgentCoreA2AApp(Starlette): |
There was a problem hiding this comment.
There should be a common app instead of one per protocol. We could have the App take in a protocol parameter to instantiate the right protocol and default to http.
Also we should use the A2A SDK, a lot of stuff around agent-card, json rpc comes built-in.
- Fix A2A error codes to match the A2A protocol specification (-32001 to -32006) - Parse region from ARN in build_runtime_url instead of requiring a separate parameter - Replace deprecated asyncio.get_event_loop() with starlette's run_in_threadpool - Sanitize error messages in JSON-RPC responses to avoid leaking internal details https://claude.ai/code/session_012D4PLASRQsAhZadWYqaPim
… async task lifecycle - Sanitize str(e) in _handle_agent_card to avoid leaking internal details (comment aws#3) - Port async task lifecycle methods from app.py: async_task decorator, add_async_task, complete_async_task, force_ping_status, clear_forced_ping_status, get_async_task_info (comment aws#2) - These methods enable developers to signal HealthyBusy status during long-running A2A workflows, preventing incorrect scaling decisions https://claude.ai/code/session_012D4PLASRQsAhZadWYqaPim
|
@padmak30 |
|
@minorun365 Thanks for putting this together — A2A support is an important addition and we appreciate the effort here, especially revising this PR multiple times. After reviewing this against the A2A spec and looking at how other frameworks (ADK, Strands, CrewAI, LangGraph, Pydantic AI) approach A2A, we had an internal discussion and decided to go in a different direction for developer experience. The core issue is that the A2A protocol's task lifecycle and streaming model don't map cleanly to the We're working on a new proposal that leans on the official a2a-sdk as a dependency and focuses our code on the AgentCore-specific value-add (runtime headers, health checks, deployment defaults). We'll be sending out a PR for this by the end of the month . This is a short snippet of what we're thinking of developing. from bedrock_agentcore.runtime.a2a import serve_a2a
# Google ADK
from google.adk.a2a.executor import A2aAgentExecutor
serve_a2a(agent_card=card, executor=A2aAgentExecutor(runner=runner))
# Strands
from strands.multiagent.a2a.executor import StrandsA2AExecutor
serve_a2a(agent_card=card, executor=StrandsA2AExecutor(agent))Closing this one out, but thank you again for your contribution to A2A support, and we welcome future . I will make sure to tag you on the new PR to get your input. |
Summary
This PR adds support for the A2A (Agent-to-Agent) protocol, enabling agents to communicate with each other using the JSON-RPC 2.0 protocol.
New Features
New Files
src/bedrock_agentcore/runtime/a2a_app.py- Main A2A application classsrc/bedrock_agentcore/runtime/a2a_models.py- A2A data modelstests/bedrock_agentcore/runtime/test_a2a_app.py- Unit tests for A2A apptests/bedrock_agentcore/runtime/test_a2a_models.py- Unit tests for A2A modelsUsage Example
Test Plan