jigar-dhulla / laravel-whatsapp-ai-agent
A Laravel package for building WhatsApp AI agents on top of wacli.
Package info
github.com/jigar-dhulla/laravel-whatsapp-ai-agent
pkg:composer/jigar-dhulla/laravel-whatsapp-ai-agent
Requires
- php: ^8.3
- laravel/ai: ^0.6
- laravel/framework: ^13.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^11.0
- phpunit/phpunit: ^12.0
This package is auto-updated.
Last update: 2026-07-09 04:36:34 UTC
README
An extension to the Laravel AI SDK that adds WhatsApp as an agent interface. It polls a wacli SQLite database for new messages, routes them through your configured agents (any class that implements Laravel\Ai\Contracts\Agent), and sends replies back via the wacli binary.
Intro & Demo
Watch the intro and demo on YouTube: https://www.youtube.com/watch?v=XNAz-Ry2-co
Requirements
- PHP 8.3+
- Laravel 13.0+
laravel/ai^0.6wacli0.8.1+ installed and authenticated on the host
Installation
composer require jigar-dhulla/laravel-whatsapp-ai-agent
The package registers itself automatically via Laravel's package auto-discovery.
Setup
Run the setup command to detect your wacli binary and write paths to .env:
php artisan wa:setup
This will:
- Locate your wacli binary
- Run
wacli doctorto detect your store directory - Prompt for the wacli SQLite database path
- Write
WA_WACLI_BINARY,WA_WACLI_STORE, andWA_WACLI_DATABASEto.env
Then publish the config to customize your agents:
php artisan vendor:publish --tag=whatsapp-agent-config
This publishes config/whatsapp-agent.php, which includes a default WhatsAppAgent ready to use. Edit the config to add your own agents or customize scopes and triggers.
Configuration
config/whatsapp-agent.php
return [ 'wacli' => [ 'binary' => env('WA_WACLI_BINARY', 'wacli'), 'database' => env('WA_WACLI_DATABASE'), 'store' => env('WA_WACLI_STORE'), ], /* | One entry per agent. The agent key must be a class that implements | Laravel\Ai\Contracts\Agent (use the Promptable trait for the full SDK | feature set). Provider and model are optional runtime overrides; if null, | the agent class resolves them via its own #[Provider]/#[Model] attributes | or config/ai.php defaults. | | - triggers: [] matches every message in this agent's scope | - chats/groups: at least one entry total — empty scope = inactive agent | - mention_sender: tag the sender in group replies so they get notified */ 'agents' => [ [ 'agent' => \JigarDhulla\LaravelWhatsApp\Agents\WhatsAppAgent::class, 'triggers' => [], 'chats' => [], 'groups' => [], 'mention_sender' => false, ], ], 'polling' => [ 'interval_seconds' => (int) env('WA_POLLING_INTERVAL', 1), ], ];
AI provider configuration (API keys, default models) belongs in config/ai.php, managed by laravel/ai — not in this package.
Environment Variables
| Variable | Default | Description |
|---|---|---|
WA_WACLI_BINARY |
wacli |
Path to the wacli binary (0.6.0+ required) |
WA_WACLI_DATABASE |
— | Path to the wacli SQLite database (set by wa:setup) |
WA_WACLI_STORE |
— | Path to the wacli store directory (set by wa:setup) |
WA_POLLING_INTERVAL |
1 |
Seconds between database polls |
WA_HISTORY_LIMIT |
100 |
Max past messages included in agent context |
Creating Custom Agents
Generate a new agent with make:agent:
php artisan make:agent CustomAgent
This creates app/Ai/Agents/CustomAgent.php. Then add it to your published config/whatsapp-agent.php:
'agents' => [ [ 'agent' => \App\Ai\Agents\CustomAgent::class, 'triggers' => ['@custom'], 'chats' => ['111@s.whatsapp.net'], 'groups' => [], ], ],
You can keep the default WhatsAppAgent in the config alongside your custom agents, or remove it entirely.
Conversation memory
Add the RemembersWhatsAppConversations trait to your agent to give it access to the full message history from the wacli SQLite database. The trait implements the Laravel AI SDK's conversation interface, so previous messages in the chat are automatically injected as context on every prompt:
use JigarDhulla\LaravelWhatsApp\Traits\RemembersWhatsAppConversations; use Laravel\Ai\Contracts\Agent; use Laravel\Ai\Contracts\Conversational; use Laravel\Ai\Promptable; class CustomAgent implements Agent, Conversational { use Promptable; use RemembersWhatsAppConversations; }
The number of historical messages included is controlled by WA_HISTORY_LIMIT (default 100). Override maxConversationMessages() in your agent class to set a per-agent limit.
Both direct messages and group chats are supported. In groups, the agent automatically distinguishes participants so it can address each person individually.
Mentioning the sender in group replies
Set mention_sender => true on an agent's config entry to have its group replies tag the person it is responding to, using WhatsApp's mention mechanism (wacli's --mention flag). The reply is prefixed with the sender's @<number> token — rendered by WhatsApp as a tappable @Name — so they are notified even when they have muted the group:
'agents' => [ [ 'agent' => \App\Ai\Agents\SupportAgent::class, 'triggers' => ['@support'], 'chats' => [], 'groups' => ['group1@g.us'], 'mention_sender' => true, ], ],
The option has no effect in direct messages, where the recipient is already the sender. If the agent's reply already contains the sender's @<number> token, no prefix is added — the existing token is used for the mention.
Multiple Agents
One message can match multiple agents simultaneously. Each match dispatches an independent queued job, so agents process in parallel:
// config/whatsapp-agent.php 'agents' => [ [ 'agent' => \App\Ai\Agents\SupportAgent::class, 'triggers' => ['@support'], 'chats' => ['111@s.whatsapp.net'], 'groups' => ['group1@g.us'], ], [ 'agent' => \App\Ai\Agents\SalesAgent::class, 'triggers' => ['@sales'], 'chats' => ['111@s.whatsapp.net'], 'groups' => [], ], [ 'agent' => \App\Ai\Agents\BroadcastAgent::class, 'triggers' => [], // empty = matches all messages in scope 'chats' => [], 'groups' => ['announcements@g.us'], ], ],
Routing rules:
- A message matches an agent when its chat/group JID is in the agent's
chatsorgroupslist and the body contains at least one trigger phrase (case-insensitive). - An empty
triggersarray matches every message in scope. - An agent with empty
chatsand emptygroupsis inactive and never fires.
Usage
Start the wacli sync daemon
wacli sync --follow --refresh-contacts --refresh-groups
You may use a 3rd party tool to make sure this keeps running.
Start the listener
php artisan wa:listen
Runs an infinite polling loop. Each iteration syncs new wacli messages, routes them via AgentRouter, and dispatches one ProcessWhatsAppMessage job per matched agent.
php artisan wa:listen --once # single iteration, then exit php artisan wa:listen --max-iterations=10 php artisan wa:listen -vv # show startup config summary php artisan wa:listen -vvv # show each message scanned
Check agent status
php artisan wa:status
Shows wacli auth/connection state and a summary of every configured agent — class name, provider/model, triggers, and scope.
Queue worker
Each matched message dispatches a ProcessWhatsAppMessage job. Run a queue worker alongside the listener:
php artisan queue:work
How It Works
wacli sync → SQLite DB → WhatsAppMessageReader → AgentRouter
↓ (one job per match)
ProcessWhatsAppMessage (queued)
↓
$agent->prompt($body)
↓
wacli send text
wa:listenruns as daemon.WhatsAppMessageReaderfetches rows newer than the last processed rowid, filtered to the union of all agents' JIDs.AgentRouter::match($chatJid, $body)returns every agent config entry whose scope contains the chat and whose triggers match the message.- One
ProcessWhatsAppMessagejob is dispatched per matched entry. - The job resolves the agent class from the container, calls
$agent->prompt($body), and sends the reply viawacli send text.
Testing
composer install vendor/bin/phpunit vendor/bin/pint
License
MIT — see LICENSE.