Skip to content

Edge Agent WebSocket Configuration

Last Verified: February 2026

Summary

The backend exposes a WebSocket endpoint for the Mac mini edge relay to receive commands in real time (reflex bubbles, scheduling, context updates).

The reference edge implementation lives in ../archety-edge.

WebSocket Connection Details

Endpoint

wss://<backend>/edge/ws?edge_agent_id={edge_agent_id}

Production (canonical): wss://api.ikiro.ai/edge/ws?edge_agent_id=...
Legacy Railway URLs may still work: wss://archety-backend-prod.up.railway.app/edge/ws?...

Required Headers

Authorization: Bearer {EDGE_TOKEN}
X-Edge-Agent-Id: {edge_agent_id}

Where {EDGE_TOKEN} is either: 1. Recommended (production): a Base64 HMAC token (see below) 2. Legacy fallback (development only): the raw EDGE_SECRET value (backend rejects this when ENVIRONMENT=production)

Example Connection (JavaScript/Node.js)

const WebSocket = require('ws');

// EDGE_TOKEN can be the raw EDGE_SECRET (dev only) OR a generated HMAC token (recommended)
const EDGE_TOKEN = process.env.EDGE_TOKEN || process.env.EDGE_SECRET;
const EDGE_AGENT_ID = 'edge_13106781670'; // Your edge agent ID

const ws = new WebSocket(
  `wss://api.ikiro.ai/edge/ws?edge_agent_id=${EDGE_AGENT_ID}`,
  {
    headers: {
      'Authorization': `Bearer ${EDGE_TOKEN}`,
      'X-Edge-Agent-Id': EDGE_AGENT_ID
    }
  }
);

ws.on('open', function open() {
  console.log('✅ WebSocket connected - real-time mode enabled');

  // Send ping every 30 seconds to keep connection alive
  setInterval(() => {
    ws.send(JSON.stringify({ type: 'ping' }));
  }, 30000);
});

ws.on('message', function message(data) {
  const msg = JSON.parse(data.toString());

  switch (msg.type) {
    case 'command':
      handleCommand(msg.data);
      sendCommandAck(msg.data.command_id);
      break;
    case 'pong':
      // Server acknowledged our ping
      break;
    case 'config_update':
      handleConfigUpdate(msg.data);
      break;
  }
});

ws.on('error', function error(err) {
  console.error('WebSocket error:', err);
});

ws.on('close', function close(code, reason) {
  console.log(`WebSocket closed: ${code} - ${reason}`);
  // Implement reconnection with exponential backoff
  setTimeout(connect, getBackoffDelay());
});

Message Protocol

1. Commands (Backend → Edge)

{
  "type": "command",
  "data": {
    "command_id": "cmd_xyz",
    "command_type": "send_message_now",
    "payload": {
      "thread_id": "iMessage;-;+13105551234",
      "text": "okok gimme 2 sec",
      "bubble_type": "reflex"
    },
    "priority": "immediate",
    "timestamp": "2026-02-16T00:00:00Z"
  }
}

2. Command Acknowledgment (Edge → Backend)

{
  "type": "command_ack",
  "data": {
    "command_id": "cmd_xyz",
    "status": "completed",
    "completed_at": "2025-11-05T20:00:01Z",
    "error": null
  }
}

3. Ping/Pong (Keepalive)

Edge sends:

{
  "type": "ping"
}

Backend responds:

{
  "type": "pong"
}

Reconnection Strategy

Implement exponential backoff:

let reconnectDelay = 1000; // Start with 1 second
const maxDelay = 60000; // Max 60 seconds

function getBackoffDelay() {
  const delay = reconnectDelay;
  reconnectDelay = Math.min(reconnectDelay * 2, maxDelay);
  return delay;
}

function connect() {
  // Create new WebSocket connection
  // Reset delay on successful connection
  ws.on('open', () => {
    reconnectDelay = 1000;
  });
}

Authentication Notes

For production, use a signed token:

const crypto = require('crypto');

function generateHMACToken(edge_agent_id, user_phone) {
  const timestamp = Math.floor(Date.now() / 1000);
  const tokenData = `${edge_agent_id}:${user_phone}:${timestamp}`;
  const signature = crypto
    .createHmac('sha256', EDGE_SECRET)
    .update(tokenData)
    .digest('hex');
  const token = `${tokenData}:${signature}`;
  return Buffer.from(token).toString('base64');
}

Send the generated token as Authorization: Bearer <token>.

Testing the Connection

  1. Check WebSocket endpoint is accessible:

    curl -I https://api.ikiro.ai/edge/ws
    # Should return 426 Upgrade Required (normal for WebSocket endpoints)
    

  2. Monitor connection in logs: Look for these log messages:

  3. "Using edge_agent_id from query params: edge_13106781670"
  4. "Authenticated with simple secret (fallback mode)"
  5. "WebSocket connection established for edge_13106781670"

  6. Test command delivery: Commands sent to your edge agent will arrive instantly via WebSocket instead of waiting for HTTP polling.

Benefits

  • Instant command delivery (<100ms vs up to 15s with HTTP polling)
  • Reduced HTTP requests (99% reduction in polling traffic)
  • Real-time bidirectional communication
  • Automatic fallback to HTTP polling if WebSocket disconnects

Troubleshooting

403 Forbidden Error

  • Verify EDGE_SECRET matches between edge client and backend
  • Check headers are properly set
  • Ensure edge_agent_id is included in query params

Connection Drops

  • Implement ping/pong keepalive (every 30 seconds)
  • Use exponential backoff for reconnection
  • Monitor network stability

Commands Not Received

  • Verify WebSocket is connected (check logs)
  • Ensure command acknowledgments are being sent
  • Check for any error messages in WebSocket message events

Environment Variables

Ensure your edge client has:

EDGE_SECRET=<shared_secret>          # shared signing secret (used to generate EDGE_TOKEN)
EDGE_TOKEN=<auth_token_to_send>      # raw EDGE_SECRET (dev only) OR generated HMAC token
EDGE_AGENT_ID=edge_13106781670  # Your specific edge agent ID
BACKEND_WS_URL=wss://api.ikiro.ai/edge/ws

Support

For issues or questions: 1. Check Railway deployment logs for backend status 2. Review edge agent logs for connection attempts 3. Verify network connectivity and firewall rules 4. Check the WebSocket statistics endpoint (requires admin key):

GET https://api.ikiro.ai/edge/websocket/stats


Backward Compatibility: HTTP polling via POST /edge/sync remains available when WebSocket is down.