Using WebSockets — Agentuity Documentation

Using WebSockets

Real-time bidirectional communication with the websocket middleware

WebSockets enable persistent, bidirectional connections between client and server. Use them for chat interfaces, live dashboards, collaborative tools, and any scenario requiring real-time two-way communication.

When to Use WebSockets

ProtocolBest For
WebSocketsChat, collaboration, real-time bidirectional data
SSELLM streaming, progress updates, server-to-client feeds
WebRTCVideo/audio calls, P2P data transfer, low-latency gaming

Basic Example

import { Hono } from 'hono';
import { type Env, websocket } from '@agentuity/runtime';
import chatAgent from '@agent/chat/agent';
 
const router = new Hono<Env>();
 
router.get('/chat', websocket((c, ws) => {
  ws.onOpen(() => {
    c.var.logger.info('Client connected');
    ws.send('Connected!');
  });
 
  ws.onMessage(async (event) => {
    const message = event.data as string;
    const response = await chatAgent.run({ message });
    ws.send(JSON.stringify(response));
  });
 
  ws.onClose(() => {
    c.var.logger.info('Client disconnected');
  });
}));
 
export default router;

Handler Structure

The websocket() middleware wraps your handler and upgrades the connection:

import { Hono } from 'hono';
import { type Env, websocket } from '@agentuity/runtime';
 
router.get('/path', websocket((c, ws) => {
  // c - Hono context
  // ws - WebSocket connection object
 
  ws.onOpen(() => { /* connection opened */ });
  ws.onMessage(async (event) => { /* message received */ });
  ws.onClose(() => { /* connection closed */ });
}));

WebSocket Events

EventTriggerExample Use Case
onOpenConnection establishedSend welcome message, initialize state
onMessageClient sends dataProcess messages, call agents
onCloseConnection endsClean up resources

With Middleware

Apply authentication or logging before the WebSocket upgrade:

import { Hono } from 'hono';
import { type Env, websocket } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
import chat from '@agent/chat/agent';
 
const router = new Hono<Env>();
 
const authMiddleware = createMiddleware(async (c, next) => {
  const token = c.req.query('token');
  if (!token) {
    return c.text('Unauthorized', 401);
  }
  c.set('userId', await validateToken(token));
  await next();
});
 
router.get('/chat', authMiddleware, websocket((c, ws) => {
  const userId = c.var.userId;
 
  ws.onOpen(() => {
    ws.send(`Welcome, user ${userId}!`);
  });
 
  ws.onMessage(async (event) => {
    const response = await chat.run({
      userId,
      message: event.data as string,
    });
    ws.send(JSON.stringify(response));
  });
}));
 
export default router;

Server Push

Send data to the client without waiting for a request:

router.get('/notifications', websocket((c, ws) => {
  let heartbeat: Timer;
 
  ws.onOpen(() => {
    ws.send(JSON.stringify({ type: 'connected' }));
 
    // Push updates every 5 seconds
    heartbeat = setInterval(() => {
      ws.send(JSON.stringify({
        type: 'heartbeat',
        time: new Date().toISOString(),
      }));
    }, 5000);
  });
 
  ws.onClose(() => {
    clearInterval(heartbeat); // Clean up!
  });
}));

Full Example

A real-time echo server with heartbeat:

import { Hono } from 'hono';
import { type Env, websocket } from '@agentuity/runtime';
import echoAgent from '@agent/echo/agent';
 
const router = new Hono<Env>();
 
router.get('/', websocket((c, ws) => {
  let heartbeat: Timer;
 
  ws.onOpen(() => {
    c.var.logger.info('WebSocket connected');
    ws.send('Connected! Send a message to echo it back.');
 
    heartbeat = setInterval(() => {
      ws.send(`Ping: ${new Date().toLocaleTimeString()}`);
    }, 5000);
  });
 
  ws.onMessage(async (event) => {
    try {
      const message = event.data as string;
      c.var.logger.info('Message received', { message });
 
      const response = await echoAgent.run(message);
      ws.send(JSON.stringify(response));
    } catch (error) {
      c.var.logger.error('Message processing failed', { error });
      ws.send(JSON.stringify({ error: 'Processing failed' }));
    }
  });
 
  ws.onClose(() => {
    c.var.logger.info('WebSocket disconnected');
    clearInterval(heartbeat);
  });
}));
 
export default router;

Resource Cleanup

Always clean up intervals, subscriptions, or other resources in onClose:

ws.onClose(() => {
  clearInterval(heartbeat);  // Prevent memory leaks
  subscription.unsubscribe();
});

Failing to clean up can cause memory leaks and unexpected behavior.

Client Connection

Connect from a browser or any WebSocket client:

const ws = new WebSocket('wss://your-project.agentuity.cloud/agent-name');
 
ws.onopen = () => {
  console.log('Connected');
  ws.send('Hello!');
};
 
ws.onmessage = (event) => {
  console.log('Received:', event.data);
};
 
ws.onclose = () => {
  console.log('Disconnected');
};

Standalone Usage

WebSocket handlers work without agents. This example broadcasts system metrics to connected clients:

import { Hono } from 'hono';
import { type Env, websocket } from '@agentuity/runtime';
 
const router = new Hono<Env>();
 
router.get('/metrics', websocket((c, ws) => {
  const interval = setInterval(() => {
    ws.send(JSON.stringify({
      cpu: Math.random() * 100,
      memory: Math.random() * 100,
      timestamp: Date.now(),
    }));
  }, 5000);
 
  ws.onClose(() => clearInterval(interval));
}));
 
export default router;

Next Steps