Skip to content

Add Think workflow prompt integration#1598

Merged
threepointone merged 7 commits into
mainfrom
think-workflows
May 28, 2026
Merged

Add Think workflow prompt integration#1598
threepointone merged 7 commits into
mainfrom
think-workflows

Conversation

@threepointone
Copy link
Copy Markdown
Contributor

@threepointone threepointone commented May 27, 2026

Summary

This adds a first-class Think integration for Cloudflare Workflows via @cloudflare/think/workflows.

The main new API is ThinkWorkflow plus step.prompt(), which lets a Workflow run a single durable Think reasoning turn and resume with typed structured output:

const draft = await step.prompt("draft-report", {
  prompt: `Draft an operational report about: ${event.payload.topic}`,
  output: reportDraftSchema,
  timeout: "3 days"
});

The call reads like a blocking Workflow step, but it is implemented as an event-driven submit-and-resume flow so it does not hold a long-lived Durable Object RPC open.

What Changed

  • Added @cloudflare/think/workflows with ThinkWorkflow, ThinkWorkflowStep, ThinkPromptOptions, and typed prompt errors.
  • Added AgentWorkflow.extendStep() so framework-specific workflow integrations can add helpers to the wrapped Workflow step without duplicating the base Agent workflow machinery.
  • Added step.prompt(name, options) for Think workflows:
    • accepts prompt: string and a Zod object output schema
    • converts the schema to JSON Schema for the submitted Think turn
    • re-validates the returned output with the original Zod schema when the Workflow resumes
    • infers submission idempotency from workflow name, workflow id, step name, and optional string key
    • supports Workflow wait timeouts and cancels the Think submission by default on timeout
  • Updated Think programmatic submissions to capture AI SDK structured output when invoked by a Workflow prompt.
  • Kept workflow prompt configuration on an internal inference-loop field so client/app body data cannot accidentally trigger workflow structured-output mode.
  • Namespaced workflow prompt control data under a private metadata envelope so public workflow/workflowPrompt metadata remains ordinary caller metadata.
  • Added a private durable workflow notification outbox in Think:
    • terminal workflow submissions enqueue a notification row
    • notification delivery calls sendWorkflowEvent() once per drain pass and relies on its built-in transient retry plus outbox alarm retry
    • Durable Object alarms continue draining pending notifications after failures
    • delivered rows are retained but have their payload cleared
    • terminal aborted, skipped, and error submissions can be recovered into missing notifications on startup
  • Added a server-side examples/think-workflows example showing:
    • starting a Workflow from an Agent with runWorkflow()
    • using step.prompt() for a report draft
    • waiting for approval with step.waitForEvent()
    • saving deterministic side effects through the originating Agent
  • Added Think docs covering:
    • when to use Workflows versus scheduled tasks, submissions, or fibers
    • how to start a Workflow from inside a Think Agent
    • why Agent workflows should use runWorkflow() rather than direct env.WORKFLOW.create()
    • idempotency, timeout, structured output, and notification delivery behavior
  • Added a changeset for @cloudflare/think and agents.

API Shape

New import:

import { ThinkWorkflow } from "@cloudflare/think/workflows";
import type { ThinkWorkflowStep } from "@cloudflare/think/workflows";

Workflow definition:

export class ReportWorkflow extends ThinkWorkflow<ReportAgent, ReportParams> {
  async run(event: AgentWorkflowEvent<ReportParams>, step: ThinkWorkflowStep) {
    const draft = await step.prompt("draft-report", {
      prompt: `Draft an operational report about: ${event.payload.topic}`,
      output: reportDraftSchema,
      timeout: "3 days"
    });

    await step.do("store-draft", async () => {
      await this.agent.saveReport({ draft, status: "drafted" });
    });
  }
}

Agent entry point:

async startReport(topic: string) {
  const reportId = crypto.randomUUID();
  const workflowId = await this.runWorkflow(
    "REPORT_WORKFLOW",
    { reportId, topic },
    { metadata: { reportId, topic } }
  );
  return { reportId, workflowId };
}

Design Notes

