A transport-agnostic relay server for secure tunneling between clients and agents. The relay enables PTY sessions, TCP/UDP tunnels with pluggable middleware, authorization, and storage backends. Designed for both single-node and distributed deployments.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Relay Server β
β β
βββββββββββ β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β βββββββββββ
β β WebSocket/gRPC/etc β β Agent β β Client β β Session β β WebSocket/gRPC/etc β β
β Clients ββββββββββββββββββββββββββΊβ β Registry β β Registry β β Manager β βββββββββββββββββββββββββββ€ Agents β
β β β ββββββββ¬βββββββ ββββββββ¬βββββββ ββββββββββββ¬βββββββββββ β β β
βββββββββββ β β β β β βββββββββββ
β ββββββββββββββββββΌβββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Authorizer β β Middleware β β Message Broker β β
β β (optional) β β Chain β β + State Store β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Agents connect to the relay and register their capabilities (PTY, TCP tunnels, UDP tunnels)
- Clients connect and request sessions with specific agents
- The Hub orchestrates session creation, routing messages between clients and agents
- Sessions are bidirectional byte streams multiplexed over a single agent connection
- All data flows through optional Middleware for logging, filtering, or transformation
| Type | Description |
|---|---|
AgentID |
Uniquely identifies an agent (the remote endpoint providing resources) |
ClientID |
Uniquely identifies a client (the caller requesting sessions) |
SessionID |
Uniquely identifies an active session between a client and agent |
| Type | Capability | Description |
|---|---|---|
pty |
CapabilityPTY |
Interactive terminal (SSH-like shell access) |
tunnel:tcp |
CapabilityTCPTunnel |
TCP port forwarding |
tunnel:udp |
CapabilityUDPTunnel |
UDP port forwarding |
All communication uses a unified Message envelope:
type Message struct {
Type MessageType // What kind of message (register, session:data, etc.)
SessionID SessionID // Which session (empty for control messages)
Payload []byte // Raw data or JSON-encoded metadata
Metadata map[string]string // Optional key-value pairs
}Message types include:
- Control:
register,heartbeat - Session lifecycle:
session:open,session:close,session:ready - Data:
session:data,pty:resize,error
The relay is built on a set of composable interfaces. Implement these to customize behavior or integrate with different backends.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MessageConn β
β Base interface for bidirectional message passing β
β βββ Send(ctx, msg) error β
β βββ Receive(ctx) (*Message, error) β
β βββ Close() error β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β AgentConn β β ClientConn β β ServerConn β
β (server β β (server β β (outbound β
β side) β β side) β β from agent β
β β β β β or client) β
β + ID() β β + ID() β β + Connect() β
β + Info() β βββββββββββββββ βββββββββββββββ
βββββββββββββββ
The base interface all connections implement:
type MessageConn interface {
Send(ctx context.Context, msg *Message) error
Receive(ctx context.Context) (*Message, error)
Close() error
}Server-side view of an agent connection:
type AgentConn interface {
MessageConn
ID() AgentID
Info() *AgentInfo
}Server-side view of a client connection:
type ClientConn interface {
MessageConn
ID() ClientID
}Outbound connection from an agent or client to the server:
type ServerConn interface {
MessageConn
Connect(ctx context.Context, url string) error
}βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AgentRegistry β
β Manages agent connections and discovery β
β βββ RegisterAgent(ctx, conn) error β
β βββ UnregisterAgent(ctx, id) error β
β βββ GetAgent(ctx, id) (*AgentInfo, error) β
β βββ GetAgentConn(ctx, id) (AgentConn, error) β
β βββ ListAgents(ctx, opts) ([]*AgentInfo, error) β
β βββ HandleHeartbeat(ctx, agentID) error β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ClientRegistry β
β Manages client connections β
β βββ RegisterClient(ctx, conn) error β
β βββ UnregisterClient(ctx, id) error β
β βββ GetClientConn(ctx, id) (ClientConn, error) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Registries store connection state. For distributed deployments, implement with Redis or similar to share state across nodes.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Session β
β Bidirectional communication channel between client and agent β
β βββ ID() SessionID β
β βββ Type() SessionType β
β βββ AgentID() AgentID β
β βββ ClientID() ClientID β
β βββ Write(ctx, data) error // Client β Agent β
β βββ DeliverOutput(ctx, data) error // Agent β Client β
β βββ NotifyClose(ctx) error β
β βββ SetOnClose(callback) β
β βββ Close(ctx) error β
β βββ Resize(ctx, rows, cols) error // PTY only β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Sessions are the fundamental data path. They're bidirectional byte streams that:
- Route input from clients to agents via
Write() - Deliver output from agents to clients via
DeliverOutput() - Support PTY resize events (returns error for non-PTY sessions)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SessionManager β
β Manages session lifecycle and message routing β
β βββ OpenSession(ctx, clientID, agentID, type, opts) (SessionID, error) β
β βββ CloseSession(ctx, sessionID) error β
β βββ GetSession(ctx, sessionID) (Session, error) β
β βββ HandleSessionMessage(ctx, agentID, msg) error β
β βββ CloseAgentSessions(ctx, agentID) error β
β βββ CloseClientSessions(ctx, clientID) error β
β βββ AdoptAgentSessions(ctx, agentID) error // For reconnection β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The SessionManager coordinates session creation, tracks state, and handles agent reconnection in distributed deployments.
For multi-node deployments, the broker handles cross-node communication:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MessageBroker β
β Routes messages between nodes β
β βββ Publish(ctx, envelope) error β
β βββ Subscribe(ctx, targetType, handler) error β
β βββ Close() error β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β StateStore β
β Generic key-value storage for distributed state β
β βββ Get(ctx, key) ([]byte, error) β
β βββ Set(ctx, key, value) error β
β βββ Delete(ctx, key) error β
β βββ List(ctx, prefix) ([][]byte, error) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The default channel-based implementations work for single-node. For horizontal scaling, implement with Redis, NATS, or similar.
type Authorizer interface {
Authorize(ctx context.Context, req *AuthRequest) (*AuthResponse, error)
}
type AuthRequest struct {
Subject string // Who (user ID, token)
Action AuthAction // What (agent:register, session:open)
Resource string // Target (agent ID)
Context map[string]string // Additional context
}Actions: agent:register, session:open, session:tunnel
// Intercepts data flowing between client and agent
type DataMiddleware interface {
ProcessClientToAgent(ctx context.Context, sessionID SessionID, data []byte) ([]byte, error)
ProcessAgentToClient(ctx context.Context, sessionID SessionID, data []byte) ([]byte, error)
}
// Receives session lifecycle events
type SessionMiddleware interface {
OnSessionOpen(ctx context.Context, agentID AgentID, sessionType SessionType, opts *SessionOptions) error
OnSessionClose(ctx context.Context, sessionID SessionID) error
}Use cases:
- Logging: Record sessions for audit
- Filtering: Block commands or patterns
- Transformation: Inject environment variables
- Rate limiting: Throttle throughput
.
βββ *.go # Core interfaces (types, session, auth, conn, etc.)
βββ agent/ # Agent implementation
β βββ agent.go # Agent logic
β βββ pty/ # PTY session handler
β βββ tcp/ # TCP tunnel handler
β βββ udp/ # UDP tunnel handler
βββ auth/ # Authorization implementations
β βββ webhook.go # Webhook-based authorizer
βββ broker/ # Message broker abstractions
β βββ broker.go # MessageBroker and StateStore interfaces
β βββ store.go # Type-safe helper functions for StateStore
β βββ channel/ # In-memory channel-based implementation
βββ cmd/ # CLI binaries
β βββ server/ # Relay server
β βββ agent/ # Agent binary
β βββ client/ # Client CLI
βββ conn/ # Connection implementations
β βββ ws/ # WebSocket connections
βββ internal/ # Internal utilities
β βββ wsutil/ # WebSocket helpers
βββ server/ # Server-side implementations
βββ hub/ # Central Hub coordinator
βββ handler/ # HTTP handlers
βββ middleware/ # Middleware interfaces
βββ proxy/ # Session proxy implementation
βββ store/distributed/ # Distributed registry and session storage
go run ./cmd/server -addr :8080 -log-level debuggo run ./cmd/agent -server ws://localhost:8080/agent/connect -id my-agent# Interactive PTY session
go run ./cmd/client -server ws://localhost:8080 -agent my-agent
# List agents via API
curl http://localhost:8080/api/agents| Endpoint | Description |
|---|---|
GET /api/agents |
List connected agents |
WS /agent/connect |
Agent WebSocket connection |
WS /client/connect?agent=<id> |
Client PTY session |
WS /tunnel/connect?agent=<id>&host=<host>&port=<port> |
TCP tunnel |
type GRPCServer struct {
hub *hub.Hub
}
func (s *GRPCServer) HandleAgentStream(stream pb.Relay_AgentStreamServer) {
agentConn := NewGRPCAgentConn(stream)
s.hub.RegisterAgent(ctx, agentConn)
s.hub.RunAgentLoop(ctx, agentConn)
s.hub.UnregisterAgent(ctx, agentConn.ID())
}Implement StateStore for distributed state:
type RedisStateStore struct {
client *redis.Client
}
func (r *RedisStateStore) Get(ctx context.Context, key string) ([]byte, error) {
return r.client.Get(ctx, key).Bytes()
}
func (r *RedisStateStore) Set(ctx context.Context, key string, value []byte) error {
return r.client.Set(ctx, key, value, 0).Err()
}
// ... implement Delete and Listtype AuditLogger struct {
db *sql.DB
}
func (a *AuditLogger) ProcessClientToAgent(ctx context.Context, sessionID relay.SessionID, data []byte) ([]byte, error) {
a.db.Exec("INSERT INTO audit_log (session_id, direction, data) VALUES (?, 'c2a', ?)", sessionID, data)
return data, nil
}
func (a *AuditLogger) ProcessAgentToClient(ctx context.Context, sessionID relay.SessionID, data []byte) ([]byte, error) {
a.db.Exec("INSERT INTO audit_log (session_id, direction, data) VALUES (?, 'a2c', ?)", sessionID, data)
return data, nil
}- Transport-agnostic: Core logic is interface-driven; transports just handle wire protocols
- Pluggable components: Registry, SessionManager, Authorizer, Middleware are all interfaces
- Sessions are tunnels: All session types share the same interface (bidirectional byte streams)
- Single-node and distributed: Channel broker for simple deployments, Redis/NATS for scaling
- Reconnection support: Sessions can survive agent reconnection via
AdoptAgentSessions - Middleware for cross-cutting concerns: Logging, filtering, transformation without modifying core logic