Work with the a2a-java SUT
The a2a-java SUT is a Quarkus application that lives in the tck module of the a2a-java repository. The TCK code generator produces two CDI producer files (TckAgentCardProducer.java and TckAgentExecutorProducer.java) from Gherkin .feature files and copies them into the user's local clone of the a2a-java repo.
Architecture overview
scenarios/*.feature → codegen (parser + steps + java_emitter) → $A2A_JAVA_DIR/tck/
- Gherkin scenarios (
scenarios/*.feature) define SUT behavior via messageId prefix matching - Code generator (
codegen/) parses.featurefiles and emits Java sources - Jinja2 templates (
codegen/a2a-java/*.j2) produce the two CDI producer files - Generated output goes into the
tckmodule of the user's local a2a-java clone
Key generated files
| File | Template | Purpose |
|---|---|---|
TckAgentExecutorProducer.java |
TckAgentExecutorProducer.java.j2 |
CDI producer for AgentExecutor; routes by messageId prefix |
TckAgentCardProducer.java |
TckAgentCardProducer.java.j2 |
CDI producer for AgentCard with all three transports |
How the executor works
The generated AgentExecutor matches on the messageId prefix from incoming messages:
if (messageId.startsWith("tck-complete-task")) {
emitter.complete(A2A.toAgentMessage("Hello from TCK"));
return;
}
The TCK tests use tck_id("complete-task") which generates tck-complete-task-<session_hex>, matching the prefix.
Step 0: Set up the a2a-java clone
The A2A_JAVA_DIR environment variable must point to the root of a local clone of the a2a-java repository.
Check if the clone already exists as a sibling directory:
ls -d ../a2a-java 2>/dev/null && echo "Found at ../a2a-java"
If it exists, set:
export A2A_JAVA_DIR=$(cd ../a2a-java && pwd)
If not, clone it:
git clone https://github.com/a2aproject/a2a-java.git ../a2a-java
export A2A_JAVA_DIR=$(cd ../a2a-java && pwd)
Step 1: Regenerate the SUT
When Gherkin scenarios change, regenerate the Java producer files:
A2A_JAVA_DIR=/path/to/a2a-java make codegen-a2a-java-sut
This runs uv run python -m codegen.generator --target a2a-java --output $A2A_JAVA_DIR/tck which:
- Parses all
scenarios/*.featurefiles - Resolves step text to Trigger/Action objects via
codegen/steps.py - Emits
TckAgentCardProducer.javaandTckAgentExecutorProducer.javausing Jinja2 templates fromcodegen/a2a-java/ - Writes them into
$A2A_JAVA_DIR/tck/src/main/java/org/a2aproject/sdk/sut/
Adding new SUT behaviors
To add new behaviors:
- Add a scenario to the appropriate
.featurefile inscenarios/ - If the step text doesn't match existing patterns in
codegen/steps.py, add a new entry - If the action type is new, add it to
codegen/model.pyand handle it incodegen/java_emitter.py - Regenerate with
make codegen-a2a-java-sut
Modifying templates
Templates are in codegen/a2a-java/:
TckAgentExecutorProducer.java.j2— handler routing and action codeTckAgentCardProducer.java.j2— agent card with capabilities and interfaces
After modifying templates, regenerate with make codegen-a2a-java-sut.
Step 2: Build the SUT
Build from the root of the a2a-java repository so the parent pom and SDK modules are installed first:
cd $A2A_JAVA_DIR && mvn clean install
Step 3: Start the SUT
cd $A2A_JAVA_DIR/tck && mvn quarkus:dev -Dquarkus.console.enabled=false
The -Dquarkus.console.enabled=false flag disables the interactive Quarkus console, which is required when running in the background or non-interactively.
The SUT listens on port 9999 and serves all three transports:
- JSON-RPC —
http://localhost:9999(POST) - gRPC —
localhost:9999(same server, no separate port) - HTTP+JSON —
http://localhost:9999(REST routes)
Verify the SUT is running
curl -s http://localhost:9999/.well-known/agent-card.json | python3 -m json.tool
The agent card should list all three supportedInterfaces (JSONRPC, GRPC, HTTP+JSON).
Manual curl commands
When sending manual requests to the SUT, use tck/requirements/base.py as the source of truth for method names and sample_input fields in requirement specs (e.g., tck/requirements/core_operations.py) for request payload format.
The A2A protocol uses protobuf enum naming conventions:
- Roles:
ROLE_USER,ROLE_AGENT(notuser/USER) - Task states:
TASK_STATE_COMPLETED,TASK_STATE_WORKING, etc. - Parts:
{"text": "..."}(not{"kind": "text", "text": "..."}— the protobufoneofuses the field name to indicate part type)
SendMessage (JSON-RPC):
curl -s -X POST http://localhost:9999/ \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"1","method":"SendMessage","params":{"message":{"messageId":"tck-complete-task-1234","role":"ROLE_USER","parts":[{"text":"Hello from TCK"}]}}}'
ListTasks (JSON-RPC):
curl -s -X POST http://localhost:9999/ \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"1","method":"ListTasks","params":{"contextId":"<contextId>"}}'
GetTask (JSON-RPC):
curl -s -X POST http://localhost:9999/ \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"1","method":"GetTask","params":{"id":"<taskId>"}}'
Port conflicts
If port 9999 is already in use:
lsof -ti:9999 | xargs kill -9
Step 4: Run the TCK against the SUT
Use the run-tck skill to run the TCK against this SUT with the host being http://localhost:9999.
Step 5: Diagnose failures
Read reports/compatibility.json for structured results. For detailed diagnosis, use the diagnose-failure skill.
Common failure patterns
- Connection refused — SUT is not running
- Agent card 404 — Wrong URL; the a2a-java SDK serves at
/.well-known/agent-card.json - Unmatched messageId prefix — The scenario prefix doesn't match what the TCK test sends; check
tck_id()usage in tests vs prefix in.featurefile - Missing action — Scenario doesn't handle the expected behavior; add the appropriate Then/And step
Step 6: Iterate
The typical development cycle is:
- Add or modify a scenario in
scenarios/*.feature - Regenerate:
A2A_JAVA_DIR=/path/to/a2a-java make codegen-a2a-java-sut - Build:
cd $A2A_JAVA_DIR && mvn clean install - Restart the SUT
- Run the relevant TCK test:
uv run ./run_tck.py --sut-host http://localhost:9999 -- -k "test_name" -v - Repeat until tests pass
Running unit tests
The codegen has its own unit tests:
make unit-test
This runs tests in tests/unit/codegen/ covering the parser, step resolution, Java emitter, and generator CLI.