fix: serve the A2A protocol contract in the sub-agents - #28
Merged
Conversation
The A2A sub-agents have never been invocable. They were built on
BedrockAgentCoreApp — the HTTP contract, /invocations on port 8080 — while
AgentCore routes A2A traffic to a JSON-RPC server on port 9000 with an agent
card. Every InvokeAgentRuntime returned HTTP 424 with clean container logs, and
module 8's verify only read an SSM parameter, so nothing ever noticed.
- agent-code/shared/a2a_serve.py: serve a Strands agent on the contract.
Strands' A2AServer provides "/" (JSON-RPC) and /.well-known/agent-card.json
but NOT /ping, which the contract also requires, so this adds it. The ping
deliberately omits time_of_last_update: a timestamp that advances on every
ping reads as a continuous status change, the idle session timeout then never
fires, sessions run to MaxLifetime and drain the account's session quota
(per the contract docs' own warning).
- code-agent / research-agent: serve via the helper instead of
BedrockAgentCoreApp, and gain a name + description so they are discoverable
on the agent card. research-agent keeps its gateway tools (web search);
the MCP client is built at startup and its OAuth token is fetched per
session, so a long-lived process still gets fresh credentials.
- Dockerfiles: EXPOSE 9000, healthcheck on :9000/ping, and both copy shared/.
code-agent's build context therefore moves to agent-code/ (app.py updated
with dockerfile_pattern, matching how every other shared-code pattern builds).
- requirements: strands-agents[a2a] — A2AServer needs the a2a-sdk extra or the
import fails at container start.
- scripts/invoke.py --a2a <component>: invoke a sub-agent properly. Two things
differ from the orchestrator and each produces a misleading error: the
payload is a JSON-RPC message/send envelope (wrong shape → 424), and A2A is
not a client-facing protocol so those runtimes have no JWT authorizer and
need SigV4 (bearer token → "Authorization method mismatch"). Found the auth
half the hard way during live testing.
- MODULE_VERIFY[8] now runs that real invoke instead of reading an SSM
parameter. A verify that cannot fail for the thing the module promises is
what let this survive.
Verified:
- LIVE, first successful A2A invocations in this project's history:
`invoke.py --a2a code-agent "Reply with exactly: A2A OK"` → "A2A OK", and
`--a2a research-agent` answered a current-events question via the gateway's
web search with a cited URL. Both stacks CREATE_COMPLETE.
- Contract checked inside the container before deploying: /ping returns
exactly {"status":"Healthy"}, the agent card renders, and a JSON-RPC
message/send returned a proper artifact.
- tests/test_a2a_contract.py: 16 static guards (port, /ping presence, the
timestamp trap, no BedrockAgentCoreApp, EXPOSE 9000 and no 8080, COPY
shared/, the a2a extra, build context, and that module 8's verify actually
invokes). 12 of them fail against the previous code.
- pytest 115 passed; check-workshop-flow and check-deploy-config pass;
hadolint clean at error threshold; ruff clean on changed files.
- docs/TROUBLESHOOTING.md: the 424 entry no longer describes this as a known
break, and the auth-mismatch entry now has the both-directions table.
PARTICIPANT_GUIDE module 8 row and verify list updated.
|
Commit: Security Scan Results
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #16.
What
The A2A sub-agents have never been invocable. They were built on
BedrockAgentCoreApp— the HTTP contract,/invocationson port 8080 — while AgentCore routes A2A traffic to a JSON-RPC server on port 9000 with an agent card. EveryInvokeAgentRuntimereturned HTTP 424, container logs were clean, and module 8's verify only read an SSM parameter — so nothing ever caught it.Changes
agent-code/shared/a2a_serve.py— serves a Strands agent on the contract.A2AServergives/(JSON-RPC) and/.well-known/agent-card.jsonbut not/ping, which the contract also requires; this adds it.time_of_last_update. Per the contract docs' own warning, a timestamp that advances on every ping reads as a continuous status change: the idle timeout never fires, sessions run toMaxLifetime, and the account's session quota drains.name+descriptionso they're discoverable on the agent card. research-agent keeps its gateway tools (web search).EXPOSE 9000, healthcheck on:9000/ping, both copyshared/(so code-agent's build context moves toagent-code/, matching every other shared-code pattern).strands-agents[a2a]; without the extra the import fails at container start.invoke.py --a2a <component>— invokes a sub-agent properly. Two things differ from the orchestrator and each produces a misleading error: the payload is a JSON-RPCmessage/sendenvelope (wrong shape → 424), and A2A is not client-facing so those runtimes have no JWT authorizer and need SigV4 (bearer →Authorization method mismatch). I hit the auth half live.MODULE_VERIFY[8]now runs that real invoke. A verify that cannot fail for the thing the module promises is what let this survive.Verified
invoke.py --a2a code-agent "Reply with exactly: A2A OK"→AGENT TEXT: A2A OKinvoke.py --a2a research-agent …→ answered a current-events question through the gateway's web search, with a cited URLCREATE_COMPLETE/pingreturns exactly{"status":"Healthy"}, the agent card renders, andmessage/sendreturned a proper artifact.tests/test_a2a_contract.py— 16 static guards (port,/ping, the timestamp trap, noBedrockAgentCoreApp,EXPOSE 9000and no 8080,COPY shared/, the a2a extra, build context, and that module 8 actually invokes). 12 fail against the previous code.