Summary
The Swift SDK (packages/sdk-swift, AgentRelaySDK.RelayCast) cannot connect to a v7.1.1 broker even when both are at matching version (7.1.1). RelayCast is still implementing the legacy hello/hello_ack WebSocket handshake, but the v7 broker's /ws endpoint is now a read-only event broadcast (handle_dashboard_ws in crates/broker/src/listen_api.rs) — it never sends hello_ack, so every Swift SDK connection times out.
Repro
npm install -g agent-relay@7.1.1
- From a fresh CWD, start a broker:
agent-relay up --no-dashboard --foreground --state-dir ./.agent-relay
- Build a minimal Swift consumer of
AgentRelaySDK:
import AgentRelaySDK
let conn = try! JSONDecoder().decode(
[String: String].self,
from: try! Data(contentsOf: URL(fileURLWithPath: ".agent-relay/connection.json"))
)
let relay = RelayCast(apiKey: conn["api_key"]!, baseURL: URL(string: conn["url"]!)!)
try await relay.channel("test").subscribe() // throws timeout
Observed:
RelayError.connectionFailed("NSURLErrorDomain Code=-1011 \"There was a bad response from the server.\" ...")
…or, if /ws resolves correctly:
RelayError.timeout("Timed out waiting for hello_ack")
Expected: Connection succeeds and events stream.
Root cause (confirmed by reading sources)
1. Wrong WebSocket path
Sources/AgentRelaySDK/RelayTransport.swift:101-107 defaults the WS path to /v1/ws:
if components?.path.isEmpty ?? true { components?.path = \"/v1/ws\" }
if !(components?.path.hasSuffix(\"/v1/ws\") ?? false) && !(components?.path.hasSuffix(\"/ws\") ?? false) {
components?.path = \"/v1/ws\"
}
But the v7 broker registers /ws (no /v1 prefix) — see crates/broker/src/listen_api.rs:414:
.route(\"/ws\", routing::get(listen_api_ws))
A GET on /v1/ws with valid auth returns 404; only /ws is live (returns 400 to a non-upgrade GET).
2. Wrong protocol on /ws
Even after correcting the path, the Swift SDK sends a hello frame (RelayCast.swift:88) and awaits hello_ack. But v7's /ws handler is handle_dashboard_ws (crates/broker/src/listen_api.rs:1994-) — it's a read-only broadcast stream. It never reads from the socket beyond ping/pong and never sends hello_ack. Result: Timed out waiting for hello_ack.
For comparison, the official Node SDK (@agent-relay/sdk v7.1.1) opens the same /ws with header X-API-Key, does not send a hello, and just receives events (packages/sdk/dist/transport.js:375-417). Actions (spawn_agent, send_message, release_agent) are now plain HTTP — POST /api/spawn, POST /api/send, DELETE /api/spawned/{name} etc. (crates/broker/src/listen_api.rs:357-412).
3. The newer Swift RelayObserver is v7-compatible but read-only
packages/sdk-swift/Sources/AgentRelaySDK/RelayObserver.swift (added in #627) implements the v7 event-stream contract correctly. But it only consumes events — there is no Swift counterpart for the HTTP action endpoints, so an SDK consumer can't actually drive agents.
Suggested fix
Realign RelayCast with the v7 contract:
- Drop the
hello/hello_ack handshake. Connect to /ws with X-API-Key, treat "connected" as "WS upgrade succeeded."
- Use
/ws, not /v1/ws. Or detect at connect time (probe with auth: 400→/ws, 404→try other).
- Move SDK actions (
spawnAgent, releaseAgent, channel post, agent dm) onto HTTP. Map to the existing broker REST endpoints:
spawnAgent → POST /api/spawn
releaseAgent → DELETE /api/spawned/{name}
- Channel
post → POST /api/send
- Keep
RelayCast.channel(...).events and brokerEvents as views over the WS event stream (same shape as today) — wire it through RelayObserver-style decoding.
This restores byte-compat behavior for consumers (spawnAndAwait etc. still work) while moving the wire format to v7.
Impact
Any Swift consumer of RelayCast against a v7 broker — including locally-spawned brokers via agent-relay up. Caught while building Axiom, a SwiftUI macOS planning-conversation app that needs to drive a local Claude agent over Relay. Blocked end-to-end until RelayCast either gets the v7 fix or we route around it via raw HTTP + RelayObserver.
Environment
- macOS 15 (Darwin 25.5.0), Apple Silicon
- Swift toolchain 5.9+, Package.swift pins
relay to ff1d4da (v7.1.1)
- Broker:
agent-relay@7.1.1 (npm), broker binary @agent-relay/broker-darwin-arm64
Summary
The Swift SDK (
packages/sdk-swift,AgentRelaySDK.RelayCast) cannot connect to a v7.1.1 broker even when both are at matching version (7.1.1).RelayCastis still implementing the legacyhello/hello_ackWebSocket handshake, but the v7 broker's/wsendpoint is now a read-only event broadcast (handle_dashboard_wsincrates/broker/src/listen_api.rs) — it never sendshello_ack, so every Swift SDK connection times out.Repro
npm install -g agent-relay@7.1.1agent-relay up --no-dashboard --foreground --state-dir ./.agent-relayAgentRelaySDK:Observed:
…or, if
/wsresolves correctly:Expected: Connection succeeds and events stream.
Root cause (confirmed by reading sources)
1. Wrong WebSocket path
Sources/AgentRelaySDK/RelayTransport.swift:101-107defaults the WS path to/v1/ws:But the v7 broker registers
/ws(no/v1prefix) — seecrates/broker/src/listen_api.rs:414:A GET on
/v1/wswith valid auth returns 404; only/wsis live (returns 400 to a non-upgrade GET).2. Wrong protocol on
/wsEven after correcting the path, the Swift SDK sends a
helloframe (RelayCast.swift:88) and awaitshello_ack. But v7's/wshandler ishandle_dashboard_ws(crates/broker/src/listen_api.rs:1994-) — it's a read-only broadcast stream. It never reads from the socket beyond ping/pong and never sendshello_ack. Result:Timed out waiting for hello_ack.For comparison, the official Node SDK (
@agent-relay/sdkv7.1.1) opens the same/wswith headerX-API-Key, does not send a hello, and just receives events (packages/sdk/dist/transport.js:375-417). Actions (spawn_agent,send_message,release_agent) are now plain HTTP —POST /api/spawn,POST /api/send,DELETE /api/spawned/{name}etc. (crates/broker/src/listen_api.rs:357-412).3. The newer Swift
RelayObserveris v7-compatible but read-onlypackages/sdk-swift/Sources/AgentRelaySDK/RelayObserver.swift(added in #627) implements the v7 event-stream contract correctly. But it only consumes events — there is no Swift counterpart for the HTTP action endpoints, so an SDK consumer can't actually drive agents.Suggested fix
Realign
RelayCastwith the v7 contract:hello/hello_ackhandshake. Connect to/wswithX-API-Key, treat "connected" as "WS upgrade succeeded."/ws, not/v1/ws. Or detect at connect time (probe with auth: 400→/ws, 404→try other).spawnAgent,releaseAgent, channelpost, agentdm) onto HTTP. Map to the existing broker REST endpoints:spawnAgent→POST /api/spawnreleaseAgent→DELETE /api/spawned/{name}post→POST /api/sendRelayCast.channel(...).eventsandbrokerEventsas views over the WS event stream (same shape as today) — wire it throughRelayObserver-style decoding.This restores byte-compat behavior for consumers (
spawnAndAwaitetc. still work) while moving the wire format to v7.Impact
Any Swift consumer of
RelayCastagainst a v7 broker — including locally-spawned brokers viaagent-relay up. Caught while building Axiom, a SwiftUI macOS planning-conversation app that needs to drive a local Claude agent over Relay. Blocked end-to-end untilRelayCasteither gets the v7 fix or we route around it via raw HTTP +RelayObserver.Environment
relaytoff1d4da(v7.1.1)agent-relay@7.1.1(npm), broker binary@agent-relay/broker-darwin-arm64