The important design choice is that step.prompt() does not synchronously wait on the Agent RPC. It submits a durable Think submission inside step.do(), then waits for a Workflow event. Think sends that event from a private notification outbox when the submission reaches a terminal state.

This gives the API a simple blocking shape while preserving the durable execution model:

  1. Workflow submits or reuses an idempotent Think submission.
  2. Think processes the turn through its existing durable submission queue.
  3. Terminal submission state enqueues a Workflow notification.
  4. Think retries notification delivery until sendWorkflowEvent() succeeds.
  5. Workflow resumes from step.waitForEvent().
  6. step.prompt() validates structured output or throws a typed error.

Tests

  • npm run test -w @cloudflare/think -- submissions.test.ts
  • npx vitest --run -c packages/agents/src/tests/vitest.config.ts packages/agents/src/tests/workflow-prototype.test.ts
  • npx nx run @cloudflare/think:build
  • npx nx run agents:build
  • npm run check
  • After the rename: npx nx run @cloudflare/think:build
  • After the rename: npm run typecheck -- examples/think-workflows/tsconfig.json packages/think/tsconfig.json packages/think/src/tests/tsconfig.json
  • After the structured output fix: npm run test -w @cloudflare/think -- submissions.test.ts
  • After the structured output fix: npx nx run @cloudflare/think:build
  • After the structured output fix: npm run typecheck -- packages/think/tsconfig.json packages/think/src/tests/tsconfig.json examples/think-workflows/tsconfig.json
  • After internalizing workflow prompt config: npm run test -w @cloudflare/think -- submissions.test.ts
  • After internalizing workflow prompt config: npx nx run @cloudflare/think:build
  • After internalizing workflow prompt config: npm run typecheck -- packages/think/tsconfig.json packages/think/src/tests/tsconfig.json examples/think-workflows/tsconfig.json
  • After the timeout cancellation fix: npx nx run @cloudflare/think:build
  • After the timeout cancellation fix: npm run typecheck -- packages/think/tsconfig.json examples/think-workflows/tsconfig.json
  • After simplifying notification delivery retries: npm run test -w @cloudflare/think -- submissions.test.ts
  • After simplifying notification delivery retries: npx nx run @cloudflare/think:build
  • After simplifying notification delivery retries: npm run typecheck -- packages/think/tsconfig.json packages/think/src/tests/tsconfig.json examples/think-workflows/tsconfig.json
  • After reserving workflow prompt metadata: npm run test -w @cloudflare/think -- submissions.test.ts
  • After reserving workflow prompt metadata: npx nx run @cloudflare/think:build
  • After reserving workflow prompt metadata: npm run typecheck -- packages/think/tsconfig.json packages/think/src/tests/tsconfig.json examples/think-workflows/tsconfig.json

The original full npm run check passed before the rename, with only the repo's existing sherif warnings. After the rename, @cloudflare/think builds and the new examples/think-workflows project typechecks; a full npm run check rerun reached typecheck and failed in unrelated examples/mcp-rpc-transport because TypeScript could not resolve workers-ai-provider from that example.

Remaining Follow-Up

There is not yet a true end-to-end Workflows runtime test that drives step.prompt() through an actual waitForEvent() resume. The focused tests cover the risky durable pieces added here: the base step-extension hook, workflow notification recovery, successful delivery, payload clearing, retry state after failed delivery, structured output capture in terminal workflow notifications, and isolation from workflow-shaped client body/metadata data.

Introduce @cloudflare/think/workflows with ThinkAgentWorkflow and step.prompt() so Workflows can run a durable Think reasoning turn, wait for completion through Workflow events, and resume with validated structured output. The API infers submission idempotency from workflow identity, step name, and an optional string key to keep repeated workflow execution retry-safe without exposing low-level keys.

Wire Think submissions to Workflows through a private durable notification outbox that records terminal submission events, retries sendWorkflowEvent(), and marks delivered rows while clearing payloads. This avoids long-lived RPCs between Workflow steps and Agents while still letting the Workflow own the final durable result.

Add a server-side think-workflows example, docs for starting workflows from an Agent with runWorkflow(), and focused tests for the step extension hook plus workflow notification recovery, delivery, and retry behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 27, 2026

🦋 Changeset detected

Latest commit: 200fde5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@cloudflare/think Minor
agents Minor

Not sure what this means? Click here to learn what changesets are.

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

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment thread packages/think/src/think.ts
Comment thread packages/think/src/think.ts Outdated
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 27, 2026

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@1598

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@1598

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@1598

hono-agents

npm i https://pkg.pr.new/hono-agents@1598

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@1598

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@1598

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@1598

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@1598

commit: 200fde5

threepointone and others added 6 commits May 27, 2026 20:32
Shorten the public workflow API names from ThinkAgentWorkflow and ThinkAgentWorkflowStep to ThinkWorkflow and ThinkWorkflowStep so the new @cloudflare/think/workflows surface reads more naturally in user code and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Preserve the AI SDK streamText output promise when a turn is configured for structured output, and pass workflow prompt metadata through the same shape that step.prompt stores on submissions. This lets workflow submissions install the JSON schema on streamText and include the parsed structured output in the terminal workflow notification.

Add a regression test that runs a workflow-tagged durable submission with a JSON schema and verifies the delivered workflow event contains the captured output.

Co-authored-by: Cursor <cursoragent@cursor.com>
Stop deriving workflow structured-output configuration from the public turn body. Durable workflow submissions now pass the parsed workflow prompt context through an internal inference-loop option, leaving client and app body data available to hooks without allowing workflow-shaped body fields to alter output mode.

Add a regression test proving workflow-shaped client body data on a normal submission does not configure structured output or emit workflow events.

Co-authored-by: Cursor <cursoragent@cursor.com>
Make the submission cancellation attempt after a prompt wait timeout best-effort so a transient Agent RPC failure cannot mask the documented ThinkPromptTimeoutError outcome from step.prompt().

Co-authored-by: Cursor <cursoragent@cursor.com>
Remove the extra outbox-level this.retry() wrapper around sendWorkflowEvent(). The Agent workflow helper already retries transient sendEvent failures, and the durable notification outbox plus alarm backoff provides the long-horizon retry loop without multiplying immediate delivery attempts.

Co-authored-by: Cursor <cursoragent@cursor.com>
Move Think workflow prompt control data under a private metadata envelope so public submitMessages metadata that happens to use workflow-shaped keys cannot enable structured output or enqueue workflow notifications.

Add regression coverage for public workflow/workflowPrompt metadata remaining ordinary caller metadata.

Co-authored-by: Cursor <cursoragent@cursor.com>
@threepointone threepointone merged commit f5e37bf into main May 28, 2026
4 checks passed
@threepointone threepointone deleted the think-workflows branch May 28, 2026 09:00
@github-actions github-actions Bot mentioned this pull request May 28, 2026
threepointone added a commit that referenced this pull request May 28, 2026
* Add Think workflow prompt integration

Introduce @cloudflare/think/workflows with ThinkAgentWorkflow and step.prompt() so Workflows can run a durable Think reasoning turn, wait for completion through Workflow events, and resume with validated structured output. The API infers submission idempotency from workflow identity, step name, and an optional string key to keep repeated workflow execution retry-safe without exposing low-level keys.

Wire Think submissions to Workflows through a private durable notification outbox that records terminal submission events, retries sendWorkflowEvent(), and marks delivered rows while clearing payloads. This avoids long-lived RPCs between Workflow steps and Agents while still letting the Workflow own the final durable result.

Add a server-side think-workflows example, docs for starting workflows from an Agent with runWorkflow(), and focused tests for the step extension hook plus workflow notification recovery, delivery, and retry behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rename Think workflow exports

Shorten the public workflow API names from ThinkAgentWorkflow and ThinkAgentWorkflowStep to ThinkWorkflow and ThinkWorkflowStep so the new @cloudflare/think/workflows surface reads more naturally in user code and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix Think workflow structured output capture

Preserve the AI SDK streamText output promise when a turn is configured for structured output, and pass workflow prompt metadata through the same shape that step.prompt stores on submissions. This lets workflow submissions install the JSON schema on streamText and include the parsed structured output in the terminal workflow notification.

