?

Pick your first transport

Every transport is a thin adapter behind the same transport.Transport interface — allowlisting, session routing, and cron delivery are identical across all of them. The differences are pairing UX and the per-transport identifier format (JID, user ID, room ID). Pick whichever you can pair fastest:

WhatsApp is the reference — hardest to pair, easiest to test (you already have the app on your phone).

Prerequisites: your phone with WhatsApp, your E.164 JID (e.g. 447900123456@s.whatsapp.net).

rousseau whatsapp --allow 447900123456@s.whatsapp.net

Scan the QR from WhatsApp > Settings > Linked devices > Link a device. Send hello to yourself; rousseau replies via WhatsApp. See below for the full walkthrough.

Why the WhatsApp walkthrough

The rest of this page uses WhatsApp as the canonical example — if you get the pattern here, every other transport is a variation on it (allowlist a stable ID, run a pairing UX once, send a test, verify the reply). Skip to the sibling transport page if you already have a token in hand:

  • Slack — Socket Mode tokens and event subscriptions.
  • Discord — bot token, intents, permission integers.
  • Telegram — BotFather token.
  • Signal — signal-cli subprocess.
  • Matrix — homeserver URL + access token.

Prerequisites

  • rousseau on $PATH (see Installation).
  • A working provider — claudecli inheriting Claude Code auth is the default; anything else needs its config filled in first (Configuration).
  • Your phone with WhatsApp installed. Your E.164 phone JID (e.g. 447900123456@s.whatsapp.net).

Step 1 — Choose the JID that will drive the daemon

Rousseau uses an allowlist to restrict inbound handling to a fixed set of JIDs. Every other sender is silently dropped. This is load-bearing: without an allowlist, anyone who knows the number could drive the agent.

Your E.164 JID is your phone number, digits only, followed by @s.whatsapp.net:

447900123456@s.whatsapp.net

Group JIDs end in @g.us; the daemon supports those too, but start with a personal JID.

Step 2 — First launch and pairing

rousseau whatsapp --allow 447900123456@s.whatsapp.net

On first launch, a QR code is printed to stdout. Open WhatsApp on your phone, go to Settings → Linked devices → Link a device, and scan the QR.

The daemon prints something like:

whatsapp.starting store=file:/home/you/.local/share/rousseau/whatsapp.db?_pragma=... allowlist=1

Once you scan, whatsmeow persists device credentials to whatsapp.db. Subsequent launches connect silently — no more QR.

Step 3 — Send a test message

From your phone, send hello to yourself. The daemon logs the inbound event, dispatches to the agent, and delivers the reply back through WhatsApp with the configured header:

💎 *Rousseau Agent*

Hello — what would you like to work on?

The reply header is configurable via whatsapp.reply_header. Set it to a single space to disable the prefix.

Step 4 — Set up a config.yaml so you don't need long flags

Create ~/.config/rousseau/config.yaml:

provider: claudecli

claudecli:
  binary: claude
  permission_mode: bypassPermissions

whatsapp:
  reply_header: "💎 *Rousseau Agent*\n\n"

Now rousseau whatsapp --allow 447900123456@s.whatsapp.net picks up the header automatically. Every transport reads its stanza from the same file — see Configuration for the full list.

bypassPermissions is the default for unattended daemons because there is no human on the other end of the terminal to approve tool calls interactively. Set up an approval policy (User Guide: Approval Policies) before you point the daemon at anything you care about.

Step 5 — Confirm end-to-end

Send a coding question from your phone:

Read the file at /workspace/README.md and summarise it in 3 bullets.

The daemon runs a read tool call, feeds the file to the model, and messages you back with the summary. You have just closed the loop:

  • Phone → WhatsApp → whatsmeow WebSocket
  • rousseau-agent → agent loop → tool call → provider call
  • reply → whatsmeow → WhatsApp → phone

Nothing crossed your network perimeter except the provider call — and if the provider was claudecli on your local Claude Code install, not even that.

Verifying with rousseau doctor

rousseau doctor

Every check for the WhatsApp path is covered:

  • provider.claudecli.binary, provider.claudecli.version — the LLM path.
  • state.path, state.db_size, state.sessions — SQLite session store.
  • whatsapp.store, whatsapp.paired — device credentials.
  • whatsapp.voice — voice-note transcription posture.

A fail row is a hard stop; a warn row is worth investigating before rolling out.

Troubleshooting

QR code prints but the phone rejects it

Three common causes. First, a partially-completed prior pairing left whatsapp.db in a state whatsmeow cannot reuse — delete ~/.local/share/rousseau/whatsapp.db and re-scan. Second, the clock is skewed by more than 30 seconds (common in containers without a working NTP client) — WhatsApp's handshake is time-sensitive. Third, an older whatsmeow version can miss a Meta protocol update; upgrade rousseau.

I sent a message but the daemon logs router.transport.rejected

Your JID does not match the allowlist. The value passed to --allow must be the sender's JID exactly as WhatsApp reports it (447900123456@s.whatsapp.net, no +, no spaces). Note that self-chat testing works because rousseau substitutes the account's own JID for the LID privacy hash (see internal/transport/whatsapp/resolve.go).

No QR code prints and the daemon exits with no rows

The whatsmeow store was never initialised. Ensure the parent directory (~/.local/share/rousseau/) exists and is writable. rousseau doctor reports this under whatsapp.store.

Rousseau replies but the model output is empty

Check provider.claudecli.binary and provider.claudecli.version in rousseau doctor. The most common empty-reply cause is a claudecli invocation returning is_error: true — the daemon logs the truncated error at warn level. Switch provider to anthropic or bedrock to isolate the subprocess.

Slack/Discord: "invalid_auth" or "401 Unauthorized"

For Slack, xapp-… (app token) and xoxb-… (bot token) are different — mixing them up produces invalid_auth. For Discord, the token displayed on Bot > Reset Token is one-shot; if you copied it once and lost it, you must reset again.

Related pages

Further reading

  • internal/transport/whatsapp/client.go — connect, QR, event pump.
  • internal/transport/whatsapp/resolve.go — LID/JID normalisation and self-chat handling.
  • internal/cli/whatsapp.go — CLI wiring, store DSN, transcriber selection.
  • internal/cli/slack.go, internal/cli/discord.go — sibling transport CLIs.
  • internal/transport/router.go — allowlist enforcement.

Type to search 150+ pages. Ranking: BM25 with title/description boost.