Skip to main content
The inbox dispatcher is a daemon that runs alongside the gateway. It does one thing: watch 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:
The --model and --thinking flags are included when the inbox item’s context JSONB contains model_override or thinking_override fields. This happens automatically for task assignments where the task specifies per-task model overrides. When absent, the agent uses its configured default model. The message is always:
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 calling openclaw agent, the dispatcher checks a series of conditions. Any failing condition skips the wake for that agent. Skip reasons are logged at 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 to INSERT 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 attempt
  • last_wake_success_at — set if the wake succeeded
These are tracking fields only — they don’t affect processing.

Reconciliation sweep (safety net)

Every RECONCILE_INTERVAL seconds (default 120), the dispatcher:
  1. Refreshes its cache of agents bound to this gateway
  2. Expires stale items older than STALE_ITEM_AGE_HOURS (default 24) by marking them as failed
  3. Reaps hung agent processes that exceed AGENT_PROCESS_TIMEOUT (default 300s)
  4. Cleans up stalled agents — marks their pending items as failed so they stop accumulating
  5. Drains the concurrency waitlist — wakes agents that were queued behind the MAX_CONCURRENT_WAKES cap
  6. Queries for all items with status=pending, status=failed AND attempt_count < 3, or status=leased AND leased_until expired (v0.2.0)
  7. Dedupes by agent, filters to local agents, calls wake_agent for each
The reconciliation sweep catches anything Realtime missed (dropped WebSocket message, agent provisioned mid-flight, items inserted while the daemon was restarting). It applies the same wake gates, so the cooldown prevents spam even if multiple items exist for the same agent.

Expired lease retry (v0.2.0)

The or filter in step 6 includes status=leased AND leased_until < now(). This catches items where an agent leased work but crashed or timed out before completing it. Without this, a stuck lease would permanently block the item. The reconciliation sweep picks it up and wakes the agent for another attempt.

Gateway filtering

The dispatcher only wakes agents bound to this gateway, identified by the GATEWAY_ID environment variable. 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)
If an agent was just provisioned it may not be in the cache yet — the one-time refresh on unknown agent handles this without waiting for the next reconciliation. GATEWAY_ID is resolved from the process environment first. If unset or default, the dispatcher falls back to reading ~/.openclaw/secrets/gateway.env, so daemons restarted outside the entrypoint still pick up the correct identity.

Blocker enrichment

For task_assignment inbox items, the dispatcher queries task_relations to check for unresolved blocked_by dependencies. If the assigned task has active blockers (blocking tasks not in done or cancelled status), the dispatcher appends the blocker names to the wake message:
The query joins through tasks!task_relations_target_task_id_fkey to fetch blocker titles and statuses in a single call. If the enrichment query fails (network issue, table not yet migrated), the wake proceeds with the original unenriched summary and logs a warning. This gives the agent awareness of dependency constraints without requiring it to query relations itself.

Dispatcher hardening (v0.2.2)

Several mechanisms were added to prevent the dispatcher from overwhelming small instances or getting stuck in runaway loops.

Burst-aware concurrency cap

MAX_CONCURRENT_WAKES (default 2) limits how many openclaw agent processes run simultaneously. When the cap is reached, additional agents are added to a waitlist instead of being silently dropped. A slot watcher thread (5-second polling interval) reaps finished processes and wakes the next agent in the queue.

Exponential backoff

When an agent’s wake process exits with a non-zero code, the dispatcher doubles the cooldown for that agent. The effective cooldown is WAKE_COOLDOWN * 2^failures, capped at MAX_WAKE_COOLDOWN (default 900 seconds / 15 minutes). This prevents the wake-loop bug where an unprovisioned or broken agent consumed all gateway resources with rapid fire-and-fail cycles. After MAX_CONSECUTIVE_FAILURES (default 5) consecutive failures, the agent enters a stalled state. The dispatcher stops attempting to wake it and marks its pending inbox items as failed. When a new inbox item arrives via Realtime, the backoff is reset and the agent gets a fresh chance — the new context may resolve whatever caused the failures.

Process timeout

Agent processes that run longer than AGENT_PROCESS_TIMEOUT (default 300 seconds) are terminated. The dispatcher first sends SIGTERM and waits 5 seconds, then escalates to SIGKILL. The timed-out process counts as a failure for backoff purposes.

Stale item expiry

Reconciliation expires pending items older than STALE_ITEM_AGE_HOURS (default 24) by setting their status to failed and attempt_count to 3 (the retry ceiling). This prevents ancient items from keeping agents in an endless retry loop.

Inbox dedup

Reconciliation deduplicates by agent — if multiple pending items exist for the same agent, only one wake call is made. The agent’s inbox processing script handles all pending items in a single session.

Configuration reference


Supabase tables accessed