Skip to content

Release 0.80.0

Latest

Choose a tag to compare

@github-actions github-actions released this 08 Oct 08:34
· 2 commits to main since this release

ACP Java SDK 0.80.0

The biggest release of the ACP Java SDK so far. It covers every stable ACP v1 method on both sides. It adds first-class Spring Boot, Quarkus and Micronaut support. Its annotation model now drives what an agent tells the protocol about itself. Every change is proven against the TypeScript, Rust, Python and Kotlin SDKs.

This release breaks the API in places, as a pre-1.0 SDK may. Every break has a migration in CHANGELOG.md. Start with Upgrading from 0.18.0 below.

Highlights

Spring Boot, Quarkus and Micronaut

Each framework has its own idiomatic integration. All three build on one framework-neutral module, acp-integration, for configuration, discovery and lifecycle.

Framework Artifact Config prefix HTTP / WebSocket
Spring Boot 4 (MVC) acp-spring-boot-starter + acp-http-servlet spring.acp.* on the app's own server.port (Tomcat or Jetty)
Spring Boot 4 (WebFlux) acp-spring-boot-starter + acp-http-webflux spring.acp.* on the app's own server.port (Reactor Netty by default)
Spring Boot 4 (no web server) acp-spring-boot-starter + acp-streamable-http-jetty spring.acp.* the SDK's embedded listener
Quarkus 3 acp-quarkus quarkus.acp.* Quarkus's own HTTP server and router (no Jetty, no Undertow)
Micronaut 4 acp-micronaut + acp-streamable-http-jetty (for HTTP / WebSocket) acp.* the SDK's embedded listener, on its own port (interim; see Known issues)

All three integrations support the following:

  • An @AcpAgent bean is discovered, including when it is proxied or intercepted.
  • stdio, Streamable HTTP and WebSocket transports.
  • A client bean built from configuration, plus customizers.
  • Handlers run on the framework's own executor (virtual threads where available).
  • A clean exit when stdin closes, and a graceful shutdown.
  • On Spring (MVC and WebFlux) and Quarkus, /acp is served by the application's own server, so the framework's security, TLS, metrics and tracing apply to it. Tests prove that Spring Security and Quarkus HTTP auth policies guard /acp, over HTTP and on the WebSocket handshake, and that the authenticated principal reaches the ACP endpoint (AcpHttpExchange.principal()).

Quarkus handlers can also return Mutiny Uni and Multi, and cancelling a prompt cancels them.

The Spring Boot integration was previously org.springaicommunity:acp-spring-boot-starter. It now ships with the SDK under com.agentclientprotocol.

The annotation model is the API

  • Advertises what you implement. The initialize response is derived from your handlers: load, list, resume, close, delete, logout and providers. Prompt and MCP capabilities, auth methods and agentInfo come from @AcpAgent and @Prompt. An explicit @Initialize is merged on top of the derived values.
  • New annotations and parameters. @Authenticate is new. Any handler can take its connection's NegotiatedCapabilities and agent, and @SetSessionConfigOption gets typed @ConfigId and @ConfigValue parameters.
  • Prompt handlers. They can check isCancelled(), and they can return CompletionStage or Publisher as well as plain values and Mono.
  • Mistakes fail at startup, with the class, method and fix named. This covers unresolvable parameters, unsupported return types, duplicate handlers and annotations in the wrong place.
  • Parity with the builder API, enforced by a test.

Protocol

  • Every stable ACP v1 method and type, on both client and agent. That includes:
    • session config options (select and boolean, grouped selects), which replace session/set_model;
    • elicitation (form and URL);
    • terminal authentication and logout;
    • session list, resume, close and delete;
    • _-prefixed extension methods;
    • $/cancel_request.
  • Cancellation follows the spec exactly. After session/cancel the agent answers cancelled, whatever the handler returns or throws. Updates sent after the answer are dropped. A configurable grace period and an optional maximum prompt duration bound the turn.
  • Forward compatible. Unknown updates, content, options and enum values from a newer peer are kept and passed on.
  • Ordering. A client's response, and any request from the agent, arrives only after the updates sent before it have been handled.
  • Clients guard themselves.
    • Calls before initialize(), and calls the agent didn't advertise, fail locally.
    • A capability advertised without its handler fails at build().
    • Prompts are no longer cut off by the request timeout; promptTimeout is optional and unset by default.
  • One error model.
    • Handlers throw AcpProtocolException.
    • Callers catch AcpError, now an AcpException, for every failed request.
    • A lost connection fails with AcpConnectionException.
    • Unexpected handler exceptions answer "Internal error" without leaking their message to the peer.

Transports and hosting

  • One protocol core, thin hosts. Every Streamable HTTP and WebSocket rule lives in one framework-neutral core (acp-http-core). Each host only adapts I/O, through a small host contract (@UnstableAcpApi in 0.80.0). Hosts in 0.80.0:

    • a servlet with Jakarta WebSocket 2.1, which upgrades on its own path in any Servlet 6 container (acp-http-servlet);
    • Spring WebFlux (acp-http-webflux);
    • Quarkus on its Vert.x router;
    • the embedded Jetty listener for plain Java (acp-streamable-http-jetty).

    One shared transport test kit runs against all nine host configurations. Plain http:// on the embedded listener gets HTTP/2 (h2c).

  • Streaming defaults every host gets:

    • SSE carries X-Accel-Buffering: no, so nginx doesn't buffer it, and isn't cut by container async timeouts;
    • WebSocket sends are serialized;
    • shutdown drains open streams promptly, with a final event and close 1001, within a bounded timeout.
  • An ACP session is a long-lived conversation, not a web request. Every WebSocket host closes a connection that stays idle past web-socket-idle-timeout (30 minutes by default, close 1001), and one that never sends initialize within initialize-timeout (30 s by default, close 1008). Set them with spring.acp.agent.transport.http.web-socket-idle-timeout / .initialize-timeout, the same keys under acp. (Micronaut) and quarkus.acp., or the transport options builder.

  • Shutdown is clean:

    • a WebSocket that opens while the server shuts down no longer leaves its agent running;
    • WebSocket clients get the going-away close (1001) when a Jetty-hosted endpoint stops;
    • a client that never answers the close holds shutdown no longer than its timeout.
  • stdio: end of input drains every answer, and a graceful client close lets the agent exit with status 0.

  • Threads: the network transports run on the application's executor. On JDK 21+, plain Java and Micronaut use virtual threads; Spring follows spring.threads.virtual.enabled; Quarkus uses its managed executor unless quarkus.acp.handler-executor=virtual.

  • Close: idempotent on every transport, with a bounded shutdown.

