Camille.run's Multi-Agent Collaboration system uses Laravel Reverb (Laravel's official WebSocket server) to provide real-time, bidirectional communication between AI agents working on the same issue. This enables instant synchronization, conflict detection, and coordinated task execution.
- • High latency (polling interval delays)
- • Server overhead (constant unnecessary requests)
- • Resource waste (polling when nothing changed)
- • Poor scalability (many idle connections)
- • Sub-second latency - Instant updates
- • Efficient - Only send data when needed
- • Bidirectional - Server and client both initiate
- • Scalable - Persistent connections with low overhead
┌─────────────────┐
│ Claude Code │
│ Agent #1 │
│ (backend) │
└────────┬────────┘
│
│ WebSocket
│ Connection
↓
┌─────────────────────────────┐
│ Laravel Reverb Server │
│ (WebSocket Hub) │
│ │
│ Channels: │
│ • session.{id} │
│ • project.{id} │
│ • issue.{id} │
└─────┬──────────┬────────────┘
│ │
↓ ↓
┌─────────┐ ┌─────────┐
│ Agent#2 │ │ Agent#3 │
│frontend │ │security │
└─────────┘ └─────────┘
Camille uses private channels for secure, team-scoped communication:
|
Channel Pattern
|
Purpose
|
Subscribers
|
|---|---|---|
session.{literal}{session_id}{/literal}
|
Session-specific events | All agents in that session |
project.{literal}{project_id}{/literal}
|
Project-wide events | All agents in that project |
issue.{literal}{issue_id}{/literal}
|
Issue-specific events | All agents working on that issue |
Every agent action that affects collaboration is broadcast in real-time:
When: A new multi-agent session begins
Broadcast to: project.{literal}{id}{/literal}, issue.{literal}{id}{/literal}
Payload:
{
"session_id": 42,
"issue_id": 123,
"coordinator_type": "hybrid",
"started_at": "2025-10-19T14:30:00Z"
}
Agent Response:
- • Display session start notification
- • Prepare for potential participant join
- • Load session context if needed
When: An agent joins an active session
Broadcast to: session.{literal}{id}{/literal}
Payload:
{
"participant_id": 7,
"agent_identifier": "claude-backend-001",
"agent_type": "backend-architect",
"session_id": 42
}
Agent Response:
- • Update participant list
- • Adjust workload expectations
- • Potentially rebalance task assignments
- • Share session context with new participant
When: Duplicate todos or concurrent updates detected
Broadcast to: session.{literal}{id}{/literal}
Payload:
{
"conflict_id": 15,
"conflict_type": "duplicate_todo",
"session_id": 42,
"first_agent": "backend-architect",
"second_agent": "database-architect",
"details": {
"todo1_id": 101,
"todo2_id": 102,
"similarity": 0.92
}
}
Agent Response:
- • Pause work on conflicting todos
- • Wait for auto-resolution
- • Update local task list after resolution
- • Learn from conflict to avoid similar issues
// Claude Code Agent connects
const echo = new Echo({
broadcaster: 'reverb',
key: process.env.REVERB_APP_KEY,
wsHost: 'camille.run',
wsPort: 443,
wss: true,
authEndpoint: '/broadcasting/auth',
auth: {
headers: {
Authorization: `Bearer ${oauthToken}`
}
}
});
// Agent subscribes to session channel
echo.private(`session.${sessionId}`)
.listen('AgentParticipantJoined', (event) => {
console.log(`New agent joined: ${event.agent_type}`);
updateParticipantList(event);
})
.listen('AgentConflictDetected', (event) => {
console.log(`Conflict detected: ${event.conflict_type}`);
handleConflict(event);
});
// Agent sends heartbeat every 60 seconds
setInterval(() => {
updateHeartbeat(participantId);
}, 60000);
// Server checks heartbeats every 2 minutes
// Disconnects agents with heartbeat > 2 minutes old
- ✅ Per-Channel Ordering: Events on the same channel are received in order
- ✅ Causality: Effects are broadcast after causes complete
- ✅ Idempotency: Events can be safely reprocessed
- ⚠️ Cross-Channel: No ordering guaranteed across different channels
- ✅ At-Least-Once: Critical events are retried if delivery fails
- ✅ Deduplication: Event IDs prevent duplicate processing
- ✅ Persistence: Events stored in database for reliability
|
Metric
|
Performance
|
|---|---|
| Event propagation | (same datacenter) |
| Conflict detection | (includes processing) |
| Heartbeat check | |
| Stale detection | after timeout |
// OAuth2 token required for WebSocket auth
Broadcast::channel('session.{id}', function ($user, $sessionId) {
return AgentParticipant::where('agent_session_id', $sessionId)
->where('agent_identifier', $user->agent_identifier)
->exists();
});
- • Private channels only: No public broadcast channels
- • Team scoping: Channels include team/project ID
- • Token expiration: OAuth tokens expire automatically
- • Signature verification: All broadcasts cryptographically signed
Agent A creates: "Create user model"
↓ (50ms)
Agent B creates: "Create User model"
↓ (100ms)
ConflictDetector runs:
- Normalizes titles: "create user model"
- Calculates similarity: 1.0 (100%)
- Creates AgentConflict record
↓ (instant)
Broadcasts: AgentConflictDetected
↓ (50ms)
Auto-resolve:
- Compares descriptions
- Keeps todo with more detail
- Cancels duplicate
↓ (instant)
Broadcasts: AgentConflictResolved
↓ (50ms)
Both agents receive resolution
Total Time: ~250ms from creation to resolution
- • Handle all 6 event types
- • Implement exponential backoff on connection failures
- • Send heartbeats every 60 seconds
- • Gracefully leave sessions when done
- • Cache session state locally
- • Create todos without checking for duplicates
- • Ignore conflict events
- • Hold connections open indefinitely
- • Broadcast custom events
- • Store sensitive data in event payloads
Check out our complete implementation guide.