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

> Discover how the agent-engine dispatcher in DeskcommCRM routes messages. Learn about its decision process for AI responses, human handoffs, or follow-up queuing.

- Repository: [Rafael Melgaço/DeskcommCRM](https://github.com/melgarafael/DeskcommCRM)
- Tags: internals
- Published: 2026-09-13

---

**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](https://github.com/melgarafael/DeskcommCRM) repository. Located primarily in [`lib/ai/dispatcher/index.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/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.

```typescript
// 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`](https://github.com/melgarafael/DeskcommCRM/blob/main/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.

### G4 – Legal and Stage Gating

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.

```typescript
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`](https://github.com/melgarafael/DeskcommCRM/blob/main/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`](https://github.com/melgarafael/DeskcommCRM/blob/main/app/api/v1/cron/agent-dispatcher/route.ts). This route invokes `dispatchAgents()` from [`lib/ai/dispatcher/index.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/index.ts), which orchestrates the batch processing, claiming, validation, guard evaluation, and dispatch logic described above.