openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
565 lines (440 loc) • 29.2 kB
Markdown
---
summary: "Signal support via signal-cli (native daemon or bbernhard container), setup paths, and number model"
read_when:
- Setting up Signal support
- Debugging Signal send/receive
title: "Signal"
---
Signal is a downloadable channel plugin (`@openclaw/signal`). The gateway talks to `signal-cli` over HTTP: either the native daemon (JSON-RPC + SSE) or the [bbernhard/signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api) container (REST + WebSocket). OpenClaw does not embed libsignal.
## The number model (read this first)
- The gateway connects to a **Signal device**: the `signal-cli` account.
- Running the bot on **your personal Signal account** makes it ignore your own messages (loop protection).
- For "I text the bot and it replies," use a **separate bot number**.
## Install
```bash
openclaw plugins install @openclaw/signal
```
`@openclaw/signal` installs from npm first, then falls back to its declared ClawHub package only when the npm target is unavailable. Use `npm:` or `clawhub:` to force a source. `plugins install` registers and enables the plugin; no separate `enable` step is needed. See [Plugins](/tools/plugin) for general install rules.
## Quick setup
<Steps>
<Step title="Pick a number">
Use a **separate Signal number** for the bot (recommended).
</Step>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/signal
```
</Step>
<Step title="Run the guided setup">
```bash
openclaw channels add
```
The wizard detects whether `signal-cli` is on `PATH` and, when missing, offers to install it: downloads the official native GraalVM build on Linux x86-64, or installs via Homebrew on macOS and other architectures. It then prompts for the bot number and `signal-cli` path.
For non-interactive setup, `openclaw channels add --channel signal` also accepts `--signal-number <e164>` for the bot phone number, plus `--http-host <host>` and `--http-port <port>` for the Signal daemon endpoint (default `127.0.0.1:8080`).
</Step>
<Step title="Link or register the account">
- **QR link (fastest):** `signal-cli link -n "OpenClaw"`, then scan with Signal. See [Path A](#setup-path-a-link-existing-signal-account-qr).
- **SMS registration:** dedicated number with captcha + SMS verification. See [Path B](#setup-path-b-register-dedicated-bot-number-sms-linux).
</Step>
<Step title="Verify and pair">
```bash
openclaw channels status --probe
```
Send a first DM and approve pairing: `openclaw pairing approve signal <CODE>`.
</Step>
</Steps>
Minimal config:
```json5
{
channels: {
signal: {
enabled: true,
account: "+15551234567",
transport: {
kind: "managed-native",
cliPath: "signal-cli",
},
dmPolicy: "pairing",
allowFrom: ["+15557654321"],
},
},
}
```
| Field | Description |
| ----------- | ------------------------------------------------- |
| `account` | Bot phone number in E.164 format (`+15551234567`) |
| `transport` | Account-owned Signal connection and process mode |
| `dmPolicy` | DM access policy (`pairing` recommended) |
| `allowFrom` | Phone numbers or `uuid:<id>` values allowed to DM |
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. Each named account owns its `transport`; it does not inherit the top-level transport. The top-level transport belongs only to the implicit `default` account. See [Multi-account channels](/gateway/config-channels#multi-account-all-channels) for the shared pattern.
Account keys use the normalized IDs shown by status. For example, `Work Phone` with its own `account` number runs as `work-phone` and uses its authored settings without running Doctor. If multiple keys normalize to the same ID, the exact key wins and Doctor reports the collision. Deletion refuses to remove that account if another stored key would then select a different identity; the error names both keys so you can resolve the collision first. Legacy aliases without their own number keep their existing inherited behavior. Doctor can clean up unambiguous keys, but refuses to rename an alias when that would activate previously ignored settings.
Omitted account `dmPolicy` and `groupPolicy` inherit the channel root; explicit account policies win. If neither scope sets them, DMs use `pairing` and groups use `allowlist`.
## What it is
- Deterministic routing: replies always go back to Signal.
- DMs share the agent's main session; with default `session.groupScope: "per-group"`, groups are isolated (`agent:<agentId>:signal:group:<groupId>`).
- By default, Signal may write config updates triggered by `/config set|unset` (requires `commands.config: true`). Disable with `channels.signal.configWrites: false`.
## Setup path A: link existing Signal account (QR)
1. Install `signal-cli` (JVM or native build), or let `openclaw channels add` install it for you.
2. Link a bot account: `signal-cli link -n "OpenClaw"`, then scan the QR in Signal.
3. Configure Signal and start the gateway.
## Setup path B: register dedicated bot number (SMS, Linux)
Use this for a dedicated bot number instead of linking an existing Signal app account. The flow below is tested on Ubuntu 24.
1. Get a number that can receive SMS (or voice verification for landlines). A dedicated bot number avoids account/session conflicts.
2. Install `signal-cli` on the gateway host:
```bash
VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/AsamK/signal-cli/releases/latest | sed -e 's/^.*\/v//')
curl -L -O "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-native.tar.gz"
sudo tar xf "signal-cli-${VERSION}-Linux-native.tar.gz" -C /opt
sudo ln -sf /opt/signal-cli /usr/local/bin/
signal-cli --version
```
If you use the JVM build (`signal-cli-${VERSION}.tar.gz`), install a JRE first. Keep `signal-cli` updated; upstream notes old releases can break as Signal server APIs change.
3. Register and verify the number:
```bash
signal-cli -a +<BOT_PHONE_NUMBER> register
```
Still inside step 3, if captcha is required (browser access is needed to complete
this step):
- Open `https://signalcaptchas.org/registration/generate.html`.
- Complete the captcha, copy the `signalcaptcha://...` link target from "Open Signal".
- Run from the same external IP as the browser session when possible (captcha tokens expire quickly).
- Register and verify immediately:
```bash
signal-cli -a +<BOT_PHONE_NUMBER> register --captcha '<SIGNALCAPTCHA_URL>'
signal-cli -a +<BOT_PHONE_NUMBER> verify <VERIFICATION_CODE>
```
4. Configure OpenClaw and verify the channel. Config changes follow [hot reload](/gateway/configuration/hot-reload); start the Gateway if it is offline. Restart it if you changed the service's `PATH` to find `signal-cli`.
```bash
openclaw doctor
openclaw channels status --probe
```
5. Pair your DM sender:
- Send any message to the bot number.
- Approve on the server: `openclaw pairing approve signal <PAIRING_CODE>`.
- Save the bot number as a contact on your phone to avoid "Unknown contact".
<Warning>
Registering a phone number account with `signal-cli` can de-authenticate the main Signal app session for that number. Prefer a dedicated bot number, or use QR link mode to keep your existing phone app setup.
</Warning>
Upstream references:
- `signal-cli` README: `https://github.com/AsamK/signal-cli`
- Captcha flow: `https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha`
- Linking flow: `https://github.com/AsamK/signal-cli/wiki/Linking-other-devices-(Provisioning)`
## External native daemon mode
To manage `signal-cli` yourself (slow JVM cold starts, container init, shared CPUs), run the daemon separately and point OpenClaw at it:
For non-interactive setup, select the endpoint kind explicitly when needed:
```bash
openclaw channels add --channel signal --signal-number +15551234567 \
--http-url http://127.0.0.1:8080 --signal-transport external-native
```
```json5
{
channels: {
signal: {
transport: {
kind: "external-native",
url: "http://127.0.0.1:8080",
},
},
},
}
```
This skips auto-spawn and OpenClaw's startup wait. For a managed daemon with a slow start, set `channels.signal.transport.startupTimeoutMs`.
## Container mode (bbernhard/signal-cli-rest-api)
Instead of running `signal-cli` natively, use the [bbernhard/signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api) Docker container, which wraps `signal-cli` behind a REST + WebSocket interface.
```bash
openclaw channels add --channel signal --signal-number +15551234567 \
--http-url http://signal-cli:8080 --signal-transport container
```
Requirements:
- The container **must** run with `MODE=json-rpc` for real-time message receiving.
- Register or link your Signal account inside the container before connecting OpenClaw.
Example `docker-compose.yml` service:
```yaml
signal-cli:
image: bbernhard/signal-cli-rest-api:latest
environment:
MODE: json-rpc
ports:
- "8080:8080"
volumes:
- signal-cli-data:/home/.local/share/signal-cli
```
OpenClaw config:
```json5
{
channels: {
signal: {
enabled: true,
account: "+15551234567",
transport: {
kind: "container",
url: "http://signal-cli:8080",
},
},
},
}
```
`transport.kind` controls which protocol and process lifecycle OpenClaw uses:
| Value | Behavior |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `"managed-native"` | Start native signal-cli and use JSON-RPC at `/api/v1/rpc` plus SSE at `/api/v1/events`; `url` may select a connection endpoint distinct from the daemon bind |
| `"external-native"` | Connect to an already-running native signal-cli daemon |
| `"container"` | Connect to bbernhard REST at `/v2/send` and WebSocket at `/v1/receive/{account}` |
Setup and `openclaw doctor --fix` may probe an existing endpoint once to identify its concrete kind. Runtime operations do not auto-detect or switch protocols.
Container mode supports the same Signal operations as native mode where the container exposes matching APIs: sends, receives, attachments, typing indicators, read/viewed receipts, reactions, groups, and styled text. OpenClaw translates native Signal RPC calls into the container's REST payloads, including `group.{base64(internal_id)}` group IDs and `text_mode: "styled"` for formatted text.
Operational notes:
- Use `MODE=json-rpc` for receiving. `MODE=normal` can make `/v1/about` look healthy, but `/v1/receive/{account}` will not WebSocket-upgrade, so container receive streaming will fail its probe.
- Set `kind: "container"` for the bbernhard REST API and `kind: "external-native"` for native `signal-cli` JSON-RPC/SSE.
- Container attachment downloads honor the same media byte limits as native mode. Oversized responses are rejected before being fully buffered when the server sends `Content-Length`, and while streaming otherwise.
## Opt-in private UNIX socket
On POSIX systems, a managed native daemon can use a UNIX socket instead of an
HTTP listener. This is useful on shared hosts where other OS users must not
control the Signal daemon. Configure `transport.socketPath` with an absolute
path (at most 103 UTF-8 bytes, without `.` or `..` path segments):
```json5
{
channels: {
signal: {
enabled: true,
account: "+15555550123",
transport: {
kind: "managed-native",
socketPath: "/home/user/.local/state/signal-private/daemon.sock",
},
},
},
}
```
The socket's immediate directory must belong to the Gateway OS user and have
mode `0700` (no group or other access). OpenClaw creates that directory if it
is missing and its parent already exists; it does not repair permissions on
existing directories. Symlink paths are rejected. On macOS, OpenClaw also
inspects ACLs beyond the BSD mode bits and rejects access-granting ACL entries
for non-owners, including inheritable entries. On Linux, POSIX ACLs are not
inspected; operators should select a parent hierarchy with no access-granting
ACLs. Use a distinct socket path for each account. Windows is not supported for
this opt-in.
`socketPath` is only valid with `kind: "managed-native"` and cannot be combined
with `url`, `httpHost`, or `httpPort`. Omit `receiveMode` or set it to `"manual"`;
OpenClaw manages receive subscriptions over the socket. Socket connection or
permission failures stop this transport; there is no HTTP fallback.
Existing managed HTTP defaults, external native daemons, and container setups
are unchanged. **Opting out leaves the existing HTTP exposure unchanged:** a
loopback bind does not prevent another local OS user from reaching an
unauthenticated daemon. The private directory separates OS users, not processes
running as the same user or administrators. `signal-cli` does not authorize a
connecting peer's UID, so this isolation guarantee depends on filesystem
authorization. Signal sender pairing and allowlists remain separate
message-access controls.
## Access control (DMs + groups)
DMs:
- Default: `channels.signal.dmPolicy = "pairing"`.
- Unknown senders get a pairing code; messages are ignored until approved (codes expire after 1 hour).
- Approve via `openclaw pairing list signal` and `openclaw pairing approve signal <CODE>`.
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/channels/pairing)
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `channels.signal.allowFrom`.
Groups:
- `channels.signal.groupPolicy = open | allowlist | disabled`.
- `channels.signal.groupAllowFrom` controls which groups or senders can trigger group replies when `allowlist` is set; entries can be Signal group IDs (raw, `group:<id>`, or `signal:group:<id>`), sender phone numbers, `uuid:<id>` values, or `*`.
- `channels.signal.groups["<group-id>" | "*"]` can override group behavior with `requireMention`, `tools`, and `toolsBySender`.
- Use `channels.signal.accounts.<id>.groups` for per-account overrides in multi-account setups.
- Allowlisting a Signal group through `groupAllowFrom` does not disable mention gating by itself. A specifically configured `channels.signal.groups["<group-id>"]` entry processes every group message unless `requireMention=true` is set.
- With `requireMention=true`, Signal native @mentions are matched from structured mention metadata against the bot account phone or `accountUuid`. Plain-text matching uses `agents.entries.*.groupChat.mentionPatterns`, then `messages.groupChat.mentionPatterns`; when neither is set, it derives patterns from the routed agent's `identity.name` and `identity.emoji`. An explicit `mentionPatterns: []` at the selected level disables this text fallback without disabling native @mentions.
- Runtime note: if `channels.signal` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
Mention-gated group with bounded context:
```json5
{
channels: {
signal: {
account: "+15551234567",
accountUuid: "bot-signal-uuid",
groupPolicy: "allowlist",
groupAllowFrom: ["group:<signal-group-id>"],
historyLimit: 8,
groups: {
"<signal-group-id>": { requireMention: true },
},
},
},
messages: {
groupChat: {
mentionPatterns: ["\\bopenclaw\\b"],
},
},
}
```
Allowed group messages that do not mention the bot stay silent and are kept only in the bounded pending history window. When a later native @mention or fallback text mention triggers the bot, OpenClaw includes that recent context and replies to the same group. Skipped attachment bodies are not downloaded; they may appear only as compact media placeholders in the pending context.
## How it works (behavior)
- Native mode: `signal-cli` runs as a daemon; the gateway reads events via SSE.
- Container mode: the gateway sends via REST API and receives via WebSocket.
- Inbound messages are normalized into the shared channel envelope.
- Replies always route back to the same number or group.
- Replies to inbound messages include native Signal quote metadata when the backend accepts the inbound timestamp and author; if quote metadata is missing or rejected, OpenClaw sends the reply as a normal message.
- Canceling the originating delivery stops subsequent send attempts, including attachments still being prepared and native-quote fallback. A request already submitted to Signal is allowed to finish, and its accepted result is preserved; cancellation does not recall that message.
- Configure native quote use with `channels.signal.replyToMode = off | first | all | batched`, or `channels.signal.replyToModeByChatType.direct/group` for per-chat-type overrides. Account-level values under `channels.signal.accounts.<id>` take precedence.
## Media + limits
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
- Optional newline chunking: set `channels.signal.streaming.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
- Attachments are supported (base64 fetched from `signal-cli`).
- Voice-note attachments use the `signal-cli` filename as a MIME fallback when `contentType` is missing, so audio transcription can still classify AAC voice memos.
- Default media cap: `channels.signal.mediaMaxMb` (default 8).
- Use `channels.signal.ignoreAttachments` to skip downloading media for any transport.
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
## Typing + read receipts
- **Typing indicators**: OpenClaw sends typing signals via `signal-cli sendTyping` and refreshes them while a reply is running.
- **Read receipts**: when `channels.signal.sendReadReceipts` is true, OpenClaw forwards read receipts for allowed DMs.
- `signal-cli` does not expose read receipts for groups.
## Lifecycle status reactions
Set `messages.statusReactions.enabled: true` to let Signal show the shared queued/thinking/tool/compaction/done/error reaction lifecycle on inbound turns. Signal uses the inbound message timestamp as the reaction target; group reactions are sent with the Signal group ID plus the original sender as the target author.
Status reactions also require an ack reaction and a matching `messages.ackReactionScope` (`direct`, `group-all`, `group-mentions`, or `all`). Set `channels.signal.reactionLevel: "off"` to disable Signal status reactions.
Signal restores the initial ack reaction after the final done/error state.
## Reactions (message tool)
Use `message action=react` with `channel=signal`.
- Targets: sender E.164 or UUID (use `uuid:<id>` from pairing output; a bare UUID also works).
- `messageId` is the Signal timestamp for the message you're reacting to.
- Group reactions require `targetAuthor` or `targetAuthorUuid`.
```text
message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥
message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true
message action=react channel=signal target=signal:group:<groupId> targetAuthor=uuid:<sender-uuid> messageId=1737630212345 emoji=✅
```
Config:
- `channels.signal.actions.reactions`: enable/disable reaction actions (default true).
- `channels.signal.reactionLevel`: `off | ack | minimal | extensive` (default `minimal`).
- `off`/`ack` disables agent reactions (message tool `react` errors).
- `minimal`/`extensive` enables agent reactions and sets the guidance level.
- Per-account overrides: `channels.signal.accounts.<id>.actions.reactions`, `channels.signal.accounts.<id>.reactionLevel`.
## Approval reactions
Signal exec and plugin approval prompts use the top-level `approvals.exec` and `approvals.plugin` routing blocks. Signal has no `channels.signal.execApprovals` block.
- `👍` approves once.
- `👎` denies.
- Use `/approve <id> allow-always` when a request offers persistent approval.
Approval reaction resolution requires explicit Signal approvers from `channels.signal.allowFrom`, `channels.signal.defaultTo`, or the matching account-level fields. Direct same-chat exec approval prompts can still suppress the duplicate local `/approve` fallback without explicit approvers; no-approver group approvals keep the local fallback visible.
## Question reactions
For an `ask_user` prompt with one non-secret, single-select question and one to four options, Signal shows `1️⃣` through `4️⃣` beside the option labels. React to the delivered prompt with the matching number to answer it. OpenClaw verifies the reaction targets the bot-authored message, then maps the number to the canonical option through the Gateway. Stale or duplicate taps are ignored. Multi-question, multi-select, and free-text prompts remain text-reply-only; normal Signal DM/group admission rules authorize the sender.
## Delivery targets (CLI/cron)
- DMs: `signal:+15551234567` (or plain E.164).
- UUID DMs: `uuid:<id>` (or bare UUID).
- Groups: `signal:group:<groupId>`.
- Usernames: `username:<name>` (if supported by your Signal account).
## Aliases
Configure aliases for stable names on recurring Signal targets. Aliases are OpenClaw-side config only; they do not create or edit Signal contacts.
```json5
{
channels: {
signal: {
aliases: {
me: "+15557654321",
jane: "uuid:123e4567-e89b-12d3-a456-426614174000",
ops: "group:<groupId>",
},
defaultTo: "signal:me",
},
},
}
```
Use aliases anywhere Signal delivery targets are accepted:
```bash
openclaw message send --channel signal --target signal:ops --message "Deployment is complete"
```
Per-account aliases inherit the top-level aliases and can add or override names:
```json5
{
channels: {
signal: {
aliases: {
me: "+15557654321",
},
accounts: {
work: {
aliases: {
ops: "group:<workGroupId>",
},
},
},
},
},
}
```
`openclaw directory peers list --channel signal` and `openclaw directory groups list --channel signal` list configured aliases. The Signal directory is config-backed; it does not live-query Signal contacts or mutate the Signal account.
## Troubleshooting
Run this ladder first:
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
Then confirm DM pairing state if needed:
```bash
openclaw pairing list signal
```
Common failures:
- Daemon reachable but no replies: verify `account`, `transport.kind`, the transport URL, and receive mode.
- DMs ignored: sender is pending pairing approval.
- Group messages ignored: group sender/mention gating blocks delivery.
- Config validation errors after edits: run `openclaw doctor --fix`.
- Signal missing from diagnostics: confirm `channels.signal.enabled: true`.
Extra checks:
```bash
openclaw pairing list signal
pgrep -af signal-cli
openclaw logs --plain --limit 500 | grep -i "signal" | tail -20
```
For triage flow: [Channels Troubleshooting](/channels/troubleshooting).
## Security notes
- `signal-cli` stores account keys locally (typically `~/.local/share/signal-cli/data/`).
- Back up Signal account state before server migration or rebuild.
- Keep `channels.signal.dmPolicy: "pairing"` unless you explicitly want broader DM access.
- SMS verification is only needed for registration or recovery flows, but losing control of the number/account can complicate re-registration.
## Configuration reference (Signal)
Full configuration: [Configuration](/gateway/configuration)
Provider options:
- `channels.signal.enabled`: enable/disable channel startup.
- `channels.signal.account`: E.164 for the bot account.
- `channels.signal.accountUuid`: optional bot account UUID for native @mention detection and loop protection.
- `channels.signal.transport`: account-owned transport. Omit it for managed native defaults.
- `channels.signal.transport.kind`: `managed-native | external-native | container`.
- `channels.signal.transport.url`: required for `external-native` and `container`; optional for `managed-native` when its connection endpoint differs from the daemon bind.
- `channels.signal.transport.cliPath`: managed-native path to `signal-cli`.
- `channels.signal.transport.configPath`: optional managed-native `signal-cli --config` directory.
- `channels.signal.transport.httpHost`, `channels.signal.transport.httpPort`: managed-native daemon bind (default `127.0.0.1:8080`).
- `channels.signal.transport.startupTimeoutMs`: managed-native startup wait in ms (min 1000, cap 120000; default 30000).
- `channels.signal.transport.receiveMode`: managed-native `on-start | manual`.
- `channels.signal.ignoreAttachments`: skip inbound attachment downloads for this account.
- `channels.signal.transport.ignoreStories`: managed-native story toggle.
- `channels.signal.sendReadReceipts`: forward read receipts.
- `channels.signal.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
- `channels.signal.allowFrom`: DM allowlist (E.164 or `uuid:<id>`). `open` requires `"*"`. Signal has no usernames; use phone/UUID IDs.
- `channels.signal.aliases`: OpenClaw-side aliases for DM or group delivery targets.
- `channels.signal.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
- `channels.signal.groupAllowFrom`: group allowlist; accepts Signal group IDs (raw, `group:<id>`, or `signal:group:<id>`), sender E.164 numbers, or `uuid:<id>` values.
- `channels.signal.groups`: per-group overrides keyed by Signal group ID (or `"*"`). Supported fields: `requireMention`, `tools`, `toolsBySender`.
- `channels.signal.accounts.<id>.groups`: per-account version of `channels.signal.groups` for multi-account setups.
- `channels.signal.accounts.<id>.aliases`: per-account aliases, merged with top-level aliases.
- `channels.signal.replyToMode`: native reply quote mode, `off | first | all | batched` (default: `all`).
- `channels.signal.replyToModeByChatType.direct`, `channels.signal.replyToModeByChatType.group`: per-chat-type native reply quote overrides.
- `channels.signal.accounts.<id>.replyToMode`, `channels.signal.accounts.<id>.replyToModeByChatType.direct`, `channels.signal.accounts.<id>.replyToModeByChatType.group`: per-account reply quote overrides.
- `channels.signal.historyLimit`: max group messages to include as context (0 disables).
- `channels.signal.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.signal.dms["<phone_or_uuid>"].historyLimit`.
- `channels.signal.textChunkLimit`: outbound chunk size in characters (default 4000).
- `channels.signal.markdown.tables`: Markdown table rendering mode, `off | bullets | code` (default `bullets`); `block` falls back to `code` (Signal has no native block tables).
- `channels.signal.streaming.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
- `channels.signal.mediaMaxMb`: inbound/outbound media cap in MB (default 8).
- `channels.signal.reactionLevel`: `off | ack | minimal | extensive` (default `minimal`). See [Reactions](#reactions-message-tool).
- `channels.signal.reactionNotifications`: `off | own | all | allowlist` (default `own`) - when the agent is notified of incoming reactions from others.
- `channels.signal.reactionAllowlist`: senders whose reactions notify the agent when `reactionNotifications: "allowlist"`.
- `channels.signal.streaming.block.enabled`, `channels.signal.streaming.block.coalesce`: block-mode streaming controls shared across channels. See [Streaming](/concepts/streaming).
Related global options:
- `agents.entries.*.groupChat.mentionPatterns` (plain-text fallback; Signal native @mentions are detected from structured metadata when the bot account identity is configured).
- `messages.groupChat.mentionPatterns` (global fallback).
- `channels.signal.responsePrefix` or an account-level `responsePrefix`.
## Related
- [Channels Overview](/channels) - all supported channels
- [Pairing](/channels/pairing) - DM authentication and pairing flow
- [Groups](/channels/groups) - group chat behavior and mention gating
- [Channel routing](/channels/channel-routing) - session routing for messages
- [Reactions](/tools/reactions) - emoji reaction semantics for the `message` tool
- [RPC adapters](/reference/rpc) - the signal-cli JSON-RPC-over-HTTP daemon pattern behind this channel
- [Security](/gateway/security) - access model and hardening