Skip to content

Releases: agentclientprotocol/java-sdk

Release 0.80.0

Choose a tag to compare

@github-actions github-actions released this 08 Oct 08:34

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, perm...
Read more

Release 0.18.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 19:17

Remote agents: the Streamable HTTP and WebSocket transport from the ACP RFD, contributed by @kamikaz1k (#7), on plain http:// (HTTP/2 over cleartext) and https://, with a new acp-streamable-http-jetty module for the agent side. Plus a choice of Jackson 2 or Jackson 3 (#12), and the fixes found reviewing, interop-testing and load-testing the transport.

Upgrading: three changes need attention

  1. acp-core no longer contains a JSON implementation (#12). If you depend on acp-core alone, add one JSON module; acp-agent-support, acp-test, acp-websocket-jetty and acp-streamable-http-jetty already bring acp-json-jackson2.
    <dependency>
        <groupId>com.agentclientprotocol</groupId>
        <artifactId>acp-json-jackson2</artifactId> <!-- or acp-json-jackson3 -->
        <version>0.18.0</version>
    </dependency>
  2. A second client on an already-connected transport now fails at construction. This fixes the Spring Boot "unicast" failure, where an AcpAsyncClient and an AcpSyncClient bean built from one transport left the second client waiting out its request timeout.
  3. Unknown-field handling moved to the mapper (#10). A JacksonAcpJsonMapper built from a bare new ObjectMapper() is now strict about unknown fields; start from JacksonAcpJsonMapper.defaultObjectMapper() to keep the lenient default.

Highlights

  • Streamable HTTP transport. Client: StreamableHttpAcpClientTransport in acp-core (JDK HttpClient, no new dependency), with SSE stream reconnect and HTTP/2 over cleartext. Agent: StreamableHttpAcpAgentTransport (Jetty 12.1) serves HTTP/SSE and a WebSocket upgrade on one path, one agent per remote connection. StreamableHttpAcpServlet mounts in any Servlet 6 container.
  • JSON modules. acp-json-jackson2 (Jackson 2) and acp-json-jackson3 (Jackson 3.1.5, tools.jackson) write identical bytes on the wire. Mapper selection is deterministic by AcpJsonMapperSupplier.priority(), and the system property acp.json.mapper.supplier overrides it.
  • Single-turn enforcement is per session, not per connection, so many sessions share one HTTP connection.
  • Fixed: the multi-prompt wedge under CPU contention (#14), reported by @krickert. CI now runs the contention tests pinned to one CPU.
  • Deprecated for removal: WebSocketAcpAgentTransport (acp-websocket-jetty). Use acp-streamable-http-jetty, which serves the same WebSocket clients.

Tested

  • 890 tests in the full build.
  • A cross-SDK suite against the TypeScript, Rust and Python SDKs in both directions, plus load scenarios up to 1,000 connections and 5,000 prompts with 0 errors: integration-testing/scripts/run-all.sh, 11/11 passing.

The complete list is in CHANGELOG.md.

Maven Central

com.agentclientprotocol:*:0.18.0: acp-core, acp-json-jackson2, acp-json-jackson3, acp-annotations, acp-agent-support, acp-test, acp-streamable-http-jetty, acp-websocket-jetty (deprecated).

Full Changelog: v0.17.0...v0.18.0

Release 0.17.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 11:32

Full Changelog: v0.16.1...v0.17.0

Release 0.16.1

Choose a tag to compare

@github-actions github-actions released this 24 Aug 20:31

Full Changelog: v0.16.0...v0.16.1

Release 0.16.0

Choose a tag to compare

@github-actions github-actions released this 24 Aug 06:11

Full Changelog: v0.15.0...v0.16.0

Release 0.15.0

Choose a tag to compare

@github-actions github-actions released this 22 Aug 01:18

Correctness and supply-chain hygiene. No protocol or public API changes — the ACP surface is
identical to 0.14.0, so this is a drop-in upgrade.

Fixed

Notification ordering. Incoming notifications were dispatched as independent fire-and-forget
subscriptions, so a handler doing any async work could observe them out of order. This is visible
with agents that stream many rapid session/update chunks. They are now serialized through a sink
drained by concatMap, preserving arrival order.

Reported and fixed by @ljiro in #13 (closes #11).

Notifications lost on graceful close. Following from the above, closeGracefully() completed
the notification sink and disposed the drain subscription in the same synchronous block, discarding
anything still queued. Because AcpClient.SyncSpec wraps every sync sessionUpdateConsumer with
subscribeOn(SYNC_HANDLER_SCHEDULER), sync clients always have async handlers — so a rapid burst
could be lost entirely on close.

closeGracefully() now waits for the drain to terminate before tearing the session down, bounded by
the session's requestTimeout so a handler that never completes cannot hang shutdown. close()
still interrupts immediately; the two methods now differ, as their names imply.

Notifications arriving after shutdown begins are still dropped — a graceful shutdown stops
accepting new work while draining what is queued, and JSON-RPC notifications carry no delivery
guarantee — but the log severity now separates that expected case (DEBUG) from overflow,
zero-subscriber and non-serialized emission, which lose traffic on a live session (ERROR).

Security

Jackson 2.21.2 → 2.21.5 and Jetty 12.0.14 → 12.0.37, clearing 17 known advisories
(5 high severity) reported against the published dependency closure. Both reach consumers as
compile-scope transitives of acp-core and acp-websocket-jetty, so upgrading is recommended for
all users.

Licensing

LICENSE is now the verbatim Apache License 2.0. The previous file was a paraphrase: it omitted
section 6 (Trademarks) entirely, renumbered the sections that follow, rewrote the section 2
copyright grant, and narrowed the Licensor and Work definitions. Automated license detection
classified the repository as NOASSERTION while every published POM declared Apache-2.0.

LICENSE and a new NOTICE are now packaged under META-INF in every module artifact, as
Apache-2.0 section 4 expects.

Build and CI

  • Integration tests now execute. Three *IT classes existed, but Surefire's default includes do not
    match *IT and no Failsafe plugin was configured, so mvn verify silently skipped them.
  • All GitHub Actions are pinned to commit SHAs.
  • Removed a redundant <repositories> declaration from the published parent POM; consumers no
    longer inherit a repository definition with snapshots enabled.

Installation

<dependency>
    <groupId>com.agentclientprotocol</groupId>
    <artifactId>acp-core</artifactId>
    <version>0.15.0</version>
</dependency>

Full Changelog: v0.14.0...v0.15.0

Release 0.14.0

Choose a tag to compare

@github-actions github-actions released this 11 Jun 04:38

Full Changelog: v0.12.0...v0.14.0

Release 0.12.0

Choose a tag to compare

@github-actions github-actions released this 26 May 01:23

Full Changelog: v0.11.0...v0.12.0

Release 0.11.0

Choose a tag to compare

@github-actions github-actions released this 12 May 20:28

Full Changelog: v0.10.0...v0.11.0

Release 0.10.0

Choose a tag to compare

@github-actions github-actions released this 10 Apr 04:15

What's Changed

New Contributors

Full Changelog: v0.9.0...v0.10.0