JetBrains AI
Supercharge your tools with AI-powered features inside many JetBrains products
Koog × A2A: KI-Agents in Kotlin vernetzen
Wenn Sie jemals versucht haben, ein System mit mehreren KI-Agents aufzubauen, dürften Sie das Problem bereits kennen. Der Anfang ist unkompliziert: Ein Agent schreibt Blogartikel, der nächste liest Korrektur, und vielleicht gibt es noch einen dritten, der Bilder vorschlägt oder generiert. Einzeln sind sie alle effektiv. Aber wie bringt man sie dazu, zusammenzuarbeiten? An dieser Stelle kommt das Projekt oft ins Schlingern.
Jeder Agent spricht seine eigene „Sprache“: Einer verwendet eine eigene API-Schnittstelle, der nächste hat ein spezielles Nachrichtenformat, und mit etwas Pech haben alle unterschiedliche Authentifizierungsanforderungen. Damit sie miteinander kommunizieren können, benötigt jede einzelne Verbindung einen individuellen Integrationscode. So vergeuden Sie Ihre Zeit damit, Brücken zwischen Ihren Agents zu bauen, statt sie intelligenter, schneller und nützlicher zu machen.
Das bietet A2A: eine agentenübergreifende Kommunikationsschicht
An dieser Stelle kommt das Agent2Agent-Protokoll (A2A) ins Spiel.
Mit A2A können Ihre Agents direkt über ein standardisiertes Protokoll kommunizieren, das als universeller Übersetzer für Ihr KI-Ökosystem fungiert. Ihr Blogschreibe-Agent übergibt die Inhalte den an den Korrektur-Agent, wodurch Ihr Bildgenerator aktiviert wird. Der Korrektor sendet Korrekturen zurück, und der Bildgenerator fordert genauere Stilvorgaben an. All dies wird über eine einzige einheitliche Kommunikationsschicht koordiniert.
Damit Sie nicht mit Dutzenden Punkt-zu-Punkt-Verbindungen jonglieren müssen, stellt A2A Folgendes bereit:
- Plug-and-play-Konnektivität: Die Agents erkennen sich automatisch und stellen eine Verbindung zueinander her.
- Standardisierte Nachrichtenübermittlung: Einheitliches Format, klares Protokoll und keine Übersetzungsprobleme.
- Integrierte Orchestrierung: Workflows müssen nur einmalig definiert werden – danach übernimmt A2A die Koordination.
- Skalierung ohne Komplexität: Sie können neue Agents hinzufügen oder bestehende Agents wiederverwenden, ohne vorhandene Verbindungen umzuschreiben.
Das Ergebnis Sie investieren Ihre Zeit in die Verbesserung Ihrer Agents, nicht in das Debugging des Austauschs zwischen ihnen. Und das Beste daran: Sie können Ihre Agents in jeder beliebigen Sprache oder jedem beliebigen Framework implementieren. Für JVM-Benutzer*innen ist Koog die erste Wahl, und seit Version 0.5.0 lässt es sich problemlos in das A2A-Ökosystem integrieren.
Das bietet Koog: eine interne Orchestrierungs-Engine
Koog ist ein auf Kotlin basierendes Framework zur Entwicklung von KI-Agents für JVM-, Android-, iOS-, WebAssembly- und Browseranwendungen. Besondere Stärken:
- Komplexes Workflow-Management: Entwerfen Sie graphbasierte Strategien mit Unterstützung für Schleifen, Verzweigungen, Fallbacks und paralleler Ausführung von Zweigen.
- Einsatzbereite Komponenten: Nutzen Sie die integrierten Knoten zum Aufrufen von LLMs und externen Tools, um Nachrichtenverläufe zusammenzufassen oder komplette Strategien auszuführen.
- Tool-Orchestrierung: Verwandeln Sie jede Funktion in Ihrem Code in ein Werkzeug, das Ihr KI-Agent sequenziell oder sogar parallel verwenden kann
- Native MCP-Integration: Mit dem Kotlin-MCP-SDK können Sie mühelos eine Verbindung zu jedem MCP-Server herstellen.
- Arbeits- und Festspeicherunterstützung: Integrierte Unterstützung für Agentenspeicher und RAG-Workflows (Retrieval-Augmented Generation) mit effizientem Kontextmanagement.
- Fehlertoleranz: Eingebaute Wiederholfunktion, Verlaufspunkte, Wiederherstellungsmöglichkeiten und Zustandsspeicherung gewährleisten eine zuverlässige Ausführung.
- Observability: Umfassende Agent-Ereignisbehandlung, Protokollierung und Unterstützung für OpenTelemetry mit eingebauten Integrationen für Langfuse und W&B Weave.
Alles in allem eignet sich Koog hervorragend für die Entwicklung zuverlässiger KI-Agents.
Wozu Koog mit A2A kombinieren?
Koog und A2A decken unterschiedliche Ebenen des KI-Agent-Stacks ab. Zusammen verwendet schließen sie die vorhandenen Lücken und ergänzen einander.
Koog übernimmt bereits die schwierigsten Aspekte der im realen Unternehmenseinsatz erforderlichen KI-Orchestrierung.
A2A schließt die verbleibende Lücke: Es ermöglicht Ihren Koog-Agents, mit allen anderen A2A-kompatiblen Agents in Ihrem Ökosystem zu kommunizieren. Anstatt für jeden externen Service individuelle Integrationen zu erstellen, können Ihre Koog-KI-Workflows andere Agents automatisch erkennen und nutzen.
Das Ergebnis ist eine perfekte Zusammenarbeit: Komplexe Koog-Workflows werden zu A2A-Aufgaben, die jeder Agent nutzen kann, während Ihre Koog-Agents die volle Leistungsfähigkeit des A2A-Ökosystems nutzen können. Da Koog im Backend, auf Geräten und in Browser-Umgebungen ausgeführt werden kann, können Sie vernetzte KI-Systeme umfassender und effektiver als je zuvor bereitstellen.
Wie ist das möglich? Sehen wir es uns an!
A2A-Protokoll
Das A2A-Protokoll definiert die wesentlichen Bausteine für die Kommunikation zwischen Agents:
- Agent-Erkennung über standardisierte Agent-Karten (JSON-Dokumente, die Fähigkeiten beschreiben).
- Nachrichtenformate für Anfragen und Antworten mit konsistenten Schemata.
- Aufgabenlebenszyklus-Verwaltung mit klaren Statuswerten: übermittelt → in Bearbeitung → abgeschlossen/fehlgeschlagen.
- Transportschichten wie JSON-RPC, gRPC und REST.
- Sicherheitskonzepte mittels Standard-OAuth2, API-Schlüsseln und JWT-Tokens.
- Fehlerbehandlung mit standardisierten Fehlercodes.

Agent-Karten: digitale Visitenkarten
Jeder Agent im A2A-Ökosystem veröffentlicht seine Fähigkeiten über eine „Agent-Karte“ – eine standardisierte JSON-Datei, die unter einer bestimmten URL, z. B. /.well-known/agent-card.json, auf der Domain des Agents gehostet wird. Die Agent-Karte fungiert als digitale Visitenkarte, die es anderen Agents ermöglicht, die bereitgestellten Services zu erkennen.
Eine Agent-Karte enthält in der Regel:
- Basisinformationen: Agentenname, Beschreibung, Version usw.
- Fähigkeiten: Was der Agent leisten kann (z. B. Dokumente verfassen, Texte korrigieren, Daten analysieren oder Bilder generieren).
- Endpunkte: Wie der Agent zu erreichen ist.
- Weitere optionale Informationen: Aktivierte Fähigkeiten, Authentifizierung und mehr.
Dieser Erkennungsmechanismus erspart uns die manuelle Integration. Wenn ein Agent eine bestimmte Fähigkeit benötigt, überprüft er einfach die entsprechende Agent-Karte, um zu verstehen, wie er mit dem Service interagieren muss.
In Koog werden Agent-Karten mittels Kotlin-Datenklassen definiert:
val agentCard = AgentCard(
name = "Blog-Autor",
description = "KI-Agent zum Erstellen hochwertiger Blogeinträge und Artikel",
url = "https://api.blog-writer.com/a2a/v1",
version = "1.0.0",
capabilities = AgentCapabilities(streaming = true),
defaultInputModes = listOf("text/plain"),
defaultOutputModes = listOf("text/markdown"),
skills = listOf(
AgentSkill(
id = "write-post",
name = "Schreiben von Blogartikeln",
description = "Generierung interessanter Blogartikel zu jedem Thema",
tags = listOf("writing", "content", "blog"),
examples = listOf("Schreibe einen Beitrag über KI-Trends")
)
)
)
Universelles Nachrichtensystem: ein einfaches Muster
A2A verwendet ein einheitliches, standardisiertes Nachrichtenformat für die gesamte Kommunikation zwischen Agents. In dieser Einfachheit liegt die Kraft – anstatt mit Dutzenden unterschiedlichen APIs zu hantieren, müssen Agents lediglich ein einziges Kommunikationsmuster verstehen.
Jede Interaktion folgt dem gleichen Ablauf:
- Senden einer Nachricht mit der Aufgabenanfrage und den Parametern.
- Empfangen entweder sofortiger Ergebnisse oder einer Aufgabe zur Statusverfolgung.
- Updates über Echtzeitkanäle für längere Vorgänge.
Dank diesem universellen Ansatz können neue Agentenfähigkeiten hinzugefügt werden, ohne die Kommunikationsprotokolle zu ändern. Unabhängig davon, ob der Agent einen Text zusammenfassen oder einen komplexen Bericht generieren soll, bleibt die Nachrichtenstruktur gleich.
In Koog können Sie Nachrichten ganz einfach mithilfe bereits implementierter Objekte und Protokolle erstellen und senden:
val message = Message(
role = Role.User,
parts = listOf(
TextPart("Schreibe einen Blogartikel über die Zukunft von KI-Agents")
),
contextId = "blog-project-456"
)
val request = Request(
data = MessageSendParams(
message = message,
configuration = MessageConfiguration(
blocking = false, // Erste Antwort abrufen
historyLength = 5 // Kontext berücksichtigen
)
)
)
val response = client.sendMessage(request)
Das Nachrichtenformat unterstützt vielfältige Inhalte durch unterschiedliche Part-Typen, darunter TextPart für reine Textinhalte, FilePart für angehängte Dateien und DataPart für strukturierte JSON-Daten.
Dadurch können Ihre Koog-Agents mühelos mit jedem A2A-kompatiblen Agent kommunizieren, ganz gleich, ob es um Textverarbeitung, Dateianalyse oder komplexe Datentransformationen geht.
Aufgabenlebenszyklus: intelligente Abläufe
A2A verwaltet unterschiedliche Arbeitstypen auf intelligente Weise, basierend auf Komplexität und Dauer:
Sofortnachrichten: Bei einfachen Vorgängen wie Textformatierung oder schnellen Berechnungen werden die Ergebnisse direkt in der Antwort der KI zurückgegeben. Es ist kein Warten und keine Statusverfolgung erforderlich.
Länger andauernde Aufgaben: Komplexe Vorgänge wie Dokumentanalysen oder mehrstufige Workflows werden in die Warteschlange gestellt und geben eine Aufgabe zurück. Der anfordernde Agent kann den Fortschritt überwachen und die Ergebnisse der Aufgabe abrufen, sobald diese fertig sind.
Echtzeit-Updates: Bei zeitaufwändigen Vorgängen stellen serverseitige Events (Server-Sent Events, SSE) Live-Updates zum Fortschritt bereit. Dadurch bleiben die Agents auch ohne ständige Statusabfragen auf dem Laufenden.
class BlogWriterExecutor : AgentExecutor {
override suspend fun execute(
context: RequestContext,
eventProcessor: SessionEventProcessor
) {
val task = Task(
contextId = context.contextId,
status = TaskStatus(
state = TaskState.Submitted,
message = Message(
role = Role.Agent,
parts = listOf(TextPart("Anfrage zum Schreiben eines Blogbeitrags erhalten")),
contextId = context.contextId,
taskId = context.taskId,
)
)
)
eventProcessor.sendTaskEvent(task)
...
}
}
Eingebaute Sicherheit: nur Industriestandards
A2A will die Sicherheit nicht neu erfinden. Stattdessen werden bewährte und weit verbreitete Standards wie OAuth2, API-Schlüssel und Standard-HTTPS verwendet.
Dies bedeutet auch, dass Sie als Entwickler*in keine neuen Authentifizierungskonzepte erlernen müssen. Wenn Sie mit der Sicherheit moderner Web-APIs vertraut sind, kennen Sie sich bereits in der A2A-Sicherheit aus. Das System übernimmt alle Tools, Best Practices und Sicherheitsaudits, die mit diesen etablierten Standards einhergehen.
"securitySchemes": {
"google": {
"openIdConnectUrl": "https://accounts.google.com/.well-known/openid-configuration",
"type": "openIdConnect"
}
}
class AuthorizedA2AServer(
agentExecutor: AgentExecutor,
agentCard: AgentCard,
agentCardExtended: AgentCard? = null,
taskStorage: TaskStorage = InMemoryTaskStorage(),
messageStorage: MessageStorage = InMemoryMessageStorage(),
private val authService: AuthService, // Für die Authentifizierung zuständiger Service
) : A2AServer(
agentExecutor = agentExecutor,
agentCard = agentCard,
agentCardExtended = agentCardExtended,
taskStorage = taskStorage,
messageStorage = messageStorage,
) {
private suspend fun authenticateAndAuthorize(
ctx: ServerCallContext,
requiredPermission: String
): AuthenticatedUser {
val token = ctx.headers["Authorization"]?.firstOrNull()
?: throw A2AInvalidParamsException("Missing Authorization token")
val user = authService.authenticate(token)
?: throw A2AInvalidParamsException("Invalid Authorization token")
if (requiredPermission !in user.permissions) {
throw A2AUnsupportedOperationException("Insufficient permissions")
}
return user
}
override suspend fun onSendMessage(
request: Request,
ctx: ServerCallContext
): Response {
val user = authenticateAndAuthorize(ctx, requiredPermission = "send_message")
// Benutzerdaten über Kontext-State an Agent-Executor übergeben
val enrichedCtx = ctx.copy(
state = ctx.state + (AuthStateKeys.USER to user)
)
// Mit erweitertem Kontext an übergeordnete Implementierung delegieren
return super.onSendMessage(request, enrichedCtx)
}
// die übrigen gewrappten A2A-Methoden
// ...
}
Koog-Agents in A2A integrieren
Das Koog-Framework bringt sowohl einen A2A-Client als auch einen A2A-Server mit. Dies bedeutet, dass Ihre Koog-Agents problemlos mit anderen A2A-fähigen Agents kommunizieren können und ihrerseits für die Außenwelt sichtbar sind. Das folgende einfache Beispiel zeigt, wie Sie das implementieren können.
Koog-Agents in A2A-Server einbinden
Definieren Sie als Erstes eine Strategie für den Agent. Koog stellt Konverter (toKoogMessage, toA2AMessage) bereit, um unkompliziert zwischen den Nachrichtenformaten von Koog und A2A zu wechseln – Sie sparen sich somit die manuelle Serialisierung. Spezielle Knoten wie nodeA2ASendMessage kümmern sich um den Nachrichtenaustausch, sodass Kommunikationsabläufe einfach zu implementieren sind:
fun blogpostWritingStrategy() = strategy("blogpost-writer-strategy") {
val blogpostRequest by node { input ->
val userMessage = input.toKoogMessage().content
llm.writeSession {
user {
+"Schreibe einen Blogbeitrag auf Grundlage der Benutzeranfrage"
+xml {
tag("user_request") {
+userMessage
}
}
}
requestLLM().toA2AMessage()
}
}
val sendMessage by nodeA2ARespondMessage()
nodeStart then blogpostRequest then sendMessage then nodeFinish
}
Definieren Sie als Nächstes den Agent selbst. Sobald Sie das Feature A2AServer installiert haben, wird Ihr Agent im Ökosystem sichtbar und zugänglich. Auf diese Weise können komplexe Netzwerke entstehen, in denen spezialisierte Agents reibungslos zusammenarbeiten.
fun createBlogpostWritingAgent(
requestContext: RequestContext,
eventProcessor: SessionEventProcessor
): AIAgent {
// Vorhandene Nachrichten für den aktuellen Unterhaltungskontext abrufen
val messageHistory = requestContext.messageStorage.getAll().map { it.toKoogMessage() }
val agentConfig = AIAgentConfig(
prompt = prompt("blogpost") {
system("Du bist ein Agent, der Blogbeiträge schreibt")
messages(messageHistory)
},
model = GoogleModels.Gemini2_5Flash,
maxAgentIterations = 5
)
return agent = AIAgent(
promptExecutor = MultiLLMPromptExecutor(
LLMProvider.Google to GoogleLLMClient(System.getenv("GOOGLE_API_KEY")),
),
strategy = blogpostWritingStrategy(),
agentConfig = agentConfig
) {
install(A2AAgentServer) {
this.context = requestContext
this.eventProcessor = eventProcessor
}
handleEvents {
onAgentFinished { ctx ->
// Antwort des Agents in den aktuellen Gesprächskontext aufnehmen
val resultMessge = ctx.result as A2AMessage
requestContext.messageStorage.save(resultMessge)
}
}
}
}
Drittens müssen wir den Agent in den Executor einbinden und anschließend einen Server definieren.
class BlogpostAgentExecutor : AgentExecutor {
override suspend fun execute(
context: RequestContext,
eventProcessor: SessionEventProcessor
) {
createBlogpostWritingAgent(context, eventProcessor)
.run(context.params.message)
}
}
val a2aServer = A2AServer(
agentExecutor = BlogpostAgentExecutor(),
agentCard = agentCard,
)
Im letzten Schritt definieren wir einen Servertransport und führen den Server aus.
val transport = HttpJSONRPCServerTransport(
requestHandler = a2aServer
)
transport.start(
engineFactory = Netty,
port = 8080,
path = "/a2a",
wait = true,
agentCard = agentCard,
agentCardPath = A2AConsts.AGENT_CARD_WELL_KNOWN_PATH
)
Ihr Agent ist nun bereit, Anfragen zu bearbeiten!
Andere A2A-Agents von einem Koog-Agent aus aufrufen
Zunächst müssen Sie einen A2A-Client konfigurieren und verbinden, um eine Agent-Karte abzurufen.
val agentUrl = "https://example.com"
val cardResolver = UrlAgentCardResolver(
baseUrl = agentUrl,
path = A2AConsts.AGENT_CARD_WELL_KNOWN_PATH,
)
val transport = HttpJSONRPCClientTransport(
url = agentUrl,
)
val a2aClient = A2AClient(
transport = transport,
agentCardResolver = cardResolver
)
// Client initialisieren und Karte abrufen
a2aClient.connect()
Anschließend können Sie in Ihrer Strategie nodeA2ASendMessage oder nodeA2ASendMessageStreaming verwenden, um diese Clients aufzurufen und eine Nachricht oder eine Aufgabe zu erhalten.
val agentId = "agent_id"
val agent = AIAgent(
promptExecutor = MultiLLMPromptExecutor(
LLMProvider.Google to GoogleLLMClient(System.getenv("GOOGLE_API_KEY")),
),
strategy = strategy("a2a") {
val nodePrepareRequest by node<String, A2AClientRequest> { input ->
A2AClientRequest(
agentId = agentId,
callContext = ClientCallContext.Default,
params = MessageSendParams(
message = A2AMessage(
messageId = Uuid.random().toString(),
role = Role.User,
parts = listOf(
TextPart(input)
)
)
)
)
}
val nodeA2A by nodeA2AClientSendMessage(agentId)
val nodeProcessResponse by node {
// Ereignis verarbeiten
when (it) {
is A2AMessage -> it.parts
.filterIsInstance()
.joinToString(separator = "n") { it.text }
is Task -> it.artifacts
.orEmpty()
.flatMap { it.parts }
.filterIsInstance()
.joinToString(separator = "n") { it.text }
}
}
nodeStart then nodePrepareRequest then nodeA2A then nodeProcessResponse then nodeFinish
},
agentConfig = agentConfig
) {
install(A2AAgentClient) {
this.a2aClients = mapOf(agentId to client)
}
}
agent.run("Schreibe einen Blogartikel über die Integration von A2A und Koog")
Nächste Schritte
Wenn Sie sich detaillierter mit Koog und A2A beschäftigen möchten, empfehlen wir Ihnen die folgenden nützlichen Ressourcen:
Beispiele für die Verwendung von Koog und A2A
Autor des ursprünglichen Blogposts