> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ensemble.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Script Components

> Reusable JavaScript/TypeScript code for Code operations

<Warning>
  **Cloudflare Workers Compatibility**: Inline code (`config.code`) is **not supported** in Cloudflare Workers due to security restrictions. All code must be in script files that are bundled at build time.
</Warning>

**Define reusable code scripts as components for consistent business logic across ensembles.**

## Overview

Script components enable you to:

* **Reuse code logic** across multiple agents and ensembles
* **Version scripts** with semantic versioning for reproducibility
* **Organize** TypeScript code separately from YAML
* **Type-safe** development with full TypeScript support
* **Test** scripts independently with unit tests
* **Bundle** scripts at build time for Workers compatibility

## Quick Start

### 1. Create a Script File

Create a TypeScript file in your `scripts/` directory:

```typescript theme={null}
// scripts/transform-data.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

interface TransformInput {
  raw_data: Array<{
    id: string
    value: number
    min: number
    max: number
    created_at: string
  }>
}

export default function transformData(context: AgentExecutionContext) {
  const { raw_data } = context.input as TransformInput

  const processed = raw_data.map(item => ({
    id: item.id,
    normalized_value: (item.value - item.min) / (item.max - item.min),
    timestamp: new Date(item.created_at).toISOString()
  }))

  return {
    processed,
    total_items: processed.length
  }
}
```

### 2. Reference in Your Ensemble

```yaml theme={null}
name: data-processor

agents:
  - name: transform
    operation: code
    config:
      script: scripts/transform-data
    input:
      raw_data: $input.raw_data

input:
  raw_data: []

output:
  result: ${transform.output}
```

### 3. Build & Deploy

Scripts are automatically discovered and bundled during the build:

```bash theme={null}
npm run build    # Scripts are bundled automatically
npm run deploy   # Deploy to Cloudflare
```

## URI Formats

Both formats are supported for referencing scripts:

| Format       | Example                         |
| ------------ | ------------------------------- |
| Shorthand    | `scripts/transforms/csv`        |
| Full URI     | `script://transforms/csv`       |
| With version | `scripts/transforms/csv@v1.0.0` |

```yaml theme={null}
# Shorthand format (recommended)
script: scripts/transform-data

# Full URI format
script: "script://transform-data"

# Nested paths
script: scripts/pipelines/etl/normalize

# With version (for edgit-managed components)
script: "script://transform-data@v1.0.0"
```

## Script File Format

Scripts must export a default function that receives `AgentExecutionContext`:

```typescript theme={null}
// scripts/my-script.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function myScript(context: AgentExecutionContext) {
  // Available in context:
  const {
    input,      // Input data from ensemble
    env,        // Environment bindings (KV, D1, etc.)
    logger,     // Logger instance
    request,    // HTTP request (if triggered via HTTP)
    cache,      // Cache utilities
  } = context

  // Your logic here
  const result = processData(input)

  // Return value becomes agent output
  return result
}
```

### Async Scripts

Scripts can be async for operations that interact with storage:

```typescript theme={null}
// scripts/with-storage.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default async function withStorage(context: AgentExecutionContext) {
  const { env, input } = context

  // Access KV storage
  const cached = await env.KV?.get('my-key')
  if (cached) {
    return JSON.parse(cached)
  }

  const result = processData(input)
  await env.KV?.put('my-key', JSON.stringify(result))

  return result
}
```

### TypeScript Types

Full TypeScript support with proper typing:

```typescript theme={null}
// scripts/typed-transform.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

interface InputData {
  items: Array<{
    id: string
    value: number
  }>
}

interface OutputData {
  processed: Array<{
    id: string
    normalized: number
  }>
  total: number
}

export default function typedTransform(
  context: AgentExecutionContext
): OutputData {
  const data = context.input as InputData

  const processed = data.items.map(item => ({
    id: item.id,
    normalized: item.value / 100
  }))

  return {
    processed,
    total: processed.length
  }
}
```

## Directory Organization

Organize scripts by domain:

```
scripts/
├── transforms/
│   ├── csv.ts
│   ├── json.ts
│   └── normalize.ts
├── validators/
│   ├── email.ts
│   └── order.ts
├── pipelines/
│   ├── extract.ts
│   ├── transform.ts
│   └── aggregate.ts
├── auth/
│   ├── verify-token.ts
│   └── hash-password.ts
└── examples/           # Excluded with --no-examples flag
    ├── health-check.ts
    └── http/
        └── greet-user.ts
```

## Multi-Step Workflows

Use multiple script agents for complex data pipelines:

