Confidence-gated escalation
The workflow finishes on its own when the model is confident and parks a review row when it is not, so people see only the uncertain cases.
The situation
A model scores each item. Most scores are high and need no one. A few are borderline, and those should land in front of a reviewer with the model’s reasoning attached, while the workflow waits for the answer.
The recipe
import { Durable } from '@hotmeshio/hotmesh';import { conditional, type LTEnvelope } from '@hotmeshio/long-tail';import * as activities from './activities';
const { analyzeItem } = Durable.workflow.proxyActivities<typeof activities>({ activities });
export async function reviewItem(envelope: LTEnvelope) { const { itemId } = envelope.data; const analysis = await analyzeItem(itemId); if (analysis.confidence >= 0.85) { return { type: 'return' as const, data: { approved: true, analysis } }; }
const { workflowId } = Durable.workflow.workflowInfo(); const review = await conditional<{ approved: boolean; notes?: string }>(`review-${workflowId}`, { role: 'reviewer', description: `Review needed (confidence ${analysis.confidence})`, metadata: { itemId, confidence: analysis.confidence }, envelope: { analysis }, timeout: '48h', }); if (!review) return { type: 'return' as const, data: { approved: false, reason: 'no-review' } }; return { type: 'return' as const, data: { approved: review.approved, notes: review.notes } };}The model call is a checkpointed activity. Above the threshold the workflow returns. Below it, conditional writes one reviewer row carrying the score as a facet and the analysis as context, then waits.
The guarantee
The confident path writes no row. The uncertain path writes exactly one row, in the same commit as the wait, and the workflow resumes with the reviewer’s typed payload. Because confidence is a metadata facet, the queue can sort and filter on it with a range query. See Conditional.
Related patterns
The escalation boundary
Answer, timeout or cancel
Every wait ends in one of three typed values: the answer, false on timeout, or null on cancel. Each branch is explicit in code.
The escalation boundary
Know who resolved it
The resolver's identity rides the wake as $resolution and lands on the row as resolved_by, so follow-on work can go to the same person.
The escalation boundary
Human flags, AI remediates
A reviewer who cannot fix the input marks it for triage, and a tool-calling workflow repairs it and runs the original step again.
Agents and MCP
Humans as tools
An agent routes work to a person through the same tool protocol it uses for everything else, and gets the typed answer back.
