Agent Client Protocol
Agent Client Protocol (ACP) is an open-source, standardized protocol that enables client applications to communicate with AI agents through a consistent, bidirectional interface. By implementing ACP in your Koog agent, you ensure it can easily integrate into any ACP-compliant environment, such as an IDE.
For more information, see the Agent Client Protocol documentation.
Integration with Koog
The Koog framework integrates with ACP using the ACP Kotlin SDK with additional API extensions. This integration provides:
- Standardized communication for a Koog agent with ACP-compliant client applications
- Automatic execution updates for tool calls, agent thoughts, and completions
- Seamless message conversion between Koog's multimodal message formats and ACP's content blocks
- Lifecycle mapping of Koog agent states to ACP session events
Note
Since ACP Kotlin SDK is JVM-specific, the ACP integration is currently available only on the JVM platform.
Add dependencies
ACP support is an optional feature that is not available in Koog by default. To implement ACP for your Koog agent, add a dependency for ai.koog:agents-features-acp, which itself has a dependency on com.agentclientprotocol:acp.
For example, in case of build.gradle.kts:
Enable ACP for a Koog agent
To bridge a Koog agent's internal event system with the ACP protocol,
install the ai.koog.agents.features.acp.AcpAgent feature.
When installed, it listens for lifecycle events (like tool calls or LLM responses)
and sends them to the ACP client.
val agent = AIAgent(
promptExecutor = simpleOpenAIExecutor(System.getenv("OPENAI_API_KEY")),
llmModel = OpenAIModels.Chat.GPT4o
) {
install(AcpAgent) {
this.sessionId = sessionId
this.protocol = protocol
this.eventsProducer = eventsProducer
this.setDefaultNotifications = true
}
}
Key configuration options:
sessionId: A unique string identifying the current conversation session.protocol: An instance ofcom.agentclientprotocol.protocol.Protocolused for low-level communication.eventsProducer: Akotlinx.coroutines.channels.ProducerScope<Event>where ACP events are sent. For more information, see Event streaming.setDefaultNotifications: Whether to register default notification handlers for agent lifecycle events. For more information, see Handling agent notifications.
This agent must run within the scope of an ACP session as described in the next chapter.
Implement an ACP-enabled agent
To connect your Koog agent to ACP clients, implement two core interfaces from the ACP Kotlin SDK:
AgentSupport: Manages the agent's identity, capabilities, and session lifecycle (creating or loading sessions).AgentSession: Manages a single conversation session, handles thepromptexecution, and manages cancellation.
Inside the prompt() method of AgentSession is where you should initialize and run the ACP-enabled Koog agent.
Here is an example:
class MyAgentSession(
override val sessionId: SessionId,
private val promptExecutor: PromptExecutor,
private val protocol: Protocol,
private val clock: Clock
) : AgentSession {
private var agentJob: Deferred<Unit>? = null
private val agentMutex = Mutex()
override suspend fun prompt(
content: List<ContentBlock>,
_meta: JsonElement?
): Flow<Event> = channelFlow {
val agentConfig = AIAgentConfig(
prompt = prompt("acp") {
system("You are a helpful assistant.")
}.appendPrompt(content),
model = OpenAIModels.Chat.GPT4o,
maxAgentIterations = 1000
)
// Ensure only one agent session runs at a time
agentMutex.withLock {
val agent = AIAgent(
promptExecutor = promptExecutor,
agentConfig = agentConfig
) {
install(AcpAgent) {
this.sessionId = this@MyAgentSession.sessionId.value
this.protocol = this@MyAgentSession.protocol
this.eventsProducer = this@channelFlow
this.setDefaultNotifications = true
}
}
agentJob = async { agent.run("Hello. How can you help me?") }
agentJob?.await()
}
}
private fun Prompt.appendPrompt(content: List<ContentBlock>): Prompt {
return withMessages { messages ->
messages + listOf(content.toKoogMessage(clock))
}
}
override suspend fun cancel() {
agentJob?.cancel()
}
}
class MyAgentSupport(
private val promptExecutor: PromptExecutor,
private val clock: Clock,
private val protocol: Protocol,
) : AgentSupport {
override suspend fun initialize(clientInfo: ClientInfo): AgentInfo {
return AgentInfo(
protocolVersion = LATEST_PROTOCOL_VERSION,
capabilities = AgentCapabilities(
loadSession = false, // Set to true if you implement session persistence
promptCapabilities = PromptCapabilities(
audio = false,
image = false,
embeddedContext = true
)
)
)
}
@OptIn(ExperimentalUuidApi::class)
override suspend fun createSession(sessionParameters: SessionCreationParameters): AgentSession {
val sessionId = SessionId(Uuid.random().toString())
return MyAgentSession(sessionId, promptExecutor, protocol, clock)
}
override suspend fun loadSession(sessionId: SessionId, sessionParameters: SessionCreationParameters): AgentSession {
throw UnsupportedOperationException("Session loading not implemented")
}
}
Event streaming
The AgentSession from the example defines a prompt() function that returns a channelFlow of events.
You then install the AcpAgent feature with this@channelFlow as eventsProducer.
This allows sending events from different coroutines.
Execution synchronization
The AgentSession from the example uses a mutex to synchronize access to the agent instance
because ACP should not trigger a new agent execution until the previous one finishes.
For this, creating and running the agent happens in the scope of withLock for the defined mutex.
You also run the agent asynchronously within the channelFlow scope
as a deferred job agentJob to ensure that the agent is not cancelled prematurely.
Handling ACP client input
ACP clients send user input as a list of ContentBlock objects.
To process these in Koog, use the List<ContentBlock>.toKoogMessage() extension function
to convert ACP content blocks to Message.User
and append it to your agent's prompt.
The AgentSession from the example defines a private function to extend the initial agent prompt in an ACP session:
private fun Prompt.appendPrompt(content: List<ContentBlock>): Prompt {
return withMessages { messages ->
messages + listOf(content.toKoogMessage(clock))
}
}
Note
A Clock instance is required to timestamp the message.
For more information, see Converting messages.
Converting messages
The agents-features-acp module provides extension functions
to seamlessly convert between Koog's internal message types
and ACP content blocks.
Use the following functions when receiving input from an ACP client:
List<ContentBlock>.toKoogMessage()converts a list of ACP content blocks toMessage.UserContentBlock.toKoogContentPart()converts a single ACP content block toContentPart
Use the following functions to construct ACP events or content blocks from Koog messages:
Message.Response.toAcpEvents()converts aMessage.Responseto a list of ACP session update eventsContentPart.toAcpContentBlock()converts aContentPartto a single ACP content block
Handling agent notifications
By default, setDefaultNotifications is set to true
and the ACP-enabled agent automatically handles the following notifications:
-
Agent completion
Sends
PromptResponseEventwithStopReason.END_TURNwhen the agent completes successfully -
Agent execution failures
Sends
PromptResponseEventwith the appropriate stop reason:StopReason.MAX_TURN_REQUESTSwhen the agent exceeds max iterationsStopReason.REFUSALfor other execution failures
-
LLM responses
Converts and sends LLM responses as ACP events (text, tool calls, reasoning)
-
Tool call lifecycle
Reports tool call status changes:
ToolCallStatus.IN_PROGRESSwhen a tool call startsToolCallStatus.COMPLETEDwhen a tool call succeedsToolCallStatus.FAILEDwhen a tool call fails
If you want to customize notification handling,
set setDefaultNotifications = false and process agent events according to the specification.
Sending custom events
Besides automatic notifications,
you can send custom events to the ACP client at any point during the agent execution
using sendEvent within the withAcpAgent block.
This is useful for progress updates, custom status messages, or plan updates.
You can do this inside an AIAgentContext, for example, in a node:
val plan: Plan = TODO()
val strategy = strategy<Unit, Unit>("my-strategy") {
val node by node<Unit, Unit> {
withAcpAgent {
sendEvent(
Event.SessionUpdateEvent(
SessionUpdate.PlanUpdate(plan.entries)
)
)
}
}
}
You can also access the underlying protocol to send custom requests to the client, such as authentication requests:
val strategy = strategy<Unit, Unit>("my-strategy") {
val node by node<Unit, Unit> {
withAcpAgent {
protocol.sendRequest(
AcpMethod.AgentMethods.Authenticate,
AuthenticateRequest(methodId = AuthMethodId("Google"))
)
}
}
}
Examples
You can find working examples of Koog agents in the Koog repository under /examples.
Running a console-based ACP client
This example runs a console-based ACP client that interacts with a simple Koog agent.
- Open /examples/simple-examples.
- See the README for information about configuring your API key for an LLM provider.
- Run the
runExampleAcpAppGradle task. - When the ACP client starts in the console, type a request for the agent, like:
- Observe the event traces in the console, which show how Koog events are converted to ACP events and sent to the client.
Connecting an ACP-enabled Koog agent to a JetBrains IDE
This example demonstrates how to create an ACP-enabled agent and connect to IntelliJ IDEA.
- Open /examples/acp-agent
- Run the
installDistGradle task. - This should create the agent executable:
build/install/acp-agent/bin/acp-agent(acp-agent.batfor Windows). - Open IntelliJ IDEA (or another JetBrains IDE).
- Go to AI Chat > Options > Add Custom Agent.
-
In the opened
acp.jsonfile, paste the following:{ "agent_servers": { "Koog Agent": { "command": "/absolute/path/to/acp-agent/build/install/acp-agent/bin/acp-agent", "args": [], "env": { "OPENAI_API_KEY": "paste-your-api-key-here" } } } }Configuration parameters:
agent_servers: Object containing one or more agent configurationsKoog Agent: Display name shown in IDE's agent selectorcommand: Absolute path to the agent executableargs: Command-line arguments (empty for this agent)env: Environment variables passed to the agent process (OpenAI API key in this example)
-
The agent should become available in the AI Chat tool window.
For more information about adding custom agents to your IDE, see AI Assistant documentation and this blog post.