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
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.
Your workflow
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
// 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
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.
01The workflow asks
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 resolvesreturn ship(orderId, result);}
02The escalation is parked
- role
- inspection
- metadata
- { "orderId": "A-1042" }
- signal_key
- inspect-A-1042
- expires_at
- in 4h
- assigned_to
- null
- resolver_payload
- null
03Person · Dashboard
A reviewer finds the escalation in their queue, claims it and submits the form.
- Inspect A-1042pending
- Inspect A-1038claimed
- Inspect A-1031pending
04The escalation holds the answer
- role
- inspection
- metadata
- { "orderId": "A-1042" }
- signal_key
- inspect-A-1042
- expires_at
- in 4h
- assigned_to
- null
- resolver_payload
- null
05Back to the workflow
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.
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.
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.
The boundary
Every real process reaches a point your code can't settle on its own.
Activities and child workflows run your code: call, checkpoint, retry. condition() waits on the world, for a
human in the loop, an agent, a machine or another system to answer. Each scenario below is one of those waits, end to end.
AI results with human review
Confident answers pass straight through. The rest wait on a reviewer with a validated form, and the decision comes back typed.
Read the scenarioAgents that ask people
An agent reaches a question only a person can answer. It calls escalate_and_wait like any other tool, a person answers in the dashboard, and the agent continues with the answer.
Read the scenarioA factory line over MQTT
Machines resolve handoff rows the way people submit forms. One route mixes both, and the workflow never tells them apart.
Read the scenarioItems into containers
Scan bags into a bin until it is full or the window closes. Both rows change or neither, and the workflow wakes once.
Read the scenarioWaiting on other systems
A partner, a lab or a carrier settles the row by the key it already knows. One call, one resolution, and a deadline for the answer that never comes.
Read the scenarioOperations analytics from the queue
Every escalation is an interval an entity spent in a state. Dwell, membership and timelines come from rows your workflows already write.
Read the scenarioPattern 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
- 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.