Module 41: Quarkus
Serve an annotated ACP agent with the Quarkus extension: @AcpAgent alone makes the class a CDI bean, served over Streamable HTTP and WebSocket on Quarkus’s own HTTP server, with Mutiny Uni/Multi return types and a cancellable stream.
What You’ll Learn
@AcpAgent alone, with no bean-scope annotation needed: build-time CDI discovery, one agent only
@NewSession returning a Uni, and @Prompt streaming a Multi
session/cancel cancelling a Multi’s subscription, and detecting it with onCancellation()
- A CDI interceptor binding (
@InterceptorBinding/@AroundInvoke) around a prompt handler
- Why
quarkus dev can’t run this module’s stdio default, and what to use instead
The Code
The agent bean
@AcpAgent alone makes this a CDI bean: a singleton, found at build time (a second @AcpAgent class
fails the build), into which other beans (Greeting) inject normally. No @Singleton or
@ApplicationScoped needed. No @Initialize method either: the SDK derives agentInfo from
@AcpAgent(name, version, title), sessionCapabilities.close from @CloseSession, and
promptCapabilities.image from @Prompt(image = true). See
Clients and Agents in Java for the full derivation rules.
Streaming with Multi, and cancelling it
@Prompt returns a Multi<String>: each item is sent as an agent message chunk as it’s emitted, and
the turn ends end_turn when the stream completes. When the client sends session/cancel, the SDK
cancels the Multi’s subscription and ends the turn cancelled; the stream sees this through
onCancellation(), with no @Cancel handler needed on the agent’s side at all:
@NewSession returning a plain Uni<NewSessionResponse> follows the same rule as any other async
return: the runtime waits for its item on the handler’s thread, the same as a Mono.
The CDI interceptor binding
@Audited is a plain CDI @InterceptorBinding, nothing ACP-specific. Binding it to the prompt
handler makes Quarkus’s ArC generate a subclass of the agent bean at build time. The SDK finds the
handlers through the @AcpAgent class that subclass extends, and invokes them on the bean ArC hands
it, so the interceptor runs around the handler, the same discovery pattern
Micronaut uses for its own @Around advice.
Reading the derived initialize answer
Serving HTTP on Quarkus’s own server
A Streamable HTTP client and a WebSocket client both connect to the same /acp path on
quarkus.http.port, the Quarkus application’s own server, not a separate SDK listener. The path sits
under quarkus.http.root-path (/ by default) and is served by one Vert.x route (the extension
depends on quarkus-vertx-http, not Undertow); no servlet container, no second server. The CDI
interceptor’s call count keeps climbing across both connections: one bean serves every connection
Quarkus accepts.
quarkus dev can’t run a stdio agent. Dev mode owns the terminal’s standard input for its own
interactive commands, so this module’s demo packages the application
(quarkus-run.jar) and runs it as a real process over HTTP instead of using quarkus dev, the shape
an editor actually launches in production. Develop a stdio agent in tests, or over HTTP as here.
Source Code
View on GitHub
Running the Example
The demo packages and starts the Quarkus application as a separate process on a free port, connects a
Streamable HTTP client that prints the derived initialize answer, starts a count to ten stream,
cancels it after the third chunk (stop reason cancelled), asks what happened, then connects a
WebSocket client to the same server and path for one more prompt. No API key required.
Back to the Overview
Tutorial Overview: all modules and the recommended paths through them.