Skip to content

Commit ae3e88e

Browse files
author
Mark Pollack
committed
Fold the Quarkus review into the acp-integration sketch
AcpServletHost serves Quarkus (undertow) as well as Spring, the agent's HTTP settings split into limits and the listener's own, Micronaut's client transport rule with Spring's actual order corrected, onTransportEnd fixed at construction, build-time discovery by class name, from() optional, and the WebSocket connection API and a Jetty-free servlet module after 0.80.0.
1 parent 735e74f commit ae3e88e

1 file changed

Lines changed: 45 additions & 19 deletions

File tree

‎acp-integration/SKETCH.md‎

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ annotation runtime. Those are SDK hooks (review section 2) and live in acp-agent
1212

1313
Quarkus and Micronaut integrations are required for 0.80.0, not previews (owner). Reviewed by acp-dev-steward
1414
on 2026-10-03; its changes are folded in below. The Micronaut review (ten items, accepted by acp-dev-steward,
15-
see `acp-java-steward/plans/journal/2026-10-03-micronaut-progress.md`) is folded in too. Quarkus's review is next. Extraction
15+
see `acp-java-steward/plans/journal/2026-10-03-micronaut-progress.md`) is folded in too, as is the Quarkus review (`acp-java-steward/plans/journal/2026-10-03-quarkus-progress.md`;
16+
Quarkus serves HTTP on its own server: the SDK servlet on quarkus-undertow plus a Vert.x WebSocket route, no Jetty). Extraction
1617
waits for those reviews and for devex slice 1.
1718

