Skip to content

One call, any engine

Every durable workflow eventually waits on something it does not control: a person, a machine, another system. Long Tail turns that wait into an escalation: routed to a role, claimable, deadlined, searchable, and answerable from the dashboard, HTTP, ltc or MCP. HotMesh is built in. Temporal and DBOS workflows plug in through a provider package and wait with the same call.

The engine owns time, retries and history. Long Tail owns everything between “a person needs to do this” and “here is the answer”.

Pick your engine. The imports and the step wrapper change; the conditional() call and its payload are the same on all three.

workflows.ts
import { Durable } from '@hotmeshio/hotmesh';
import { conditional } from '@hotmeshio/long-tail';
import * as activities from './activities';
type Decision = { approved: boolean };
const { scoreOrder, shipOrder } =
Durable.workflow.proxyActivities<typeof activities>({ activities });
export async function reviewOrder(orderId: string) {
await scoreOrder(orderId);
const decision = await conditional<Decision>(`review-${orderId}`, {
role: 'reviewer',
type: 'order-review',
description: `Review order ${orderId}`,
priority: 2,
metadata: { orderId },
timeout: '48h',
});
if (!decision) return 'expired or cancelled';
if (!decision.approved) return 'rejected';
return shipOrder(orderId);
}
const answer = await conditional<T>(key, ask);
  • key names this wait inside the workflow. Opening the same key again returns the same escalation, so a retried step reuses it.
  • ask describes the escalation: who owns it, what kind of work it is, what people read, what they search by and when it expires.
  • answer is the resolver’s payload, typed as T. When the escalation expires or someone cancels it, the workflow gets a falsy value and decides what that means.

All three engines accept this object as it is:

{
"role": "reviewer",
"type": "order-review",
"description": "Review order 42",
"priority": 2,
"metadata": { "orderId": "42" },
"timeout": "48h"
}
FieldMeaning
roleThe queue. Only members see, claim and resolve the escalation
typeThe kind of work, for routing and filtering
descriptionThe line people read in the queue
priority1 (critical) to 4 (low)
metadataIndexed facets: search, filter, group and claim by orderId. Validated against the role’s metadata schema
timeoutThe SLA: '30s', '15m', '48h', '7d', '2w' or milliseconds. Long Tail expires the escalation when it passes
The engineLong Tail
Time, checkpoints and step retriesThe escalation, role routing and RBAC
Recovery after a crash or a redeployClaims, forms, schema enforcement and facets
The workflow’s historyPriority, SLA expiry and cancellation
Your workflow and activity codeDelivery of exactly one outcome back to the workflow

The lifecycle is the same on every engine; only the last arrow’s transport changes.

workflow Long Tail people, services, agents
──────── ───────── ────────────────────────
conditional(key, ask)
└─ open the wait ────────► one escalation in the role's queue ───────► see, claim, fill the form
(idempotent on key) RBAC, facets, SLA timer resolve or cancel
(or the SLA passes)
escalation reaches a terminal state ◄───────
relay delivers the outcome once
◄─ answer, or a falsy value HotMesh: same commit · Temporal: signal · DBOS: message
workflow continues ──────────► dashboard sees workflow.completed
  • Exactly one outcome. Resolved with the payload, expired, or cancelled. The relay delivers under a lease, retries with capped backoff, and records a workflow that has ended as gone.
  • Any surface resolves it. The dashboard, HTTP, ltc, MCP tools, bulk actions, scan rules and expiry all deliver the same way. The workflow sees one answer.
  • Restarts are routine. Temporal holds the signal and DBOS holds the message while a worker or app is down; the workflow reads it on recovery.
  • The dashboard stays live. Resolving pushes escalation.resolved, and the workflow’s completion pushes workflow.completed, over WebSockets.
HotMeshTemporalDBOS
Package@hotmeshio/long-tail@hotmeshio/long-tail-temporal@hotmeshio/long-tail-dbos
Import conditional from@hotmeshio/long-tail@hotmeshio/long-tail-temporal/workflow@hotmeshio/long-tail-dbos
Context shown to the resolverenvelopedatadata
Expiredfalsenullnull
Cancellednullnullnull
Form version pinschemaVersionthe role’s latest formthe role’s latest form
How the answer arrivesin the same database commit that resolves the escalationTemporal signal lt.outcomeDBOS message, idempotent on the escalation id
InfrastructurePostgresPostgres and a Temporal clusterPostgres

if (!decision) handles expiry and cancellation on every engine. To tell them apart on HotMesh, compare with false and null; see conditional.

HotMesh workflows also use batches and accumulators, triage, YAML pipelines and starting workflows from the dashboard, ltc and MCP.

  • Every engine: queues, claims, forms, escalation detail, facets and search, the pace board, analytics, roles, users, bots and MCP tools.
  • Temporal and DBOS: an Executions list read from the engine (filter by workflow id prefix, type and state), and each workflow’s timeline built from the engine’s own history: steps, timers, child workflows and each wait from open to outcome. On Temporal, a link opens the workflow in the Temporal UI.
  • HotMesh: also YAML pipelines, streams, the control plane, triage and AI query. With another engine these pages are hidden, and their API routes answer 501 with the engine’s name.

GET /api/settings reports the engine, and the dashboard reads it to choose its pages. See Settings.