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:
- Start — A workflow is started and begins executing
- Wait — The workflow job calls a wait point's
.wait()method, which suspends execution and parks it in the database - Resolve — Another script calls the same wait point's
.resolve()method with the execution ID, 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
Wait points are declared with createWaitPoint (single) or createWaitPoints (multiple, via a builder callback) from @tailor-platform/sdk. Each returns an object with .wait() and .resolve() methods, typed by a Payload (sent to .wait()) and a Result (returned from .wait(), produced by the .resolve() callback) — both must be JSON-serializable values.
import { createWaitPoint } from "@tailor-platform/sdk";
export const approval = createWaitPoint<
{ orderId: string; amount: number; requestedBy: string },
{ approved: boolean }
>("approval");wait(payload)
Suspends the current workflow job and waits for an external signal.
Arguments:
payload(optional) — JSON-serializable data to persist while waiting. This data is available to the.resolve()callback. Omit if the wait point'sPayloadtype isundefined.
Return value:
- The result returned by the
.resolve()callback when the workflow resumes.
Example:
import { createWorkflowJob } from "@tailor-platform/sdk";
import { approval } from "../approval";
export const processOrder = createWorkflowJob({
name: "process-order",
body: async (input: { orderId: string; totalAmount: number; userId: string }) => {
const order = { id: input.orderId };
// Pause and wait for approval
const result = await approval.wait({
orderId: order.id,
amount: input.totalAmount,
requestedBy: input.userId,
});
if (!result.approved) {
return { orderId: order.id, status: "rejected" as const };
}
return { orderId: order.id, status: "approved" as const };
},
});resolve(executionId, callback)
Resolves a waiting workflow, causing it to resume execution.
Arguments:
executionId(string, required) — The execution ID of the waiting workflow.callback(function, required) — A function that receives the payload passed to.wait()and returns theResult. The returned value is passed back to the.wait()caller when the workflow resumes.
Return value:
- None. The callback result is delivered to the waiting workflow asynchronously.
Example:
import { createResolver, t } from "@tailor-platform/sdk";
import { approval } from "../workflows/approval";
export default createResolver({
name: "resolveApproval",
operation: "mutation",
input: {
executionId: t.string(),
approved: t.bool(),
},
body: async ({ input }) => {
await approval.resolve(input.executionId, (payload) => {
// payload contains the data passed to wait()
// e.g., { orderId: "...", amount: 1000, requestedBy: "..." }
return { approved: input.approved };
});
return { resolved: true };
},
output: t.object({ resolved: t.bool() }),
});Wait points can be imported and used in any file (workflow jobs, resolvers, executors).
Typical Pattern
A common implementation pattern involves three components:
- A workflow that starts processing and pauses on a wait point's
.wait() - A TailorDB record that stores the execution ID for later retrieval
- A resolver function that calls the wait point's
.resolve()when the human decision is made
Step 1: Start the workflow and save the execution ID
// Resolver or executor function
export async function main(args) {
const executionId = await tailor.workflow.startWorkflow("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 on approval.wait()
import { createWorkflowJob } from "@tailor-platform/sdk";
import { approval } from "../approval";
export const prepareOrder = createWorkflowJob({
name: "prepare-order",
body: async (input: { orderId: string; items: unknown[]; total: number }) => {
// Pause and wait for human approval
const decision = await approval.wait({
orderId: input.orderId,
items: input.items,
total: input.total,
});
return { status: decision.approved ? "completed" : "rejected" };
},
});Step 3: Resolve from a resolver function
import { createResolver, t } from "@tailor-platform/sdk";
import { getDB } from "../generated/tailordb";
import { approval } from "../workflows/approval";
export default createResolver({
name: "resolveApproval",
operation: "mutation",
input: {
orderId: t.uuid(),
approved: t.bool(),
},
body: async ({ input }) => {
// Retrieve the execution ID from TailorDB
const db = getDB("tailordb");
const order = await db
.selectFrom("Order")
.select("workflowExecutionId")
.where("id", "=", input.orderId)
.executeTakeFirstOrThrow();
// Resolve the waiting workflow
await approval.resolve(order.workflowExecutionId, (payload) => {
return { approved: input.approved };
});
return { resolved: true };
},
output: t.object({ resolved: t.bool() }),
});Execution Status
When a workflow calls wait(), the execution status transitions to waiting:
You can check the waiting status using the CLI:
tailor 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 — Each wait point has its own key (the string passed to
createWaitPoint, or the property name undercreateWaitPoints). Resolving requires importing and calling.resolve()on that same wait point value — you can't resolve a suspended execution against a different wait point. - 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().