Skip to content
NewCLI, MCP, HTTP and MQTT on one boundary

Durable workflow solves reliability.Long Tail solves uncertainty.

Retrying doesn't fix uncertainty. Ask an unsure model again and you get another guess. Long Tail's conditional() solves uncertainty by avoiding the retry altogether. It's a condition() with everything teams reinvent over and over.

  • Postgres is the only dependency
  • MCP native
  • Embeds in Node and NestJS
workflows/analyze-document.ts
import { Durable } from '@hotmeshio/hotmesh';
import { conditional, type LTEnvelope } from '@hotmeshio/long-tail';
import * as activities from './activities';
const THRESHOLD = 0.85;
const { extractFields } = Durable.workflow
.proxyActivities<typeof activities>({ activities });
export async function analyzeDocument(envelope: LTEnvelope) {
const { s3Url } = envelope.data;
const result = await extractFields(s3Url);
if (result.confidence >= THRESHOLD) return { data: result };
const review = await conditional(`review-${result.docId}`, {
role: 'reviewer',
envelope: { s3Url, result },
timeout: '4h',
});
return { data: review || result };
}

A document arrives. A new scan lands in S3, and analyzeDocument starts with its s3Url.

The usual plumbing

The scaffolding teams rebuild around every wait, already built.

Long Tail's conditional() makes the most common tasks configurable. A human-in-the-loop review or a manufacturing handoff becomes one call and one role: the escalation, its queue, claims, form, validation, RBAC, timeout and audit come with it. Here is one document review, both ways.

Read why it exists

Your workflow

workflows/analyze-document.ts
import { Durable } from '@hotmeshio/hotmesh';
import { conditional, type LTEnvelope } from '@hotmeshio/long-tail';
import * as activities from './activities';
const { extractFields } = Durable.workflow
.proxyActivities<typeof activities>({ activities });
const THRESHOLD = 0.85;
export async function analyzeDocument(envelope: LTEnvelope) {
const { s3Url } = envelope.data;
const result = await extractFields(s3Url);
if (result.confidence >= THRESHOLD) return { data: result };
const review = await conditional(`review-${result.docId}`, {
role: 'reviewer',
envelope: { s3Url, formDefaults: result },
timeout: '4h',
});
return { data: review || result };
}

One call, one escalation. Every conditional() call creates an escalation for the reviewer role and waits for its answer. Pick any item to see where it comes from.

The role, as configuration

roles.ts
// passed to start({ roles: ROLES })
export const ROLES = [{
role: 'reviewer',
title: 'Document reviewer',
form_schema: {
title: 'Document review',
required: ['total', 'reason'],
properties: {
vendor: { type: 'string', title: 'Vendor', readOnly: true },
confidence: {
type: 'number',
title: 'Confidence',
readOnly: true,
},
approved: { type: 'boolean', title: 'Fields match the scan' },
total: {
type: 'number',
title: 'Total',
'x-lt-showIf': 'resolver.approved',
},
reason: {
type: 'string',
format: 'textarea',
title: 'Reason',
'x-lt-showIf': '!resolver.approved',
},
},
},
}];

What the reviewer getsthe dashboard surface

Live previewClaim it, check the fields, submit.

Included9 of 9 included

One escalation, every surface

CLI, MCP, HTTP and MQTT meet at one boundary.

Every conditional() call creates an escalation. A person resolves it on the dashboard, a service over HTTP, an operator from the terminal, an agent over MCP, a machine over MQTT. The escalation, the wait and the result are the same in every case. So are the validation, the RBAC and the audit.

See every surface

01The workflow asks

workflows/inspect.ts
import { Durable } from '@hotmeshio/hotmesh';
import { conditional } from '@hotmeshio/long-tail';
import * as activities from './activities';
const { photograph, ship } = Durable.workflow
.proxyActivities<typeof activities>({ activities });
export async function inspect(orderId: string) {
await photograph(orderId);
const result = await conditional(`inspect-${orderId}`, {
role: 'inspection',
metadata: { orderId },
timeout: '4h',
});
// paused, durably, until the escalation resolves
return ship(orderId, result);
}
creates an escalation

02The escalation is parked

lt_escalations · 4b1f…pending
role
inspection
metadata
{ "orderId": "A-1042" }
signal_key
inspect-A-1042
expires_at
in 4h
assigned_to
null
resolver_payload
null
person resolves it

03Person · Dashboard

A reviewer finds the escalation in their queue, claims it and submits the form.

orderId: A-1042
  • Inspect A-1042pending
  • Inspect A-1038claimed
  • Inspect A-1031pending
the escalation holds the answer

04The escalation holds the answer

lt_escalations · 4b1f…pending
role
inspection
metadata
{ "orderId": "A-1042" }
signal_key
inspect-A-1042
expires_at
in 4h
assigned_to
null
resolver_payload
null
wakes the workflow

05Back to the workflow

// paused, durably, until the escalation resolves

Runs where you run

Point at Postgres. Everything else is optional.

Standalone with the dashboard, workers on their own, or embedded inside the app you already have. All modes share one database and scale independently.

index.ts
import { start } from '@hotmeshio/long-tail';
import { reviewContent } from './workflows/review';

await start({
  database: { connectionString: process.env.DATABASE_URL },
  workers: [{ taskQueue: 'default', workflow: reviewContent }],
});

Dashboard, API and workers in one process. The dashboard serves on port 3000.

The dashboard comes with it

Every role gets a workspace on day one.

Queues, rendered forms, claim timers, pinned views and wall-screen portals, all scoped by role. The role’s schema is the UI.

Portals: counts and pinned lists for a station.
Queues: every escalation a role can see, filterable by facet.

What the primitive buys

The waiting queue gets qualities the deterministic one never needed.

RBAC is intrinsic

The queue is a role. Any member answers, a person in the dashboard or a service account through the API, and the workflow is indifferent to which.

The form is data

A versioned JSON Schema on the role renders the resolver UI and validates every submission on every surface.

Claims are locks

A claim is a time-boxed window with a resolve guard behind it. A submission against an expired claim is rejected in the same statement.

Cancel is home

Cancellation is a first-class settlement. The waiting flow sees null and reconciles to a modeled resting state.

One shared surface

Every actor writes to the same metadata-keyed escalation row. One place to search, one place to claim, one place to audit.

History accretes

Intent at creation, outcome at settlement, every crossing in between. The escalation row is the production log.

Objects get lifecycles

Give an object this queue and a loop, and it lives: it advertises when free, asks when it needs service and reconciles when reality answers.

What repeats stops needing people

Every settled escalation is a worked example. Recurring cases become deterministic tools.

Pattern library

A recipe for every kind of wait.

Timeouts that deliver, containers that fill, claims by attribute, badges that attribute without granting, machines that answer like people. Each pattern is a short page with the code and the guarantee.

The long tail gets shorter

What a person resolves today, a tool runs next month.

Every settled escalation records the intent, the answer and who gave it. Recurring cases move from people to agents to compiled, deterministic tools. The head of reliable, automated work grows. The tail of novel problems shrinks.

Who resolves a recurring case

Week 1
Month 1
Month 3
Month 6
  • Resolved by a person
  • Resolved by an agent
  • Resolved by a compiled tool

Illustrative. Each settled row is a worked example the next run can reuse.

Start with one await.

Install the package, point it at Postgres, and park your first intent. The dashboard is running before your coffee is.