Wait / Resolve
Overview
The Wait / Resolve API enables human-in-the-loop patterns in workflows. A workflow can pause execution at any point and wait for an external signal before resuming, without consuming resources while waiting.
This is useful for scenarios where a workflow needs to wait for:
- Manual approval (e.g., order approval, content review)
- External system completion (e.g., payment confirmation)
- User input or decision before proceeding
How It Works
The Wait / Resolve flow consists of three phases:
- Trigger — A workflow is triggered and starts executing
- Wait — The workflow calls
wait(), which suspends execution and parks it in the database - Resolve — Another script calls
resolve()with the execution ID and key, which resumes the workflow
While waiting, the workflow runner exits and no resources are consumed. The execution is parked in the database until resolve() is called.
API Reference
tailor.workflow.wait(key, payload)
Suspends the current workflow execution and waits for an external signal.
Arguments:
key(string, required) — A unique identifier for this wait point within the workflow. Must match the pattern^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$(lowercase alphanumeric with hyphens, 3–63 characters).payload(object, optional) — JSON-serializable data to persist while waiting. This data is available to theresolve()callback. Defaults to{}if omitted.
Return value:
- The result returned by the
resolve()callback when the workflow resumes.
Example:
export async function main(args) {
const order = tailor.workflow.triggerJobFunction("createOrder", args);
// Pause and wait for approval
const approval = await tailor.workflow.wait("approval", {
orderId: order.id,
amount: order.totalAmount,
requestedBy: args.userId,
});
if (approval.approved) {
tailor.workflow.triggerJobFunction("fulfillOrder", {
orderId: order.id,
});
}
return { orderId: order.id, approved: approval.approved };
}tailor.workflow.resolve(executionId, key, callback)
Resolves a waiting workflow, causing it to resume execution.
Arguments:
executionId(string, required) — The execution ID of the waiting workflow.key(string, required) — The wait key to resolve. Must match the key passed towait().callback(function, required) — A function that receives thewaitPayload(the data passed towait()) and returns a JSON-serializable result. The returned value is passed back to thewait()caller when the workflow resumes.
Return value:
- None. The callback result is delivered to the waiting workflow asynchronously.
Example:
export async function main(args) {
await tailor.workflow.resolve(
args.executionId,
"approval",
(waitPayload) => {
// waitPayload contains the data passed to wait()
// e.g., { orderId: "...", amount: 1000, requestedBy: "..." }
return {
approved: args.approved,
approvedBy: args.approverId,
approvedAt: new Date().toISOString(),
};
},
);
}Typical Pattern
A common implementation pattern involves three components:
- A workflow that triggers processing and pauses with
wait() - A TailorDB record that stores the execution ID for later retrieval
- A pipeline resolver function that calls
resolve()when the human decision is made
Step 1: Trigger the workflow and save the execution ID
// Pipeline resolver or executor function
export async function main(args) {
const executionId = await tailor.workflow.triggerWorkflow(
"order-approval-workflow",
{ orderId: args.orderId },
);
// Save executionId to TailorDB for later retrieval
await gql.mutation({
updateOrder: {
__args: {
id: args.orderId,
input: { workflowExecutionId: executionId },
},
id: true,
},
});
return { executionId };
}Step 2: Workflow pauses with wait()
// Workflow job function
export async function main(args) {
const order = tailor.workflow.triggerJobFunction("prepareOrder", args);
// Pause and wait for human approval
const decision = await tailor.workflow.wait("approval", {
orderId: order.id,
items: order.items,
total: order.total,
});
// Resume after approval
if (decision.approved) {
tailor.workflow.triggerJobFunction("processOrder", {
orderId: order.id,
});
}
return { status: decision.approved ? "completed" : "rejected" };
}Step 3: Resolve from a pipeline resolver function
// Pipeline resolver function called when an approver submits their decision
export async function main(args) {
// Retrieve the execution ID from TailorDB
const order = await gql.query({
order: {
__args: { id: args.orderId },
workflowExecutionId: true,
},
});
// Resolve the waiting workflow
await tailor.workflow.resolve(
order.workflowExecutionId,
"approval",
(waitPayload) => {
return {
approved: args.approved,
approvedBy: args.approverId,
};
},
);
}Execution Status
When a workflow calls wait(), the execution status transitions to waiting:
You can check the waiting status using the CLI:
tailor-sdk workflow executions --status WAITINGKey Behaviors
- No resource consumption while waiting — The workflow runner exits when
wait()is called. The execution is parked in the database. - Durable state — The wait payload and all previous job function results are preserved across the wait/resume cycle.
- Key matching — The
keyinresolve()must exactly match thekeyinwait(). A mismatched key results in an error. - Single resolve — Each wait point can only be resolved once. Concurrent resolve attempts for the same execution and key are safely rejected.
- Cache-aware — Wait results are integrated into the durable execution cache. If a resumed workflow is later retried, the cached wait result is reused without requiring another
resolve().