Cloudflare Workers Compatibility: Inline code (config.code) is not supported in Cloudflare Workers due to security restrictions. Workers blocks new Function() and eval() by design. Use config.script to reference bundled script files instead.
Use it for transformations, validations, calculations, and custom logic that doesn’t require AI or external services.
Basic Usage
Create a script file in your scripts/ directory:
// scripts/calculate-profit.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function calculateProfit(context: AgentExecutionContext) {
const { revenue, costs } = context.input as { revenue: number; costs: number }
const profit = revenue - costs
return { profit, margin: (profit / revenue) * 100 }
}
Reference it in your ensemble:
agents:
- name: transform
operation: code
config:
script: scripts/calculate-profit
input:
revenue: $input.revenue
costs: $input.costs
Configuration
config:
script: string # Path to bundled TypeScript/JavaScript script
The script receives the full AgentExecutionContext:
Scripts must export a default function:
// scripts/my-script.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function myScript(context: AgentExecutionContext) {
const input = context.input as MyInputType
// Your logic here
return { result: 'success' }
}
Async Scripts
Scripts can be async for operations that need to wait:
// scripts/process-with-storage.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function processWithStorage(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
}
Both formats are supported for referencing scripts:
| Format | Example |
|---|
| Shorthand | scripts/transforms/csv |
| Full URI | script://transforms/csv |
| With version | scripts/transforms/[email protected] |
Common Use Cases
// scripts/transforms/format-output.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
interface FormatInput {
firstName: string
lastName: string
email: string
timestamp: string
}
export default function formatOutput(context: AgentExecutionContext) {
const input = context.input as FormatInput
return {
fullName: `${input.firstName} ${input.lastName}`,
email: input.email.toLowerCase(),
displayDate: new Date(input.timestamp).toLocaleDateString()
}
}
agents:
- name: format-output
operation: code
config:
script: scripts/transforms/format-output
input:
firstName: $input.firstName
lastName: $input.lastName
email: $input.email
timestamp: $input.timestamp
2. Calculations
// scripts/finance/calculate-roi.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
interface RoiInput {
initialInvestment: number
currentValue: number
years: number
}
export default function calculateRoi(context: AgentExecutionContext) {
const { initialInvestment, currentValue, years } = context.input as RoiInput
const profit = currentValue - initialInvestment
const roi = (profit / initialInvestment) * 100
const annualizedROI = Math.pow(currentValue / initialInvestment, 1 / years) - 1
return {
profit,
roi: parseFloat(roi.toFixed(2)),
annualizedROI: parseFloat((annualizedROI * 100).toFixed(2))
}
}
3. Validation
// scripts/validators/validate-order.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
interface OrderInput {
email?: string
amount?: number
items?: unknown[]
}
export default function validateOrder(context: AgentExecutionContext) {
const input = context.input as OrderInput
const errors: string[] = []
// Validate email
if (!input.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.email)) {
errors.push('Invalid email address')
}
// Validate amount
if (!input.amount || input.amount <= 0) {
errors.push('Amount must be positive')
}
// Validate items
if (!input.items || input.items.length === 0) {
errors.push('Order must contain at least one item')
}
return {
valid: errors.length === 0,
errors
}
}
4. Array Operations
// scripts/utils/analyze-array.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function analyzeArray(context: AgentExecutionContext) {
const { numbers } = context.input as { numbers: number[] }
const sum = numbers.reduce((acc, n) => acc + n, 0)
const avg = sum / numbers.length
const sorted = [...numbers].sort((a, b) => a - b)
const median = sorted[Math.floor(sorted.length / 2)]
const min = Math.min(...numbers)
const max = Math.max(...numbers)
return {
sum,
average: avg,
median,
min,
max,
count: numbers.length
}
}
5. Conditional Logic
// scripts/business/determine-action.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
interface ActionInput {
amount: number
riskScore: number
}
export default function determineAction(context: AgentExecutionContext) {
const { amount, riskScore } = context.input as ActionInput
if (amount > 1000) {
return { action: 'manual_review', reason: 'high_value' }
} else if (riskScore > 0.8) {
return { action: 'flag', reason: 'high_risk' }
} else {
return { action: 'approve', reason: 'normal' }
}
}
6. Date/Time Operations
// scripts/utils/process-dates.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function processDates(context: AgentExecutionContext) {
const { timestamp } = context.input as { timestamp: string }
const date = new Date(timestamp)
return {
iso: date.toISOString(),
local: date.toLocaleString(),
date: date.toLocaleDateString(),
time: date.toLocaleTimeString(),
unix: Math.floor(date.getTime() / 1000),
dayOfWeek: date.toLocaleDateString('en-US', { weekday: 'long' }),
month: date.toLocaleDateString('en-US', { month: 'long' }),
year: date.getFullYear()
}
}
7. Location-Aware Logic
Use the location context for geo-aware business logic:
// scripts/geo/localized-pricing.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function localizedPricing(context: AgentExecutionContext) {
const { basePrice } = context.input as { basePrice: number }
const { location } = context
// Apply regional pricing
const regionalMultipliers: Record<string, number> = {
US: 1.0,
GB: 0.85,
EU: 0.90,
IN: 0.50,
}
const region = location?.isIn(['EU']) ? 'EU' : location?.country
const multiplier = regionalMultipliers[region ?? ''] ?? 1.0
return {
price: basePrice * multiplier,
currency: location?.isIn(['GB']) ? 'GBP' : location?.isIn(['EU']) ? 'EUR' : 'USD',
country: location?.country,
requiresConsent: location?.requiresConsent('analytics')
}
}
8. Security & Compliance Checks
Use the edge context for security decisions:
// scripts/security/traffic-filter.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function trafficFilter(context: AgentExecutionContext) {
const { edge, location, logger } = context
let riskScore = 0
const flags: string[] = []
// Cloud provider traffic often indicates automation
if (edge?.isFromCloudProvider()) {
riskScore += 30
flags.push(`cloud_provider:${edge.getCloudProvider()}`)
}
// VPN usage
if (edge?.isFromVPN()) {
riskScore += 10
flags.push('vpn_detected')
}
// Outdated TLS
if (!edge?.isModernTLS()) {
riskScore += 20
flags.push('legacy_tls')
}
logger?.info('Traffic analysis', { riskScore, flags, asn: edge?.asn })
return {
allowed: riskScore < 50,
riskScore,
flags,
datacenter: edge?.coloName
}
}
Using Previous Agent Outputs
Reference outputs from previous agents via the input mapping:
agents:
- name: fetch-data
operation: http
config:
url: https://api.example.com/data
- name: process-results
operation: code
config:
script: scripts/process-api-data
input:
apiData: $fetch-data.output.data
// scripts/process-api-data.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function processApiData(context: AgentExecutionContext) {
const { apiData } = context.input as { apiData: Array<{ id: string; value: number }> }
const processed = apiData.map(item => ({
id: item.id,
value: item.value * 2
}))
return { processed }
}
Accessing Environment
Scripts have access to Cloudflare bindings via context.env:
// scripts/with-env.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function withEnv(context: AgentExecutionContext) {
const { env, input } = context
// Access KV storage
const cached = await env.KV?.get(`cache:${input.key}`)
if (cached) {
return JSON.parse(cached)
}
// Access D1 database
const result = await env.DB?.prepare('SELECT * FROM users WHERE id = ?')
.bind(input.userId)
.first()
// Access environment variables
const apiKey = env.API_KEY
return { user: result, apiKey: apiKey ? 'present' : 'missing' }
}
Error Handling
Throw Errors
// scripts/divide.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function divide(context: AgentExecutionContext) {
const { numerator, denominator } = context.input as { numerator: number; denominator: number }
if (denominator === 0) {
throw new Error('Division by zero')
}
return { result: numerator / denominator }
}
Return Error State
// scripts/safe-operation.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function safeOperation(context: AgentExecutionContext) {
try {
const result = performRiskyOperation(context.input)
return { success: true, result }
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
fallback: getDefaultValue()
}
}
}
Directory Organization
Organize your scripts by domain:
scripts/
├── transforms/
│ ├── csv.ts
│ ├── json.ts
│ └── normalize.ts
├── validators/
│ ├── email.ts
│ └── order.ts
├── finance/
│ ├── calculate-roi.ts
│ └── compound-interest.ts
├── utils/
│ ├── format-date.ts
│ └── parse-json.ts
└── auth/
├── verify-token.ts
└── hash-password.ts
Reference them with their path:
agents:
- name: validate
operation: code
config:
script: scripts/validators/email
Best Practices
1. Keep Scripts Focused
// 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 in one script
export default function doEverything(context: AgentExecutionContext) {
// Validate, transform, calculate, format... 200 lines
}
2. Type Your Inputs
// Good: Clear types
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 Nulls
// Good: Check for null/undefined
export default function processItems(context: AgentExecutionContext) {
const items = (context.input as { items?: unknown[] }).items || []
return { count: items.length }
}
// Bad: Assume data exists
export default function processItems(context: AgentExecutionContext) {
return { count: (context.input as any).items.length } // May throw
}
4. Return Objects
// Good: Return object
export default function calculate(context: AgentExecutionContext) {
return { result: value }
}
// Bad: Return primitive (less flexible)
export default function calculate(context: AgentExecutionContext) {
return value
}
5. Don’t Mutate Input
// Good: Create new array
export default function sortItems(context: AgentExecutionContext) {
const items = [...(context.input as { items: number[] }).items]
items.sort()
return { items }
}
// Bad: Mutate original
export default function sortItems(context: AgentExecutionContext) {
const items = (context.input as { items: number[] }).items
items.sort() // Mutates original
return { items }
}
Migration from Inline Code
If you have existing ensembles with inline code, migrate them to script files:
Before (Not Supported in Workers)
# ❌ This won't work in Cloudflare Workers
agents:
- name: transform
operation: code
config:
code: |
const { revenue, costs } = input;
return { profit: revenue - costs };
After (Recommended)
// scripts/calculate-profit.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function calculateProfit(context: AgentExecutionContext) {
const { revenue, costs } = context.input as { revenue: number; costs: number }
return { profit: revenue - costs }
}
# ✅ Works everywhere
agents:
- name: transform
operation: code
config:
script: scripts/calculate-profit
input:
revenue: $input.revenue
costs: $input.costs
Key Differences
| Inline Code | Script Files |
|---|
input variable | context.input property |
Direct return | return from function |
| No types | Full TypeScript support |
No env access | context.env for bindings |
| Dynamic evaluation | Build-time bundling |
Testing
Test your scripts with full TypeScript support:
// scripts/__tests__/calculate-profit.test.ts
import calculateProfit from '../calculate-profit'
describe('calculateProfit', () => {
it('calculates profit correctly', () => {
const mockContext = {
input: { revenue: 1000, costs: 600 },
env: {},
} as any
const result = calculateProfit(mockContext)
expect(result.profit).toBe(400)
})
})
When to Use Custom Agents Instead
Use full custom agents (with agents/ directory) when you need:
- Multiple related operations - Group related functionality
- Complex state management - Shared state across operations
- External dependencies - npm packages
- Database access - Complex queries
- Reusable across projects - Publish as agent package
See Creating Agents for more.
Next Steps