Skip to content

feat(workflow): add wait/resolve support for human-in-the-loop workflows#985

Closed
toiroakr wants to merge 1 commit into
mainfrom
feat/workflow-resolve
Closed

feat(workflow): add wait/resolve support for human-in-the-loop workflows#985
toiroakr wants to merge 1 commit into
mainfrom
feat/workflow-resolve

Conversation

@toiroakr

@toiroakr toiroakr commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements SDK support for tailor.workflow.wait() and tailor.workflow.resolve() (platform-planning#995, function#154).

  • Add waitPoints config to createWorkflowJob with waitPoint<Payload, Result>() helper for typed wait/resolve definitions
  • Body context receives a typed wait(key, payload?) function; callers use job.resolve(key, executionId, callback) to resume waiting executions
  • Bundler injects tailor.workflow.wait into entry code for jobs with wait points
  • Bundler transforms .resolve() calls to tailor.workflow.resolve() with argument reordering (key, execId, cbexecId, key, cb)
  • Local testing uses in-memory promise coordination between wait and resolve
  • Add setupWaitPointMock test utility for bundled workflow tests

Usage

export const processOrder = createWorkflowJob({
  name: "process-order",
  waitPoints: {
    approval: waitPoint<{ message: string }, { approved: boolean }>(),
  },
  body: async (input: { orderId: string }, { wait }) => {
    const result = await wait("approval", { message: `Approve ${input.orderId}?` });
    return { orderId: input.orderId, approved: result.approved };
  },
});

// From resolver/executor:
await processOrder.resolve("approval", executionId, (payload) => {
  return { approved: true };
});

Open with Devin

Add typed `waitPoints` config to `createWorkflowJob` and `waitPoint<P, R>()`
helper for defining wait/resolve points. The body context receives a typed
`wait` function; callers use `job.resolve()` to resume waiting executions.

Bundler injects `tailor.workflow.wait` into entry code and transforms
`.resolve()` calls to `tailor.workflow.resolve()` with argument reordering.
Local testing uses in-memory promise coordination.
@toiroakr
toiroakr requested a review from remiposo as a code owner April 14, 2026 13:53
@changeset-bot

changeset-bot Bot commented Apr 14, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: b6cff10

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Apr 14, 2026

Copy link
Copy Markdown

⚡ pkg.pr.new

@tailor-platform/sdk

pnpm add https://pkg.pr.new/@tailor-platform/sdk@b6cff10
pnpm dlx https://pkg.pr.new/@tailor-platform/sdk@b6cff10 --help

@tailor-platform/create-sdk

pnpm add https://pkg.pr.new/@tailor-platform/create-sdk@b6cff10
pnpm dlx https://pkg.pr.new/@tailor-platform/create-sdk@b6cff10 my-app

commit: b6cff10

@claude

claude Bot commented Apr 14, 2026

Copy link
Copy Markdown

📖 Docs Consistency Check

⚠️ Inconsistencies Found

File Issue Suggested Fix
packages/sdk/docs/services/workflow.md New wait/resolve feature not documented Add section documenting waitPoints, waitPoint(), wait(), and job.resolve()
CLAUDE.md Wait points not mentioned in workflow gotchas Add non-obvious rules about wait/resolve behavior
packages/sdk/docs/testing.md setupWaitPointMock() utility not documented Add section on testing workflows with wait points

Details

1. packages/sdk/docs/services/workflow.md

Issue: The workflow documentation does not cover the new wait/resolve feature for human-in-the-loop workflows.

Missing API documentation:

  • waitPoints config option in createWorkflowJob (see packages/sdk/src/configure/services/workflow/job.ts:228)
  • waitPoint<Payload, Result>() helper function (see packages/sdk/src/configure/services/workflow/wait-point.ts:90)
  • wait(key, payload?) function available in job body context when waitPoints is defined
  • job.resolve(key, executionId, callback) method for resuming waiting executions

Example from the implementation:

export const processOrder = createWorkflowJob({
  name: "process-order",
  waitPoints: {
    approval: waitPoint<{ message: string }, { approved: boolean }>(),
  },
  body: async (input: { orderId: string }, { wait }) => {
    const result = await wait("approval", { message: `Approve ${input.orderId}?` });
    return { orderId: input.orderId, approved: result.approved };
  },
});

// From resolver/executor:
await processOrder.resolve("approval", executionId, (payload) => {
  return { approved: true };
});

Suggested location: Add a new section after "Triggering Jobs" (line 88) titled "Wait Points for Human-in-the-Loop Workflows"


2. CLAUDE.md

Issue: The "Non-obvious Rules and Gotchas" > "Workflows" section (lines 51-57) does not mention wait points.

Missing information:

  • When a job declares waitPoints, the context includes a typed wait function
  • The bundler transforms .resolve() calls to reorder arguments: job.resolve("key", execId, cb)tailor.workflow.resolve(execId, "key", cb)
  • Local testing uses in-memory promise coordination; production uses platform wait/resolve

Suggested addition: Add to the workflow gotchas section:

- Jobs with `waitPoints` receive a typed `wait()` function in the body context
- `.resolve()` argument order is transformed during bundling (key comes before executionId in source, executionId comes first in runtime)

3. packages/sdk/docs/testing.md

Issue: The "Workflow Tests" section (lines 174-253) does not cover testing workflows with wait points.

Missing utility documentation:

  • setupWaitPointMock() - exported from @tailor-platform/sdk/test (see packages/sdk/src/utils/test/index.ts:11)
  • How to test workflows that use wait() and resolve()

Example from implementation:

import { setupWaitPointMock } from "@tailor-platform/sdk/test";

const { waitCalls, resolveCalls } = setupWaitPointMock({
  onWait: (key, payload) => {
    return { approved: true };
  }
});

Suggested location: Add a new subsection under "Workflow Tests" titled "Testing Wait Points"


Recommended Actions

  1. Update packages/sdk/docs/services/workflow.md:

    • Add comprehensive section on wait/resolve feature with examples
    • Document type constraints (payload must be JSON-compatible, result can be Jsonifiable)
    • Explain local vs production behavior
  2. Update CLAUDE.md:

    • Add wait point rules to the "Workflows" gotchas section
    • Mention argument reordering during bundling
  3. Update packages/sdk/docs/testing.md:

    • Add section on testing workflows with wait points
    • Document setupWaitPointMock() utility with example usage
  4. Reference the example:

    • Consider adding a reference to example/workflows/approval.ts in the workflow docs as a working example

@github-actions

Copy link
Copy Markdown

Code Metrics Report (packages/sdk)

main (b5d120d) #985 (6d3b825) +/-
Coverage 57.8% 57.9% +0.0%
Code to Test Ratio 1:0.4 1:0.4 +0.0
Details
  |                    | main (b5d120d) | #985 (6d3b825) |  +/-  |
  |--------------------|----------------|----------------|-------|
+ | Coverage           |          57.8% |          57.9% | +0.0% |
  |   Files            |            343 |            344 |    +1 |
  |   Lines            |          11475 |          11587 |  +112 |
+ |   Covered          |           6639 |           6715 |   +76 |
+ | Code to Test Ratio |          1:0.4 |          1:0.4 |  +0.0 |
  |   Code             |          71996 |          72688 |  +692 |
+ |   Test             |          29425 |          29753 |  +328 |

Code coverage of files in pull request scope (85.6% → 82.3%)

Files Coverage +/- Status
packages/sdk/src/cli/services/workflow/bundler.ts 92.5% +0.0% modified
packages/sdk/src/cli/services/workflow/job-detector.ts 100.0% 0.0% modified
packages/sdk/src/cli/services/workflow/service.ts 80.5% +0.5% modified
packages/sdk/src/cli/services/workflow/source-transformer.ts 69.8% -23.7% modified
packages/sdk/src/cli/services/workflow/trigger-transformer.ts 91.2% +1.3% modified
packages/sdk/src/cli/shared/trigger-context.ts 78.2% -3.8% modified
packages/sdk/src/configure/services/workflow/index.ts 0.0% 0.0% modified
packages/sdk/src/configure/services/workflow/job.ts 100.0% +50.0% modified
packages/sdk/src/configure/services/workflow/wait-point.ts 100.0% +100.0% added
packages/sdk/src/parser/service/workflow/schema.ts 61.5% 0.0% modified
packages/sdk/src/utils/test/mock.ts 24.3% -7.9% modified

SDK Configure Bundle Size

main (b5d120d) #985 (6d3b825) +/-
configure-index-size 12.9KB 13.5KB 0.6KB
dependency-chunks-size 44.27KB 45.45KB 1.18KB
total-bundle-size 57.17KB 58.95KB 1.78KB

Runtime Performance

main (b5d120d) #985 (6d3b825) +/-
Generate Median 2,577ms 2,422ms -155ms
Generate Max 2,790ms 2,633ms -157ms
Apply Build Median 2,595ms 2,459ms -136ms
Apply Build Max 2,626ms 2,471ms -155ms

Type Performance (instantiations)

main (b5d120d) #985 (6d3b825) +/-
tailordb-basic 46,168 47,246 1,078
tailordb-optional 3,826 3,826 0
tailordb-relation 3,970 3,970 0
tailordb-validate 2,824 2,824 0
tailordb-hooks 5,689 5,689 0
tailordb-object 11,470 11,470 0
tailordb-enum 2,720 2,720 0
resolver-basic 9,253 9,253 0
resolver-nested 25,640 25,640 0
resolver-array 17,876 17,876 0
executor-schedule 4,234 4,239 5
executor-webhook 873 878 5
executor-record 4,736 4,741 5
executor-resolver 4,272 4,277 5
executor-operation-function 867 872 5
executor-operation-gql 869 874 5
executor-operation-webhook 888 893 5
executor-operation-workflow 2,280 3,133 853

Reported by octocov

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

hasAwait: false,
});
}
return; // Skip further processing for this node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Early return in detectExtendedTriggerCalls prevents walking children of resolve call nodes, missing nested trigger/resolve calls inside callbacks

In detectExtendedTriggerCalls, after detecting a .resolve() call on a known job, the return at line 164 exits the walk function entirely, skipping the child-walking loop at lines 225-232. This means any .trigger() or .resolve() calls nested inside the resolve callback argument won't be detected or transformed.

Affected code paths and example

This function is used by transformFunctionTriggers which is the sole transform for resolver, executor, and auth hook bundlers (via createTriggerTransformPlugin at trigger-context.ts:143). For example, a resolver with:

await processOrder.resolve("approval", executionId, async (payload) => {
  const data = await fetchData.trigger({ id: payload.id });
  return { approved: true, data };
});

The fetchData.trigger() inside the callback won't be transformed to tailor.workflow.triggerJobFunction(...), producing bundled code that references the non-existent fetchData.trigger at runtime.

By contrast, the analogous detectResolveCalls in source-transformer.ts:36-90 does NOT have this early return — after pushing a known resolve call it falls through to the child-walking loop, correctly detecting nested calls.

Prompt for agents
In detectExtendedTriggerCalls (trigger-transformer.ts around line 164), the `return;` after detecting a .resolve() call prevents the AST walker from visiting child nodes of the call expression. This means nested .trigger() or .resolve() calls inside the callback argument are never detected.

The fix is to remove or restructure the early return so that after pushing a resolve call, the function still falls through to the child-walking loop (lines 225-232). The return was likely intended to prevent the trigger detection block (lines 167-221) from running on the same node, but that block already checks for propertyName === 'trigger' so it won't match a resolve call. Simply removing the `return;` at line 164 should be sufficient.

For reference, see how source-transformer.ts detectResolveCalls handles this correctly — it pushes the call and continues to walk children without returning early.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@toiroakr toiroakr closed this Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant