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.
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:
// 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
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:
npm run build # Scripts are bundled automatically
npm run deploy # Deploy to Cloudflare
Both formats are supported for referencing scripts:
| Format | Example |
|---|
| Shorthand | scripts/transforms/csv |
| Full URI | script://transforms/csv |
| With version | scripts/transforms/[email protected] |
# 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://[email protected]"
Scripts must export a default function that receives AgentExecutionContext:
// 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:
// 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:
// 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:
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:
// 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']))
}))
}
// 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()
}))
}
// 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
}
}
// 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:
// 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
}
TypeScript helps catch errors at build time:
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
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
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'
}
}
}
// 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:
// 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
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
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:
# 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:
agents:
- name: transform
operation: code
config:
script: "script://[email protected]"
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:
// 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
// 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
// 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
// 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
// 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