Gateway

The gateway is the unified run command for lsbot. It starts all configured platform bots (Telegram, Slack, Discord, etc.) and a WebSocket API server in a single process.

lsbot gateway --api-key sk-ant-xxx

On startup the gateway:

  1. Loads ~/.lingti.yaml for platform credentials, agent definitions, and routing bindings
  2. Registers all configured platforms as bot listeners
  3. Starts the WebSocket server on :18789 (use --no-ws to disable)
  4. Routes each incoming message to the correct agent based on bindings
  5. Writes ~/.lingti/gateway.pid so gateway restart can reload config without a full restart

Quick Start

# 1. Save credentials (once)
lsbot channels add --channel telegram --token 123456:ABC-xxx
lsbot channels add --channel slack --bot-token xoxb-... --app-token xapp-...

# 2. (Optional) Define agents and routing
lsbot agents add main --default
lsbot agents bind --bind telegram

# 3. Start
lsbot gateway --api-key sk-ant-xxx

Managing Channels

Channels are platform credentials stored in ~/.lingti.yaml. The channels command is the preferred way to manage them — it reads, updates, and writes the config file for you.

# Add a platform
lsbot channels add --channel telegram --token YOUR_TOKEN
lsbot channels add --channel slack --bot-token xoxb-... --app-token xapp-...
lsbot channels add --channel discord --token MTxxx
lsbot channels add --channel webapp --port 8080  # built-in web UI

# List current status
lsbot channels list

# Remove a platform
lsbot channels remove --channel discord

Once saved, credentials are loaded automatically every time you run gateway — no flags needed.

Managing Agents

An agent is an isolated AI brain with its own workspace, session memory, model, instructions, and tool permissions. You can have multiple agents and route different platforms or channels to different agents.

Adding agents

# Default agent — used when no binding matches
lsbot agents add main --default

# A focused work agent with a different model
lsbot agents add work \
  --model claude-opus-4-6 \
  --instructions "You are a focused work assistant. Be concise and precise."

# An agent that can only read (no write/edit/shell)
lsbot agents add readonly \
  --deny-tools "write,edit,shell"

Each agent gets its own workspace directory (~/.lingti/agents/<id> by default). Workspace, model, provider, API key, and instructions are all optional — unset fields inherit from the global ai: config.

Routing bindings

Bindings control which agent handles messages from which source. Resolution order (most specific wins):

PriorityMatchExample
1 (highest)platform + channel_idOnly messages from slack:C_WORK
2platform onlyAll messages from telegram
3 (lowest)Default agentAnything unmatched
# Route all Telegram messages to 'main'
lsbot agents bind --bind telegram

# Route a specific Slack channel to 'work'
lsbot agents bind --agent work --bind slack:C_WORK_CHANNEL

# See agents and their bindings
lsbot agents list --bindings

# Remove a binding
lsbot agents unbind --agent work --bind slack:C_WORK_CHANNEL

# Remove all bindings for an agent
lsbot agents unbind --agent work --all

Agent config in ~/.lingti.yaml

agents:
  - id: main
    default: true
    workspace: ~/.lingti/agents/main

  - id: work
    workspace: ~/my-work-workspace
    model: claude-opus-4-6
    instructions: "You are a focused work assistant."
    deny_tools: [write, edit, shell]

  - id: readonly
    allow_tools: [read, glob, grep]

bindings:
  - agent_id: main
    match:
      platform: telegram
  - agent_id: work
    match:
      platform: slack
      channel_id: C_WORK_CHANNEL

allow_tools non-empty = whitelist (only those tools available). deny_tools = blacklist (those tools removed from the full set).

Gateway Flags

FlagEnv VarDefaultDescription
--addrGATEWAY_ADDR:18789WebSocket listen address
--auth-tokenGATEWAY_AUTH_TOKENSingle auth token for WebSocket clients
--auth-tokensGATEWAY_AUTH_TOKENSComma-separated auth tokens
--no-wsfalseDisable WebSocket server
--providerAI_PROVIDERclaudeAI provider
--api-keyAI_API_KEYAI API key (required)
--base-urlAI_BASE_URLCustom AI API base URL
--modelAI_MODELModel name
--instructionsPath to custom instructions file
--call-timeoutAI_CALL_TIMEOUT90AI API call timeout (seconds)
--webapp-portWEBAPP_PORT0Web chat UI port (0 = disabled)
--debug-dirBROWSER_DEBUG_DIRBrowser debug screenshot directory

All platform credential flags are also accepted (e.g. --telegram-token, --slack-bot-token) as one-time overrides. See the CLI Reference for the full table.

Disabling the WebSocket Server

The WebSocket server starts by default. Disable it if you only want platform bots:

lsbot gateway --no-ws --api-key sk-ant-xxx

Reloading Config

After changing ~/.lingti.yaml (e.g. adding a channel or agent), reload the running gateway without restarting it:

lsbot gateway restart

This sends SIGHUP to the running process via ~/.lingti/gateway.pid.

Docker / CI (Flags Only)

For deployments without a config file, all credentials can be supplied via flags or environment variables:

# Flags
lsbot gateway \
  --api-key sk-ant-xxx \
  --telegram-token 123456:ABC-xxx

# Environment variables
export AI_API_KEY=sk-ant-xxx
export TELEGRAM_BOT_TOKEN=123456:ABC-xxx
lsbot gateway

# docker-compose.yml
services:
  lsbot:
    image: lsbot
    environment:
      AI_API_KEY: ${AI_API_KEY}
      TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
    command: gateway

WebSocket API

The gateway also exposes a WebSocket API on :18789 for custom clients (web UIs, scripts, mobile apps). This is independent of platform bots — both run concurrently.

Authentication

By default all WebSocket connections are accepted. To require a token:

lsbot gateway --auth-token my-secret --api-key sk-ant-xxx

# Multiple tokens (each person gets their own)
lsbot gateway --auth-tokens "alice-token,bob-token" --api-key sk-ant-xxx

When auth is enabled, clients must send an auth message before chatting.

HTTP endpoints

MethodPathDescription
GET/healthReturns {"status":"ok"}
GET/statusRunning status, client count, auth state
GET/wsWebSocket upgrade endpoint
curl http://localhost:18789/health
curl http://localhost:18789/status

Message protocol

All WebSocket messages are JSON with this envelope:

{
  "id":        "unique-message-id",
  "type":      "message-type",
  "payload":   { ... },
  "timestamp": 1700000000000
}

Client → Server

TypeDescription
pingKeep-alive; server replies with pong
authAuthenticate: {"payload": {"token": "..."}}
chatSend message: {"payload": {"text": "...", "session_id": "optional"}}
commandBuilt-in command: {"payload": {"command": "status"}} or "clear"

Server → Client

TypeDescription
pongReply to ping
auth_resultAuth outcome: {"payload": {"success": true}}
responseAI reply: {"payload": {"text": "...", "session_id": "...", "done": true}}
eventCommand result
errorError: {"payload": {"code": "unauthorized", "message": "..."}}

Error codes: unauthorized, invalid_message, invalid_payload, handler_error, no_handler, unknown_type, unknown_command

Example clients

JavaScript:

const ws = new WebSocket("ws://localhost:18789/ws");

ws.onopen = () => {
  // Skip if no auth configured
  ws.send(JSON.stringify({ type: "auth", payload: { token: "my-secret" } }));
};

ws.onmessage = ({ data }) => {
  const msg = JSON.parse(data);
  if (msg.type === "auth_result" && msg.payload.success) {
    ws.send(JSON.stringify({
      id: "req-1",
      type: "chat",
      payload: { text: "Hello, what can you do?" }
    }));
  }
  if (msg.type === "response" && msg.payload.done) {
    console.log("AI:", msg.payload.text);
  }
};

Python:

import json, websocket

ws = websocket.create_connection("ws://localhost:18789/ws")
ws.send(json.dumps({"type": "auth", "payload": {"token": "my-secret"}}))
json.loads(ws.recv())  # auth_result

ws.send(json.dumps({"id": "1", "type": "chat", "payload": {"text": "Hello"}}))
print(json.loads(ws.recv())["payload"]["text"])
ws.close()

Sessions

Each WebSocket connection has one active session. The session ID is established on the first chat message:

  • Provide session_id in the payload to use a specific session
  • Omit it to use the connection's client ID as the session

Send {"type": "command", "payload": {"command": "clear"}} to reset the session and start a fresh conversation.

Connection lifecycle

Client                          Gateway
  |--- WebSocket upgrade -------->|
  |<-- connection accepted -------|
  |--- auth (if required) ------->|
  |<-- auth_result ---------------|
  |--- chat {"text": "Hi"} ------>|
  |<-- response {"done": true} ---|
  |--- command "clear" ---------->|
  |<-- event "cleared" ----------|
  |--- [disconnect] ------------->|

The server sends WebSocket-level Ping frames every 30 seconds. Connections that don't respond with Pong within 60 seconds are closed.