> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Signal Bot

> Connect your AI agent to Signal via a local signal-cli-rest-api bridge — end-to-end encrypted, no cloud tokens

Connect your AI agent to Signal — end-to-end encrypted, linked through a local `signal-cli-rest-api` bridge, no cloud bot token needed.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai_bot.bots import SignalBot

agent = Agent(name="assistant", instructions="Be helpful on Signal.", llm="gpt-4o-mini")
bot = SignalBot(account="+15551234567", bridge_url="http://localhost:8080", agent=agent)

import asyncio
asyncio.run(bot.start())
```

The user messages you on Signal; the bridge relays the chat to the agent and the agent replies in the same thread.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    User[📱 Signal User] --> Signal[🔒 Signal Network]
    Signal --> Bridge[📡 signal-cli-rest-api]
    Bridge --> Bot[🤖 SignalBot]
    Bot --> Agent[🧠 AI Agent]
    Agent --> Bot
    Bot --> Bridge

    classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff

    class User user
    class Signal,Bridge,Bot process
    class Agent agent
```

## Quick Start

<Steps>
  <Step title="Run the bridge">
    Signal links a device through the [signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api) bridge. Start it with Docker in `native` mode.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    docker run -d --name signal-api -p 8080:8080 \
      -v ./signal-data:/home/.local/share/signal-cli \
      -e MODE=native bbernhard/signal-cli-rest-api
    ```
  </Step>

  <Step title="Link your device">
    Open the bridge link URL, then scan the QR from Signal → **Settings → Linked Devices → Link New Device**.

    ```
    http://localhost:8080/v1/qrcodelink?device_name=praisonai
    ```
  </Step>

  <Step title="Start the bot">
    Point the bot at your linked account and the bridge.

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonai_bot.bots import SignalBot

    agent = Agent(name="assistant", instructions="Be helpful on Signal.", llm="gpt-4o-mini")
    bot = SignalBot(account="+15551234567", bridge_url="http://localhost:8080", agent=agent)

    import asyncio
    asyncio.run(bot.start())
    ```

    Message your linked Signal number and the agent replies.
  </Step>
</Steps>

<Note>
  No token needed — Signal links a device via the local bridge. The `token` constructor argument exists only for adapter-wiring parity and is unused.
</Note>

***

## How It Works

Signal is poll-based: the bot fetches inbound envelopes from the bridge, routes text to the agent, and sends the reply back through the bridge.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User as 📱 Signal User
    participant Bridge as 📡 Bridge
    participant Bot as 🤖 SignalBot
    participant Agent as 🧠 Agent

    User->>Bridge: Send message
    loop Every poll_interval
        Bot->>Bridge: GET /v1/receive/{account}
        Bridge-->>Bot: Envelopes (DMs + groups)
    end
    Bot->>Agent: chat(content)
    Agent-->>Bot: Response
    Bot->>Bridge: POST /v2/send
    Bridge-->>User: Reply

    classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef agent fill:#10B981,stroke:#7C90A0,color:#fff

    class User user
    class Bridge,Bot process
    class Agent agent
```

The adapter talks HTTP/JSON to these bridge endpoints:

| Endpoint                             | Purpose                                         |
| ------------------------------------ | ----------------------------------------------- |
| `GET /v1/receive/{account}`          | Poll inbound envelopes (DMs + groups)           |
| `POST /v2/send`                      | Send text                                       |
| `PUT /v1/typing-indicator/{account}` | Typing indicator (best-effort)                  |
| `GET /v1/accounts`                   | Bridge reachability + linkage check (`probe()`) |

***

## Configuration

Signal needs an account and a bridge URL. Configure it with YAML, environment variables, or the Python SDK.

<Tabs>
  <Tab title="YAML">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # bot.yaml
    channels:
      signal:
        account: "+15551234567"
        bridge_url: "http://localhost:8080"
        allowed_users: ["+15557654321"]
    ```

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai_bot.bots import BotOS

    BotOS(agent=agent, platforms=["telegram", "signal"]).start()
    ```
  </Tab>

  <Tab title="Environment">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export SIGNAL_ACCOUNT="+15551234567"
    export SIGNAL_BRIDGE_URL="http://localhost:8080"
    ```

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonai_bot.bots import SignalBot

    agent = Agent(name="assistant", instructions="Be helpful.", llm="gpt-4o-mini")
    bot = SignalBot(agent=agent)  # reads SIGNAL_ACCOUNT / SIGNAL_BRIDGE_URL

    import asyncio
    asyncio.run(bot.start())
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonai_bot.bots import SignalBot

    agent = Agent(name="assistant", instructions="Be helpful.", llm="gpt-4o-mini")

    bot = SignalBot(
        account="+15551234567",
        bridge_url="http://localhost:8080",
        agent=agent,
        allowed_users=["+15557654321"],
        poll_interval=1.0,
    )

    import asyncio
    asyncio.run(bot.start())
    ```
  </Tab>
</Tabs>

### Config keys

| Field        | Env var             | Required | Default                 | Description                                          |
| ------------ | ------------------- | :------: | ----------------------- | ---------------------------------------------------- |
| `account`    | `SIGNAL_ACCOUNT`    |     ✅    | —                       | Linked Signal phone number, e.g. `+15551234567`      |
| `bridge_url` | `SIGNAL_BRIDGE_URL` |     ❌    | `http://localhost:8080` | Base URL of the running `signal-cli-rest-api` bridge |

