Skip to main content

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.