Skip to main content

code Operation

Execute custom JavaScript/TypeScript for data transformation, business logic, calculations, and any task that doesn’t require AI or external services. The code operation is fast, free, and fully under your control. Use it for transformations, validations, calculations, and custom logic.

Basic Usage

operations:
  - name: transform
    operation: code
    config:
      code: |
        const { revenue, costs } = ${input};
        const profit = revenue - costs;
        return { profit, margin: (profit / revenue) * 100 };

Configuration

config:
  code: string  # JavaScript/TypeScript code to execute
The code has access to:
  • 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

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

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

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

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

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

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

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

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:
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

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

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

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

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

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

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

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

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

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

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

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

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:
// 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))
  };
}
# agents/my-agent/agent.yaml
operations:
  - name: calculate
    operation: code
    config:
      code: |
        return await operations.calculateMetrics(${input});

Testing

Test code operations in your agents:
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 Simple
# 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...
2. Return Objects
# Good: Return object
code: |
  return { result: value };

# Bad: Return primitive
code: |
  return value;
3. Handle Nulls
# Good: Check for null/undefined
code: |
  const items = ${input.items} || [];
  return { count: items.length };

# Bad: Assume data exists
code: |
  return { count: ${input.items}.length };
4. Use Template Expressions
# 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) };
5. Validate Input
# 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() };
6. Extract Complex Logic
# 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...
7. Document Tricky Code
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)) };
8. Use Meaningful Names
# 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

# 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

# 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

# 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:
  1. TypeScript type safety
  2. Complex business logic (>50 lines)
  3. Reusable functions
  4. External dependencies
  5. Async operations (fetch, database)
  6. Access to environment variables
  7. State management
  8. Unit testing with full test coverage
See Creating Agents for more.

Next Steps