A2A protocol integration for Embabel Agent Framework enabling agent-to-agent communication
Quick solutions for frequently needed operations.
@Configuration
class CustomA2AConfig {
@Bean
fun defaultAgentCardHandler(
agentPlatform: AgentPlatform,
requestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = "custom-path", // Changes /a2a to /custom-path
agentPlatform = agentPlatform,
a2ARequestHandler = requestHandler,
goalFilter = { true }
)
}
}Result: Endpoints at /custom-path/.well-known/agent.json and /custom-path
By Tag:
goalFilter = { goal -> goal.tags.contains("public") }Exclude Internal:
goalFilter = { goal ->
goal.tags.contains("external") && !goal.tags.contains("internal")
}By Name Pattern:
goalFilter = { goal -> goal.name.startsWith("external_") }Complex Logic:
goalFilter = { goal ->
goal.tags.contains("a2a-enabled") &&
!goal.tags.contains("internal") &&
goal.description.isNotEmpty()
}Expose different capability sets at different paths:
@Configuration
class MultiAgentConfig {
@Bean
fun publicHandler(
agentPlatform: AgentPlatform,
requestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = "a2a-public",
agentPlatform = agentPlatform,
a2ARequestHandler = requestHandler,
goalFilter = { goal -> goal.tags.contains("public") }
)
}
@Bean
fun partnerHandler(
agentPlatform: AgentPlatform,
requestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = "a2a-partner",
agentPlatform = agentPlatform,
a2ARequestHandler = requestHandler,
goalFilter = { goal -> goal.tags.contains("partner-api") }
)
}
}Result:
/a2a-public/.well-known/agent.json and /a2a-public/a2a-partner/.well-known/agent.json and /a2a-partnerAllow Public AgentCard, Secure JSON-RPC:
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.authorizeHttpRequests { auth ->
auth
.requestMatchers("/*/.well-known/agent.json").permitAll()
.requestMatchers("/*/").authenticated()
}
return http.build()
}
}@Component
class A2ALogger {
@EventListener
fun logRequest(event: A2ARequestEvent) {
logger.info(
"[A2A Request] method={}, id={}, platform={}",
event.request.method,
event.request.id,
event.agentPlatform.name
)
}
@EventListener
fun logResponse(event: A2AResponseEvent) {
logger.info(
"[A2A Response] id={}, success={}",
event.response.id,
event.response !is JSONRPCErrorResponse
)
}
}@Component
class A2AMetrics(private val meterRegistry: MeterRegistry) {
@EventListener
fun recordRequest(event: A2ARequestEvent) {
meterRegistry.counter(
"a2a.requests",
"method", event.request.method,
"platform", event.agentPlatform.name
).increment()
}
@EventListener
fun recordResponse(event: A2AResponseEvent) {
val status = if (event.response is JSONRPCErrorResponse) "error" else "success"
meterRegistry.counter("a2a.responses", "status", status).increment()
}
}Profile-Based:
@Configuration
@Profile("a2a")
class A2AProfileConfiguration {
@Bean
fun agentCardHandler(...): AgentCardHandler { /* ... */ }
}Property-Based:
@Configuration
@ConditionalOnProperty(
prefix = "embabel.a2a",
name = ["enabled"],
havingValue = "true"
)
class ConditionalA2AConfig {
@Bean
fun agentCardHandler(...): AgentCardHandler { /* ... */ }
}application.yml:
embabel:
a2a:
enabled: true@Configuration
class ConfigurableA2APath {
@Value("\${a2a.endpoint.path:a2a}")
private lateinit var endpointPath: String
@Bean
fun agentCardHandler(
agentPlatform: AgentPlatform,
requestHandler: AutonomyA2ARequestHandler
): AgentCardHandler {
return EmbabelServerGoalsAgentCardHandler(
path = endpointPath,
agentPlatform = agentPlatform,
a2ARequestHandler = requestHandler,
goalFilter = { true }
)
}
}application.yml:
a2a:
endpoint:
path: my-agent-endpoint@Component
class A2AMonitor {
private val activeRequests = ConcurrentHashMap<String, Instant>()
@EventListener
fun onRequest(event: A2ARequestEvent) {
val requestId = event.request.id?.toString() ?: return
activeRequests[requestId] = event.timestamp
}
@EventListener
fun onResponse(event: A2AResponseEvent) {
val responseId = event.response.id?.toString() ?: return
val requestTime = activeRequests.remove(responseId) ?: return
val duration = Duration.between(requestTime, event.timestamp)
logger.info("Request {} completed in {}ms", responseId, duration.toMillis())
if (duration.toSeconds() > 30) {
logger.warn("Slow request: {} took {}s", responseId, duration.toSeconds())
}
}
}@Component
class TaskCleanup(private val taskStateManager: TaskStateManager) {
@Scheduled(cron = "0 0 2 * * ?") // Daily at 2 AM
fun cleanupOldTasks() {
val cutoff = Instant.now().minus(Duration.ofDays(7))
taskStateManager.cleanupOldTasks(cutoff)
logger.info("Cleaned up tasks older than {}", cutoff)
}
}@Component
class A2AErrorNotifier(private val notificationService: NotificationService) {
@EventListener
fun notifyOnError(event: A2AResponseEvent) {
if (event.response is JSONRPCErrorResponse) {
val error = event.response.error
notificationService.send(
subject = "A2A Request Failed",
message = """
Error Code: ${error.code}
Message: ${error.message}
Request ID: ${event.response.id}
Platform: ${event.agentPlatform.name}
""".trimIndent()
)
}
}
}tessl i tessl/maven-com-embabel-agent--embabel-agent-a2a@0.3.3