A2A protocol integration for Embabel Agent Framework enabling agent-to-agent communication
Spring Boot auto-configuration for A2A endpoints.
Main configuration class providing default AgentCardHandler bean.
/**
* Spring Boot auto-configuration for A2A support.
* Automatically creates AgentCardHandler bean when module is on classpath.
*/
@Configuration
class A2AConfiguration {
/**
* Creates default AgentCardHandler bean.
* Exposes A2A endpoint at path "a2a" with all goals.
*
* @param agentPlatform Embabel agent platform
* @param a2aMessageHandler Request handler implementation
* @return AgentCardHandler instance
*/
@Bean
fun defaultAgentCardHandler(
agentPlatform: AgentPlatform,
a2aMessageHandler: AutonomyA2ARequestHandler
): AgentCardHandler
}Default Implementation:
return EmbabelServerGoalsAgentCardHandler(
path = "a2a",
agentPlatform = agentPlatform,
a2ARequestHandler = a2aMessageHandler,
goalFilter = { true } // Expose all goals
)Simply include the module as dependency:
Maven:
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-a2a</artifactId>
<version>0.3.3</version>
</dependency>Gradle:
implementation("com.embabel.agent:embabel-agent-a2a:0.3.3")Automatic Setup:
Default Endpoints:
GET /a2a/.well-known/agent.jsonPOST /a2aReplace default bean with custom configuration:
@Configuration
class CustomA2AConfiguration {
@Bean
fun defaultAgentCardHandler(
agentPlatform: AgentPlatform,
a2aRequestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = "my-agent",
agentPlatform = agentPlatform,
a2ARequestHandler = a2aRequestHandler,
goalFilter = { goal -> goal.tags.contains("external") }
)
}
}Define multiple AgentCardHandler beans:
@Configuration
class MultiEndpointConfiguration {
@Bean
fun publicEndpoint(
agentPlatform: AgentPlatform,
a2aRequestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = "a2a-public",
agentPlatform = agentPlatform,
a2ARequestHandler = a2aRequestHandler,
goalFilter = { goal -> goal.tags.contains("public") }
)
}
@Bean
fun internalEndpoint(
agentPlatform: AgentPlatform,
a2aRequestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = "a2a-internal",
agentPlatform = agentPlatform,
a2ARequestHandler = a2aRequestHandler,
goalFilter = { goal -> goal.tags.contains("internal") }
)
}
}
// Creates endpoints:
// - /a2a-public/.well-known/agent.json and /a2a-public
// - /a2a-internal/.well-known/agent.json and /a2a-internal@Configuration
@Profile("a2a")
class A2AProfileConfiguration {
@Bean
fun agentCardHandler(
agentPlatform: AgentPlatform,
a2aRequestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = "a2a",
agentPlatform = agentPlatform,
a2ARequestHandler = a2aRequestHandler,
goalFilter = { true }
)
}
}
// Enable with: --spring.profiles.active=a2a@Configuration
@ConditionalOnProperty(
prefix = "embabel.a2a",
name = ["enabled"],
havingValue = "true"
)
class ConditionalA2AConfiguration {
@Bean
fun agentCardHandler(
agentPlatform: AgentPlatform,
a2aRequestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = "a2a",
agentPlatform = agentPlatform,
a2ARequestHandler = a2aRequestHandler,
goalFilter = { true }
)
}
}application.yml:
embabel:
a2a:
enabled: true@Configuration
class PathConfiguredA2A {
@Value("\${a2a.endpoint.path:a2a}")
private lateinit var endpointPath: String
@Bean
fun agentCardHandler(
agentPlatform: AgentPlatform,
a2aRequestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = endpointPath,
agentPlatform = agentPlatform,
a2ARequestHandler = a2aRequestHandler,
goalFilter = { true }
)
}
}application.yml:
a2a:
endpoint:
path: my-agent-endpoint@Configuration
class FilterConfiguredA2A {
@Value("\${a2a.goal.tags:}")
private lateinit var allowedTags: String
@Bean
fun agentCardHandler(
agentPlatform: AgentPlatform,
a2aRequestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
val tagSet = allowedTags.split(",").map { it.trim() }.toSet()
return EmbabelServerGoalsAgentCardHandler(
path = "a2a",
agentPlatform = agentPlatform,
a2ARequestHandler = a2aRequestHandler,
goalFilter = { goal ->
tagSet.isEmpty() || goal.tags.any { it in tagSet }
}
)
}
}application.yml:
a2a:
goal:
tags: public,external,partnerA2AConfiguration depends on these Spring beans:
@Bean
fun agentPlatform(): AgentPlatform {
// Provided by embabel-agent-core
}@Service
class AutonomyA2ARequestHandler(
private val autonomy: Autonomy,
private val agenticEventListener: AgenticEventListener,
private val streamingHandler: A2AStreamingHandler
) : A2ARequestHandler
// Automatically provided by embabel-agent-a2a@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.authorizeHttpRequests { auth ->
auth
// Public AgentCard
.requestMatchers("/a2a/.well-known/agent.json").permitAll()
// Secured JSON-RPC
.requestMatchers("/a2a").authenticated()
.anyRequest().permitAll()
}.httpBasic()
return http.build()
}
}@SpringBootTest
class A2AConfigurationTest {
@Autowired
lateinit var agentCardHandler: AgentCardHandler
@Test
fun `should create default agent card handler`() {
assertNotNull(agentCardHandler)
assertEquals("a2a", agentCardHandler.path)
}
@Test
fun `should generate agent card`() {
val card = agentCardHandler.agentCard("http", "localhost", 8080)
assertEquals("http://localhost:8080/a2a", card.url)
assertTrue(card.capabilities.streaming)
}
}Issue: No AgentCardHandler beans found
Solution: Ensure component scanning includes A2A package:
@SpringBootApplication
@ComponentScan(basePackages = [
"com.embabel.agent.a2a",
"your.application.package"
])
class ApplicationIssue: Multiple handlers with same path
Solution: Ensure unique paths:
@Bean
fun handler1(...): AgentCardHandler =
EmbabelServerGoalsAgentCardHandler(path = "a2a-1", ...)
@Bean
fun handler2(...): AgentCardHandler =
EmbabelServerGoalsAgentCardHandler(path = "a2a-2", ...)Issue: Endpoints unavailable after startup
Solution: Check logs for A2AEndpointRegistrar messages:
INFO c.e.a.a.s.A2AEndpointRegistrar - Registering 1 A2A endpoints
INFO c.e.a.a.s.A2AEndpointRegistrar - Registering web endpoint under /a2a/.well-known/agent.jsontessl i tessl/maven-com-embabel-agent--embabel-agent-a2a@0.3.3