# comm-sdk: give an AI agent real communication channels You are integrating messaging into an AI agent using comm-sdk. It gives an agent a real presence on many channels behind ONE `on_message` handler and one `message.reply()` - the same code answers on every channel. The gateway is hosted at https://api.clawies.ai. ## How to drive this (read first) The developer may just say "add comm-sdk" without naming channels. Do this: 1. Do steps 1-3 below (key, install, the shared handler) - always needed. 2. Discover which channels are actually connectable RIGHT NOW: `curl -s https://api.clawies.ai/v1/channels -H "Authorization: Bearer $COMM_API_KEY"` That returns the live channels (e.g. email, telegram, discord, whatsapp, imessage). Only offer those - other channels exist in the SDK but aren't enabled on this gateway and will 400 if you try them. A channel may appear MORE THAN ONCE - each row is a different `provider` for that channel (e.g. whatsapp via both `twilio-whatsapp` and `meta-whatsapp`). When that happens, ask the developer which provider they want and pass it as `provider=` to `connect_*()` (see the WhatsApp section). 3. ASK the developer which of the live channels they want (email needs nothing; others need a token or an OAuth click - see the per-channel sections). Don't assume; list the live options and let them choose. Email is a safe default if they don't care. 4. Connect each chosen channel (sections below), then verify with a test. Every channel uses the SAME handler from step 3 - adding one is just another `client.connect_*()` call, never new handler code. ## 1. Get an API key (no signup) ```bash curl -s -X POST https://api.clawies.ai/v1/projects/sandbox \ -H 'Content-Type: application/json' -d '{"name":"my-agent"}' ``` Returns `{"project_id": "...", "api_key": "comm_sandbox_..."}`. Write these to the project's `.env`: ``` COMM_API_KEY= COMM_BASE_URL=https://api.clawies.ai ``` Alternative with the CLI (does the same and writes .env for you): ```bash uvx --from "git+https://github.com/Clawies/agent-communication-sdk#subdirectory=apps/cli" comm init --gateway https://api.clawies.ai ``` ## 2. Install the SDK ```bash uv add "comm-sdk @ git+https://github.com/Clawies/agent-communication-sdk#subdirectory=sdks/python" # or: pip install "comm-sdk @ git+https://github.com/Clawies/agent-communication-sdk#subdirectory=sdks/python" ``` ## 3. Integrate - one handler for every channel The SDK reads COMM_API_KEY and COMM_BASE_URL from the environment or ./.env automatically. Write the handler ONCE; it fires for whatever channels you connect below. ```python from comm_sdk import CommClient client = CommClient() # Connect the channels the developer chose (see the per-channel sections). # Email needs nothing and is the safe default: email = client.connect_email(display_name="My Agent") print("Agent email:", email["address"]) # client.connect_telegram(bot_token=...) # if they want Telegram # client.connect_discord(bot_token=...) # if they want Discord @client.on_message def handle(message): # Same for every channel: message.text, message.sender, message.conversation_id # (conversation_id is stable per thread - key your agent's memory on it) answer = your_agent_logic(message.text) message.reply(answer) # replies on whichever channel the message arrived from client.listen() ``` Wire `your_agent_logic` to whatever the developer's agent framework is (OpenAI Agents SDK, LangGraph, plain LLM call). The reply must be plain text. Before connecting email, ASK the developer: "Default address on @clawies.ai, or your own custom domain?" If they want their own, follow the custom-domain section, then ask for the exact username (e.g. `client.connect_email( domain="agents.acme.com", username="kernel")` -> kernel@agents.acme.com). Don't silently pick the default - let them choose. ## 4. Verify it works With the integration running in one terminal, deliver a test email: ```bash curl -s -X POST https://api.clawies.ai/v1/test-emails -H "Authorization: Bearer $COMM_API_KEY" \ -H 'Content-Type: application/json' -d '{"text":"hello, are you alive?"}' ``` Within ~10 seconds the running integration must print the inbound message and send a reply. Confirm by checking events: ```bash curl -s "https://api.clawies.ai/v1/events?type=message.sent" -H "Authorization: Bearer $COMM_API_KEY" ``` If a `message.sent` event appears, the integration is complete. Tell the developer their agent's email address; real emails to it from any mail client now reach the handler. ## Custom email domain (optional) By default addresses live on the platform domain. If the developer wants their own brand (e.g. support-x@agents.acme.com), ask them: > Do you want agent email addresses on your own domain? If yes, tell me a > dedicated subdomain of a domain you control (e.g. agents.yourcompany.com). > Do not use your root domain - its existing mail would be affected. Then: 1. Register it: `client.add_domain("agents.acme.com")` (REST: POST https://api.clawies.ai/v1/domains with `{"domain": "agents.acme.com"}`). 2. Show the developer the returned `dns_records` and tell them to add each at their DNS provider. A zone file for bulk import is at `https://api.clawies.ai/v1/domains/{id}/zone-file`. 3. Poll `GET https://api.clawies.ai/v1/domains/{id}` until `status` is `active` (DNS can take minutes). Do not guess; wait for the developer to confirm the records are in. 4. Connect on the domain: `client.connect_email(domain="agents.acme.com")` - the address is now on their domain, sending is DKIM-signed as their domain. To pick the exact address, add `username="kernel"` -> kernel@agents.acme.com (custom domains only; 409 if taken). ## Adding Slack (ask which kind of app) Like Discord, Slack comes two ways - STOP and ask the developer: > Slack connects as an app installed to your workspace. Two options: > a) Quick (one click) - add our shared app; no Slack app to create. You give it > a custom name + icon, so it still looks like your brand. > b) Branded - create your OWN Slack app (~5 min) for full control. > Which - Quick (shared) or Branded (your own)? ### a) Quick - shared app (one-click OAuth, no app to create) Ask the developer what name (and optional icon URL) the agent should post under: > What should the agent be called in Slack? (e.g. "Acme Support") Optionally an > icon image URL too. Then: `result = client.install_slack(display_name="", icon_url="")` returns a connection with an `authorize_url` ("Add to Slack"). Give the developer that URL: "Open this, pick your workspace, and authorize." The shared app installs into their workspace, the connection flips to `active` on its own (poll `GET https://api.clawies.ai/v1/connections/{id}` or watch for `connection.active`), and the agent posts under their name + icon. Messages in that workspace reach the same `on_message` handler. If `install_slack()` 400s, the shared app isn't configured on this gateway - use (b). NEVER default the name to "Caspian"/an infra name - ask. ### b) Branded - bring your own Slack app > Create a Slack app at api.slack.com/apps (choose "From a manifest" - I can give > you one pointed at this gateway). Turn on Token Rotation. Then paste me the > app's Client ID, Client Secret, and Signing Secret. Then `client.connect_slack(slack_client_id=..., slack_client_secret=..., slack_signing_secret=...)` returns a connection with an `authorize_url`. Either way: give the developer the `authorize_url`, they approve in the browser, the connection goes `active`, and the same `on_message` handler answers. CLI: `comm connect slack` prints the authorize link. ## Adding Instagram or Facebook (OAuth channels) Same OAuth-link pattern (gateway-configured app): 1. `client.connect_instagram(...)` / `connect_facebook(...)` returns a connection with an `authorize_url`. 2. Give the developer that URL: "Click this to authorize in your account." 3. They approve; the platform redirects to our callback and the connection flips to active. Poll `GET https://api.clawies.ai/v1/connections/{id}` until `active`. 4. The same `on_message` handler now answers on that channel. ## Adding Discord (ask which kind of bot) The agent connects to Discord as a bot (two-way: people message it, it replies). There are two ways to get that bot - STOP and ask the developer: > Discord connects as a bot. Two options: > a) Quick (one click) - add our shared bot to your server; no token to create. > You give it a custom name; it uses a shared avatar. > b) Branded - create your OWN bot (~5 min) so it has your own name AND avatar. > Which - Quick (shared) or Branded (your own)? ### a) Quick - shared bot (one-click OAuth, no token) First ASK the developer what name the bot should show in their server: > What should the bot be called in your server? (e.g. "Acme Support") Then pass it as `display_name`: `result = client.install_discord(display_name="")` returns a connection with an `authorize_url`. Give the developer that URL: "Open this, pick your server, and authorize." Discord adds the shared bot, renames it to their chosen name in that server, and the connection flips to `active` on its own (poll `GET /v1/connections/{id}` or watch for `connection.active`). Messages in that server then reach the same `on_message` handler; `message.reply()` answers. If `install_discord()` 400s, the shared bot isn't configured on this gateway - use (b). A 409 on the callback means that server is already linked to an agent. (The custom name is a per-server nickname; avatar stays shared - use (b) for a custom avatar too.) NEVER default the name to "Caspian" or an infra name - ask. ### b) Branded - bring your own bot > Create a bot at discord.com/developers -> New Application -> Bot -> Reset > Token -> copy it. Enable the "Message Content Intent" under Bot settings. > Then paste me the token. Then `client.connect_discord(bot_token="")`. Same `on_message` handler answers; `message.reply()` replies in Discord. A 409 means that bot is already connected elsewhere - use a different one. Either way, replies use the developer's agent - only the bot's name/avatar differs (shared name vs their own bot). Never brand the bot as Caspian/the gateway. ## Adding Telegram (optional second channel) Telegram needs one thing only a human can create: a bot token. STOP and ask the developer for it with exactly these instructions: > Open Telegram and message @BotFather. Send /newbot, choose a name and a > username, and paste me the token it gives you (looks like 7123456789:AAE...). Do not proceed without the token and never invent one. Once the developer provides it: ```python telegram = client.connect_telegram(bot_token="") print("Telegram bot:", telegram["address"]) # @their_bot ``` Or via CLI: `comm connect telegram --bot-token `. The same @client.on_message handler receives Telegram messages; message.reply() answers in the chat. No other setup - the gateway registers the webhook. To verify: tell the developer to open Telegram, search for the bot's @username, and send it any message. The running integration must print it and reply within a few seconds. Error handling: a 409 from connect means that bot token is already connected elsewhere - ask the developer for a different bot. A 422 naming `bot_token` means it was missing or malformed. ## Adding WhatsApp (ask which provider first) WhatsApp is served by TWO providers on this gateway, so it appears twice in /v1/channels. They behave differently, so STOP and ask the developer which one with exactly this choice: > WhatsApp has two options here: > 1. Twilio - fastest to test. A shared sandbox number works in minutes (great > for a demo); a dedicated production number needs a paid Twilio account + > WhatsApp sender approval. > 2. Meta (direct) - connect your OWN branded WhatsApp Business number through > a one-time Meta popup (Embedded Signup). Best for production and brand. > Which do you want - Twilio (quick test) or Meta (branded)? Pass their choice as `provider=` on connect. Same `on_message` handler either way. ### Option 1 - Twilio (test now) ```python wa = client.connect_whatsapp(provider="twilio-whatsapp") print("WhatsApp on:", wa["address"]) # the Twilio number (sandbox by default) ``` For inbound to reach the agent, the developer must do two one-time things in the Twilio Console (Messaging -> Try it out -> Send a WhatsApp message): 1. Set "When a message comes in" to `https://api.clawies.ai/internal/providers/twilio-whatsapp/webhooks` (HTTP POST), and Save. 2. From their own WhatsApp, send the shown `join ` to the sandbox number (+1 415 523 8886) to opt in. Then any message they send that number reaches the same handler and gets a reply. ### Option 2 - Meta (branded, Embedded Signup) A one-time Meta popup connects the developer's (or their customer's) own WhatsApp Business number - no tokens to copy or paste: ```python session = client.start_whatsapp_onboarding() print("Open this to connect WhatsApp:", session["launcher_url"]) ``` Give the developer that `launcher_url`. They click through the Meta popup once and their number provisions onto the agent. Poll `GET https://api.clawies.ai/v1/connections/{id}` until status is `active` (or watch for the `connection.active` event). If `start_whatsapp_onboarding()` returns 400, Embedded Signup isn't configured on this gateway yet - fall back to Option 1 (Twilio). Either way, WhatsApp business rules apply: the agent can reply freely for 24h after a user messages it; starting a conversation cold needs an approved template. The developer's `on_message` code doesn't change - the gateway handles the difference. ## Adding iMessage (no setup - like email) iMessage needs NOTHING from the developer - the gateway owns the number (via an AgentPhone relay, since Apple has no public API). Just connect: ```python imessage = client.connect_imessage() print("iMessage number:", imessage["address"]) ``` The same `on_message` handler answers over iMessage. It also supports cold-initiate (message a number that never messaged first), unlike WhatsApp. To verify: text the printed number from an iPhone - the running integration prints the inbound message and replies within a few seconds. Note: this gateway shares one iMessage number, so it's one connection at a time (fine for a demo; per-agent numbers are a follow-up). A 409 / "already connected" on connect means another agent currently holds it. ## Notes - `connect_email()` is idempotent: calling it again (e.g. on agent restart) returns the same connection and address instead of provisioning a new inbox. - `connect_email()` with no arguments uses a default customer/agent scope. For multi-tenant products pass explicit `customer_id` and `agent_id` (create them via `client.create_customer(name)` / `client.create_agent(name)`). - `client.listen()` blocks forever. For custom loops poll `client.events(after_seq=cursor)`; events are strictly ordered by `seq`. - Do not reply to the same message twice; `listen()` already dedupes within a run. Persist your own cursor if you need replies across restarts. - REST reference: https://api.clawies.ai/docs