Skip to content

About

LLM agent in your WhatsApp self-chat — drafts, sends, and tidies up in your own voice

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

whatsapp-self-chat-agent

Forward messages to your WhatsApp Message Yourself chat, discuss them with an LLM, and let it send replies from your own number — wrapped in a ban-safety envelope (reply-only, humanized pacing, circuit breaker, daily cap).

Status: runs as a single always-on worker (Docker container or plain Node process). No inbound ports — the only HTTP listener is a loopback webhook.

How it works

WhatsApp phone ──forward──▶ "Message Yourself" chat
                                   │  wacli sync --follow --webhook (loopback)
                                   ▼
                              bot.js (127.0.0.1 only)
                                   │  Azure OpenAI chat + tools
                                   ▼
              reply in self-chat / send to any chat via wacli send
  • bot.js spawns wacli sync --follow with a localhost webhook and supervises it (restart with backoff, store-lock discipline).
  • Only messages in your self-chat are processed. Bot replies are prefixed 🤖 so they never loop back into the model.
  • The model gets four tools: send_message, search_contacts, list_chats, read_messages. It only sends when you explicitly tell it to.
  • Messages arriving within DEBOUNCE_MS are batched into one turn, so multi-message forwards are read together.
  • Forward-origin derivation: WhatsApp strips the source from forwards, but the local store has full history — the bot full-text-searches it to recover which chat a forward came from, so "reply to that" needs no follow-up question.
  • A style primer sampled from your own sent messages keeps drafts in your voice.
  • Conversation memory persists in state.json (/reset in the chat clears it).

Second channel: a dedicated bot number

The bot can also run on its own WhatsApp number, so you DM it like a normal contact instead of using the self-chat. Both channels work in parallel.

  1. Get a real (non-VoIP) number and register it on WhatsApp (WhatsApp Business on your existing phone works — it runs alongside regular WhatsApp).
  2. Link it to wacli's second store: wacli --store "$BOT_STORE" auth
  3. Nothing to restart — the running bot checks the store every minute, auto-starts the channel, and messages you when live.

Only your JIDs (SELF_JID / SELF_LID) can talk to the bot number; everyone else is ignored and logged.

Quickstart

git clone https://github.com/BaseLayerAI/whatsapp-self-chat-agent
cd whatsapp-self-chat-agent
cp .env.example .env      # fill in Azure creds + your JIDs
wacli auth                # scan the QR from WhatsApp (Settings → Linked devices)
./run.sh                  # starts webhook server + sync + bot

Requires Node 22+ and the wacli binary on PATH.

Usage (in the Message Yourself chat)

  • Forward any message(s), then: "draft a polite decline", "summarize", "translate".
  • "Send that to Alex" / "reply to mom: on my way" → sends from your number.
  • /reset clears memory, /status shows health + send safety, /help shows help.
  • Tidy commands (delete-for-you — vanish from your phone, the server store keeps a copy): /clearall, /clearlast N, /undo, /cleannow.

Self-chat auto-cleanup

Once the bot completes an action for a thread (e.g. sends a message on your behalf) and isn't waiting on you, that exchange is deleted-for-you after the chat has been idle 15 min. Pure discussions and open questions are never auto-deleted. CLEANUP_SELF_CHAT=0 disables; CLEANUP_IDLE_MS tunes the window.

Unanswered-message nudges

Hourly sweep for direct chats where the last message is inbound and older than 24h; the bot pings you with a drafted reply and asks whether to send it. Muted/archived chats, groups, and one-way notification numbers are skipped. Capped per sweep and per day.

Ban-safety design

Unofficial WhatsApp automation carries account-restriction risk. The research behind these choices is in docs/ban-research.md; the short version is that recipient consent and account behavior dominate, so the design is conservative by construction:

  • Reply-only. Never initiates to non-contacts; no automated hello on link. Only whitelisted owner JIDs get replies.
  • Companion-device linking (QR) with a real SIM, passive sync — the lowest-risk integration path.
  • Humanized pacing: all outbound serialized, 5–8 s randomized gap, typing indicator sized to the message for bot-number/third-party sends.
  • Circuit breaker: any send error that looks like WhatsApp pushback (rate limit / restricted / forbidden / spam) pauses all sending for 1 h; 5 consecutive failures of any kind pause 10 min.
  • Daily cap of 200 sends as a runaway-loop backstop.

Check the live safety state any time with /status.

Environment variables

Variable Required Description
AZURE_OPENAI_ENDPOINT yes Azure OpenAI resource endpoint
AZURE_OPENAI_KEY yes Azure OpenAI API key
AZURE_OPENAI_DEPLOYMENT yes Chat model deployment name
SELF_JID yes Your phone JID ([email protected])
SELF_LID no Your account's LID (alternate identifier)
OWNER_NAME no How the model refers to you (default the owner)
SELF_STORE no wacli store for the personal account (default ~/.wacli)
BOT_STORE no wacli store for the bot account (default ~/.wacli-bot)
BOT_NUMBER no Bot account's phone number (confirmed from the store at startup)
WEBHOOK_PORT no Loopback webhook port (default 8790)
DEBOUNCE_MS no Message batching window (default 3000)
STATE_DIR / LOG_DIR no Where state.json and logs live (server: a volume)
CLEANUP_SELF_CHAT / CLEANUP_IDLE_MS / CLEANUP_SWEEP_MS no Auto-cleanup knobs
NUDGE_* no Unanswered-message nudge knobs (see .env.example)

Deployment

Docker / Railway (or any container host)

The Dockerfile builds a self-contained image (Node base + wacli release binary). Mount a persistent volume and point the stores at it so linked-device sessions survive redeploys:

SELF_STORE=/data/wacli
BOT_STORE=/data/wacli-bot
STATE_DIR=/data

railway.toml is included: Dockerfile build, always-restart, one replica. The worker needs no public ingress. To authenticate on a server, run wacli auth against the volume-backed store (or link locally and copy the store to the volume once).

A linked-device session must only run in one place — running a second wacli sync against a copy of the same store connects the session twice and can get the account flagged.

launchd (macOS, run at login)

cat > ~/Library/LaunchAgents/com.baselayer.whatsapp-agent.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.baselayer.whatsapp-agent</string>
  <key>ProgramArguments</key><array>
    <string>/bin/bash</string><string>$HOME/whatsapp-self-chat-agent/run.sh</string>
  </array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>EnvironmentVariables</key><dict>
    <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
  </dict>
  <key>StandardOutPath</key><string>$HOME/whatsapp-self-chat-agent/logs/launchd.log</string>
  <key>StandardErrorPath</key><string>$HOME/whatsapp-self-chat-agent/logs/launchd.log</string>
</dict></plist>
EOF
launchctl load ~/Library/LaunchAgents/com.baselayer.whatsapp-agent.plist

Operational details (credentials, failure signatures, recovery) are in docs/RUNBOOK.md.

Notes

  • WhatsApp unlinks companion devices after long inactivity; if sends fail with "not authenticated", run wacli auth again.
  • Logs: logs/bot.log (bot), logs/sync-self.log / logs/sync-bot.log (wacli sync).

About

LLM agent in your WhatsApp self-chat — drafts, sends, and tidies up in your own voice

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages