Skip to content

Add A2A (Agent-to-Agent) protocol support#217

Closed
minorun365 wants to merge 8 commits intoaws:mainfrom
minorun365:feature/a2a-protocol-support
Closed

Add A2A (Agent-to-Agent) protocol support#217
minorun365 wants to merge 8 commits intoaws:mainfrom
minorun365:feature/a2a-protocol-support

Conversation

@minorun365
Copy link

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

  • BedrockAgentCoreA2AApp: A Starlette-based application class for hosting A2A agents
  • AgentCard & AgentSkill: Models for describing agent capabilities
  • @entrypoint decorator: For defining message handlers
  • JSON-RPC 2.0 support: Standard A2A protocol implementation on port 9000

New Files

  • src/bedrock_agentcore/runtime/a2a_app.py - Main A2A application class
  • src/bedrock_agentcore/runtime/a2a_models.py - A2A data models
  • tests/bedrock_agentcore/runtime/test_a2a_app.py - Unit tests for A2A app
  • tests/bedrock_agentcore/runtime/test_a2a_models.py - Unit tests for A2A models

Usage Example

from bedrock_agentcore.runtime import AgentCard, AgentSkill, BedrockAgentCoreA2AApp

agent_card = AgentCard(
    name="My Agent",
    description="A helpful assistant",
    version="1.0.0",
    skills=[AgentSkill(id="qa", name="Q&A", description="Answer questions")],
)

app = BedrockAgentCoreA2AApp(agent_card=agent_card)

@app.entrypoint
async def handle_message(request, context):
    user_text = request.params.get("message", {}).get("parts", [{}])[0].get("text", "")
    return {"artifacts": [{"parts": [{"kind": "text", "text": f"Response to: {user_text}"}]}]}

if __name__ == "__main__":
    app.run()

Test Plan

  • All 54 unit tests pass
  • Linter checks pass
  • Tested locally with curl commands
  • Deployed to AgentCore Runtime and verified working

- 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)
@Moophic
Copy link

Moophic commented Mar 2, 2026

@sundargthb,
Can we get this beautiful PR reviewed and merged please?

from urllib.parse import quote


class JsonRpcErrorCode(int, Enum):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as _handle_agent_card..

)


class A2ARequestContextFormatter(logging.Formatter):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

minorun365 pushed a commit to minorun365/bedrock-agentcore-sdk-python that referenced this pull request Mar 12, 2026
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
@minorun365 minorun365 requested a review from a team March 12, 2026 06:48
- 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
@minorun365
Copy link
Author

@sundargthb Thanks for your review! Fixed it.

@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 85.48813% with 55 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@52bc194). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/bedrock_agentcore/runtime/a2a_app.py 81.22% 34 Missing and 12 partials ⚠️
src/bedrock_agentcore/runtime/a2a_models.py 93.18% 3 Missing and 6 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #217   +/-   ##
=======================================
  Coverage        ?   90.38%           
=======================================
  Files           ?       44           
  Lines           ?     4420           
  Branches        ?      664           
=======================================
  Hits            ?     3995           
  Misses          ?      240           
  Partials        ?      185           
Flag Coverage Δ
unittests 90.38% <85.48%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +25 to +28
VALIDATION_ERROR = -32502
THROTTLING = -32503
RESOURCE_CONFLICT = -32504
RUNTIME_CLIENT_ERROR = -32505
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""
# 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/"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Region can be parsed from the ARN

if asyncio.iscoroutinefunction(handler):
return await handler(*args)
else:
loop = asyncio.get_event_loop()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

claude and others added 3 commits March 13, 2026 02:23
- 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
@minorun365
Copy link
Author

@padmak30
Thank you for your review. Fixed it!
As for using A2A SDK, I think we should try it in another PR because it will be a large PR.

@tejaskash
Copy link
Contributor

@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 @app.entrypoint decorator pattern  and the industry has largely converged on the official a2a-sdk rather than custom implementations.

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.

@tejaskash tejaskash closed this Mar 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants