Skip to content

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Jan 27, 2026

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

[email protected]

Patch Changes

  • #799 d1a0c2b Thanks @threepointone! - feat: Add Cloudflare Workflows integration for Agents

    Adds seamless integration between Cloudflare Agents and Cloudflare Workflows for durable, multi-step background processing.

    Why use Workflows with Agents?

    Agents excel at real-time communication and state management, while Workflows excel at durable execution. Together:

    • Agents handle WebSocket connections and quick operations
    • Workflows handle long-running tasks, retries, and human-in-the-loop flows

    AgentWorkflow Base Class

    Extend AgentWorkflow instead of WorkflowEntrypoint to get typed access to the originating Agent:

    export class ProcessingWorkflow extends AgentWorkflow<MyAgent, TaskParams> {
      async run(event: AgentWorkflowEvent<TaskParams>, step: AgentWorkflowStep) {
        const params = event.payload;
    
        // Call Agent methods via RPC
        await this.agent.updateStatus(params.taskId, "processing");
    
        // Non-durable: progress reporting (lightweight, for frequent updates)
        await this.reportProgress({
          step: "process",
          percent: 0.5,
          message: "Halfway done"
        });
        this.broadcastToClients({ type: "update", taskId: params.taskId });
    
        // Durable via step: idempotent, won't repeat on retry
        await step.mergeAgentState({ taskProgress: 0.5 });
        await step.reportComplete(result);
    
        return result;
      }
    }

    Agent Methods

    • runWorkflow(workflowName, params, options?) - Start workflow with optional metadata for querying
    • sendWorkflowEvent(workflowName, workflowId, event) - Send events to waiting workflows
    • getWorkflow(workflowId) - Get tracked workflow by ID
    • getWorkflows(criteria?) - Query by status, workflowName, or metadata
    • deleteWorkflow(workflowId) - Delete a workflow tracking record
    • deleteWorkflows(criteria?) - Delete workflows by criteria (status, workflowName, metadata, createdBefore)
    • approveWorkflow(workflowId, data?) - Approve a waiting workflow
    • rejectWorkflow(workflowId, data?) - Reject a waiting workflow

    AgentWorkflow Methods

    On this (non-durable, lightweight):

    • reportProgress(progress) - Report typed progress object to Agent
    • broadcastToClients(message) - Broadcast to WebSocket clients
    • waitForApproval(step, opts?) - Wait for approval (throws on rejection)

    On step (durable, idempotent):

    • step.reportComplete(result?) - Report successful completion
    • step.reportError(error) - Report an error
    • step.sendEvent(event) - Send custom event to Agent
    • step.updateAgentState(state) - Replace Agent state (broadcasts to clients)
    • step.mergeAgentState(partial) - Merge into Agent state (broadcasts to clients)
    • step.resetAgentState() - Reset Agent state to initialState (broadcasts to clients)

    Lifecycle Callbacks

    Override these methods to handle workflow events (workflowName is first for easy differentiation):

    async onWorkflowProgress(workflowName, workflowId, progress) {} // progress is typed object
    async onWorkflowComplete(workflowName, workflowId, result?) {}
    async onWorkflowError(workflowName, workflowId, error) {}
    async onWorkflowEvent(workflowName, workflowId, event) {}

    Workflow Tracking

    Workflows are automatically tracked in cf_agents_workflows SQLite table:

    • Status, timestamps, errors
    • Optional metadata field for queryable key-value data
    • Params/output NOT stored by default (could be large)

    See docs/workflows.md for full documentation.

  • #781 fd79481 Thanks @HueCodes! - fix: properly type tool error content in getAITools

  • #811 f604008 Thanks @threepointone! - ### Secure Email Reply Routing

    This release introduces secure email reply routing with HMAC-SHA256 signed headers, preventing unauthorized routing of emails to arbitrary agent instances.

    Breaking Changes

    createHeaderBasedEmailResolver removed: This function now throws an error with migration guidance. It was removed because it trusted attacker-controlled email headers for routing.

    Migration:

    • For inbound mail: use createAddressBasedEmailResolver(agentName)
    • For reply flows: use createSecureReplyEmailResolver(secret) with signed headers

    EmailSendOptions type removed: This type was unused and has been removed.

    New Features

    createSecureReplyEmailResolver: A new resolver that verifies HMAC-SHA256 signatures on incoming emails before routing. Signatures include a timestamp and expire after 30 days by default.

    const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
      maxAge: 7 * 24 * 60 * 60, // Optional: 7 days (default: 30 days)
      onInvalidSignature: (email, reason) => {
        // Optional: log failures for debugging
        // reason: "missing_headers" | "expired" | "invalid" | "malformed_timestamp"
        console.warn(`Invalid signature from ${email.from}: ${reason}`);
      }
    });

    signAgentHeaders: Helper function to manually sign agent routing headers for use with external email services.

    const headers = await signAgentHeaders(secret, agentName, agentId);
    // Returns: { "X-Agent-Name", "X-Agent-ID", "X-Agent-Sig", "X-Agent-Sig-Ts" }

    replyToEmail signing: The replyToEmail method now accepts a secret option to automatically sign outbound email headers.

    await this.replyToEmail(email, {
      fromName: "My Agent",
      body: "Thanks!",
      secret: this.env.EMAIL_SECRET // Signs headers for secure reply routing
    });

    If an email was routed via createSecureReplyEmailResolver, calling replyToEmail without a secret will throw an error (pass explicit null to opt-out).

    onNoRoute callback: routeAgentEmail now accepts an onNoRoute callback for handling emails that don't match any routing rule.

    await routeAgentEmail(message, env, {
      resolver,
      onNoRoute: (email) => {
        email.setReject("Unknown recipient");
      }
    });
  • #800 a54edf5 Thanks @threepointone! - Update dependencies

  • Updated dependencies [77be4f8, a54edf5, 99cbca0]:

@cloudflare/[email protected]

Patch Changes

@cloudflare/[email protected]

Patch Changes

[email protected]

Patch Changes

@github-actions github-actions bot force-pushed the changeset-release/main branch 7 times, most recently from 47d3741 to 200ebd7 Compare January 29, 2026 10:14
@github-actions github-actions bot force-pushed the changeset-release/main branch from 200ebd7 to f9bc243 Compare January 29, 2026 22:50
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.

0 participants