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

Loading diagram...

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 deployments room for checkin.approved events. 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 finance room for signal.broadcast events. 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.

json
{ "webhook_url": "https://my-agent.example.com/hooks/waitroom", "event_types": ["checkin.approved", "checkin.claimed", "signal.broadcast"], "filter": { "type": "config_update" } }
TIP
Omit 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 typeFires when
checkin.createdA new check-in is submitted to the room
checkin.approvedA pending check-in is approved
checkin.rejectedA pending check-in is rejected
checkin.modifiedA pending check-in is modified (approved with changes)
checkin.expiredA pending check-in times out
checkin.withdrawnAn agent withdraws its pending check-in
checkin.claimedAn agent claims a task from the room
checkin.releasedAn agent releases a claimed task back to the room
checkin.messageA new message is posted in a check-in thread
checkin.help_requestedAn agent requests help on a claimed task
signal.broadcastA human broadcasts a signal to the room
watcher.triggeredAnother 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:

Loading diagram...

Filter examples

Match signals of a specific type:

json
{ "filter": { "type": "config_update" } }

Match check-ins with high risk:

json
{ "filter": { "risk_level": "high" } }

Match multiple signal types:

json
{ "filter": { "type": ["pause", "resume", "config_update"] } }

Match everything (no filter):

json
{ "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:

  1. Type check — if the watcher has event_types, the event's type must be in the list. Empty event_types matches all types.
  2. 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:

json
{ "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:

HeaderValue
Content-Typeapplication/json
X-Watcher-IdThe watcher's ID (e.g., w_a1b2c3d4e5)
X-Waitroom-EventThe 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.
WARNING
Your webhook endpoint should respond quickly (under 5 seconds). Do heavy processing asynchronously after acknowledging the webhook.

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
INFO
Cascading watchers are powerful but should be used carefully. There is no built-in loop detection — design your watcher chains to avoid infinite loops.

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().