```yaml theme={null}
name: data-pipeline

flow:
  - agent: extract
  - agent: transform
  - agent: validate
  - agent: aggregate

agents:
  - name: extract
    operation: code
    config:
      script: scripts/pipelines/extract
    input:
      data: $input.data

  - name: transform
    operation: code
    config:
      script: scripts/pipelines/transform
    input:
      extracted: $extract.output

  - name: validate
    operation: code
    config:
      script: scripts/pipelines/validate
    input:
      transformed: $transform.output

  - name: aggregate
    operation: code
    config:
      script: scripts/pipelines/aggregate
    input:
      validated: $validate.output.valid_items

input:
  data: []

output:
  result: ${aggregate.output}
```

With corresponding script files:

```typescript theme={null}
// scripts/pipelines/extract.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function extract(context: AgentExecutionContext) {
  const { data } = context.input as { data: Array<Record<string, unknown>> }

  return data.map(item => ({
    id: item['ID'],
    name: item['Full Name'],
    amount: parseFloat(String(item['Amount']))
  }))
}
```

```typescript theme={null}
// scripts/pipelines/transform.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

interface ExtractedItem {
  id: string
  name: string
  amount: number
}

export default function transform(context: AgentExecutionContext) {
  const { extracted } = context.input as { extracted: ExtractedItem[] }

  return extracted.map(item => ({
    ...item,
    amount_cents: Math.round(item.amount * 100),
    processed_at: new Date().toISOString(),
    normalized_name: item.name.toUpperCase().trim()
  }))
}
```

```typescript theme={null}
// scripts/pipelines/validate.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

interface TransformedItem {
  id: string
  amount_cents: number
}

export default function validate(context: AgentExecutionContext) {
  const { transformed } = context.input as { transformed: TransformedItem[] }

  const valid = transformed.filter(item => item.id && item.amount_cents > 0)
  const invalid = transformed.filter(item => !item.id || item.amount_cents <= 0)

  return {
    valid_items: valid,
    invalid_items: invalid,
    success_rate: (valid.length / transformed.length) * 100
  }
}
```

```typescript theme={null}
// scripts/pipelines/aggregate.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

interface ValidatedItem {
  amount_cents: number
}

export default function aggregate(context: AgentExecutionContext) {
  const { validated } = context.input as { validated: ValidatedItem[] }

  return {
    total_items: validated.length,
    total_amount: validated.reduce((sum, item) => sum + item.amount_cents, 0),
    items: validated,
    processed_at: new Date().toISOString()
  }
}
```

## Best Practices

### 1. Keep Scripts Focused

Each script should do one thing well:

```typescript theme={null}
// Good: Single responsibility
export default function calculateTotal(context: AgentExecutionContext) {
  const items = context.input as Array<{ price: number }>
  return { total: items.reduce((sum, item) => sum + item.price, 0) }
}

// Bad: Multiple responsibilities
export default function doEverything(context: AgentExecutionContext) {
  // Validate, transform, calculate, format... too much
}
```

### 2. Type Your Inputs

TypeScript helps catch errors at build time:

```typescript theme={null}
interface OrderInput {
  items: Array<{ price: number; quantity: number }>
  discount?: number
}

export default function processOrder(context: AgentExecutionContext) {
  const input = context.input as OrderInput
  // TypeScript helps catch errors
}
```

### 3. Handle Edge Cases

```typescript theme={null}
export default function processItems(context: AgentExecutionContext) {
  const { items } = context.input as { items?: unknown[] }

  // Handle missing or empty arrays
  if (!items || items.length === 0) {
    return { count: 0, items: [] }
  }

  return { count: items.length, items }
}
```

### 4. Add Error Handling

```typescript theme={null}
export default function parseJson(context: AgentExecutionContext) {
  const { json_string } = context.input as { json_string: string }

  try {
    const data = JSON.parse(json_string)
    return { success: true, data }
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Parse failed'
    }
  }
}
```

### 5. Don't Mutate Input

```typescript theme={null}
// Good: Create new array
export default function sortItems(context: AgentExecutionContext) {
  const { items } = context.input as { items: number[] }
  const sorted = [...items].sort((a, b) => a - b)
  return { sorted }
}

// Bad: Mutates original
export default function sortItems(context: AgentExecutionContext) {
  const { items } = context.input as { items: number[] }
  items.sort() // Mutates input!
  return { sorted: items }
}
```

## Testing Scripts

Test scripts with unit tests:

```typescript theme={null}
// scripts/__tests__/transform-data.test.ts
import transformData from '../transform-data'

describe('transformData', () => {
  it('normalizes values correctly', () => {
    const context = {
      input: {
        raw_data: [
          { id: '1', value: 50, min: 0, max: 100, created_at: '2024-01-01' }
        ]
      },
      env: {}
    } as any

    const result = transformData(context)

    expect(result.processed[0].normalized_value).toBe(0.5)
    expect(result.total_items).toBe(1)
  })
})
```

