Team members communicate via a built-in mailbox system. Members can send direct messages and read unread messages. The lead agent does not have access to the team_message tool — it is removed from the lead's tool list by policy. Messages flow through the message bus with real-time delivery.
All team members access the mailbox via the team_message tool. Actions:
| Action | Params | Description |
|---|---|---|
send |
to, text, media (optional) |
Send direct message to specific teammate |
broadcast |
text |
Send message to all teammates (except self); system/teammate channel only |
read |
none | Get unread messages; auto-marks as read |
Member sends message to another member:
{
"action": "send",
"to": "analyst_agent",
"text": "Please review my findings from task 123. I need your input on the methodology."
}What happens:
- Message is persisted to database
- A "message" task is auto-created on the team task board (visible in Tasks tab)
- Recipient is notified in real-time via message bus (channel:
system, sender:teammate:{sender_key}) - Event broadcast to UI for real-time updates
Response:
Message sent to analyst_agent.
Cross-team protection: You can only message team members. Attempting to message someone outside your team fails with "agent is not a member of your team".
Broadcast delivers a message to all team members simultaneously. This action is restricted to system/teammate channels (internal operations) — regular member agents cannot call broadcast directly.
{
"action": "broadcast",
"text": "Important update: We've decided to focus on the top 5 findings. Please adjust your work accordingly."
}What happens:
- Message persisted as broadcast (to_agent_id = NULL)
- Message type:
broadcast - Each team member (except sender) receives the message
- Event broadcast to UI for all to see
Response:
Broadcast sent to all teammates.
Check mailbox:
{
"action": "read"
}Response:
{
"messages": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"team_id": "...",
"from_agent_id": "...",
"from_agent_key": "researcher_agent",
"to_agent_key": "analyst_agent",
"message_type": "chat",
"content": "Please review my findings...",
"read": false,
"created_at": "2025-03-08T10:30:00Z"
}
],
"count": 1
}Auto-marking: Reading messages automatically marks them as read. Next read call will only show new unread messages.
Pagination: Returns up to 50 unread messages per call. If more exist, the response includes "has_more": true and a note to call read again after processing.
Messages flow through the system with special routing:
flowchart TD
SEND["team_message send/broadcast"] --> PERSIST["Persist to DB"]
PERSIST --> BUS["Message Bus<br/>Channel: 'system'<br/>SenderID: 'teammate:{sender_key}'"]
BUS --> TARGET["Route to target agent session"]
TARGET --> DISPLAY["Display in conversation"]
Message format on delivery:
[Team message from researcher_agent]: Please review my findings...
The teammate: prefix in the sender ID tells the consumer to route the message to the correct team member's session, not the general user session.
In addition to mailbox messages, GoClaw uses a typed Domain Event Bus (eventbus.DomainEventBus) for internal event propagation across the v3 pipeline. This is separate from the channel message bus used for routing.
The domain event bus is defined in internal/eventbus/domain_event_bus.go:
type DomainEventBus interface {
Publish(event DomainEvent) // non-blocking enqueue
Subscribe(eventType EventType, handler DomainEventHandler) func() // returns unsubscribe fn
Start(ctx context.Context)
Drain(timeout time.Duration) error
}Key properties:
- Async worker pool (default 2 workers, queue depth 1000)
- Per-
SourceIDdedup window (default 5 minutes) — prevents duplicate processing - Configurable retry (default 3 attempts with exponential backoff)
- Graceful drain on shutdown
Event types catalog (defined in eventbus/event_types.go):
| Event Type | Trigger |
|---|---|
session.completed |
Session ends or context is compacted |
episodic.created |
Episodic memory summary stored |
entity.upserted |
Knowledge graph entity updated |
run.completed |
Agent pipeline run finishes |
tool.executed |
Tool call completes (for metrics) |
vault.doc_upserted |
Vault document registered or updated |
delegate.sent |
Delegation dispatched to member |
delegate.completed |
Delegatee finishes successfully |
delegate.failed |
Delegation fails |
These events power the v3 enrichment pipeline (episodic memory, knowledge graph, vault indexing) independently from the WebSocket team events used by the UI.
For UI real-time updates, team activity emits WebSocket events via msgBus.Broadcast. These are separate from the domain event bus and target connected dashboard clients.
When messages are sent, real-time events are broadcast to UI:
{
"event": "team.message.sent",
"payload": {
"team_id": "550e8400-e29b-41d4-a716-446655440000",
"from_agent_key": "researcher_agent",
"from_display_name": "Research Expert",
"to_agent_key": "analyst_agent",
"to_display_name": "Data Analyst",
"message_type": "chat",
"preview": "Please review my findings...",
"user_id": "...",
"channel": "telegram",
"chat_id": "..."
}
}Task lifecycle events (create, assign, complete, approve, reject, comment, fail, etc.) are also available via the REST endpoint:
GET /v1/teams/{id}/events
This returns a paginated audit log of all task state changes for the team, useful for compliance review or building custom dashboards.
Member → Member: "Task 123 is ready for your review. The data shows..."
Member → Member: "I'm blocked on step 2 — do you have the raw dataset I need?"
Broadcast (system-level only): "Changing priorities. Focus on tasks 1, 2, 5 instead of 3, 4."
Note: Leads coordinate via
team_tasks, notteam_message. Useteam_tasks(action="progress")to report status updates instead of direct messages.
If a member agent's run is terminated by the loop detector (stuck or infinite loop), the task automatically transitions to failed:
- The loop detector identifies stuck patterns — same tool calls with same args and results repeated, or read-only streaks without progress
- When critical level triggers, the run is killed and the team task manager marks the task as
failed - The lead agent is notified and can reassign or retry with updated instructions
This prevents infinite loops from blocking team progress — agents can safely attempt exploratory tasks without risk of permanent stall.
Team task events can be forwarded to chat channels. The default configuration is conservative — only high-signal events are on by default to reduce noise.
| Event | Default | Description |
|---|---|---|
dispatched |
ON | Task dispatched to a member |
new_task |
ON | New task created (human-initiated) |
completed |
ON | Task completed |
progress |
OFF | Member updates progress |
failed |
OFF | Task failed |
commented |
OFF | Task comment added |
slow_tool |
OFF | System alert when a tool call exceeds the adaptive threshold |
Delivery mode is direct by default (outbound channel). Set mode: "leader" to route all notifications through the lead agent.
Configure notifications in team settings:
{
"notifications": {
"dispatched": true,
"new_task": true,
"completed": true,
"progress": false,
"failed": false,
"commented": false,
"slow_tool": false,
"mode": "direct"
}
}- Be concise: Keep messages focused and actionable
- Use broadcasts for team-wide info: Don't send identical messages to multiple members
- Direct message for discussion: Back-and-forth coordination use direct messages
- Reference tasks: Mention task IDs for context ("Task 123 is blocked by...")
- Check regularly: Members should check their mailbox if waiting for updates
All messages are persisted to the database:
- Direct messages link sender → specific recipient
- Broadcasts link sender → NULL (means all members)
- Timestamps and read status tracked
- Full message history available for audit/review