Skip to main content
acp-micronaut is a required (non-preview) module of the ACP Java SDK as of 0.80.0: Micronaut 4 integration that serves your @AcpAgent bean and builds an ACP client from configuration, the same shape as the Spring Boot integration but wired through Micronaut’s compile-time bean definitions instead of Spring’s runtime ones.

Quick Start

Micronaut’s annotation processor (micronaut-inject-java) must be on your compiler’s annotation processor path, as in any Micronaut application; Java 17+.

An agent, annotated

Annotate the agent class @AcpAgent, and make it a bean with @Singleton (any bean-defining annotation works). It’s an ordinary Micronaut bean, so other beans inject into it normally:
Handler methods, their parameters, and return types are the same ones documented on Clients and Agents in Java: @Prompt, @NewSession, @SessionId, NegotiatedCapabilities, AcpSyncAgent/AcpAsyncAgent, and so on. There’s no Micronaut-specific builder alternative; this module is annotation-only.

Discovery is compile-time, not a classpath scan

AcpAgentRuntime reads the @AcpAgent bean from the bean definitions Micronaut’s annotation processor wrote at compile time, not by scanning the classpath at startup. Exactly one such bean is allowed per application; with more than one, startup fails and names them all (IllegalStateException, listing every bean’s class name). An application with none, such as a client-only one, simply starts no agent.

agentInfo and capabilities come from the annotations

The initialize answer is derived the same way as plain Java and Spring: agentInfo from @AcpAgent’s name/version, and a capability for each handler method the class declares. One bean instance serves every session, and (over HTTP) every connection, so keep per-session state in a map keyed by session id rather than on the bean itself.

Micronaut AOP works

Discovery finds the agent by its declared bean type, which carries the handler annotations; invocation goes through Micronaut’s own context.getBean(...), which is the actual runtime instance, a generated AOP subclass when @Around advice applies to the bean. So an @Around-intercepted agent is discovered correctly off its user class (where the @Prompt and friends live), and calling it at runtime still goes through the generated subclass, so the advice actually runs. No special wiring needed beyond the ordinary Micronaut AOP annotations.

Return types: Mono, Flux, CompletionStage, or any Publisher

A handler may return its response type directly, a Mono, a CompletionStage, or any single-value Reactive Streams Publisher: a Flux, for instance, if that’s what a Micronaut data or HTTP client call already returns. Its first element (or completion value) becomes the response; an empty one means no response. The handler still runs, and is waited on, synchronously under the hood (the runtime serves handlers through the sync agent), so this is a convenience for signature compatibility with reactive Micronaut code, not a path to true non-blocking handling.
If you build the Publisher with Micronaut’s own Publishers helper (io.micronaut.core.async.publisher.Publishers) rather than Reactor or Mutiny directly, add micronaut-core-reactive to the classpath: that helper needs it and isn’t pulled in by acp-micronaut alone. Returning a Mono or Flux directly needs nothing extra.

Lifecycle

  • The agent starts with the application context (StartupEvent) and closes gracefully with it.
  • Over stdio, the application stays up while the transport runs; when the client closes the agent’s input and every answer has been written, the context closes and the process exits 0 (acp.agent.shutdown-on-transport-end).
  • Over HTTP, the listener runs until the application stops.
  • On SIGTERM, the agent closes gracefully. An application with no embedded server has no shutdown hook of Micronaut’s own, so the runtime registers one.
  • With micronaut.lifecycle.graceful-shutdown.enabled=true, the HTTP listener drains through Micronaut’s graceful shutdown first.
An application bean of type AcpAgentTransport replaces stdio, for example acp-test’s in-memory transport in a test.
An HTTP or WebSocket agent runs on its own port, not inside Micronaut’s own HTTP server. It runs alongside a Micronaut HTTP server if your application has one, not mounted on it; the two never share a port. Because of that, Micronaut’s own server configuration (TLS, security, metrics) doesn’t reach the ACP endpoint in 0.80.0 the way it does on Spring Boot and Quarkus. Mounting the ACP endpoint on Micronaut’s own server, so that configuration applies automatically, is planned for 0.81.0.

Agent Settings (acp.agent.*)

Only ACP’s own web-socket-idle-timeout applies; the listener’s own idle-timeout setting doesn’t reach the upgraded socket.

A client, from configuration

A client is configured only when acp.client.transport.type, .stdio.command, .websocket.uri, or .http.uri is set, giving three beans: the transport, an AcpAsyncClient, and an AcpSyncClient facade over that same client (one session on one connection). Setting some other acp.client.transport.* property alone, such as .websocket.connect-timeout or .stdio.args with no .stdio.command, creates no client at all, rather than one that then fails for lacking a transport. The client connects when you call initialize(), and closes gracefully, once, with the context. Customize it before it’s built with AcpClientCustomizer beans, applied in @Order:
The handler pairing is enforced, not just advised: setting a capability (other than boolean-config-options, which needs no handler) true without registering its handler fails bean creation, naming the property to turn off or the handler to add, for example “fs.readTextFile needs readTextFileHandler… The capabilities come from acp.client.capabilities.read-text-file=true”. A customizer’s own sessionUpdateHandler replaces the bean’s DEBUG-logging one rather than running alongside it.
AcpClientCustomizer and AcpTransportType (used above by acp.client.transport.type) are not Micronaut-specific types: both live in com.agentclientprotocol.sdk.integration, the framework-neutral module Spring Boot and Quarkus build on too; see Concepts: the framework-neutral layer. The client (and its transport) is closed exactly once, bounded by its request timeout plus 10 seconds, by that same shared layer.

Extension Beans

Beans of type AcpInterceptor, ArgumentResolver, and ReturnValueHandler are added to the agent, in bean order, the Micronaut-native way to extend the handler pipeline without subclassing anything.

Where handlers run

On JDK 21 and later, handler methods run on Micronaut’s virtual-thread executor (TaskExecutors.VIRTUAL), a bean this module looks up by name rather than something it builds itself; on JDK 17 they run on the SDK’s own default pool (see Concepts: Where handlers run) instead. This is automatic, not a property: there’s no configuration to point handlers at a different executor, and in particular not at Micronaut’s TaskExecutors.BLOCKING (the I/O pool), whose non-daemon threads would keep a stdio application’s process alive past the point its context closes.

Logging and sensitive data

See Transports: Logging and sensitive data for what the SDK logs at each level. Without a session-update handler of your own, session updates are logged at DEBUG, content included. A stdio agent’s standard error is logged at INFO, unfiltered, on com.agentclientprotocol.sdk.client.transport.agent-stderr; set that logger to WARN in your logback.xml to silence it.

Dependencies

The Micronaut modules import the Micronaut 4.10 platform BOM; the SDK’s own Reactor, Jackson, Jetty, and JUnit versions take precedence over it. The wire format is the SDK’s own Jackson mapper (see JSON Mappers), not Micronaut Serialization.

Spring Boot Starter

The equivalent integration for Spring Boot applications

Clients and Agents in Java

The annotation model this module builds on

Transports

Where ACP listens in every deployment shape, Micronaut included