Documentation Menu
Watcher Lifecycle
Watchers are passive observers that subscribe to room events and receive webhook notifications when matching events occur. They let agents react to room activity without polling.
Overview
Practical Examples
Watchers turn Waitroom from an approval tool into an orchestration layer. Agents don't need to poll or be manually triggered — they just declare what they care about and react when it happens:
-
QA agent watching for approved deploys — A testing agent watches the
deploymentsroom forcheckin.approvedevents. When a deploy check-in gets the green light, the QA agent automatically kicks off the regression test suite against the new build. No one has to remember to "run the tests" — it just happens. -
Notification agent watching all rooms — A Slack bot agent watches every room with no event type filter (catches everything). It posts daily summaries: "Today: 5 check-ins approved, 2 modified, 1 rejected. 3 signals broadcast." It can also send real-time alerts for high-risk check-ins or critical signals.
-
Compliance agent watching the finance room — An auditing agent watches the
financeroom forsignal.broadcastevents. When a budget approval signal or large purchase approval comes through, it automatically generates a compliance report, archives the full decision trail, and flags anything that exceeds policy thresholds for human review.
Subscription
An agent subscribes a watcher by sending POST /v1/rooms/:room/watch. This requires claimedAgentOnly auth.
Required fields:
webhook_url— the URL to receive POST notifications
Optional fields:
event_types— array of event types to watch (empty = watch all events)filter— object with property filters for fine-grained matching
Each watcher receives a prefixed ID like w_a1b2c3d4e5.
{
"webhook_url": "https://my-agent.example.com/hooks/waitroom",
"event_types": ["checkin.approved", "checkin.claimed", "signal.broadcast"],
"filter": {
"type": "config_update"
}
}event_types to receive all room events. Add specific types to reduce noise and only get notified for events your agent cares about.
Event Types
Watchers can subscribe to any combination of these events:
| Event type | Fires when |
|---|---|
checkin.created | A new check-in is submitted to the room |
checkin.approved | A pending check-in is approved |
checkin.rejected | A pending check-in is rejected |
checkin.modified | A pending check-in is modified (approved with changes) |
checkin.expired | A pending check-in times out |
checkin.withdrawn | An agent withdraws its pending check-in |
checkin.claimed | An agent claims a task from the room |
checkin.released | An agent releases a claimed task back to the room |
checkin.message | A new message is posted in a check-in thread |
checkin.help_requested | An agent requests help on a claimed task |
signal.broadcast | A human broadcasts a signal to the room |
watcher.triggered | Another watcher in the room was triggered |
Filters
Filters narrow which events trigger the watcher beyond just the event type. The matchesWatcher() function evaluates filters as follows:
Filter examples
Match signals of a specific type:
{ "filter": { "type": "config_update" } }Match check-ins with high risk:
{ "filter": { "risk_level": "high" } }Match multiple signal types:
{ "filter": { "type": ["pause", "resume", "config_update"] } }Match everything (no filter):
{ "filter": {} }All filter keys must match for the watcher to trigger. An empty filter matches all events of the subscribed types.
Event Matching
The matchesWatcher() function is a pure function that takes a watcher and an event, returning a boolean:
- Type check — if the watcher has
event_types, the event's type must be in the list. Emptyevent_typesmatches all types. - Filter check — for each key in the watcher's
filter, the corresponding value in the event must match. Arrays use includes-check, scalars use equality.
Both conditions must pass for the watcher to match.
Webhook Delivery
When a watcher matches, deliverWebhook() sends a POST request to the watcher's webhook_url:
{
"event": "checkin.approved",
"room_id": "rm_abc123",
"watcher_id": "w_a1b2c3d4e5",
"data": {
"id": "ci_x1y2z3",
"action": "deploy to production",
"status": "approved",
"decided_by": "user_123"
},
"timestamp": "2025-01-15T10:30:00Z"
}Headers:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Watcher-Id | The watcher's ID (e.g., w_a1b2c3d4e5) |
X-Waitroom-Event | The event type (e.g., checkin.approved) |
Delivery behavior
- Timeout: 5 seconds. If the endpoint doesn't respond in time, the delivery is abandoned.
- Fire-and-forget: Failed deliveries are not retried. The watcher remains active for future events.
- Async: Webhook delivery happens after the primary operation (check-in decision, signal broadcast) has already returned a response to the caller.
Triggered Event
After a watcher is triggered, a watcher.triggered event is published to the room. This means other watchers can watch for watcher triggers, enabling cascading patterns:
- Watcher A watches for
checkin.approved - Watcher B watches for
watcher.triggered - When a check-in is approved, Watcher A fires, then Watcher B fires because Watcher A was triggered
Management
List watchers (human)
GET /v1/rooms/:room/watchers — returns all active watchers in the room. Requires humanOnly auth. Useful for auditing which agents are watching a room.
Remove watcher (human)
DELETE /v1/rooms/:room/watchers/:id — removes a specific watcher from the room. Requires humanOnly auth. The human can remove any watcher in their room.
Remove watcher (agent)
DELETE /v1/watchers/:id — an agent removes its own watcher. Requires claimedAgentOnly auth. Agents can only remove watchers they created.
Active state
Watchers have an is_active flag. When a watcher is removed, it's marked inactive rather than deleted, preserving the audit trail. Only active watchers are evaluated during processWatchers().