Skip to main content

Relay Operations

This page is the operational reference for the Otto relay daemon. Use it when configuring a relay deployment, diagnosing routing issues, or integrating controller clients.

Source-of-truth code paths

ConcernSource
Relay HTTP + WebSocket serverpackages/relay/src/index.ts
  • Integration validation suite: packages/relay/test/integration.test.mjs
  • Shared protocol contracts: packages/shared-protocol/src/index.ts

Startup

Relay starts on port 8787 by default. Global @telepat/otto installs include relay runtime dependencies so daemon lifecycle commands do not require separate @telepat/otto-relay installation.

CommandBehavior
otto startStart relay daemon
otto stopStop relay daemon
otto restartRestart relay daemon
otto statusReport running or stopped; when stopped suggests otto start
otto setupEnsures relay daemon is running on the setup relay URL port before setup completes

Setup daemon outcomes: started (setup launched daemon for selected port), already_running (existing daemon reused). Setup fails on daemon port mismatch with explicit remediation: run otto stop, then rerun setup with the intended relay URL.

Environment variables

VariableDefaultDescription
OTTO_RELAY_PORT8787HTTP and WebSocket listen port
OTTO_TOKEN_SECRETRequired. JWT signing secret
OTTO_TOKEN_PREVIOUS_SECRETGrace-period verification for secret rotation
OTTO_TOKEN_ISSUERJWT iss claim
OTTO_TOKEN_AUDIENCEJWT aud claim
OTTO_TOKEN_TTL_MINUTESAccess-token lifetime
OTTO_REFRESH_TTL_DAYSRefresh-token lifetime
OTTO_EXTENSION_ORIGINAllowed browser extension origin
OTTO_LOG_DIRDirectory for JSONL operation logs
OTTO_LOG_MAX_FILE_BYTES100MBDay-file size before spillover
OTTO_RATE_LIMIT_PER_MINWebSocket frame rate limit per client
OTTO_REPLAY_WINDOW_MSNonce deduplication window
OTTO_TAB_QUEUE_LIMITMax queued commands per tab
OTTO_CONTROLLER_QUEUE_LIMITMax queued commands per controller
OTTO_DEFAULT_CONTROLLER_SCOPESScopes applied to newly registered controllers
OTTO_ALLOW_REMOTE_CONTROLLER_REGISTRATIONAllow unauthenticated remote controller registration
OTTO_CONTROLLER_REGISTRATION_SECRETRequired when remote registration is enabled
OTTO_CONTROLLER_HEARTBEAT_INTERVAL_MS8000Heartbeat check interval
OTTO_CONTROLLER_HEARTBEAT_MISS_LIMIT3Missed heartbeats before stale disconnect
warning

OTTO_TOKEN_SECRET is required and must be kept secret. Do not commit it to source control or expose it in logs.

Endpoints

Pairing and auth

MethodPathAuth
POST/api/pairing/requestNone
GET/api/pairing/pendingNone
POST/api/pairing/approveNode bearer
GET/api/pairing/statusNone
POST/api/auth/refreshRefresh token
POST/api/auth/revokeRefresh token
GET/api/nodes/connectedController bearer

Controller client registration and ACL

MethodPathAuthNotes
POST/api/controller/registerNone or registration secretBody: { name, description, avatarSeed? }
POST/api/controller/tokenClient secretReturns access + refresh token pair
POST/api/controller/removeController bearerBody: { clientId } — revokes record, tears down ACL, refresh, and sessions
POST/api/controller/remove-allController bearerRevokes and purges all controller client records
GET/api/controller/accessNode bearerList active ACL grants
POST/api/controller/accessNode bearerGrant controller access

ACL enforcement: node-targeted controller commands without an active grant return acl_missing_node_grant. Client secret is only used for /api/controller/token; runtime authorization uses access-token scopes and node ACL grants.

Logs

MethodPathQuery params
GET/api/logssince, level, source, latest, nodeId, requestId
GET/api/logs/status
GET/api/logs/exportSame as /api/logs

source supports relay, controller, node, all. latest limits to newest N entries. Invalid filter values return 400.

WebSocket

RoleURL
Controllerws://host:port?role=controller
Nodews://host:port?role=node

Runtime responsibilities

  • Enforce hello/auth frame sequencing before routing any command
  • Validate and route command frames to the correct node session by targetNodeId
  • Maintain command correlation and terminal timeout tracking
  • Emit synthetic disconnect failures for in-flight requests when nodes go offline
  • Manage tab lock leases, detect conflicts, and emit lock lifecycle events
  • Persist and stream structured operation logs
  • Disconnect stale controllers by heartbeat policy and trigger owner-scoped orphan tab cleanup

Log storage model

Relay writes logs into day-windowed JSONL files in OTTO_LOG_DIR:

  • Active day file: operations-YYYY-MM-DD.jsonl
  • Spillover on file size: operations-YYYY-MM-DD-1.jsonl, -2.jsonl, etc.
  • File retention: 14 days
  • /api/logs/status reports aggregate byte size across all operation log files

Extension-originated node logs are ingested when authenticated node clients send event frames with type=extension_log. Relay binds each entry to the authenticated node identity, applies redaction, and stores as source=node.

Listener management

  • Relay activates a listener subscription only after a successful result for listener.subscribe
  • Active listener ownership is keyed by subscribe requestId and bound to controller + node identity
  • listener.unsubscribe validates payload.targetRequestId, ownership, and node match before routing
  • Successful unsubscribe removes listener state; future updates on that requestId are rejected with listener_not_found
  • Listener state is cleaned up on controller disconnect and node disconnect

Controller disconnect behavior

Relay marks a controller stale when no authenticated frames arrive within OTTO_CONTROLLER_HEARTBEAT_INTERVAL_MS x OTTO_CONTROLLER_HEARTBEAT_MISS_LIMIT. On disconnect or timeout:

  1. Relay removes that controller's listener and command stream state.
  2. Relay drops queued commands owned by the disconnected controller immediately.
  3. Relay dispatches primitive.tab.close_owned to connected nodes with the disconnected controller clientId.
  4. Node runtime closes only tabs owned by that controller identity.

Operational notes

  • Terminal outcomes for accepted commands are guaranteed: result or error frame.
  • Per-tab command execution is FIFO; cross-tab execution is parallelized.
  • Refresh sessions persist in OTTO_LOG_DIR/refresh-sessions.jsonl across relay restarts.
  • Refresh tokens are rotated on successful /api/auth/refresh; the previous token is invalidated immediately.
  • Relay startup logs include effective TTL configuration and load stats for refresh sessions, controller clients, and ACL stores.

Operational troubleshooting checklist:

  1. Confirm controller is authenticated (auth_ack observed in logs).
  2. Confirm action is included in controller scopes.
  3. Confirm target node is online (node_offline means the node has not connected or has disconnected).
  4. Check queue limits if commands are piling up (tab_queue_overflow, controller_queue_overflow).
  5. Check rate limits if commands are dropping under high frequency.

Next steps