Social bridges

Reach werbos from Telegram, Slack, and WhatsApp. One SocialAdapter Protocol; three working bridges; zero new dependencies. All existing werbos surfaces (skills, personas, mirror slider, vision, private mode) work over any platform unchanged.

werbos can already chat over CLI, REPL, the React SPA, and werbos serve’s HTTP/MCP surface. Sprint Soc.1 (Phase 12) extends reachability to where people actually are — Telegram, Slack, WhatsApp via Twilio. Twitter/X is queued for Soc.2 once the paid API gate clears.

The architecture mirrors how the orchestration plane shipped: protocol first, runtime second. One SocialAdapter Protocol; each platform plugs in independently. All existing werbos surfaces (skills auto-fire, /see, persona, mirror slider, private mode, tool dispatch) work over any adapter unchanged — the adapter only translates the wire format; it doesn’t re-implement chat behavior.

How it fits

werbos chat / chat REPL / SPA
                  │
                  ▼
          ChatSession.step
                  ▲
                  │
        SocialMessageRouter
        ├─ SocialIdentityStore (~/werbos/social.db)
        └─ adapters: { telegram, slack, whatsapp }
                  ▲
                  │
   webhook routes mounted on werbos serve:
   /api/social/telegram/webhook    (X-Telegram-Bot-Api-Secret-Token)
   /api/social/slack/events        (HMAC-SHA256, v0:ts:body)
   /api/social/whatsapp/webhook    (HMAC-SHA1, URL + sorted params)

Each (platform, sender_id) maps to a persistent werbos chat session — one row in ~/werbos/social.db. Conversations survive across days; /new is the deliberate roll-over (Day 6 surface). Pairing on first contact: unknown senders get a “reply with /pair <code>” prompt. Codes are TTL’d (15 min), one-shot, stored as SHA-256 hashes.

Pairing flow

  1. Operator-side: mint a code via werbos chat → /social code (Day 6 surface) or via Python:
    from werbos.integrations.store import SocialIdentityStore
    from pathlib import Path
    store = SocialIdentityStore(Path.home() / "werbos" / "social.db")
    print(store.mint_pairing_code())
    
  2. Sender-side: open the platform (Telegram / Slack / WhatsApp), send any message to the bot. werbos replies with the pairing prompt.
  3. Sender-side: reply /pair <code>. The bot confirms the pair.
  4. Sender-side: chat normally. Every existing werbos surface (skills, persona, mirror slider, /see if --vision-model is set, private mode) works unchanged.

Telegram

External gate: free token from @BotFather (5 min).

export TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
# Optional production webhook (otherwise the adapter long-polls):
export TELEGRAM_WEBHOOK_URL=https://your.host/api/social/telegram/webhook
export TELEGRAM_WEBHOOK_SECRET=...    # auto-generated if unset
werbos serve

Image attachments flow through the existing vision pipeline: the adapter picks the largest size variant from Telegram’s photo array, calls getFile, downloads + base64-encodes, drops into Attachment.image_b64. Pair with --vision-model llama3.2-vision:11b and the bot can describe photos you send.

Polling fallback for dev: when TELEGRAM_WEBHOOK_URL is unset, a background task long-polls getUpdates. Works without ngrok.

Slack

External gate: Slack app + bot token + signing secret (~30 min, one-time setup).

The repo ships docs/integrations/slack-app-manifest.yml for one-click app provisioning:

  1. Go to api.slack.com/apps → Create New App → From a manifest.
  2. Paste the YAML from docs/integrations/slack-app-manifest.yml, replacing https://your.host with your public URL.
  3. Install the app to your workspace.
  4. Copy the Bot User OAuth Token (xoxb-...) and Signing Secret (Basic Information → App Credentials).
export SLACK_BOT_TOKEN=xoxb-...
export SLACK_SIGNING_SECRET=...
werbos serve

Surface at v1: DMs and @-mentions in channels. Block-kit interactive cards, slash commands (/werbos), and image attachments are deferred to Soc.2.

Bot mention tag stripping — both <@U_BOT> and <@U_BOT|werbos> forms are removed from the message text before it reaches the model, so the persona doesn’t have to ignore a mention envelope.

