How the Agent-Engine Dispatcher Decides Between AI Response, Human Handoff, or Follow-Up Queuing

The agent-engine dispatcher in DeskcommCRM uses a sequential guard-pipeline architecture that checks organization settings, validates payload integrity, and evaluates handoff triggers before routing messages to AI agents, human operators, or a retry queue.

The agent-engine dispatcher serves as the central routing component for conversation events in the DeskcommCRM repository. Located primarily in lib/ai/dispatcher/index.ts, this system determines whether an incoming message receives an automated AI response, escalates to human support, or returns to the queue for later processing. Its decision hierarchy follows a strict priority order defined by organizational configuration and contractual guard conditions.

Organization Dispatch Mode and External Delegation

Before processing any event, the dispatcher inspects the organizations.settings.ai_dispatch_mode field to determine routing authority. This setting accepts two values: native (default) or external.

When the mode is external, the dispatcher immediately skips the event with outcome skipped_external_dispatch, leaving the row untouched for an external Vendaval runtime to claim later. This delegation occurs before the dispatcher attempts to claim the event, ensuring no conflicts with external processors.

// G6-02: skip BEFORE claim. 'external' orgs delegate dispatch to Vendaval.
if (dispatchModeByOrg.get(event.organization_id) === "external") {
  summary.outcomes.skipped_external_dispatch += 1;
  continue;
}

Event Claiming and Payload Validation

For native dispatch organizations, the dispatcher attempts an optimistic claim using compare-and-swap (CAS) logic on the status='pending' field. If another worker has already claimed the event, the current iteration skips to the next row.

Once claimed, the dispatcher validates the payload structure in processEvent(). Missing organization_id, conversation_id, channel_session_id, or inbound_message_id fields trigger an immediate skipped_invalid_payload outcome, preventing processing of malformed events.

The Guard Pipeline: Handoff Triggers

If validation passes, the dispatcher executes a sequential guard pipeline imported from @/lib/ai/handoff/triggers. The guards—G1, G3, and G4—evaluate specific escalation conditions with short-circuit behavior: the first triggered guard immediately invokes triggerHandoff() from lib/ai/handoff/orchestrator.ts and stops further AI processing.

G1 – Direct Human Request Detection

The checkG1 function scans the inbound message for explicit human-request signals. When detected, the dispatcher calls triggerHandoff() with reason handoff_g1_requested_human, creating a handoff event and updating the conversation state to route to a human agent.

G3 – AI Confidence Threshold

The checkG3 guard evaluates AI confidence scores against organizational thresholds. If confidence falls below the configured minimum, the dispatcher triggers a handoff with reason low_confidence, ensuring low-certainty interactions receive human oversight rather than automated responses.

The dispatcher runs checkG4Legal and checkG4Stage to enforce compliance boundaries. If the conversation resides in a legal-review stage or a stage configured to force human escalation, triggerHandoff() executes immediately with the appropriate legal or stage-based reason code.

AI Dispatch and Follow-Up Queuing

When no guard conditions trigger, the dispatcher proceeds to AI response generation. It queries for a published agent version matching the incoming trigger via triggerMatches(). Upon finding a match, it creates an ai_agent_runs row and dispatches a fire-and-forget request to /api/internal/agents/run, returning outcome "dispatched".

If triggerMatches() returns no results—meaning no published agent matches the current trigger—the dispatcher records outcome "no_match" and leaves the event in pending status. This effectively queues the follow-up for the next cron tick, allowing the dispatcher to retry once matching agents are published.

async function processEvent(event: EventRow): Promise<DispatchOutcome> {
  // payload validation …
  if (!orgId || !conversationId || !channelSessionId || !inboundMessageId) {
    await markEventProcessed(event, "skipped_invalid_payload");
    return "skipped_invalid_payload";
  }

  // ----- Guard pipeline -----
  const g1 = await checkG1(event);
  if (g1) return await triggerHandoff(event, g1);   // → human handoff

  const g3 = await checkG3(event);
  if (g3) return await triggerHandoff(event, g3);   // → low‑confidence handoff

  const g4 = await checkG4Legal(event) ?? await checkG4Stage(event);
  if (g4) return await triggerHandoff(event, g4);   // → legal/stage handoff

  // ----- No handoff ⇒ AI response -----
  const match = await triggerMatches(event);
  if (!match) {
    await markEventProcessed(event, "no_match");
    return "no_match";                               // → follow‑up queue
  }

  await dispatchAiResponse(event, match);
  return "dispatched";
}

The cron entry point at app/api/v1/cron/agent-dispatcher/route.ts initiates this entire flow by calling dispatchAgents({ batchSize: 100 }), processing events in configurable batches.

Summary

  • The agent-engine dispatcher checks organizations.settings.ai_dispatch_mode first, skipping events with skipped_external_dispatch when mode is external.
  • Payload validation requires organization_id, conversation_id, channel_session_id, and inbound_message_id; missing fields result in skipped_invalid_payload.
  • Guard checks (G1, G3, G4) execute sequentially in processEvent(), triggering immediate human handoff via triggerHandoff() upon the first match.
  • When guards clear, the dispatcher matches against published agents; successful matches dispatch AI responses, while failures record no_match and queue the event for retry.
  • All state transitions write to event_log using unified status values: pending, processing, done, or dead.

Frequently Asked Questions

What happens when ai_dispatch_mode is set to external?

The dispatcher skips the event entirely before attempting to claim it, recording outcome skipped_external_dispatch. The event remains in pending status for an external Vendaval runtime to process, preventing the native dispatcher from interfering with external routing logic.

How does the dispatcher choose between AI and human handoff?

The dispatcher evaluates handoff guards (G1, G3, G4) in strict sequence. If any guard returns a truthy value, the system immediately calls triggerHandoff() and marks the event processed. Only when all guards return false does the dispatcher attempt AI matching and dispatch.

What causes an event to enter the follow-up queue instead of receiving an AI response?

When triggerMatches() finds no published agent version matching the incoming trigger, the dispatcher returns outcome no_match and leaves the event in pending status. The event will be re-evaluated during subsequent cron executions until a matching agent is published or the event expires.

Where is the entry point for the dispatcher cron job?

The cron endpoint resides at app/api/v1/cron/agent-dispatcher/route.ts. This route invokes dispatchAgents() from lib/ai/dispatcher/index.ts, which orchestrates the batch processing, claiming, validation, guard evaluation, and dispatch logic described above.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →