Docs / Design
Edit on GitHub

Protocol

OpenViber’s runtime has three components that communicate:

  1. Viber runtime (daemon) — runs on the user’s machine, executes AI tasks.
  2. Gateway — central coordinator that routes messages between Viber runtimes and the web app.
  3. Web App (Viber Board) — SvelteKit frontend that operators use to interact with tasks.

Terminology note: in this doc, “daemon” refers to the Viber runtime process. “Gateway” is the central coordinator (started with viber gateway). This is distinct from the Channels server (viber channels, enterprise channel webhooks) and the Skill Hub (src/skills/hub/).

The protocol is intentionally simple. The AI SDK handles the complex parts (streaming, tool calls, message formatting). OpenViber’s protocol is just the plumbing that connects them.


1. Architecture

┌──────────┐   HTTP/SSE   ┌──────────┐   WebSocket   ┌──────────┐    AI SDK    ┌─────┐
│ Browser  │ ←──────────→ │ Web App  │ ←───────────→ │ Gateway  │ ←──────────→ │Daemon│
│ (Svelte) │              │(SvelteKit)│               │ (Node)   │              │(Node)│
└──────────┘              └──────────┘               └──────────┘              └──────┘
  @ai-sdk/svelte            API routes                REST + WS             streamText()
  Chat class               gateway                  task routing         toUIMessageStream

Transport Summary

PathTransportProtocol
Browser ↔ Web AppHTTP + SSEAI SDK UI Message Stream
Web App → GatewayHTTP (REST)JSON API
Gateway → Web AppHTTP (SSE)AI SDK UI Message Stream (passthrough)
Daemon → GatewayWebSocket (outbound)JSON messages
Gateway → DaemonWebSocketJSON messages

2. Gateway REST API

The gateway exposes a REST API for the web app (via src/gateway/):

MethodEndpointPurpose
GET/healthHealth check (status, Viber count, Task count)
GET/api/vibersList connected Vibers
GET/api/tasksList all tasks
POST/api/tasksCreate a new task on a Viber
GET/api/tasks/:idGet task status and events
POST/api/tasks/:id/stopStop a running task
GET/api/tasks/:id/streamSSE stream of AI SDK response chunks
POST/api/vibers/:id/config-pushPush config to a Viber (triggers config:push WS message)

SSE Stream Endpoint

GET /api/tasks/:id/stream holds the connection open and relays AI SDK SSE bytes from the daemon. Key headers:

Content-Type: text/event-stream
x-vercel-ai-ui-message-stream: v1

The stream closes when the task completes, errors, or is stopped.


3. Gateway ↔ Viber Runtime (Daemon) WebSocket Protocol

Viber runtimes (daemons) connect outbound to the gateway at ws://{gateway}/ws with auth headers:

Authorization: Bearer {token}
X-Viber-Id: {viberId}
X-Viber-Version: {version}

Viber Runtime → Gateway Messages

TypePayloadWhen
connected{ viber: ViberInfo }On WebSocket open
task:started{ taskId, spaceId }Task execution begins
task:stream-chunk{ taskId, chunk }Raw AI SDK SSE bytes
task:progress{ taskId, event }Progress envelope (status, deltas)
task:completed{ taskId, result }Task finished successfully
task:error{ taskId, error }Task failed
heartbeat{ status: ViberStatus }Periodic health (every 30s)
pong{}Response to ping
config:ack{ configVersion, validations }Response to config:push
terminal:*VariousTerminal streaming responses

Gateway → Viber Runtime Messages

TypePayloadWhen
task:submit{ taskId, goal, messages?, options? }New task from operator
task:stop{ taskId }Stop a running task
task:message{ taskId, message, injectionMode? }Follow-up message during task
ping{}Keepalive
config:update{ config }Runtime config change (deprecated)
config:push{}Request Viber to pull and validate latest config
terminal:list{}Request terminal list
terminal:attach{ target, appId? }Attach to terminal
terminal:detach{ target, appId? }Detach from terminal
terminal:input{ target, keys, appId? }Send input to terminal
terminal:resize{ target, cols, rows, appId? }Resize terminal

ViberInfo Shape

interface ViberInfo {
  id: string;
  name: string;
  version: string;
  platform: string;
  capabilities: string[];
  runningTasks: string[];
  skills?: ViberSkillInfo[];
}

interface ViberSkillInfo {
  id: string;
  name: string;
  description: string;
  available: boolean;
  status: "AVAILABLE" | "NOT_AVAILABLE" | "UNKNOWN";
  healthSummary?: string;
}

interface ConfigState {
  configVersion: string;       // hash of current config
  lastConfigPullAt: string;    // ISO timestamp
  validations: ConfigValidation[];
}

interface ConfigValidation {
  category: "llm_keys" | "oauth" | "env_secrets" | "skills" | "binary_deps";
  status: "verified" | "failed" | "unchecked";
  message?: string;
  checkedAt: string;
}

4. Task States

Tasks have a simple lifecycle:

pending → running → completed
                  → error
                  → stopped
StateMeaning
pendingTask created, waiting for Viber runtime (daemon) to start
runningViber runtime (daemon) is executing (streaming in progress)
completedTask finished successfully
errorTask failed (provider error, tool error, etc.)
stoppedOperator explicitly stopped the task

5. Message Injection Modes

When an operator sends a follow-up message during a running task, the injectionMode controls behavior:

ModeBehavior
collectBuffer the message; merge into one follow-up after current run
steerQueue for immediate processing; abort current run at next safe point
followupQueue for processing after current run completes

6. Terminal Streaming

Terminal I/O uses the same WebSocket connection between Viber runtime (daemon) and gateway, with a dedicated message namespace (terminal:*). The gateway relays terminal data to the web app via a separate WebSocket connection on port 6008 (not through the SSE stream).


7. Security

  • Outbound-only: Viber runtimes (daemons) connect outbound to the gateway. No inbound ports needed.
  • Auth headers: WebSocket connections include Authorization and X-Viber-Id headers.
  • CORS: Gateway sets Access-Control-Allow-Origin: * for development (should be restricted in production).
  • No secrets in stream: The SSE stream contains only AI response content, never API keys or credentials.

8. Design Decisions

DecisionRationale
Passthrough SSE relayAvoids re-encoding; the AI SDK format is the source of truth
Gateway as stateless coordinatorGateway can restart without losing Viber runtime (daemon) connections (daemons auto-reconnect)
WebSocket for Viber runtime, SSE for browserWebSocket is bidirectional (needed for task control); SSE is simpler for browser consumption
Simple task statesThe AI SDK manages the complex agent loop; OpenViber just tracks the outer lifecycle
Chunk buffering in gatewayLate-connecting SSE subscribers need to catch up without data loss