Your workflow owns causality.Long Tail owns uncertainty.
The execution environment for everything your workflow waits on but doesn't control. A person, an agent, a script or a machine resolves the wait, and your code picks up exactly where it paused.
- Postgres is the only dependency
- MCP native
- Embeds in Node and NestJS
const result = await conditional('inspect-A-1042', { role: 'inspection', metadata: { orderId: 'A-1042' }, timeout: '4h', });
- role
- inspection
- orderId
- A-1042
- deadline
- in 4h
The boundary
Every real process reaches a point your code can't settle on its own.
Durable workflows are excellent at causality: run this activity, then that one, retry, wait for a condition. Then reality steps in. The workflow can say what it needs. How reality satisfies that need is out of its hands.
A person must inspect something
A printer must become available
Twenty physical items must arrive
An AI result needs review
A machine lifecycle must finish
Another system must respond
Without a boundary
Teams rebuild the same scaffolding around every wait.
Task tables, status columns, queues, claims, timeouts, forms, RBAC, callbacks, indexes, audit trails and the dashboards to watch them. Long Tail makes the boundary first-class, so all of it comes with one call.
Read why it exists- task tables
- status columns
- queues
- assignments
- claims
- timeouts
- forms
- RBAC
- callbacks
- metadata indexes
- batch operations
- audit trails
- dashboards
- reporting
The contract
Intent in. Resolution out.
The workflow provides an intent. Long Tail turns it into a searchable, claimable escalation and manages everything needed to resolve it. When reality produces an answer, Long Tail returns the resolution and durable execution continues.
01 · intent
The workflow says what it needs
A role, a deadline and the facts to route on. One crash-safe write.
02 · escalation
Long Tail owns the wait
Searchable, claimable, deadlined and role-gated. Every surface sees the same row.
03 · resolution
The answer comes back typed
A payload,
falseon timeout,nullon cancel. Execution continues.
import { Durable } from '@hotmeshio/hotmesh';
import { conditional, type LTEnvelope } from '@hotmeshio/long-tail';
import * as activities from './activities';
const { analyzeContent } = Durable.workflow.proxyActivities<typeof activities>({ activities });
export async function reviewContent(envelope: LTEnvelope) {
const analysis = await analyzeContent(envelope.data.content);
if (analysis.confidence >= 0.85) {
return { data: { approved: true, analysis } };
}
const { workflowId } = Durable.workflow.workflowInfo();
const decision = await conditional<{ decision: 'approve' | 'reject'; notes?: string }>(`review-${workflowId}`, {
role: 'reviewer',
type: 'content-review',
priority: 2,
description: `Confidence ${analysis.confidence}. Needs a human.`,
metadata: { contentId: envelope.data.contentId },
timeout: '72h',
schemaVersion: 1,
});
if (decision === false) return { data: { approved: false, reason: 'sla' } };
if (decision === null) return { data: { cancelled: true } };
return { data: { approved: decision.decision === 'approve', analysis } };
}{
"title": "Content Review",
"required": ["decision"],
"properties": {
"decision": {
"type": "string",
"enum": ["approve", "reject"],
"description": "Approve this content?"
},
"notes": {
"type": "string",
"format": "textarea",
"description": "What you saw"
}
}
}Renders as
reviewer · content-review
Content Review
Confidence 0.62. Needs a human.
Decision *
Approve this content?
Notes
What you saw
One row, every surface
CLI, MCP, HTTP and MQTT meet at one boundary.
A person resolves it on the dashboard, a service over HTTP, an operator from the terminal, an agent over MCP, and a machine over MQTT. The row, the wait and the result are the same in every case. So are the validation, the RBAC and the audit.
See every surfaceconst decision = await conditional<{ decision: 'approve' | 'reject' }>(`review-${workflowId}`, {
role: 'reviewer',
metadata: { orderId: 'A-1042' },
timeout: '72h',
});
// decision: the payload, false on timeout, null on cancelThe workflow states its intent and pauses. One crash-safe write mints the row, its facets and its deadline.
Dashboard
Person
HTTP
Service
SDK
Service
CLI
Operator
MCP
Agent
MQTT
Machine
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 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 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 row is a worked example. Recurring cases become deterministic tools.
Use cases
Built for the waits real systems hit.
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 statement, 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.
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 long tail gets shorter
What a person resolves today, a tool runs next month.
Every settled row 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.


