Skip to content

Connectors

A Connector is the transport adapter between the Maelstrom client and the engine backend. It should move protocol messages across a boundary; it should not decide layout, mutate Vessels, or hide engine errors.

Maelstrom ships with two connector implementations that cover the most common deployment shapes:

Connector When to use it
WebSocketConnector Long-lived browser-to-server connections with live connection state
HttpConnector Request/response flows without persistent sessions

Most React apps start with WebSocketConnector because interactive sessions benefit from a long-lived connection and the browser needs live status feedback.

Use a custom Connector when the built-in transport does not match your deployment shape: a proprietary WebSocket gateway, an HTTP endpoint with bespoke auth, or an in-process test harness. Keep it transport-focused and keep application authentication and authorization in the app’s existing boundary. For a bounded design, use the connector/auth prompt or the connector/auth reference from the portable skill bundle.

Design connector and app authentication

Design or implement the transport and application-authentication boundary for
this Maelstrom integration.

Goal: [USER / PRODUCT GOAL]
Existing authentication: [DETAILS OR "inspect"]
Connector / transport: [DETAILS OR "inspect"]
Engine host: [DETAILS OR "inspect"]
Implementation authorized: [YES / NO]

## Inspect first

Inspect installed package versions and the app's session/auth code, Connector
configuration, request/route boundary, deployment environment, and relevant
failure handling. Confirm current Connector constructors, interfaces, and
connection lifecycle from the installed package exports, types, source, and
version-matched docs. Do not invent an endpoint, token format, retry policy, or
Engine contract where repository evidence is absent; list it as a question.

## Boundaries

- Keep the Connector focused on moving Envelopes (or their HTTP equivalents)
  across the selected transport. Do not put Vessel mutation, layout decisions,
  product policy, or application authorization inside it.
- Authentication and authorization remain application-owned. Reuse the current
  session mechanism only if the inspected transport supports it safely; otherwise
  identify the smallest explicit app-owned server boundary.
- Never request, print, commit, or place literal credentials in browser code.
  Provider keys stay server-side. Use secret references only, and review the
  diff for accidental credential material.
- Distinguish retryable transport failures, cancellation/disposal, authentication
  expiry, authorization rejection, and terminal errors using behavior actually
  supported by the selected Connector. Do not add blind retry loops or claim
  retries are safe for a request with side effects.
- Do not silently alter unrelated registry, auth, routing, or transport config.

If implementation is authorized, make only the bounded changes approved by the
goal after inspection. Otherwise return a plan and make no edits. Add deterministic
tests for supported request/auth/error behavior without calling a real service.
Run affected checks and report exact commands. Separate test-double results from
live transport verification.

Return an ownership/sequence outline, evidence-backed API/version anchors,
changed files or plan, expiry/reconnect/cancel/error handling, redaction review,
tests and actual results, open questions, and rollback steps.

A connector owns four jobs:

  1. open and close the transport session
  2. send intent or chart messages to the engine
  3. deliver engine responses back to the client
  4. expose connection failures in a way the UI can render

It should preserve the protocol payload. If the transport changes from WebSocket to HTTP, the message shape should not.

A WebSocket connector is the usual browser-to-server shape because it supports long-lived sessions and streaming extensions.

src/connector.ts
import { WebSocketConnector } from '@maelstrom-co/client';
export const connector = new WebSocketConnector({
url: import.meta.env.PUBLIC_MAELSTROM_WS_URL,
});

Use this shape when the engine lives behind a server route and the browser needs live connection state.

For tests, keep the engine in the same process and avoid a network transport entirely. The connector still implements the same EngineConnector shape; only the transport disappears.

test/create-test-connector.ts
import type { Engine } from '@maelstrom-co/engine';
import type { EngineConnector } from '@maelstrom-co/client';
import { isOk } from '@maelstrom-co/protocol';
export function createInMemoryConnector(engine: Engine): EngineConnector {
return {
capabilities: { streaming: false, stateful: false },
async connect() {},
async disconnect() {},
subscribe() {
return () => {};
},
async addAssistantMessage() {},
async getHistory() {
return [];
},
async process(message) {
const result = await engine.handle(message);
if (isOk(result) && result.value.type === 'chart_response') {
return result.value.response;
}
throw new Error('Engine did not return a chart response.');
},
};
}

Treat this as a test seam, not a production browser connector. It is useful because the protocol stays the same while transport disappears.

Do not swallow transport failures. Surface them through the same connection state your UI already reads from useMaelstrom(). The user-facing recovery path should be clear:

Failure Connector behavior UI behavior
Initial connection fails report an error and stay disconnected show unavailable state and retry affordance
Mid-session drop update connection status disable intent submission until reconnected
Request timeout reject or return a failed result through the normal channel show processing error and let the user resubmit
Malformed response preserve enough detail for logging show generic failure, report diagnostic details server-side