WhatsApp via Twilio

External gate: Twilio account + sandbox enable (~5 min, free) OR production WABA number (~weeks of Meta business verification).

The repo ships docs/integrations/whatsapp-twilio-setup.md with a full walkthrough. Quick version:

  1. Sign up at twilio.com/try-twilio.

  2. Console → Messaging → Try it out → Send a WhatsApp message; note the sandbox number + join code.

  3. Text the join code to the sandbox number from WhatsApp.

  4. Configure the webhook URL in the sandbox settings.

  5. Set env vars + run:

    export TWILIO_ACCOUNT_SID=ACxxxxxxxx...
    export TWILIO_AUTH_TOKEN=...
    export TWILIO_WHATSAPP_FROM=+14155238886    # sandbox number, no `whatsapp:` prefix
    export TWILIO_WHATSAPP_WEBHOOK_URL=https://your.host/api/social/whatsapp/webhook
    werbos serve
    

TWILIO_WHATSAPP_WEBHOOK_URL must match exactly what’s configured in the Twilio console — Twilio includes the URL in the HMAC-SHA1 signature, so https/http/trailing-slash differences fail verification.

Image attachments work end-to-end via Twilio’s authenticated media URLs.

Production WABA: same env vars, just swap TWILIO_WHATSAPP_FROM to the provisioned number once Meta approves your business verification. Code path is identical to sandbox.

Audit shape

Every paired inbound message emits a KIND_SOCIAL_INBOUND event into the chat’s PFM recorder chain:

{
  "kind": "social_inbound",
  "actor": "system",
  "payload": {
    "platform": "telegram",
    "sender_id_prefix": "U-LONG",
    "sender_name": "Alice",
    "chars_in": 42,
    "attach_count": 0,
    "received_at": "2026-05-09T22:30:00.000000Z"
  }
}

Metadata only — never the message body. The text lives in the chat’s existing KIND_HUMAN_TURN_END event; this is the platform-level provenance envelope. Auditors get a chain they can verify (which platform delivered which turn, when, with how many bytes) without reading content. Same compliance shape as KIND_USER_PROFILE_LOADED and KIND_TOM_ATTESTATION.

sender_id is truncated to a 6-char prefix so audit rows for the same sender cluster but full PII (phone numbers, Slack user IDs) doesn’t leak via archived .pfm files.

Identity safety

  • Pairing failure modes: invalid code, expired code (>15 min), wrong-platform code (when minted with a platform_hint). Each surfaces a specific error to the sender so they know how to retry.
  • Block / unblock: werbos chat → /social block <platform> <sender_id> (Day 6 surface) marks the row blocked. Future inbounds from that sender drop silently — no enumeration oracle.
  • Multi-tenant scoping: every store row carries tenant_id + user_id. Single-user mode today; the gatekeep hosted SKU is a default-value change, not a refactor.

What’s NOT in v1

These are tracked for Soc.2:

  • Twitter/X — DMs first, mentions later. Gated on the X API basic tier (~$100/mo).
  • Outbound media — the bot only sends text replies in v1. Sending images / files is per-platform and adds non-trivial surface.
  • Slack block-kit cards — interactivity, slash commands, shortcuts. /werbos, /werbos-new, /werbos-mirror N.
  • WhatsApp templates — proactive outreach (paid feature).
  • Discord — deliberate omission; the dogfood loop hasn’t pulled it forward yet.
  • iMessage / SMS — gated on Apple Business Chat / Twilio SMS (both with business-verification + recurring fees).
  • Voice notes — a P.1 audio-stack adjacency once the TTS/STT path is wired.

See also

  • Personas — the voice/character/Tom/named- persona ladder. Picks the persona for each social bridge session via werbos serve --persona-name.
  • Skills — auto-fire knowledge injection. Same skills fire on social-bridge messages as on CLI / SPA.
  • Vision — image attachments from Telegram / WhatsApp flow into UserMessage.images automatically; pair with --vision-model.
  • Chat surfaces — every slash inside werbos chat; /social code (Day 6) mints pairing codes.