### Constructor parameters

| Parameter       | Type                  | Default                               | Description                                                               |
| --------------- | --------------------- | ------------------------------------- | ------------------------------------------------------------------------- |
| `token`         | `str`                 | `""`                                  | Accepted for adapter-wiring parity; **unused** (Signal is token-free)     |
| `account`       | `str`                 | `""` → `SIGNAL_ACCOUNT`               | Linked Signal number. Required at `start()`; raises `ValueError` if empty |
| `bridge_url`    | `str`                 | `""` → env → `http://localhost:8080`  | Bridge base URL; trailing slash stripped                                  |
| `agent`         | `Optional[Agent]`     | `None`                                | The PraisonAI agent to route messages to                                  |
| `config`        | `Optional[BotConfig]` | `BotConfig(token="", mode="polling")` | Shared bot config                                                         |
| `allowed_users` | `Optional[List[str]]` | `None`                                | DM allowlist. Numbers normalise to digits so `+1 555…` and `1555…` match  |
| `poll_interval` | `float`               | `1.0`                                 | Seconds between `/v1/receive` polls; clamped to a minimum of `0.2`        |

***

## Capabilities & Limits

Signal declares honest capabilities so shared engines degrade correctly.

| Capability           |                      Value                      |
| -------------------- | :---------------------------------------------: |
| `max_message_length` |                      `2000`                     |
| `supports_edit`      | `False` (streaming falls back to chunked sends) |
| `supports_typing`    |                      `True`                     |
| `needs_rate_limit`   |                      `True`                     |
| `accepts_webhooks`   |               `False` (poll-based)              |
| `supports_media`     |       `False` (text-only in this release)       |

***

## Built-in Commands

Every Signal channel ships these commands out of the box.

| Command   | Description                  |
| --------- | ---------------------------- |
| `/status` | Show bot status and info     |
| `/new`    | Reset conversation session   |
| `/help`   | Show help + custom commands  |
| `/stop`   | Cancel the current agent run |

Register custom commands:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
@bot.on_command("ping")
async def ping(msg):
    return "Pong!"
```

***

## DM Allowlist

Restrict who can DM the bot with `allowed_users` — phone numbers are normalised to digits so formatting never blocks a match.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
bot = SignalBot(
    account="+15551234567",
    agent=agent,
    allowed_users=["+1 555 765 4321", "15559876543"],
)
```

Groups pass through the allowlist — **group membership is the gate there**, not the DM list.

***

## What's Not Supported Yet

This release ships text messaging. The following are intentionally not wired:

| Feature                                | Behaviour                                  |
| -------------------------------------- | ------------------------------------------ |
| `edit_message()`                       | Sends a new message (Signal has no edit)   |
| `delete_message()`                     | Returns `False` (bridge doesn't expose it) |
| `add_reaction()` / `remove_reaction()` | Return `False` (not wired)                 |
| Media in/out                           | `supports_media=False` — text only         |
| Webhooks                               | Poll-based; `accepts_webhooks=False`       |

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="ValueError: Signal account is required">
    The account is empty. Set it via the constructor, `channels.signal.account`, or the `SIGNAL_ACCOUNT` environment variable before calling `start()`.
  </Accordion>

  <Accordion title="ImportError: aiohttp is required">
    The Signal adapter uses `aiohttp` (lazy-imported). Install it:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    pip install aiohttp
    ```
  </Accordion>

  <Accordion title="No messages arrive">
    Confirm the bridge is linked and reachable. Check `GET /v1/accounts` returns your number, and lower `poll_interval` (minimum `0.2s`) for faster pickup. Verify the bridge runs in `native` or `json-rpc` mode so `/v1/receive` returns envelopes.
  </Accordion>

  <Accordion title="Bridge unreachable on probe">
    `probe()` calls `GET /v1/accounts`. A non-200 means the bridge is down or the URL is wrong. Ensure `bridge_url` matches the Docker port mapping (default `http://localhost:8080`).
  </Accordion>
</AccordionGroup>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Keep the bridge private">
    The `signal-cli-rest-api` bridge holds your linked device. Run it on localhost or a private network — never expose port 8080 to the public internet.
  </Accordion>

  <Accordion title="Use the allowlist for DMs">
    Set `allowed_users` so only trusted numbers can start a conversation. Numbers normalise to digits, so any formatting works.
  </Accordion>

  <Accordion title="Tune the poll interval">
    `poll_interval` trades latency for load. Keep the default `1.0s` for most bots; lower it toward `0.2s` for snappier replies, raise it to reduce bridge traffic.
  </Accordion>

  <Accordion title="Expect plain text">
    Signal has no message editing and this release is text-only. Design replies as self-contained plain-text messages rather than relying on edits or rich media.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="WhatsApp Bot" icon="whatsapp" href="/docs/features/whatsapp-bot">
    The closest sibling — linked-device / local-bridge model
  </Card>

  <Card title="Channels Gateway" icon="network-wired" href="/docs/features/channels-gateway">
    Run Signal alongside Telegram, Discord, Slack, and more
  </Card>

  <Card title="Messaging Bots" icon="robot" href="/docs/features/messaging-bots">
    All supported messaging platforms
  </Card>

  <Card title="Bot Gateway" icon="server" href="/docs/features/bot-gateway">
    Multi-channel gateway architecture
  </Card>
</CardGroup>
