code Operation
Execute custom JavaScript/TypeScript for data transformation, business logic, calculations, and any task that doesn’t require AI or external services. Thecode operation is fast, free, and fully under your control. Use it for transformations, validations, calculations, and custom logic.
Basic Usage
Copy
operations:
- name: transform
operation: code
config:
code: |
const { revenue, costs } = ${input};
const profit = revenue - costs;
return { profit, margin: (profit / revenue) * 100 };
Configuration
Copy
config:
code: string # JavaScript/TypeScript code to execute
input- The input data passed to the operation- All previous operation outputs via template expressions
- Standard JavaScript/TypeScript globals
- Node.js built-ins
Common Use Cases
1. Data Transformation
Copy
operations:
- name: format-output
operation: code
config:
code: |
return {
fullName: `${input.firstName} ${input.lastName}`,
email: ${input.email}.toLowerCase(),
displayDate: new Date(${input.timestamp}).toLocaleDateString()
};
2. Calculations
Copy
operations:
- name: calculate-roi
operation: code
config:
code: |
const { initialInvestment, currentValue, years } = ${input};
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
Copy
operations:
- name: validate-order
operation: code
config:
code: |
const errors = [];
// 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. String Manipulation
Copy
operations:
- name: process-text
operation: code
config:
code: |
const text = ${input.text};
return {
length: text.length,
wordCount: text.split(/\s+/).length,
uppercase: text.toUpperCase(),
lowercase: text.toLowerCase(),
titleCase: text.replace(/\w\S*/g, (word) =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
),
slug: text.toLowerCase().replace(/\s+/g, '-').replace(/[^\w\-]/g, '')
};
5. Array Operations
Copy
operations:
- name: analyze-array
operation: code
config:
code: |
const numbers = ${input.numbers};
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
};
6. JSON Processing
Copy
operations:
- name: transform-json
operation: code
config:
code: |
const data = ${input.data};
const mapping = ${input.mapping};
// Transform object keys based on mapping
const transformed = {};
for (const [oldKey, newKey] of Object.entries(mapping)) {
if (data[oldKey] !== undefined) {
transformed[newKey] = data[oldKey];
}
}
return { transformed };
7. Conditional Logic
Copy
operations:
- name: determine-action
operation: code
config:
code: |
const amount = ${input.amount};
const riskScore = ${assess-risk.output.score};
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' };
}
8. Date/Time Operations
Copy
operations:
- name: process-dates
operation: code
config:
code: |
const timestamp = ${input.timestamp};
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()
};
Accessing Previous Outputs
Reference outputs from previous operations:Copy
operations:
- name: fetch-data
operation: http
config:
url: https://api.example.com/data
- name: process-results
operation: code
config:
code: |
const apiData = ${fetch-data.output.data};
const processed = apiData.map(item => ({
id: item.id,
value: item.value * 2
}));
return { processed };
Complex Transformations
Object Flattening
Copy
operations:
- name: flatten-object
operation: code
config:
code: |
function flatten(obj, prefix = '') {
return Object.keys(obj).reduce((acc, key) => {
const value = obj[key];
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
Object.assign(acc, flatten(value, newKey));
} else {
acc[newKey] = value;
}
return acc;
}, {});
}
return { flattened: flatten(${input.nested}) };
Aggregation
Copy
operations:
- name: aggregate-results
operation: code
config:
code: |
const results = [
${process-a.output},
${process-b.output},
${process-c.output}
];
return {
total: results.length,
successful: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length,
avgDuration: results.reduce((sum, r) => sum + r.duration, 0) / results.length
};
Data Filtering
Copy
operations:
- name: filter-items
operation: code
config:
code: |
const items = ${input.items};
const minPrice = ${input.minPrice} || 0;
const category = ${input.category};
const filtered = items.filter(item =>
item.price >= minPrice &&
(!category || item.category === category) &&
item.available === true
);
return {
items: filtered,
count: filtered.length,
totalValue: filtered.reduce((sum, item) => sum + item.price, 0)
};
Data Grouping
Copy
operations:
- name: group-by-category
operation: code
config:
code: |
const items = ${input.items};
const grouped = items.reduce((acc, item) => {
const category = item.category || 'uncategorized';
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(item);
return acc;
}, {});
return { grouped };
Error Handling
Throw Errors
Copy
operations:
- name: divide
operation: code
config:
code: |
const { numerator, denominator } = ${input};
if (denominator === 0) {
throw new Error('Division by zero');
}
return { result: numerator / denominator };
Return Error State
Copy
operations:
- name: safe-operation
operation: code
config:
code: |
try {
const result = performRiskyOperation(${input.data});
return { success: true, result };
} catch (error) {
return {
success: false,
error: error.message,
fallback: getDefaultValue()
};
}
Graceful Degradation
Copy
operations:
- name: parse-json
operation: code
config:
code: |
const text = ${input.text};
try {
const parsed = JSON.parse(text);
return { valid: true, data: parsed };
} catch (error) {
return {
valid: false,
error: 'Invalid JSON',
raw: text
};
}
Advanced Patterns
Memoization
Copy
operations:
- name: fibonacci
operation: code
config:
code: |
const cache = new Map();
function fib(n) {
if (n <= 1) return n;
if (cache.has(n)) return cache.get(n);
const result = fib(n - 1) + fib(n - 2);
cache.set(n, result);
return result;
}
return { result: fib(${input.n}) };
Regular Expressions
Copy
operations:
- name: extract-patterns
operation: code
config:
code: |
const text = ${input.text};
// Extract emails
const emails = text.match(/[^\s@]+@[^\s@]+\.[^\s@]+/g) || [];
// Extract URLs
const urls = text.match(/https?:\/\/[^\s]+/g) || [];
// Extract phone numbers
const phones = text.match(/\d{3}-\d{3}-\d{4}/g) || [];
return { emails, urls, phones };
Recursive Processing
Copy
operations:
- name: traverse-tree
operation: code
config:
code: |
function traverse(node, depth = 0) {
const result = {
id: node.id,
depth,
children: []
};
if (node.children && node.children.length > 0) {
result.children = node.children.map(child =>
traverse(child, depth + 1)
);
}
return result;
}
return { tree: traverse(${input.root}) };
Performance Optimization
Early Returns
Copy
operations:
- name: optimize-processing
operation: code
config:
code: |
const data = ${input.data};
// Quick validation
if (!data || data.length === 0) {
return { processed: 0, skipped: true };
}
// Early return for cached
if (${input.useCache} && cachedResult) {
return cachedResult;
}
// Expensive processing only if needed
return expensiveProcessing(data);
Batch Processing
Copy
operations:
- name: batch-process
operation: code
config:
code: |
const items = ${input.items};
const batchSize = 100;
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const processed = processBatch(batch);
results.push(...processed);
}
return { results, totalProcessed: results.length };
Type Safety
For complex operations, prefer creating custom agents with TypeScript:Copy
// agents/my-agent/operations.ts
export async function calculateMetrics(input: { revenue: number; costs: number }) {
const profit = input.revenue - input.costs;
const margin = (profit / input.revenue) * 100;
return {
profit,
margin: parseFloat(margin.toFixed(2))
};
}
Copy
# agents/my-agent/agent.yaml
operations:
- name: calculate
operation: code
config:
code: |
return await operations.calculateMetrics(${input});
Testing
Test code operations in your agents:Copy
import { TestConductor } from '@ensemble/conductor/testing';
describe('calculate operation', () => {
it('should calculate correctly', async () => {
const conductor = await TestConductor.create({
projectPath: './conductor'
});
const result = await conductor.executeAgent('calculator', {
revenue: 1000,
costs: 600
});
expect(result.output.profit).toBe(400);
expect(result.output.margin).toBe(40);
});
});
Best Practices
1. Keep It SimpleCopy
# Good: Simple transformation
code: |
return { total: ${input.items}.reduce((sum, item) => sum + item.price, 0) };
# Bad: Complex logic belongs in custom agents
code: |
// 100+ lines of complex business logic...
Copy
# Good: Return object
code: |
return { result: value };
# Bad: Return primitive
code: |
return value;
Copy
# Good: Check for null/undefined
code: |
const items = ${input.items} || [];
return { count: items.length };
# Bad: Assume data exists
code: |
return { count: ${input.items}.length };
Copy
# Good: Template expressions for data
code: |
const data = ${input.data};
return { processed: data.map(x => x * 2) };
# Bad: Hardcoded values
code: |
const data = [1, 2, 3];
return { processed: data.map(x => x * 2) };
Copy
# Good: Validate first
code: |
if (!${input.email}) {
throw new Error('Email is required');
}
return { valid: true };
# Bad: Assume valid input
code: |
return { email: ${input.email}.toLowerCase() };
Copy
# Good: Complex logic in custom agent
operations:
- name: process
agent: my-processor
# Bad: Inline complex logic
operations:
- name: process
operation: code
config:
code: |
// 200 lines of complex logic...
Copy
operations:
- name: calculate-cagr
operation: code
config:
code: |
// Calculate compound annual growth rate (CAGR)
// Formula: CAGR = (Ending Value / Beginning Value)^(1/Years) - 1
const { beginningValue, endingValue, years } = ${input};
const cagr = Math.pow(endingValue / beginningValue, 1 / years) - 1;
return { cagr: parseFloat((cagr * 100).toFixed(2)) };
Copy
# Good: Clear variable names
code: |
const totalRevenue = ${input.items}.reduce((sum, item) => sum + item.revenue, 0);
return { totalRevenue };
# Bad: Unclear names
code: |
const x = ${input.items}.reduce((a, b) => a + b.revenue, 0);
return { x };
Common Pitfalls
Pitfall: Mutating Input
Copy
# Bad: Mutating input
code: |
const items = ${input.items};
items.sort(); # Mutates original
return { items };
# Good: Create new array
code: |
const items = [...${input.items}];
items.sort();
return { items };
Pitfall: Async Operations
Copy
# Bad: Code operation doesn't support async
code: |
const data = await fetch('...'); # Won't work
return { data };
# Good: Use http operation instead
operations:
- name: fetch
operation: http
config:
url: https://api.example.com
Pitfall: Side Effects
Copy
# Bad: Side effects
let counter = 0;
code: |
counter++; # Don't use external state
return { count: counter };
# Good: Pure function
code: |
return { count: ${input.count} + 1 };
When to Use Custom Agents Instead
Use custom agents when you need:- TypeScript type safety
- Complex business logic (>50 lines)
- Reusable functions
- External dependencies
- Async operations (fetch, database)
- Access to environment variables
- State management
- Unit testing with full test coverage

