The inbox dispatcher is a daemon that runs alongside the gateway. It does one thing: watchDocumentation Index
Fetch the complete documentation index at: https://docs.yourhq.ai/llms.txt
Use this file to discover all available pages before exploring further.
agent_inbox_items for new work and wake the right agent to process it.
The dispatcher does not process work. It only wakes agents. The agent’s background inbox session (started by OpenClaw) does the actual processing.
Wake mechanism
When the dispatcher decides an agent needs waking, it calls:reason is either New: <item-summary> (from a Realtime event) or Reconciliation: pending inbox items found (from the periodic sweep).
The call is fire-and-forget (subprocess.Popen with stdout/stderr discarded). The dispatcher doesn’t wait for the agent to finish — it moves on immediately.
Wake gating
Before callingopenclaw agent, the dispatcher checks a series of conditions. Any failing condition skips the wake for that agent.
| Condition | Skip reason | Details |
|---|---|---|
| Cooldown | cooldown | At least WAKE_COOLDOWN seconds (default 30) must have passed since the last successful wake for this agent |
| In-flight wake | wake_in_flight | A wake for this agent is already running (in-memory, per-process) |
| Agent paused/hibernating | agent_paused | Checks agents.status — won’t wake a user-paused or hibernating agent |
| Active lease | active_lease | Agent already has a status=leased inbox item with a future leased_until — background processing is already in progress |
| Budget exceeded | budget_exceeded | agent_budgets.status = 'exceeded' AND hard_cutoff = true — hard budget limit enforced here as a second ring (the plugin is ring 1 during prompt build) |
| No actionable work | no_actionable_work | No items in pending status, and no failed items with attempt_count < 3 |
info level so you can see why an agent wasn’t woken:
Realtime vs reconciliation
The dispatcher uses two paths to catch inbox work:Realtime (primary)
Subscribes toINSERT events on agent_inbox_items via Supabase Realtime. When a new item lands, the dispatcher immediately evaluates whether to wake the agent (applying all the gates above).
On each Realtime wake, the dispatcher also updates the item row:
last_wake_attempt_at— set before the wake attemptlast_wake_success_at— set if the wake succeeded
Reconciliation sweep (safety net)
EveryRECONCILE_INTERVAL seconds (default 120), the dispatcher:
- Refreshes its cache of agents bound to this gateway
- Queries for all items with
status=pendingorstatus=failed AND attempt_count < 3 - Dedupes by agent, filters to local agents, calls
wake_agentfor each
Gateway filtering
The dispatcher only wakes agents bound to this gateway. It maintains a local cache (LOCAL_AGENT_IDS) of agent UUIDs belonging to this gateway’s slug, refreshed on:
- Startup
- Every reconciliation sweep
- Any time a Realtime event arrives for an unknown agent (one-time refresh + re-check)
Supabase tables accessed
| Table | Operation | Purpose |
|---|---|---|
workspace | SELECT | Fetch workspace slug for OpenClaw agent ID prefix |
gateways | SELECT | Resolve gateway UUID from slug to filter agents |
agents | SELECT | Cache local agent IDs; check status per wake |
agent_inbox_items | SELECT, PATCH | Query actionable items; update wake tracking fields |
agent_budgets | SELECT | Check budget status before waking |