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:--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 callingopenclaw 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 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
- Expires stale items older than
STALE_ITEM_AGE_HOURS(default 24) by marking them as failed - Reaps hung agent processes that exceed
AGENT_PROCESS_TIMEOUT(default 300s) - Cleans up stalled agents — marks their pending items as failed so they stop accumulating
- Drains the concurrency waitlist — wakes agents that were queued behind the
MAX_CONCURRENT_WAKEScap - Queries for all items with
status=pending,status=failed AND attempt_count < 3, orstatus=leased AND leased_untilexpired (v0.2.0) - Dedupes by agent, filters to local agents, calls
wake_agentfor each
Expired lease retry (v0.2.0)
Theor 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 theGATEWAY_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)
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
Fortask_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:
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 isWAKE_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 thanAGENT_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 thanSTALE_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.

