From be95c47e4790a67129edbbb059a30077bd074749 Mon Sep 17 00:00:00 2001 From: Jeff Mesnil Date: Wed, 1 Apr 2026 14:53:54 +0200 Subject: [PATCH 1/5] ci: update TCK workflow to use a2a-tck 1.0-dev branch with codegen SUT Switch the TCK CI workflow to pull from the 1.0-dev branch of a2a-tck and use its codegen to generate and run the a2a-java SUT, instead of the local tck/ module. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/run-tck.yml | 135 +++++++++++----------------------- 1 file changed, 44 insertions(+), 91 deletions(-) diff --git a/.github/workflows/run-tck.yml b/.github/workflows/run-tck.yml index 87a16980d..eeaa75c46 100644 --- a/.github/workflows/run-tck.yml +++ b/.github/workflows/run-tck.yml @@ -12,17 +12,10 @@ on: env: # Tag/branch of the TCK - TCK_VERSION: main - # Tell the TCK runner to report failure if the quality tests fail - A2A_TCK_FAIL_ON_QUALITY: 1 - # Tell the TCK runner to report failure if the features tests fail - A2A_TCK_FAIL_ON_FEATURES: 1 + TCK_VERSION: 1.0-dev # Tells uv to not need a venv, and instead use system UV_SYSTEM_PYTHON: 1 - # SUT_JSONRPC_URL to use for the TCK and the server agent - SUT_JSONRPC_URL: http://localhost:9999 - # Slow system on CI - TCK_STREAMING_TIMEOUT: 5.0 + SUT_URL: http://localhost:9999 # Only run the latest job concurrency: @@ -38,138 +31,98 @@ jobs: steps: - name: Checkout a2a-java uses: actions/checkout@v6 - - name: Checkout a2a-tck - uses: actions/checkout@v6 - with: - repository: a2aproject/a2a-tck - path: tck/a2a-tck - ref: ${{ env.TCK_VERSION }} - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@v5 with: java-version: ${{ matrix.java-version }} distribution: 'temurin' cache: maven - - name: check java_home - run: echo $JAVA_HOME + - name: Build a2a-java SDK + run: mvn -B install -DskipTests + - name: Extract a2a-java version + id: extract-version + run: | + A2A_JAVA_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "version=$A2A_JAVA_VERSION" >> $GITHUB_OUTPUT + echo "Detected a2a-java version: $A2A_JAVA_VERSION" + - name: Checkout a2a-tck + uses: actions/checkout@v6 + with: + repository: a2aproject/a2a-tck + path: a2a-tck + ref: ${{ env.TCK_VERSION }} - name: Set up Python uses: actions/setup-python@v5 with: - python-version-file: "tck/a2a-tck/pyproject.toml" + python-version-file: "a2a-tck/pyproject.toml" - name: Install uv and Python dependencies run: | pip install uv uv pip install -e . - working-directory: tck/a2a-tck - - name: Build with Maven, skipping tests - run: mvn -B install -DskipTests + working-directory: a2a-tck + - name: Generate a2a-java SUT + run: A2A_JAVA_SDK_VERSION=${{ steps.extract-version.outputs.version }} make codegen-a2a-java-sut + working-directory: a2a-tck - name: Start SUT - run: SUT_GRPC_URL=${{ env.SUT_JSONRPC_URL }} SUT_REST_URL=${{ env.SUT_JSONRPC_URL }} mvn -B quarkus:dev & #SUT_JSONRPC_URL already set - working-directory: tck + run: mvn -B quarkus:dev -Dquarkus.console.enabled=false & + working-directory: a2a-tck/sut/a2a-java - name: Wait for SUT to start run: | - URL="${{ env.SUT_JSONRPC_URL }}/.well-known/agent-card.json" + URL="${{ env.SUT_URL }}/.well-known/agent-card.json" EXPECTED_STATUS=200 TIMEOUT=120 RETRY_INTERVAL=2 START_TIME=$(date +%s) while true; do - # Calculate elapsed time CURRENT_TIME=$(date +%s) ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) - # Check for timeout if [ "$ELAPSED_TIME" -ge "$TIMEOUT" ]; then - echo "❌ Timeout: Server did not respond with status $EXPECTED_STATUS within $TIMEOUT seconds." + echo "Timeout: Server did not respond with status $EXPECTED_STATUS within $TIMEOUT seconds." exit 1 fi - # Get HTTP status code. || true is to reporting a failure to connect as an error HTTP_STATUS=$(curl --output /dev/null --silent --write-out "%{http_code}" "$URL") || true - echo "STATUS: ${HTTP_STATUS}" - # Check if we got the correct status code if [ "$HTTP_STATUS" -eq "$EXPECTED_STATUS" ]; then - echo "✅ Server is up! Received status $HTTP_STATUS after $ELAPSED_TIME seconds." + echo "Server is up! Received status $HTTP_STATUS after $ELAPSED_TIME seconds." break; fi - # Wait before retrying - echo "⏳ Server not ready (status: $HTTP_STATUS). Retrying in $RETRY_INTERVAL seconds..." + echo "Server not ready (status: $HTTP_STATUS). Retrying in $RETRY_INTERVAL seconds..." sleep "$RETRY_INTERVAL" done - - name: Run TCK id: run-tck timeout-minutes: 5 run: | set -o pipefail - ./run_tck.py --sut-url ${{ env.SUT_JSONRPC_URL }} --category all --transports jsonrpc,grpc,rest --compliance-report report.json 2>&1 | tee tck-output.log - working-directory: tck/a2a-tck - - name: Capture Diagnostics on Failure - if: failure() + uv run ./run_tck.py --sut-host ${{ env.SUT_URL }} -v 2>&1 | tee tck-output.log + working-directory: a2a-tck + - name: TCK Summary + if: always() && steps.run-tck.outcome != 'skipped' run: | - echo "=== Capturing diagnostic information ===" - - # Create diagnostics directory - mkdir -p tck/target/diagnostics - - # Capture process list - echo "📋 Capturing process list..." - ps auxww > tck/target/diagnostics/processes.txt - - # Find the actual Quarkus JVM (child of Maven process), not the Maven parent - # Look for the dev.jar process which is the actual application - QUARKUS_PID=$(pgrep -f "a2a-tck-server-dev.jar" || echo "") - if [ -n "$QUARKUS_PID" ]; then - echo "📊 Capturing thread dump for Quarkus JVM PID $QUARKUS_PID" - jstack $QUARKUS_PID > tck/target/diagnostics/thread-dump.txt || echo "Failed to capture thread dump" - if [ -f tck/target/diagnostics/thread-dump.txt ]; then - echo "✅ Thread dump captured ($(wc -l < tck/target/diagnostics/thread-dump.txt) lines)" + if [ -f a2a-tck/tck-output.log ]; then + # Extract everything after the first ═══ separator line + SUMMARY=$(sed -n '/^═══/,$p' a2a-tck/tck-output.log) + if [ -n "$SUMMARY" ]; then + echo '### TCK Results (Java ${{ matrix.java-version }})' >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY fi - else - echo "⚠️ No Quarkus JVM process found for thread dump" - echo "Available Java processes:" - ps aux | grep java | tee -a tck/target/diagnostics/processes.txt || true - fi - - # Capture Quarkus application logs (if available) - echo "📝 Checking for Quarkus logs..." - if [ -f tck/target/quarkus.log ]; then - cp tck/target/quarkus.log tck/target/diagnostics/ - echo "✅ Copied quarkus.log ($(wc -l < tck/target/quarkus.log) lines)" fi - - # Copy TCK server logs - if [ -f tck/target/tck-test.log ]; then - cp tck/target/tck-test.log tck/target/diagnostics/ - echo "✅ Copied tck-test.log ($(wc -l < tck/target/tck-test.log) lines)" - fi - - echo "" - echo "=== Diagnostic capture complete ===" - - name: Stop Quarkus Server + - name: Stop SUT if: always() run: | - # Find and kill the Quarkus process to ensure logs are flushed pkill -f "quarkus:dev" || true sleep 2 - - name: Upload TCK Diagnostics - if: failure() - uses: actions/upload-artifact@v6 - with: - name: tck-diagnostics-java-${{ matrix.java-version }} - path: | - tck/target/diagnostics/ - tck/a2a-tck/tck-output.log - retention-days: 7 - if-no-files-found: warn - - name: Upload TCK Compliance Report + - name: Upload TCK Reports if: always() uses: actions/upload-artifact@v6 with: - name: tck-compliance-report-java-${{ matrix.java-version }} - path: tck/a2a-tck/report.json + name: tck-reports-java-${{ matrix.java-version }} + path: a2a-tck/reports/ retention-days: 14 - if-no-files-found: ignore + if-no-files-found: warn \ No newline at end of file From 0af033223b08ca9dec10dd45be1ca88b834a5684 Mon Sep 17 00:00:00 2001 From: Jeff Mesnil Date: Wed, 1 Apr 2026 15:26:17 +0200 Subject: [PATCH 2/5] ci: run TCK workflow only after successful Build and Test Use workflow_run trigger to chain the TCK workflow after the "Build and Test" workflow completes successfully. Manual dispatch still works independently. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/run-tck.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run-tck.yml b/.github/workflows/run-tck.yml index eeaa75c46..deaeaafcb 100644 --- a/.github/workflows/run-tck.yml +++ b/.github/workflows/run-tck.yml @@ -1,13 +1,10 @@ name: Run TCK on: - # Handle all branches for now - push: - branches: - - main - pull_request: - branches: - - main + workflow_run: + workflows: ["Build and Test"] + types: + - completed workflow_dispatch: env: @@ -24,10 +21,11 @@ concurrency: jobs: tck-test: + if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest strategy: matrix: - java-version: [17, 21, 25] + java-version: [17] steps: - name: Checkout a2a-java uses: actions/checkout@v6 From 65fa0658de12ca12feca4fb4320cf0bed6cb1e49 Mon Sep 17 00:00:00 2001 From: Jeff Mesnil Date: Wed, 1 Apr 2026 16:26:07 +0200 Subject: [PATCH 3/5] ci: revert to push/PR triggers for TCK workflow Run TCK directly on push to main and PRs against main instead of chaining via workflow_run. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/run-tck.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tck.yml b/.github/workflows/run-tck.yml index deaeaafcb..f3d66d281 100644 --- a/.github/workflows/run-tck.yml +++ b/.github/workflows/run-tck.yml @@ -1,10 +1,12 @@ name: Run TCK on: - workflow_run: - workflows: ["Build and Test"] - types: - - completed + push: + branches: + - main + pull_request: + branches: + - main workflow_dispatch: env: @@ -21,7 +23,6 @@ concurrency: jobs: tck-test: - if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest strategy: matrix: From ced658fde5e20439fd1c3b5c49ebc4ca5dfa4d5f Mon Sep 17 00:00:00 2001 From: Jeff Mesnil Date: Thu, 9 Apr 2026 09:17:41 +0200 Subject: [PATCH 4/5] chore: remove tck module The TCK server is now generated by the a2a-tck codegen tool, making this hand-written module obsolete. Co-Authored-By: Claude Opus 4.6 --- pom.xml | 1 - tck/pom.xml | 65 ------------ .../io/a2a/tck/server/AgentCardProducer.java | 59 ----------- .../a2a/tck/server/AgentExecutorProducer.java | 100 ------------------ .../java/io/a2a/tck/server/package-info.java | 10 -- tck/src/main/resources/application.properties | 21 ---- 6 files changed, 256 deletions(-) delete mode 100644 tck/pom.xml delete mode 100644 tck/src/main/java/io/a2a/tck/server/AgentCardProducer.java delete mode 100644 tck/src/main/java/io/a2a/tck/server/AgentExecutorProducer.java delete mode 100644 tck/src/main/java/io/a2a/tck/server/package-info.java delete mode 100644 tck/src/main/resources/application.properties diff --git a/pom.xml b/pom.xml index 8078b339a..31c762137 100644 --- a/pom.xml +++ b/pom.xml @@ -570,7 +570,6 @@ server-common spec spec-grpc - tck test-utils-docker tests/server-common transport/jsonrpc diff --git a/tck/pom.xml b/tck/pom.xml deleted file mode 100644 index 026715dfb..000000000 --- a/tck/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - - org.a2aproject.sdk - a2a-java-sdk-parent - 1.0.0.Beta1-SNAPSHOT - - - a2a-tck-server - - Java SDK A2A TCK Server - Server example to use with the A2A TCK - - - - ${project.groupId} - a2a-java-sdk-reference-jsonrpc - - - org.a2aproject.sdk - a2a-java-sdk-reference-grpc - - - org.a2aproject.sdk - a2a-java-sdk-reference-rest - - - io.quarkus - quarkus-rest - provided - - - jakarta.enterprise - jakarta.enterprise.cdi-api - provided - - - jakarta.ws.rs - jakarta.ws.rs-api - - - - - - - io.quarkus - quarkus-maven-plugin - true - - - - build - generate-code - generate-code-tests - - - - - - - diff --git a/tck/src/main/java/io/a2a/tck/server/AgentCardProducer.java b/tck/src/main/java/io/a2a/tck/server/AgentCardProducer.java deleted file mode 100644 index 571e2bb1f..000000000 --- a/tck/src/main/java/io/a2a/tck/server/AgentCardProducer.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.a2a.tck.server; - - -import java.util.Collections; -import java.util.List; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; - -import io.a2a.server.PublicAgentCard; -import io.a2a.spec.AgentCapabilities; -import io.a2a.spec.AgentCard; -import io.a2a.spec.AgentInterface; -import io.a2a.spec.AgentSkill; -import io.a2a.spec.TransportProtocol; - -@ApplicationScoped -public class AgentCardProducer { - - private static final String DEFAULT_SUT_URL = "http://localhost:9999"; - - @Produces - @PublicAgentCard - public AgentCard agentCard() { - - String sutJsonRpcUrl = getEnvOrDefault("SUT_JSONRPC_URL", DEFAULT_SUT_URL); - String sutGrpcUrl = getEnvOrDefault("SUT_GRPC_URL", DEFAULT_SUT_URL); - String sutRestcUrl = getEnvOrDefault("SUT_REST_URL", DEFAULT_SUT_URL); - return AgentCard.builder() - .name("Hello World Agent") - .description("Just a hello world agent") - .supportedInterfaces(List.of( - new AgentInterface(TransportProtocol.JSONRPC.asString(), sutJsonRpcUrl), - new AgentInterface(TransportProtocol.GRPC.asString(), sutGrpcUrl), - new AgentInterface(TransportProtocol.HTTP_JSON.asString(), sutRestcUrl))) - .version("1.0.0") - .documentationUrl("http://example.com/docs") - .capabilities(AgentCapabilities.builder() - .streaming(true) - .pushNotifications(true) - .build()) - .defaultInputModes(Collections.singletonList("text")) - .defaultOutputModes(Collections.singletonList("text")) - .skills(Collections.singletonList(AgentSkill.builder() - .id("hello_world") - .name("Returns hello world") - .description("just returns hello world") - .tags(Collections.singletonList("hello world")) - .examples(List.of("hi", "hello world")) - .build())) - .build(); - } - - private static String getEnvOrDefault(String envVar, String defaultValue) { - String value = System.getenv(envVar); - return value == null || value.isBlank() ? defaultValue : value; - } -} - diff --git a/tck/src/main/java/io/a2a/tck/server/AgentExecutorProducer.java b/tck/src/main/java/io/a2a/tck/server/AgentExecutorProducer.java deleted file mode 100644 index b69a902e4..000000000 --- a/tck/src/main/java/io/a2a/tck/server/AgentExecutorProducer.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.a2a.tck.server; - -import java.util.List; - -import jakarta.annotation.PreDestroy; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; - -import io.a2a.server.agentexecution.AgentExecutor; -import io.a2a.server.agentexecution.RequestContext; -import io.a2a.server.tasks.AgentEmitter; -import io.a2a.spec.A2AError; -import io.a2a.spec.Task; -import io.a2a.spec.TaskNotCancelableError; -import io.a2a.spec.TaskState; -import io.a2a.spec.TaskStatus; - -@ApplicationScoped -public class AgentExecutorProducer { - - @Produces - public AgentExecutor agentExecutor() { - return new FireAndForgetAgentExecutor(); - } - - private static class FireAndForgetAgentExecutor implements AgentExecutor { - - @Override - public void execute(RequestContext context, AgentEmitter agentEmitter) throws A2AError { - Task task = context.getTask(); - - if (task == null) { - if (context == null) { - throw new IllegalArgumentException("RequestContext may not be null"); - } - if (context.getTaskId() == null) { - throw new IllegalArgumentException("Parameter 'id' may not be null"); - } - if (context.getContextId() == null) { - throw new IllegalArgumentException("Parameter 'contextId' may not be null"); - } - task = Task.builder() - .id(context.getTaskId()) - .contextId(context.getContextId()) - .status(new TaskStatus(TaskState.TASK_STATE_SUBMITTED)) - .history(List.of(context.getMessage())) - .build(); - agentEmitter.addTask(task); - } - - // Sleep to allow task state persistence before TCK subscribe test - if (context.getMessage() != null && context.getMessage().messageId().startsWith("test-subscribe-message-id")) { - int timeoutMs = Integer.parseInt(System.getenv().getOrDefault("RESUBSCRIBE_TIMEOUT_MS", "3000")); - System.out.println("====> task id starts with test-subscribe-message-id, sleeping for " + timeoutMs + " ms"); - try { - Thread.sleep(timeoutMs); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - // Immediately set to WORKING state - agentEmitter.startWork(); - System.out.println("====> task set to WORKING, starting background execution"); - - // Method returns immediately - task continues in background - System.out.println("====> execute() method returning immediately, task running in background"); - } - - @Override - public void cancel(RequestContext context, AgentEmitter agentEmitter) throws A2AError { - System.out.println("====> task cancel request received"); - Task task = context.getTask(); - if (task == null) { - System.out.println("====> No task found"); - throw new TaskNotCancelableError(); - } - if (task.status().state() == TaskState.TASK_STATE_CANCELED) { - System.out.println("====> task already canceled"); - throw new TaskNotCancelableError(); - } - - if (task.status().state() == TaskState.TASK_STATE_COMPLETED) { - System.out.println("====> task already completed"); - throw new TaskNotCancelableError(); - } - - agentEmitter.cancel(); - System.out.println("====> task canceled"); - } - - /** - * Cleanup method for proper resource management - */ - @PreDestroy - public void cleanup() { - System.out.println("====> shutting down task executor"); - } - } -} diff --git a/tck/src/main/java/io/a2a/tck/server/package-info.java b/tck/src/main/java/io/a2a/tck/server/package-info.java deleted file mode 100644 index f2b9319f2..000000000 --- a/tck/src/main/java/io/a2a/tck/server/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -@NullMarked -package io.a2a.tck.server; - -import org.jspecify.annotations.NullMarked; - -//The following had @Nullable annotation applied from JSpecify -//AgentCardProducer.java getEnvOrDefault method, -//AgentExecutorProducer.java execute method -// - diff --git a/tck/src/main/resources/application.properties b/tck/src/main/resources/application.properties deleted file mode 100644 index b23747b00..000000000 --- a/tck/src/main/resources/application.properties +++ /dev/null @@ -1,21 +0,0 @@ -# Use the new gRPC implementation which uses the main HTTP port -quarkus.grpc.server.use-separate-server=false -%dev.quarkus.http.port=9999 - -# Thread pool configuration for TCK testing -# Limit max threads to prevent resource exhaustion in CI environments -a2a.executor.core-pool-size=5 -a2a.executor.max-pool-size=15 -a2a.executor.keep-alive-seconds=60 - -# Enable debug logging for troubleshooting TCK failures -quarkus.log.category."io.a2a.server.requesthandlers".level=DEBUG -quarkus.log.category."io.a2a.server.events".level=DEBUG -quarkus.log.category."io.a2a.server.tasks".level=DEBUG -io.a2a.server.diagnostics.ThreadStats.level=DEBUG - -# Log to file for analysis -quarkus.log.file.enable=true -quarkus.log.file.path=target/tck-test.log -quarkus.log.file.level=DEBUG -quarkus.log.console.level=INFO From cc23f85c4424370e797011008b0e4152192e42fe Mon Sep 17 00:00:00 2001 From: Jeff Mesnil Date: Thu, 9 Apr 2026 10:36:19 +0200 Subject: [PATCH 5/5] chore: update actions/setup-python to v6 Signed-off-by: Jeff Mesnil --- .github/workflows/run-tck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tck.yml b/.github/workflows/run-tck.yml index f3d66d281..d0a0212be 100644 --- a/.github/workflows/run-tck.yml +++ b/.github/workflows/run-tck.yml @@ -51,7 +51,7 @@ jobs: path: a2a-tck ref: ${{ env.TCK_VERSION }} - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version-file: "a2a-tck/pyproject.toml" - name: Install uv and Python dependencies