1819
## Settings
@@ -33,18 +34,29 @@ public record AcpAgentSettings(
3334
// listener serves both); default STDIO
3435
Http http) {
3536

36-
public record Http(int port, String path, // 8080, "/acp"; port 0 = ephemeral
37-
@Nullable Long maxPostBodyBytes, @Nullable Duration keepAliveInterval,
37+
/** The endpoint, wherever it is served: the framework's own server or the SDK listener. */
38+
public record Http(String path, // "/acp"
39+
Limits limits, Listener listener) {}
40+
41+
/** Message and stream bounds; unset → SDK default. */
42+
public record Limits(@Nullable Long maxPostBodyBytes, @Nullable Duration keepAliveInterval,
3843
@Nullable Integer mailboxCapacity, @Nullable Integer maxPendingSseEvents,
3944
@Nullable Integer maxWebSocketPendingFrames, @Nullable Integer maxProvisionalSessions,
40-
@Nullable Integer maxConcurrentStreamsPerConnection, @Nullable Duration shutdownTimeout) {
41-
public StreamableHttpAcpAgentTransportOptions toOptions(); // unset → SDK default
42-
}
45+
@Nullable Duration shutdownTimeout) {}
46+
47+
/** Only for the SDK's own listener; meaningless inside a framework's server. */
48+
public record Listener(int port, // 8080; 0 = ephemeral
49+
@Nullable Integer maxConcurrentStreamsPerConnection) {}
50+
51+
/** Limits plus, for the listener, its stream limit; unset → SDK default. */
52+
public StreamableHttpAcpAgentTransportOptions toOptions(boolean listener);
4353

4454
public static Builder builder();
4555
/**
46-
* Keys relative to prefix, kebab-case only, e.g. "request-timeout", "transport.type",
47-
* "transport.http.port". Normalising the framework's own key forms to kebab-case is the binder's job.
56+
* Optional path: keys relative to prefix, kebab-case only, e.g. "request-timeout", "transport.type",
57+
* "transport.http.listener.port". Normalising the framework's own key forms to kebab-case is the
58+
* binder's job. A framework that binds its configuration straight onto the builders (Quarkus
59+
* @ConfigMapping) does not use it; Micronaut does.
4860
*/
4961
public static AcpAgentSettings from(SettingsSource source, String prefix);
5062
}
@@ -86,8 +98,9 @@ public interface SettingsSource {
8698
public final class AcpClientTransports {
8799
/**
88100
* An explicit type wins. Otherwise exactly one of stdio.command, websocket.uri and http.uri selects
89-
* the transport; more than one with no explicit type throws, naming them (a change for Spring, which
90-
* picked one; it goes in the CHANGELOG at extraction). Empty when none is set (a client-only app).
101+
* the transport; more than one with no explicit type throws, naming them. That replaces Spring's rule,
102+
* which picked websocket, then http, then stdio; it goes in the CHANGELOG at extraction, and a test in
103+
* acp-integration pins it. Empty when none is set (a client-only app).
91104
* An explicit type without its property throws IllegalStateException naming the property.
92105
*/
93106
public static Optional<AcpClientTransport> create(AcpClientSettings settings);
@@ -120,6 +133,9 @@ public final class AcpAgentDiscovery {
120133
*/
121134
public static Optional<AgentCandidate<?>> requireSingle(Collection<? extends AgentCandidate<?>> candidates);
122135

136+
/** Build-time discovery (Quarkus/Jandex): the same rule over class names, no classes loaded. */
137+
public static Optional<String> requireSingle(List<String> classNames);
138+
123139
public record AgentCandidate<T>(String name, Class<T> userClass, Supplier<? extends T> instance) {}
124140
}
125141

@@ -181,21 +197,21 @@ public interface AcpHost {
181197
* and fix4's AutoCloseable AcpSyncAgent/AcpAgentSupport, not a copy of their start and stop logic.
182198
*/
183199
public final class AcpAgentHost implements AcpHost {
184-
public AcpAgentHost(AcpAgentSupport agent, AcpAgentTransport transport);
185200
/**
186-
* Latched: runs once, on a host-owned thread (never the transport's), also when the transport had
187-
* already ended before the call. Not run when the host itself stopped the agent. The framework passes
201+
* onTransportEnd is latched: it runs once, on a host-owned thread (never the transport's), also when
202+
* the transport ended before the host saw it, and not when the host itself stopped the agent. It is
203+
* fixed at construction, so there is no window in which the end is missed. The framework passes
188204
* "close my container" (Spring: ConfigurableApplicationContext::close; Quarkus: () -> Quarkus.asyncExit(0)).
189205
*/
190-
public void onTransportEnd(Runnable action);
206+
public AcpAgentHost(AcpAgentSupport agent, AcpAgentTransport transport, Runnable onTransportEnd);
191207
}
192208

193209
/** The HTTP listener (in AcpListeners' optional part). */
194210
public final class AcpListenerHost implements AcpHost {
195211
public AcpListenerHost(StreamableHttpAcpAgentTransport listener);
196212
}
197213

198-
/** The servlet inside a framework's Servlet container. Servlet-only: Spring is its user. */
214+
/** The servlet inside a framework's Servlet container: Spring (Tomcat, Jetty) and Quarkus (undertow). */
199215
public final class AcpServletHost {
200216
/**
201217
* Call before the container's graceful shutdown: each connection holds an open SSE response,
@@ -212,7 +228,8 @@ public final class AcpClientHost {
212228
}
213229
```
214230

215-
Not here: the `Publisher` return-value handler belongs in acp-agent-support (devex is adding it).
231+
Not here: the `CompletionStage` and `Publisher` return-value handlers belong in acp-agent-support (devex);
232+
Mutiny's `Uni` and `Multi` stay in acp-quarkus.
216233

217234
## What stays in each framework
218235

@@ -228,17 +245,26 @@ Not here: the `Publisher` return-value handler belongs in acp-agent-support (dev
228245
## Settled questions (acp-dev-steward, 2026-10-03)
229246

230247
1. `from(lookup, prefix)` takes kebab-case keys only; normalising them is the binder's job.
231-
2. Micronaut uses the SDK listener or its own Netty binding; `AcpServletHost` stays servlet-only.
248+
2. Micronaut uses the SDK listener or its own Netty binding. `AcpServletHost` is for Servlet containers:
249+
Spring and Quarkus (undertow).
232250
3. The DEBUG default consumer applies in every framework, and is replaced (not added to) when the
233251
application registers its own consumer.
234252

253+
## After 0.80.0
254+
255+
- A container-neutral WebSocket connection API, so Quarkus (Vert.x), Micronaut (Netty) and others route
256+
WebSocket upgrades to the SDK without Jetty.
257+
- A Jetty-free `acp-streamable-http-servlet` module, with the servlet apart from the Jetty listener.
258+
235259
## Spring layer changes that follow at extraction
236260

237261
- The `request-timeout` properties stop defaulting to 60s (they become null, meaning the SDK default).
238262
- A new `prompt-timeout` client property.
239263
- The new capability properties.
240264
- The DEBUG consumer becomes replace-not-add (once the SDK supports it). Today the Spring client adds it beside the
241265
customizers' consumers.
242-
- Several client transport properties set with no `type` fail at startup (today: stdio, then websocket, then http
243-
wins). It goes in the CHANGELOG with the extraction.
266+
- Several client transport properties set with no `type` fail at startup (today websocket, then http, then
267+
stdio wins). It goes in the CHANGELOG with the extraction.
268+
- The agent's `transport.http.port` and `max-concurrent-streams-per-connection` move under
269+
`transport.http.listener.*`.
244270
- `spring.acp.agent.transport.type=websocket` becomes a synonym for `http`.

0 commit comments

Comments
 (0)