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.
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.
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_modefirst, skipping events withskipped_external_dispatchwhen mode isexternal. - Payload validation requires
organization_id,conversation_id,channel_session_id, andinbound_message_id; missing fields result inskipped_invalid_payload. - Guard checks (G1, G3, G4) execute sequentially in
processEvent(), triggering immediate human handoff viatriggerHandoff()upon the first match. - When guards clear, the dispatcher matches against published agents; successful matches dispatch AI responses, while failures record
no_matchand queue the event for retry. - All state transitions write to
event_logusing unified status values:pending,processing,done, ordead.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →