Pluggable resolution strategy
One registered class decides what a resolution does: re-run the step or hand it to triage. Routing rules live in code you own.
The situation
Your team wants triage only for one document type, and only when the reviewer left a note. The built-in strategies are all-or-nothing; the decision belongs in your own code.
The recipe
import type { LTEscalationStrategy, ResolutionContext, ResolutionDirective } from '@hotmeshio/long-tail';
export class NotesTriageStrategy implements LTEscalationStrategy { async onResolution(context: ResolutionContext): Promise<ResolutionDirective> { const { escalation, resolverPayload } = context; if (escalation.type === 'intake' && resolverPayload?.notes) { return { action: 'triage', triageEnvelope: buildTriageEnvelope(context) }; } return { action: 'rerun' }; }}await start({ database: { connectionString: process.env.DATABASE_URL }, workers: [/* ... */], escalation: { adapter: new NotesTriageStrategy() },});The strategy sees the escalation record, the resolver’s payload and the original envelope, and returns a directive. Register it through start() or with escalationStrategyRegistry.register(...).
The guarantee
The resolution route consults the registered strategy before it starts a re-run, every time. With no strategy configured the default applies and always answers rerun. Swapping the strategy changes routing without touching any workflow. See Triage.
Related patterns
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.
The escalation boundary
A checklist computed at runtime
The workflow decides which steps a person must confirm. The form renders them from the row, so one schema serves every variation.
The escalation boundary
Errors become escalations
When retries run out, the failure becomes a row an operator can act on. The run waits for retry or skip instead of disappearing.