Security

  • The embedded listener binds to loopback by default (127.0.0.1, and ::1 where available). Exposing it is an explicit opt-in (…transport.http.listener.host). Framework-hosted ACP keeps the framework's own bind setting.
  • Browser origins are checked on HTTP and on the WebSocket handshake, on every host. Requests with no Origin, or a loopback origin, are allowed. Others get 403 unless listed in allowed-origins; * disables the check. This defends against DNS rebinding.
  • Logs don't carry payloads above DEBUG.
    • A malformed message is reported without its content.
    • The WebSocket client logs its endpoint without query or user info.
    • A peer's error details are logged only at DEBUG.
    • Agent stderr is logged at INFO on its own logger (…client.transport.agent-stderr), and the README says how to silence it.
    • DEBUG and TRACE log message content, and the docs say not to enable them where logs are shipped.
  • Handler exceptions answer "Internal error" without leaking their message.

Quality

These checks are enforced in CI, with no baselines:

  • NullAway (JSpecify, every package)
  • Error Prone
  • ArchUnit (no cycles, framework-neutral core)
  • PMD and CPD
  • SpotBugs, including the concurrency patterns
  • JaCoCo coverage floors
  • Lincheck model checks of the concurrency core
  • PIT mutation testing on the protocol core (a dedicated CI job)
  • an OSV dependency scan

A cross-SDK suite runs the Java client and agent against TypeScript, Rust, Python and Kotlin, over stdio, HTTP and WebSocket, plus Spring, Micronaut and Quarkus-hosted agents.

The public API is documented throughout, and the Javadoc examples are compile-checked.

Upgrading from 0.18.0

The most common changes are listed below. CHANGELOG.md has every break and its migration.

  • Removed APIs:
    • session/set_model is gone; offer a model category config option instead.
    • acp-websocket-jetty is removed; StreamableHttpAcpAgentTransport accepts WebSocket upgrades.
    • PromptResponse.text(...) is removed; return the String from @Prompt, or sendMessage then endTurn().
  • Values and records:
    • Several enums are now open value types. Compare them with equals, not switch.
    • The short session-update constructors no longer take the discriminator.
  • Client:
    • initialize(InitializeRequest) is removed; set capabilities and clientInfo on the builder.
    • Register each client and agent handler once; a second registration throws.
    • The default request timeout is 60 s everywhere.
  • Prompt context and handlers:
    • context.sendUpdate(u) is now context.sendSessionUpdate(u);
    • the raw client calls (file system, permission, terminal, elicitation) moved behind context.client();
    • sessionUpdateConsumer / defaultSessionUpdateConsumer are now sessionUpdateHandler / defaultSessionUpdateHandler;
    • a client with handlers but no explicit clientCapabilities now advertises them.
  • HTTP hosting:
    • A servlet web app (Spring MVC included) depends on acp-http-servlet, a WebFlux app on acp-http-webflux, and a plain-Java app keeps acp-streamable-http-jetty.
    • The embedded listener binds to loopback; set …listener.host to expose it.
    • Foreign browser origins get 403 unless listed in allowed-origins.
    • WebSocket shutdown closes with 1001, not 1000.
    • Quarkus serves /acp under quarkus.http.root-path and no longer brings in Undertow.
    • Micronaut's listener keys are under acp.agent.transport.http.listener.*.
  • Spring Boot:
    • New coordinates com.agentclientprotocol:acp-spring-boot-starter.
    • Packages moved to com.agentclientprotocol.sdk.spring.boot.autoconfigure.
    • Some properties moved; see the CHANGELOG and the module README.

Requirements

  • Java 17 or later.
  • Jackson 2.18.1 or later, or Jackson 3.0.0 or later. The version is checked at startup.
  • Spring Boot 4.1, Quarkus 3.40, or Micronaut 4.10, for the framework modules.

Known issues

  • Spring WebFlux on Jetty: Jetty's connection idle timeout, 30 s by default, closes an ACP WebSocket after 30 s without traffic. Set server.jetty.connection-idle-timeout (e.g. 30m), or use Reactor Netty, the default for WebFlux. A fix is planned for 0.80.1.
  • Micronaut serves ACP on the SDK's embedded listener, on its own port, so Micronaut's own security does not apply to it. This is an interim measure until the Netty host in 0.81.0.
  • An oversized request body may occasionally surface as a connection error rather than HTTP 413 on Tomcat (reactive) and Jetty hosts. The request is still refused; only the error the client sees differs. Bodies within the limit (16 MiB by default) are unaffected.
  • Outbound queues on the stdio transports and the WebSocket client are unbounded: a peer that keeps sending requests without reading responses grows the sender's memory, about one response per request. Server-side HTTP and WebSocket hosts are bounded. Bounded outbound queues are planned for 0.81.0.

Full Changelog: v0.18.0...v0.80.0