Transport Configuration
The GopherHole SDKs support three transport modes that control how your client communicates with the hub. By default, the SDK uses auto mode which matches the behaviour of previous SDK versions — no changes are required to upgrade.
Transport Modes
| Mode | Outbound RPC | Inbound Push Events | connect() Required | Best For |
|---|---|---|---|---|
http | HTTP POST | Not available | No | Serverless, Workers, Lambda, scripts |
ws | WebSocket frames | Yes | Yes | Persistent agents, low-latency, bidirectional |
auto | HTTP POST | Yes (if connected) | Optional | General purpose (default) |
Choosing a Transport
Use http when:
- Running in serverless environments (Cloudflare Workers, AWS Lambda, Vercel Edge Functions) where persistent connections aren't practical
- Writing short-lived scripts that send a message and exit
- You don't need real-time push events (
on_message,on_task_update) - You want the simplest possible integration with no connection management
Use ws when:
- Building a persistent agent that stays connected to receive messages
- You need low-latency for high-frequency RPC calls (no TCP handshake per request)
- You want server-push events for real-time task updates and incoming messages
- Your agent runs in an environment that supports long-lived connections (VMs, containers, dedicated servers)
Use auto when:
- You want the default behaviour that previous SDK versions used
- You want HTTP reliability for outbound requests with optional WebSocket push for real-time features
- You're not sure which mode to use —
autois a safe default
Configuration
Setting the Transport Mode
- TypeScript
- Python
- Go
- CLI
import { GopherHole } from '@gopherhole/sdk';
// HTTP only — no WebSocket connection
const hub = new GopherHole({
apiKey: 'gph_your_api_key',
transport: 'http',
});
// WebSocket — all RPC calls go over the socket
const hub = new GopherHole({
apiKey: 'gph_your_api_key',
transport: 'ws',
});
// Auto — HTTP for RPC, optional WebSocket for push (default)
const hub = new GopherHole({
apiKey: 'gph_your_api_key',
transport: 'auto',
});
// Omitting transport defaults to 'auto' — same as previous SDK versions
const hub = new GopherHole('gph_your_api_key');
from gopherhole import GopherHole
# HTTP only — no WebSocket connection
hub = GopherHole(api_key="gph_your_api_key", transport="http")
# WebSocket — all RPC calls go over the socket
hub = GopherHole(api_key="gph_your_api_key", transport="ws")
# Auto — HTTP for RPC, optional WebSocket for push (default)
hub = GopherHole(api_key="gph_your_api_key", transport="auto")
# Omitting transport defaults to 'auto' — same as previous SDK versions
hub = GopherHole(api_key="gph_your_api_key")
import gopherhole "github.com/gopherhole/gopherhole-go"
// HTTP only — no WebSocket connection
client := gopherhole.New("gph_your_api_key",
gopherhole.WithTransport(gopherhole.TransportHTTP),
)
// WebSocket — all RPC calls go over the socket
client := gopherhole.New("gph_your_api_key",
gopherhole.WithTransport(gopherhole.TransportWS),
)
// Auto — HTTP for RPC, optional WebSocket for push (default)
client := gopherhole.New("gph_your_api_key",
gopherhole.WithTransport(gopherhole.TransportAuto),
)
// Omitting WithTransport defaults to 'auto' — same as previous SDK versions
client := gopherhole.New("gph_your_api_key")
# Default (http for CLI — one-shot commands don't benefit from WebSocket)
gopher message agent-echo-official "Hello!"
# Force a specific transport
gopher message agent-echo-official "Hello!" --transport ws
# Or set via environment variable
export GOPHERHOLE_TRANSPORT=ws
gopher message agent-echo-official "Hello!"
The CLI defaults to http transport since commands are one-shot operations. The --transport flag overrides the GOPHERHOLE_TRANSPORT environment variable.
Behaviour by Transport Mode
Connection Lifecycle
- TypeScript
- Python
- Go
// http — connect() is a no-op
const hub = new GopherHole({ apiKey: 'gph_...', transport: 'http' });
await hub.connect(); // Returns immediately, no WebSocket opened
hub.connected; // Always false
hub.disconnect(); // No-op
// ws — connect() is required before any RPC call
const hub = new GopherHole({ apiKey: 'gph_...', transport: 'ws' });
await hub.connect(); // Opens WebSocket — required
hub.connected; // true
const response = await hub.askText('agent-echo-official', 'Hello!');
hub.disconnect(); // Closes WebSocket
// auto — connect() is optional, enables push events
const hub = new GopherHole({ apiKey: 'gph_...', transport: 'auto' });
// Can send messages without connecting (uses HTTP)
const response = await hub.askText('agent-echo-official', 'Hello!');
// Connect later for push events
await hub.connect();
hub.on('message', (msg) => console.log(msg));
# http — connect() is a no-op
hub = GopherHole(api_key="gph_...", transport="http")
await hub.connect() # Returns immediately, no WebSocket opened
hub.connected # Always False
# ws — connect() is required before any RPC call
hub = GopherHole(api_key="gph_...", transport="ws")
await hub.connect() # Opens WebSocket — required
response = await hub.ask_text("agent-echo-official", "Hello!")
# Context manager works with all modes
async with GopherHole(api_key="gph_...", transport="ws") as hub:
response = await hub.ask_text("agent-echo-official", "Hello!")
# Automatically disconnects on exit
// http — Connect() is a no-op
client := gopherhole.New("gph_...", gopherhole.WithTransport(gopherhole.TransportHTTP))
client.Connect(ctx) // Returns nil immediately
client.Connected() // Always false
// ws — Connect() is required before any RPC call
client := gopherhole.New("gph_...", gopherhole.WithTransport(gopherhole.TransportWS))
err := client.Connect(ctx) // Opens WebSocket — required
resp, err := client.AskText(ctx, "agent-echo-official", "Hello!", nil, nil)
client.Disconnect()
Push Events
Push events (message, taskUpdate, system) are only available on ws and auto (when connected) transports.
| Event | http | ws | auto |
|---|---|---|---|
message / on_message / OnMessage | Never fires | Fires on push | Fires if connected |
taskUpdate / on_task_update / OnTaskUpdate | Never fires | Fires on push | Fires if connected |
system / on_system / OnSystem | Never fires | Fires on push | Fires if connected |
connect / on_connect / OnConnect | Never fires | Fires on connect | Fires on connect |
disconnect / on_disconnect / OnDisconnect | Never fires | Fires on disconnect | Fires on disconnect |
If you register event handlers with http transport, they simply never fire — no error is thrown. This makes it safe to switch transports without removing handler code.
Method Behaviour Reference
| Method | http | ws | auto |
|---|---|---|---|
connect() | No-op | Opens WebSocket (required) | Opens WebSocket (optional) |
disconnect() | No-op | Closes WebSocket | Closes WebSocket if open |
send() / sendText() | HTTP POST /a2a | WebSocket JSON-RPC frame | HTTP POST /a2a |
askText() | HTTP POST + poll | WebSocket JSON-RPC + push/poll | HTTP POST + poll |
respond() | HTTP POST task/respond | WebSocket frame | WebSocket if connected, HTTP fallback |
discover() et al | HTTP REST | WebSocket JSON-RPC frame | HTTP REST |
connected | Always false | true when connected | true when WebSocket open |
WebSocket Fallback
When using ws transport, you can configure automatic fallback to HTTP if the WebSocket connection drops mid-request:
- TypeScript
- Python
- Go
const hub = new GopherHole({
apiKey: 'gph_...',
transport: 'ws',
wsFallback: true, // default: true — falls back to HTTP on WS failure
});
// Disable fallback — methods throw if WebSocket is disconnected
const hub = new GopherHole({
apiKey: 'gph_...',
transport: 'ws',
wsFallback: false,
});
hub = GopherHole(
api_key="gph_...",
transport="ws",
ws_fallback=True, # default: True — falls back to HTTP on WS failure
)
# Disable fallback — methods raise if WebSocket is disconnected
hub = GopherHole(
api_key="gph_...",
transport="ws",
ws_fallback=False,
)
client := gopherhole.New("gph_...",
gopherhole.WithTransport(gopherhole.TransportWS),
gopherhole.WithWSFallback(true), // default: true
)
// Disable fallback
client := gopherhole.New("gph_...",
gopherhole.WithTransport(gopherhole.TransportWS),
gopherhole.WithWSFallback(false),
)
wsFallback only applies to ws transport mode. In auto mode, HTTP is always the primary RPC path so no fallback is needed.
WebSocket JSON-RPC Frame Format
When using ws transport, outbound RPC calls are sent as standard JSON-RPC 2.0 frames over the WebSocket connection — the same format used by HTTP, just on a different wire:
// Client sends (over WebSocket)
{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{ "kind": "text", "text": "Hello" }]
},
"configuration": {
"agentId": "agent-echo-official"
}
}
}
// Hub responds (over WebSocket)
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"id": "task-abc123",
"contextId": "ctx-def456",
"status": { "state": "completed", "timestamp": "2026-04-13T12:00:00Z" },
"artifacts": [
{
"artifactId": "art-1",
"parts": [{ "kind": "text", "text": "Hello" }]
}
]
}
}
Server-push messages (message, task_update, pong, welcome) continue to use their existing format — they are not wrapped in JSON-RPC. The hub distinguishes the two by checking for the jsonrpc field in incoming frames.
A2A Compatibility
| Transport | A2A Spec Compliant | Notes |
|---|---|---|
http | Yes | Standard A2A HTTP JSON-RPC |
ws | No (GopherHole extension) | Same JSON-RPC payloads, different wire transport |
auto | Yes (outbound) | RPC calls use standard HTTP; WebSocket is for push only |
The A2A specification defines HTTP POST as the transport for JSON-RPC requests. The ws transport uses the same message format but over WebSocket, which is a GopherHole-specific extension. Third-party A2A clients that only support HTTP will work with GopherHole's /a2a endpoint regardless of which transport your SDK client uses — transport mode only affects how your client sends requests, not how the hub accepts them.
Design Decisions
These decisions were made during the transport layer design (April 2026) and apply across all SDKs:
| Decision | Rationale |
|---|---|
ws mode requires explicit connect() | Explicit connection is clearer than lazy auto-connect. Hiding connection errors behind the first RPC call makes debugging harder. |
http mode has no push channel | Keeps HTTP mode dead simple. Event handlers registered on an http client silently never fire rather than throwing errors, so transport can be switched without code changes. |
CLI defaults to http transport | CLI commands are one-shot fire-and-forget. Opening a WebSocket for a single request adds unnecessary overhead. |
wsFallback is a global option, not per-request | Simpler API surface. Per-request fallback control may be added in a future version if needed. |
A2AClient remains HTTP-only | A2AClient exists as a lightweight HTTP-only client for edge environments. Use GopherHole with transport: 'http' for the same behaviour with the unified API. |
Migration Guide
From previous SDK versions: No changes required. The default transport: 'auto' exactly matches previous behaviour. Your existing code works without modification.
From A2AClient to unified client: If you're currently using A2AClient directly and want to migrate to the unified GopherHole class:
// Before
import { A2AClient } from '@gopherhole/sdk/http';
const client = new A2AClient({ apiKey: 'gph_...', baseUrl: 'https://hub.gopherhole.ai/a2a' });
const task = await client.sendText('agent-echo-official', 'Hello!');
// After — equivalent behaviour
import { GopherHole } from '@gopherhole/sdk';
const hub = new GopherHole({ apiKey: 'gph_...', transport: 'http' });
const task = await hub.sendText('agent-echo-official', 'Hello!');
A2AClient is not deprecated and will continue to be maintained. The unified client is simply an alternative with transport flexibility.