The four WebSocket transports — arcp-middleware-spring-boot/SpringWebSocketTransport, arcp-middleware-jakarta/JakartaWebSocketTransport, arcp-middleware-vertx/VertxWebSocketTransport, and arcp-runtime-jetty/WebSocketJsonTransport — all implement Transport.send(Envelope) by calling the underlying socket's text-send method directly. None of them serialize concurrent writes. The Jakarta WebSocket specification explicitly requires application-level synchronization around RemoteEndpoint.Basic.sendText; Spring's WebSocketSession.sendMessage likewise throws IllegalStateException if two threads attempt simultaneous sends. The runtime exercises this constantly: SessionLoop sends from at least three thread families (the agent worker on runtime.workerPool(), the scheduler's heartbeat tick, and the inbound dispatch thread driving onNext). Under load, two threads in any of these will overlap a send() call and the WebSocket layer will throw, dropping the session and any in-flight events.\n\nThe defect is in arcp-middleware-spring-boot/src/main/java/dev/arcp/middleware/spring/SpringWebSocketTransport.java around the send method, arcp-middleware-jakarta/src/main/java/dev/arcp/middleware/jakarta/JakartaWebSocketTransport.java around the send method, arcp-middleware-vertx/src/main/java/dev/arcp/middleware/vertx/VertxWebSocketTransport.java around the send method, and arcp-runtime-jetty/src/main/java/dev/arcp/runtime/jetty/WebSocketJsonTransport.java around the send method. The StdioTransport already demonstrates the correct pattern with its writeLock ReentrantLock guarding the write/flush region.\n\nFix prompt: In each of the four WebSocket-backed Transport implementations listed above (SpringWebSocketTransport, JakartaWebSocketTransport, VertxWebSocketTransport, WebSocketJsonTransport), add a private final ReentrantLock writeLock = new ReentrantLock() field and serialize the body of send(Envelope) under writeLock.lock() / writeLock.unlock() in a try/finally — exactly mirroring the pattern in arcp-core/src/main/java/dev/arcp/core/transport/StdioTransport.java. The serialization must cover both the mapper.writeValueAsString call and the underlying socket send so the bytes of a single envelope are not interleaved with another envelope's. For Vert.x specifically, also confirm that calling socket.writeTextMessage from a non-event-loop thread is safe; if not, dispatch the write to the socket's context via socket.context().runOnContext(...) instead of relying on a Java lock. Add a JUnit 5 test in each middleware module that fires 100 concurrent Transport.send calls from a fixed thread pool of size 8 and asserts no exceptions are thrown and the receiver observes 100 well-formed envelopes.
The four WebSocket transports —
arcp-middleware-spring-boot/SpringWebSocketTransport,arcp-middleware-jakarta/JakartaWebSocketTransport,arcp-middleware-vertx/VertxWebSocketTransport, andarcp-runtime-jetty/WebSocketJsonTransport— all implementTransport.send(Envelope)by calling the underlying socket's text-send method directly. None of them serialize concurrent writes. The Jakarta WebSocket specification explicitly requires application-level synchronization aroundRemoteEndpoint.Basic.sendText; Spring'sWebSocketSession.sendMessagelikewise throwsIllegalStateExceptionif two threads attempt simultaneous sends. The runtime exercises this constantly:SessionLoopsends from at least three thread families (the agent worker onruntime.workerPool(), the scheduler's heartbeat tick, and the inbound dispatch thread drivingonNext). Under load, two threads in any of these will overlap asend()call and the WebSocket layer will throw, dropping the session and any in-flight events.\n\nThe defect is in arcp-middleware-spring-boot/src/main/java/dev/arcp/middleware/spring/SpringWebSocketTransport.java around thesendmethod, arcp-middleware-jakarta/src/main/java/dev/arcp/middleware/jakarta/JakartaWebSocketTransport.java around thesendmethod, arcp-middleware-vertx/src/main/java/dev/arcp/middleware/vertx/VertxWebSocketTransport.java around thesendmethod, and arcp-runtime-jetty/src/main/java/dev/arcp/runtime/jetty/WebSocketJsonTransport.java around thesendmethod. The StdioTransport already demonstrates the correct pattern with itswriteLockReentrantLock guarding the write/flush region.\n\nFix prompt: In each of the four WebSocket-backed Transport implementations listed above (SpringWebSocketTransport, JakartaWebSocketTransport, VertxWebSocketTransport, WebSocketJsonTransport), add a private finalReentrantLock writeLock = new ReentrantLock()field and serialize the body ofsend(Envelope)underwriteLock.lock()/writeLock.unlock()in a try/finally — exactly mirroring the pattern inarcp-core/src/main/java/dev/arcp/core/transport/StdioTransport.java. The serialization must cover both themapper.writeValueAsStringcall and the underlying socket send so the bytes of a single envelope are not interleaved with another envelope's. For Vert.x specifically, also confirm that callingsocket.writeTextMessagefrom a non-event-loop thread is safe; if not, dispatch the write to the socket's context viasocket.context().runOnContext(...)instead of relying on a Java lock. Add a JUnit 5 test in each middleware module that fires 100 concurrentTransport.sendcalls from a fixed thread pool of size 8 and asserts no exceptions are thrown and the receiver observes 100 well-formed envelopes.