Add a regression test that runs a workflow-tagged durable submission with a JSON schema and verifies the delivered workflow event contains the captured output.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Keep workflow prompt config internal

Stop deriving workflow structured-output configuration from the public turn body. Durable workflow submissions now pass the parsed workflow prompt context through an internal inference-loop option, leaving client and app body data available to hooks without allowing workflow-shaped body fields to alter output mode.

Add a regression test proving workflow-shaped client body data on a normal submission does not configure structured output or emit workflow events.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Preserve Think workflow timeout errors

Make the submission cancellation attempt after a prompt wait timeout best-effort so a transient Agent RPC failure cannot mask the documented ThinkPromptTimeoutError outcome from step.prompt().

Co-authored-by: Cursor <cursoragent@cursor.com>

* Simplify workflow notification delivery retries

Remove the extra outbox-level this.retry() wrapper around sendWorkflowEvent(). The Agent workflow helper already retries transient sendEvent failures, and the durable notification outbox plus alarm backoff provides the long-horizon retry loop without multiplying immediate delivery attempts.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Reserve workflow prompt metadata

Move Think workflow prompt control data under a private metadata envelope so public submitMessages metadata that happens to use workflow-shaped keys cannot enable structured output or enqueue workflow notifications.

Add regression coverage for public workflow/workflowPrompt metadata remaining ordinary caller metadata.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
threepointone added a commit that referenced this pull request May 28, 2026
* Add Think workflow prompt integration

Introduce @cloudflare/think/workflows with ThinkAgentWorkflow and step.prompt() so Workflows can run a durable Think reasoning turn, wait for completion through Workflow events, and resume with validated structured output. The API infers submission idempotency from workflow identity, step name, and an optional string key to keep repeated workflow execution retry-safe without exposing low-level keys.

Wire Think submissions to Workflows through a private durable notification outbox that records terminal submission events, retries sendWorkflowEvent(), and marks delivered rows while clearing payloads. This avoids long-lived RPCs between Workflow steps and Agents while still letting the Workflow own the final durable result.

Add a server-side think-workflows example, docs for starting workflows from an Agent with runWorkflow(), and focused tests for the step extension hook plus workflow notification recovery, delivery, and retry behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rename Think workflow exports

Shorten the public workflow API names from ThinkAgentWorkflow and ThinkAgentWorkflowStep to ThinkWorkflow and ThinkWorkflowStep so the new @cloudflare/think/workflows surface reads more naturally in user code and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix Think workflow structured output capture

Preserve the AI SDK streamText output promise when a turn is configured for structured output, and pass workflow prompt metadata through the same shape that step.prompt stores on submissions. This lets workflow submissions install the JSON schema on streamText and include the parsed structured output in the terminal workflow notification.

Add a regression test that runs a workflow-tagged durable submission with a JSON schema and verifies the delivered workflow event contains the captured output.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Keep workflow prompt config internal

Stop deriving workflow structured-output configuration from the public turn body. Durable workflow submissions now pass the parsed workflow prompt context through an internal inference-loop option, leaving client and app body data available to hooks without allowing workflow-shaped body fields to alter output mode.

Add a regression test proving workflow-shaped client body data on a normal submission does not configure structured output or emit workflow events.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Preserve Think workflow timeout errors

Make the submission cancellation attempt after a prompt wait timeout best-effort so a transient Agent RPC failure cannot mask the documented ThinkPromptTimeoutError outcome from step.prompt().

Co-authored-by: Cursor <cursoragent@cursor.com>

* Simplify workflow notification delivery retries

Remove the extra outbox-level this.retry() wrapper around sendWorkflowEvent(). The Agent workflow helper already retries transient sendEvent failures, and the durable notification outbox plus alarm backoff provides the long-horizon retry loop without multiplying immediate delivery attempts.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Reserve workflow prompt metadata

Move Think workflow prompt control data under a private metadata envelope so public submitMessages metadata that happens to use workflow-shaped keys cannot enable structured output or enqueue workflow notifications.

Add regression coverage for public workflow/workflowPrompt metadata remaining ordinary caller metadata.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
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