## Integration with Other Operations

### With Think Agents

```yaml theme={null}
agents:
  - name: prepare
    operation: code
    config:
      script: scripts/prepare-for-ai
    input:
      text: $input.text

  - name: analyze
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: |
        Analyze this prepared data:
        ${prepare.output}
```

### With Data Agents

```yaml theme={null}
agents:
  - name: fetch
    operation: data
    config:
      database: d1
      query: "query://get-users@v1"

  - name: process
    operation: code
    config:
      script: scripts/process-users
    input:
      users: $fetch.output.rows

output:
  stats: ${process.output}
```

## Versioning with Edgit

For production deployments, use edgit to version and manage scripts:

```bash theme={null}
# Add script to edgit
edgit components add transform-data scripts/transform-data.ts script

# Create version tag
edgit tag create transform-data v1.0.0

# Tag for production
edgit tag set transform-data production v1.0.0
edgit push --tags --force
```

Reference versioned scripts:

```yaml theme={null}
agents:
  - name: transform
    operation: code
    config:
      script: "script://transform-data@v1.0.0"
```

## Using ctx API in Agents

When building custom agents with TypeScript handlers, you can access and execute other scripts through the `ctx` API:

### ctx.scripts.get(name)

Get and execute a script by name:

```typescript theme={null}
// agents/orchestrator/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default async function orchestrate(ctx: AgentExecutionContext) {
  // Get and execute another script
  const transformScript = await ctx.scripts.get('transform-data')

  // Execute the script with custom input
  const result = await transformScript({
    ...ctx,
    input: { data: [1, 2, 3, 4, 5] }
  })

  return {
    transformed: result,
    original: ctx.input
  }
}
```

### Composing Multiple Scripts

```typescript theme={null}
// agents/pipeline/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default async function runPipeline(ctx: AgentExecutionContext) {
  const { data } = ctx.input as { data: any[] }

  // Load multiple scripts
  const extract = await ctx.scripts.get('pipelines/extract')
  const transform = await ctx.scripts.get('pipelines/transform')
  const validate = await ctx.scripts.get('pipelines/validate')

  // Execute pipeline
  const extracted = await extract({ ...ctx, input: { data } })
  const transformed = await transform({ ...ctx, input: { extracted } })
  const validated = await validate({ ...ctx, input: { transformed } })

  return validated
}
```

### Dynamic Script Loading

```typescript theme={null}
// agents/dynamic-processor/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default async function processWithScript(ctx: AgentExecutionContext) {
  const { processorType, data } = ctx.input as {
    processorType: 'csv' | 'json' | 'xml'
    data: string
  }

  // Load processor based on type
  const scriptName = `transforms/${processorType}`
  const processor = await ctx.scripts.get(scriptName)

  const result = await processor({ ...ctx, input: { data } })

  return {
    type: processorType,
    result
  }
}
```

### Script with Shared Utilities

```typescript theme={null}
// agents/with-utilities/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default async function processWithUtils(ctx: AgentExecutionContext) {
  const { items } = ctx.input as { items: any[] }

  // Load utility scripts
  const validate = await ctx.scripts.get('utils/validate')
  const normalize = await ctx.scripts.get('utils/normalize')

  // Use utilities
  const validated = await Promise.all(
    items.map(item => validate({ ...ctx, input: { item } }))
  )

  const normalized = await Promise.all(
    validated.map(item => normalize({ ...ctx, input: { item } }))
  )

  return {
    processed: normalized,
    total: items.length
  }
}
```

### Error Handling with Scripts

```typescript theme={null}
// agents/safe-processor/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default async function safeProcess(ctx: AgentExecutionContext) {
  const { data } = ctx.input as { data: any }

  try {
    // Try primary processor
    const processor = await ctx.scripts.get('processors/primary')
    const result = await processor({ ...ctx, input: { data } })

    return {
      success: true,
      result,
      processor: 'primary'
    }
  } catch (error) {
    // Fallback to backup processor
    const fallback = await ctx.scripts.get('processors/fallback')
    const result = await fallback({ ...ctx, input: { data } })

    return {
      success: true,
      result,
      processor: 'fallback',
      primaryError: error instanceof Error ? error.message : 'Unknown error'
    }
  }
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Code Operation" icon="terminal" href="/conductor/operations/code">
    Full code operation docs
  </Card>

  <Card title="Prompt Components" icon="wand2" href="/conductor/components/prompts">
    Reusable AI instructions
  </Card>

  <Card title="Query Components" icon="database" href="/conductor/components/queries">
    SQL queries as components
  </Card>

  <Card title="Creating Agents" icon="hammer" href="/conductor/building/creating-agents">
    Build custom agents
  </Card>
</CardGroup>
