---
name: output-dev-workflow-function
description: Create workflow.ts files for Output SDK workflows. Use when defining workflow functions, orchestrating steps, or fixing workflow structure issues.
allowed-tools: [Read, Write, Edit]
---

# Creating workflow.ts Files

## Overview

This skill documents how to create `workflow.ts` files for Output SDK workflows. The workflow file contains the main orchestration logic that coordinates step execution.

## When to Use This Skill

- Creating a new workflow's main definition
- Understanding workflow structure requirements
- Debugging workflow orchestration issues
- Refactoring existing workflow logic

## Critical Rules

### 1. Import Pattern

```typescript
// CORRECT - Import from @outputai/core
import { workflow, z } from '@outputai/core';

// WRONG - Never import z from zod
import { z } from 'zod';
```

### 2. ES Module Imports

All imports MUST use `.js` extension:

```typescript
// CORRECT
import { stepName } from './steps.js';
import { WorkflowInputSchema } from './types.js';

// WRONG - Missing .js extension
import { stepName } from './steps';
import { WorkflowInputSchema } from './types';
```

### 3. Determinism Requirement

**CRITICAL**: The workflow `fn` must be deterministic. No direct I/O operations are allowed in the workflow function.

```typescript
// WRONG - Direct I/O in workflow
export default workflow( {
  // ...
  fn: async input => {
    const response = await fetch( 'https://api.example.com' ); // NEVER do this!
    return response.json();
  }
} );

// CORRECT - Delegate I/O to steps
export default workflow( {
  // ...
  fn: async input => {
    const result = await fetchDataStep( input ); // Steps handle I/O
    return result;
  }
} );
```

**Related Skill**: `output-error-nondeterminism`

## Basic Structure

```typescript
import { workflow, z } from '@outputai/core';

import { stepOne, stepTwo } from './steps.js';
import { WorkflowInputSchema, WorkflowOutput } from './types.js';

export default workflow( {
  name: 'workflowName',
  description: 'Brief description of what the workflow does',
  inputSchema: WorkflowInputSchema,
  outputSchema: z.object( { /* output shape */ } ),
  fn: async ( input ): Promise<WorkflowOutput> => {
    // Orchestrate step calls
    const result = await stepOne( input );
    const final = await stepTwo( result );
    return final;
  }
} );
```

## Required Properties

### name (string)
Unique identifier for the workflow. Use camelCase.

```typescript
name: 'contentUtilsImageInfographicNano'
```

### description (string)
Human-readable description of the workflow's purpose.

```typescript
description: 'Generate high-quality infographic images using AI-powered ideation'
```

### inputSchema (Zod schema)
Schema for validating workflow input. Import from `types.ts`.

```typescript
inputSchema: WorkflowInputSchema
```

**Related Skill**: `output-dev-types-file`

### outputSchema (Zod schema)
Schema for validating workflow output.

```typescript
outputSchema: z.object( {
  results: z.array( z.string() ),
  metadata: z.object( {
    processedAt: z.string()
  } )
} )
```

### fn (async function)
The workflow execution function. Must be deterministic.

```typescript
fn: async ( input ): Promise<WorkflowOutput> => {
  // Step orchestration only - no direct I/O
  const result = await processStep( input );
  return result;
}
```

## Complete Example

Based on a real workflow (`image_infographic_nano`):

```typescript
import { workflow, z } from '@outputai/core';

import {
  generateImageIdeas,
  generateImages,
  validateReferenceImages
} from './steps.js';
import {
  WorkflowInput,
  WorkflowInputSchema,
  WorkflowOutput
} from './types.js';
import { normalizeReferenceImageUrls } from './utils.js';

export default workflow( {
  name: 'contentUtilsImageInfographicNano',
  description: 'Generate high-quality infographic images using Google Gemini 3 Pro Image model with AI-powered ideation',
  inputSchema: WorkflowInputSchema,
  outputSchema: z.array( z.string() ),
  fn: async ( rawInput: WorkflowInput ): Promise<WorkflowOutput> => {
    // Pre-process input (pure function - OK in workflow)
    const input = {
      ...rawInput,
      referenceImageUrls: normalizeReferenceImageUrls( rawInput.referenceImageUrls )
    };

    // Conditional step execution
    if ( input.referenceImageUrls && input.referenceImageUrls.length > 0 ) {
      await validateReferenceImages( {
        referenceImageUrls: input.referenceImageUrls as string[]
      } );
    }

    // Sequential step execution
    const ideas = await generateImageIdeas( {
      content: input.content,
      numberOfIdeas: input.numberOfIdeas,
      colorPalette: input.colorPalette,
      artDirection: input.artDirection
    } );

    // Parallel step execution
    const generations = await Promise.all(
      ideas.map( idea =>
        generateImages( {
          input: {
            referenceImageUrls: input.referenceImageUrls,
            aspectRatio: input.aspectRatio,
            resolution: input.resolution,
            numberOfGenerations: input.numberOfGenerations,
            storageNamespace: input.storageNamespace
          },
          prompt: idea
        } )
      )
    );

    return generations.flat();
  }
} );
```

## Orchestration Patterns

### Sequential Execution

Execute steps one after another:

```typescript
fn: async input => {
  const step1Result = await stepOne( input );
  const step2Result = await stepTwo( step1Result );
  const step3Result = await stepThree( step2Result );
  return step3Result;
}
```

### Parallel Execution

Execute independent steps concurrently:

```typescript
fn: async input => {
  const [ resultA, resultB, resultC ] = await Promise.all( [
    stepA( input ),
    stepB( input ),
    stepC( input )
  ] );
  return { resultA, resultB, resultC };
}
```

### Conditional Execution

Execute steps based on conditions:

```typescript
fn: async input => {
  if ( input.includeImages ) {
    await processImages( input );
  }

  const result = input.mode === 'fast' ?
    await quickProcess( input ) :
    await detailedProcess( input );

  return result;
}
```

### Fan-Out Pattern

Process multiple items in parallel:

```typescript
fn: async input => {
  const results = await Promise.all(
    input.items.map( item => processItem( { item } ) )
  );
  return { processedItems: results };
}
```

### Pipeline Pattern

Chain multiple transformations:

```typescript
fn: async input => {
  const extracted = await extractData( input );
  const transformed = await transformData( extracted );
  const validated = await validateData( transformed );
  const enriched = await enrichData( validated );
  return enriched;
}
```

## What is Allowed in Workflow fn

### Allowed (Deterministic Operations)
- Calling step functions
- Pure data transformations
- Conditional logic based on input
- Array operations (map, filter, reduce)
- Object destructuring and construction
- Promise.all for parallel steps
- Control flow (if/else, loops)

### NOT Allowed (Non-Deterministic Operations)
- HTTP requests (use steps)
- Database queries (use steps)
- File system operations (use steps)
- Random number generation
- Current date/time access
- Environment variable access
- Any external service calls

## Verification Checklist

- [ ] `workflow` and `z` imported from `@outputai/core`
- [ ] All imports use `.js` extension
- [ ] Default export used for the workflow
- [ ] `name` is camelCase and unique
- [ ] `description` clearly explains the workflow
- [ ] `inputSchema` imported from `types.ts`
- [ ] `outputSchema` matches actual return type
- [ ] `fn` is deterministic (no direct I/O)
- [ ] All I/O delegated to step functions
- [ ] Code follows style conventions (see output-dev-code-style)

## Related Skills

- `output-dev-step-function` - Creating step functions that handle I/O
- `output-dev-evaluator-function` - Using steps in evaluator functions
- `output-dev-types-file` - Defining input/output schemas
- `output-dev-folder-structure` - Where workflow.ts belongs
- `output-error-nondeterminism` - Fixing determinism violations
- `output-error-zod-import` - Fixing schema import issues
- `output-dev-code-style`
