Package Version: 0.0.1 Status: SDK v1.0 architecture in place; release validation pending Last Updated: 2026-05-10
A dedicated Agent-to-Agent (A2A) protocol daemon engine for distributed agent communication and multi-agent orchestration using the official A2A SDK server pattern.
The SDK Starlette app is the primary HTTP application and owns the A2A protocol surface:
GET /.well-known/agent-card.jsonPOST /— JSON-RPC compatibility endpoint accepting slash-style methods (message/send,tasks/get,tasks/cancel)POST /v1— SDK native JSON-RPC dispatcher (SendMessage,GetTask,CancelTask, with v0.3 compatibility enabled)GET /tasks/{task_id}/stream
The FastAPI app is mounted under /rest for operations only:
GET /rest/healthGET /rest/meGET /rest/{endpoint_id}POST /rest/{endpoint_id}/a2a_core_graphql
Removed legacy surfaces:
/rest/a2a-jsonrpc/rest/a2a/{endpoint_id}/...- direct
action=...dispatch throughA2ADaemonEngine.a2a() handlers/a2a_jsonrpc.pyhandlers/a2a_sdk_compat.py
- A2A SDK JSON-RPC over HTTP — slash-style compatibility at
POST /and the SDK native dispatcher atPOST /v1 - Public Agent Card at
/.well-known/agent-card.json - SDK
AgentExecutorintegration - DynamoDB-backed SDK
TaskStore - SSE task streaming with
Last-Event-IDreplay buffer - GraphQL operations API for daemon data
- JWT authentication for operations routes
- HTTP and experimental gRPC transports
poetry installexport A2A_TRANSPORT=http
export PORT=8001
export REGION_NAME=us-east-1
export AWS_ACCESS_KEY_ID=your_key
export AWS_SECRET_ACCESS_KEY=your_secret
export AUTH_PROVIDER=local
# Required for AUTH_PROVIDER=local. Must be ≥ 32 chars and not a default/weak
# value (e.g. CHANGEME, secret, password). See "Authentication" below.
export JWT_SECRET_KEY=replace-me-with-a-strong-random-secret-of-32-or-more-charspoetry run python -m a2a_daemon_engine.maincurl http://localhost:8001/rest/health
curl http://localhost:8001/.well-known/agent-card.json
# Compatibility endpoint (slash-style methods)
curl -X POST http://localhost:8001/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "method": "message/send", "params": {"message": {"role": "user", "parts": [{"text": "hello"}]}}, "id": 1}'
# SDK native dispatcher
curl -X POST http://localhost:8001/v1 \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "method": "SendMessage", "params": {"message": {"role": "user", "parts": [{"text": "hello"}]}}, "id": 1}'
# SSE task stream (replace TASK_ID with a real task identifier)
curl -N http://localhost:8001/tasks/TASK_ID/streamimport requests
response = requests.post(
"http://localhost:8001/",
json={
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{"text": "Hello"}],
}
},
"id": 1,
},
)
print(response.json())The operations app under /rest is JWT-authenticated. Two providers are
supported, selected at startup with AUTH_PROVIDER:
| Provider | Algorithm | Required env vars |
|---|---|---|
local |
HS256 | JWT_SECRET_KEY (≥ 32 chars, must not be a default/weak value); optional LOCAL_USER_FILE, ADMIN_STATIC_TOKEN |
cognito |
RS256 + JWKS | COGNITO_USER_POOL_ID, COGNITO_APP_CLIENT_ID, COGNITO_APP_SECRET, optional COGNITO_JWKS_URL |
Local users are obtained via POST /rest/auth/token (OAuth2 password grant
form-encoded). Public protocol routes (/, /v1, /.well-known/...,
/tasks/{task_id}/stream) are not gated by FlexJWTMiddleware.
DynamoDB partitioning uses a composite key:
partition_key = "{endpoint_id}#{part_id}"
endpoint_id is taken from URL paths like /rest/{endpoint_id}/a2a_core_graphql;
part_id is read from the Part-ID request header (or query parameter when
that path component is absent). All persistence and GraphQL queries flow
through this composite key, so cross-tenant access is hard-isolated at the row
level.
The HTTP daemon is started with the same command shown in Run; it
listens on http://0.0.0.0:${PORT:-8001} and serves both the SDK protocol
surface and the /rest operations app from a single process. For container
deployments, package the project with poetry install --without dev and run
the same command as the container entrypoint.
A2ADaemonEngine.a2a(**event) accepts JSON-RPC 2.0 dictionaries only — non
JSON-RPC payloads are rejected.
from a2a_daemon_engine.main import A2ADaemonEngine
daemon = A2ADaemonEngine(logger, transport="lambda", port=0, ...)
def lambda_handler(event, context):
# event must be a JSON-RPC 2.0 dictionary
return daemon.a2a(**event)The bridge in handlers/a2a_jsonrpc_bridge.py converts the JSON-RPC request
into the SDK's protobuf request types and dispatches to the same
DefaultRequestHandler used by the HTTP path.