Skip to main content
acp-quarkus is a required (non-preview) Quarkus extension for the ACP Java SDK as of 0.80.0, built against Quarkus 3.40.1. With it, all three JVM framework integrations the SDK ships are the same shape: annotate one class, let the framework serve it. JVM mode only for 0.80.0; there’s no native image yet.

Quick Start

Without the jackson-bom import, Quarkus’s own managed Jackson (2.21.7, as of Quarkus 3.40.1) applies instead; both lines are supported, and the extension’s own tests run on the Quarkus-managed version. Reactor (3.8.7) is the same either way, and Netty is Quarkus’s own.

An agent, annotated

@AcpAgent alone makes the class a CDI bean, a singleton unless it declares its own scope. It’s discovered at build time: a second @AcpAgent class fails the build, naming both. With none, the application is a client only. Capabilities and agentInfo are derived from the annotations the same way as everywhere else; see Clients and Agents in Java for the full derivation and @Initialize-merge rules, which apply unchanged here.

Intercepted beans work

So when an interceptor (@Transactional, a custom @Interceptor, or anything else CDI wraps the bean in) applies to the agent, its advice still runs on every ACP call: discovery still finds @Prompt and friends on the class they’re actually declared on, while invocation goes through the container’s managed instance. No special wiring needed.

AcpInterceptor, ArgumentResolver, and ReturnValueHandler beans

Beans of these three types are picked up and added to the agent automatically, the CDI-native way to extend the handler pipeline.

Mutiny return types

A handler may return a Mutiny Uni of what it could return directly: a Uni<String> from @Prompt sends the text and ends the turn, a Uni<Void> just ends it. The handler’s thread waits for the item, the same as it would for a Mono. @Prompt may also return a Multi, which no other handler method may: streaming String (sent as a message chunk), SessionUpdate (sent as that update), or ContentBlock (wrapped in a message chunk) items, each as it’s emitted, ending the turn (end_turn) when the stream completes. Cancelling the prompt cancels the Multi’s subscription and ends the turn cancelled; a failed stream fails the prompt.

Over stdio (the default)

Standard output carries the protocol, so a stdio build sets these defaults, your own settings win: quarkus.log.console.stderr=true, quarkus.banner.enabled=false, quarkus.http.host-enabled=false. Never print to System.out. The application exits when the client closes standard input (quarkus.acp.agent.shutdown-on-transport-end=false keeps it running).
quarkus dev can’t run a stdio agent, because dev mode owns the terminal’s standard input for its own interactive commands. Develop a stdio agent in tests (an application bean of type AcpAgentTransport, such as acp-test’s in-memory one, replaces stdio) or over HTTP instead.

Over HTTP and WebSocket: Quarkus’s own server

The agent is served directly on Quarkus’s own HTTP server, on quarkus.http.port (under quarkus.http.root-path): Streamable HTTP, SSE and the WebSocket upgrade, all on one Vert.x route of the Quarkus router, with no servlet container and no second server to run or port to open; unlike Micronaut and Spring Boot in their non-web mode, which each open a standalone listener of their own. The route sits behind Quarkus’s own authentication and permission checks, so HTTP security policies apply to /acp, the WebSocket handshake included. Each connection runs its own agent runtime over the one bean, so its handlers must be thread-safe, same as the other two integrations. On shutdown the endpoint drains first, within quarkus.acp.agent.transport.http.shutdown-timeout: each open SSE stream gets a closing comment and completes, and each WebSocket closes with 1001, the drain waiting for the close frame to actually go out before reporting that connection closed. A client that never answers holds the shutdown no longer than the timeout, after which it’s logged at WARN and closed at once. An HTTP build raises these Quarkus defaults to what the SDK’s listener admits (your own settings win): quarkus.http.limits.max-body-size=16M, quarkus.http.websocket-server.max-message-size and max-frame-size to 16 MiB, and quarkus.http.limits.max-concurrent-streams=1024 (each SSE stream is one HTTP/2 stream). Raise them too if you raise max-post-body-size past 16 MiB. Quarkus’s own quarkus.http.idle-timeout (30 minutes by default) also closes idle ACP WebSockets; the shorter of it and quarkus.acp.agent.transport.http.web-socket-idle-timeout wins.

Native integration, not a bolt-on

The agent is served on Quarkus’s own HTTP server: one server, one port, one TLS setup, one lifecycle that starts and stops with the application, one place for metrics and logs, and Quarkus’s own threads. There’s nothing ACP-specific to configure alongside what the application already has.

If you need it: securing /acp

Most ACP use (an IDE talking stdio, a handful of local sessions) needs no security configuration at all. If you do expose the agent remotely, it’s served behind Quarkus’s own request handling, so ordinary Quarkus HTTP security policies and @RolesAllowed apply to /acp the same as any other endpoint, the WebSocket handshake included. Proven by the SDK’s own test: an unauthenticated request gets 401 on HTTP and on the WebSocket handshake, an authenticated one gets 200/101, same as any other secured endpoint. Restricting the ACP endpoint to a role, for example:

A client, from configuration

The client beans are created when first injected and closed when the application stops; a bean of your own of the same type replaces each.
Client callbacks run on SDK threads, not on a Vert.x context. From a Uni built over a client call, hop back onto the Vert.x context with emitOn when you need it there.

Configuration (quarkus.acp.*)

Build time (fixed when the application is built): Run time:
“Only what a customizer registers handlers for” is enforced at startup: setting a capability true without its handler fails client creation, naming the property to turn off or the handler to add, for example “fs.readTextFile needs readTextFileHandler… The capabilities come from quarkus.acp.client.capabilities.read-text-file=true”. A customizer’s own sessionUpdateHandler replaces the client’s DEBUG-logging one rather than running alongside it.
Compared with Spring Boot’s spring.acp.*: the HTTP port is quarkus.http.port (not an acp.*-namespaced property), and the listener’s max-concurrent-streams-per-connection is quarkus.http.limits.max-concurrent-streams, Quarkus’s own HTTP tuning, not the SDK’s. AcpClientCustomizer and AcpTransportType (used above by quarkus.acp.client.transport.type) are not Quarkus-specific types: both live in com.agentclientprotocol.sdk.integration, the framework-neutral module Spring Boot and Micronaut 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.

Where handlers run

By default (quarkus.acp.handler-executor=managed), handler methods run on the ManagedExecutor (Quarkus’ own worker pool, where blocking work belongs, never the Vert.x event loop), with the application’s contexts propagated, instead of a second pool of the SDK’s (see Concepts: Where handlers run). Set quarkus.acp.handler-executor=virtual to opt into Quarkus’ virtual-thread executor on JDK 21 and later (it falls back to managed on earlier JDKs). Virtual threads keep the Vert.x context of the submitting thread, but propagate no MicroProfile Context Propagation context: a handler gets no CDI request context of its own unless the method carries @ActivateRequestContext, and no security identity, transaction, or tracing context captured from the submitting thread.

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; silence it with quarkus.log.category."com.agentclientprotocol.sdk.client.transport.agent-stderr".level=WARN.

Spring Boot Starter

The equivalent integration for Spring Boot applications

Micronaut Integration

The equivalent integration for Micronaut applications

Clients and Agents in Java

The annotation model this module builds on

Transports

Where ACP listens in every deployment shape, Quarkus included